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 }