/* See LICENSE file for copyright and license details. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "decode.h" #include "diff.h" #include "util.h" #ifndef VERSION #define VERSION "0.1" #endif #ifndef DEFAULT_THRESH #define DEFAULT_THRESH 8 #endif static void usage(void) { fprintf(stderr, "usage: sframe [-v] [-t threshold] [-f png|jpg]" " [-o outdir] video [...]\n" "\n" "options:\n" " -t N hash threshold 0-64" " (default: %d, lower = stricter)\n" " -f fmt output format: png or jpg" " (default: png)\n" " -o dir output directory" " (default: ~/frames)\n" " -v print version\n", DEFAULT_THRESH); exit(1); } /* * Strip file extension and directory, return allocated * copy of the base name suitable for use as a directory * and filename prefix. Replaces spaces with underscores. */ static char * video_name(const char *path) { char *copy, *base, *dot, *name, *p; copy = estrdup(path); base = basename(copy); dot = strrchr(base, '.'); if (dot) *dot = '\0'; name = estrdup(base); free(copy); /* sanitize: replace spaces and slashes */ for (p = name; *p; p++) { if (*p == ' ' || *p == '/') *p = '_'; } return name; } static void ts_string(double secs, char *buf, size_t len) { int h, m, s, ms; if (secs < 0.0) secs = 0.0; h = (int)(secs / 3600.0); secs -= h * 3600.0; m = (int)(secs / 60.0); secs -= m * 60.0; s = (int)secs; ms = (int)((secs - s) * 1000.0); snprintf(buf, len, "%02d-%02d-%02d.%03d", h, m, s, ms); } static int process_video(const char *path, const char *outdir, int threshold, const char *fmt) { Decoder dec; AVFrame *frame; char *name; char dirpath[4096]; char filepath[4096]; char tsbuf[32]; uint64_t prev_hash; uint64_t cur_hash; double ts; int dist; int saved; int total; int first; name = video_name(path); snprintf(dirpath, sizeof(dirpath), "%s/%s", outdir, name); if (mkdirp(dirpath) < 0) die("cannot create directory '%s':", dirpath); if (dec_open(&dec, path) < 0) { free(name); return -1; } frame = av_frame_alloc(); if (!frame) { dec_close(&dec); free(name); die("cannot allocate frame"); } fprintf(stderr, "processing: %s (%dx%d)\n", path, dec.width, dec.height); fprintf(stderr, "output: %s/\n", dirpath); fprintf(stderr, "threshold: %d\n", threshold); saved = 0; total = 0; first = 1; prev_hash = 0; while (dec_read_frame(&dec, frame) == 0) { total++; cur_hash = diff_phash(frame, dec.sws_gray, dec.width, dec.height); if (!first) { dist = diff_hamming(prev_hash, cur_hash); if (dist <= threshold) { av_frame_unref(frame); continue; } } ts = dec_frame_time(&dec, frame); ts_string(ts, tsbuf, sizeof(tsbuf)); snprintf(filepath, sizeof(filepath), "%s/%s-%s.%s", dirpath, name, tsbuf, fmt); if (diff_save_frame(frame, dec.sws_rgb, dec.width, dec.height, filepath, fmt) == 0) { saved++; if (saved % 50 == 0) { fprintf(stderr, "\r saved %d unique frames" " (%d scanned)...", saved, total); } } prev_hash = cur_hash; first = 0; av_frame_unref(frame); } fprintf(stderr, "\r done: %d unique frames from %d total\n", saved, total); av_frame_free(&frame); dec_close(&dec); free(name); return 0; } int main(int argc, char *argv[]) { const char *outdir; const char *fmt; char default_outdir[4096]; const char *home; int threshold; int opt; int i; int ret; threshold = DEFAULT_THRESH; fmt = "png"; outdir = NULL; while ((opt = getopt(argc, argv, "t:f:o:v")) != -1) { switch (opt) { case 't': threshold = atoi(optarg); if (threshold < 0 || threshold > 64) die("threshold must be 0-64"); break; case 'f': if (strcmp(optarg, "png") != 0 && strcmp(optarg, "jpg") != 0 && strcmp(optarg, "jpeg") != 0) die("format must be png or jpg"); fmt = optarg; break; case 'o': outdir = optarg; break; case 'v': fprintf(stdout, "sframe %s\n", VERSION); return 0; default: usage(); } } if (optind >= argc) usage(); /* default output: ~/frames */ if (!outdir) { home = getenv("HOME"); if (!home) die("HOME not set"); snprintf(default_outdir, sizeof(default_outdir), "%s/frames", home); outdir = default_outdir; } if (mkdirp(outdir) < 0) die("cannot create output directory '%s':", outdir); ret = 0; for (i = optind; i < argc; i++) { if (process_video(argv[i], outdir, threshold, fmt) < 0) ret = 1; } return ret; }