sframe

Simple Frame — extract unique frames from videos
git clone git clone https://git.krisyotam.com/krisyotam/sframe.git
Log | Files | Refs | README | LICENSE

main.c (4818B)


      1 /* See LICENSE file for copyright and license details. */
      2 
      3 #define _POSIX_C_SOURCE 200809L
      4 
      5 #include <libgen.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <unistd.h>
     10 
     11 #include "decode.h"
     12 #include "diff.h"
     13 #include "util.h"
     14 
     15 #ifndef VERSION
     16 #define VERSION "0.1"
     17 #endif
     18 
     19 #ifndef DEFAULT_THRESH
     20 #define DEFAULT_THRESH 8
     21 #endif
     22 
     23 static void
     24 usage(void)
     25 {
     26 	fprintf(stderr,
     27 	        "usage: sframe [-v] [-t threshold] [-f png|jpg]"
     28 	        " [-o outdir] video [...]\n"
     29 	        "\n"
     30 	        "options:\n"
     31 	        "  -t N   hash threshold 0-64"
     32 	        " (default: %d, lower = stricter)\n"
     33 	        "  -f fmt output format: png or jpg"
     34 	        " (default: png)\n"
     35 	        "  -o dir output directory"
     36 	        " (default: ~/frames)\n"
     37 	        "  -v     print version\n",
     38 	        DEFAULT_THRESH);
     39 	exit(1);
     40 }
     41 
     42 /*
     43  * Strip file extension and directory, return allocated
     44  * copy of the base name suitable for use as a directory
     45  * and filename prefix. Replaces spaces with underscores.
     46  */
     47 static char *
     48 video_name(const char *path)
     49 {
     50 	char *copy, *base, *dot, *name, *p;
     51 
     52 	copy = estrdup(path);
     53 	base = basename(copy);
     54 
     55 	dot = strrchr(base, '.');
     56 	if (dot)
     57 		*dot = '\0';
     58 
     59 	name = estrdup(base);
     60 	free(copy);
     61 
     62 	/* sanitize: replace spaces and slashes */
     63 	for (p = name; *p; p++) {
     64 		if (*p == ' ' || *p == '/')
     65 			*p = '_';
     66 	}
     67 
     68 	return name;
     69 }
     70 
     71 static void
     72 ts_string(double secs, char *buf, size_t len)
     73 {
     74 	int h, m, s, ms;
     75 
     76 	if (secs < 0.0)
     77 		secs = 0.0;
     78 
     79 	h = (int)(secs / 3600.0);
     80 	secs -= h * 3600.0;
     81 	m = (int)(secs / 60.0);
     82 	secs -= m * 60.0;
     83 	s = (int)secs;
     84 	ms = (int)((secs - s) * 1000.0);
     85 
     86 	snprintf(buf, len, "%02d-%02d-%02d.%03d", h, m, s, ms);
     87 }
     88 
     89 static int
     90 process_video(const char *path, const char *outdir,
     91               int threshold, const char *fmt)
     92 {
     93 	Decoder dec;
     94 	AVFrame *frame;
     95 	char *name;
     96 	char dirpath[4096];
     97 	char filepath[4096];
     98 	char tsbuf[32];
     99 	uint64_t prev_hash;
    100 	uint64_t cur_hash;
    101 	double ts;
    102 	int dist;
    103 	int saved;
    104 	int total;
    105 	int first;
    106 
    107 	name = video_name(path);
    108 
    109 	snprintf(dirpath, sizeof(dirpath), "%s/%s", outdir, name);
    110 	if (mkdirp(dirpath) < 0)
    111 		die("cannot create directory '%s':", dirpath);
    112 
    113 	if (dec_open(&dec, path) < 0) {
    114 		free(name);
    115 		return -1;
    116 	}
    117 
    118 	frame = av_frame_alloc();
    119 	if (!frame) {
    120 		dec_close(&dec);
    121 		free(name);
    122 		die("cannot allocate frame");
    123 	}
    124 
    125 	fprintf(stderr, "processing: %s (%dx%d)\n",
    126 	        path, dec.width, dec.height);
    127 	fprintf(stderr, "output:     %s/\n", dirpath);
    128 	fprintf(stderr, "threshold:  %d\n", threshold);
    129 
    130 	saved = 0;
    131 	total = 0;
    132 	first = 1;
    133 	prev_hash = 0;
    134 
    135 	while (dec_read_frame(&dec, frame) == 0) {
    136 		total++;
    137 
    138 		cur_hash = diff_phash(frame, dec.sws_gray,
    139 		                      dec.width, dec.height);
    140 
    141 		if (!first) {
    142 			dist = diff_hamming(prev_hash, cur_hash);
    143 			if (dist <= threshold) {
    144 				av_frame_unref(frame);
    145 				continue;
    146 			}
    147 		}
    148 
    149 		ts = dec_frame_time(&dec, frame);
    150 		ts_string(ts, tsbuf, sizeof(tsbuf));
    151 
    152 		snprintf(filepath, sizeof(filepath),
    153 		         "%s/%s-%s.%s",
    154 		         dirpath, name, tsbuf, fmt);
    155 
    156 		if (diff_save_frame(frame, dec.sws_rgb,
    157 		                    dec.width, dec.height,
    158 		                    filepath, fmt) == 0) {
    159 			saved++;
    160 			if (saved % 50 == 0) {
    161 				fprintf(stderr,
    162 				        "\r  saved %d unique frames"
    163 				        " (%d scanned)...",
    164 				        saved, total);
    165 			}
    166 		}
    167 
    168 		prev_hash = cur_hash;
    169 		first = 0;
    170 		av_frame_unref(frame);
    171 	}
    172 
    173 	fprintf(stderr,
    174 	        "\r  done: %d unique frames from %d total\n",
    175 	        saved, total);
    176 
    177 	av_frame_free(&frame);
    178 	dec_close(&dec);
    179 	free(name);
    180 	return 0;
    181 }
    182 
    183 int
    184 main(int argc, char *argv[])
    185 {
    186 	const char *outdir;
    187 	const char *fmt;
    188 	char default_outdir[4096];
    189 	const char *home;
    190 	int threshold;
    191 	int opt;
    192 	int i;
    193 	int ret;
    194 
    195 	threshold = DEFAULT_THRESH;
    196 	fmt = "png";
    197 	outdir = NULL;
    198 
    199 	while ((opt = getopt(argc, argv, "t:f:o:v")) != -1) {
    200 		switch (opt) {
    201 		case 't':
    202 			threshold = atoi(optarg);
    203 			if (threshold < 0 || threshold > 64)
    204 				die("threshold must be 0-64");
    205 			break;
    206 		case 'f':
    207 			if (strcmp(optarg, "png") != 0
    208 			    && strcmp(optarg, "jpg") != 0
    209 			    && strcmp(optarg, "jpeg") != 0)
    210 				die("format must be png or jpg");
    211 			fmt = optarg;
    212 			break;
    213 		case 'o':
    214 			outdir = optarg;
    215 			break;
    216 		case 'v':
    217 			fprintf(stdout, "sframe %s\n", VERSION);
    218 			return 0;
    219 		default:
    220 			usage();
    221 		}
    222 	}
    223 
    224 	if (optind >= argc)
    225 		usage();
    226 
    227 	/* default output: ~/frames */
    228 	if (!outdir) {
    229 		home = getenv("HOME");
    230 		if (!home)
    231 			die("HOME not set");
    232 		snprintf(default_outdir, sizeof(default_outdir),
    233 		         "%s/frames", home);
    234 		outdir = default_outdir;
    235 	}
    236 
    237 	if (mkdirp(outdir) < 0)
    238 		die("cannot create output directory '%s':", outdir);
    239 
    240 	ret = 0;
    241 	for (i = optind; i < argc; i++) {
    242 		if (process_video(argv[i], outdir,
    243 		                  threshold, fmt) < 0)
    244 			ret = 1;
    245 	}
    246 
    247 	return ret;
    248 }