/* See LICENSE file for copyright and license details. */
#define _POSIX_C_SOURCE 200809L
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}