Commit 650cb151 authored by Ronald S. Bultje's avatar Ronald S. Bultje

Add option to write each frame to separate output file

For per-file yuv/y4m writes, this can be automatically specified
using e.g. -o file_%w_%h_%5n.yuv/y4m. --muxer=framemd5 -o - --quiet
will accomplish the same for per-frame md5sums.

Addresses part of #310.
parent aae5ac81
......@@ -105,9 +105,10 @@ static void usage(const char *const app, const char *const reason, ...) {
fprintf(stderr, "Usage: %s [options]\n\n", app);
fprintf(stderr, "Supported options:\n"
" --input/-i $file: input file\n"
" --output/-o $file: output file\n"
" --output/-o $file: output file (optionally use %%w, %%h and %%n in the name to write per-frame files)\n"
" --demuxer $name: force demuxer type ('ivf', 'section5' or 'annexb'; default: detect from extension)\n"
" --muxer $name: force muxer type ('md5', 'yuv', 'yuv4mpeg2' or 'null'; default: detect from extension)\n"
" --muxer $name: force muxer type ('md5', 'yuv', 'yuv4mpeg2' or 'null', use 'frame' as prefix to write per-frame files; default: detect from extension)\n"
" --quiet/-q: disable status messages\n"
" --frametimes $file: dump frame times to file\n"
" --limit/-l $num: stop decoding after $num frames\n"
......
......@@ -33,6 +33,7 @@
#include <string.h>
#include "common/attributes.h"
#include "common/intops.h"
#include "output/output.h"
#include "output/muxer.h"
......@@ -40,6 +41,10 @@
struct MuxerContext {
MuxerPriv *data;
const Muxer *impl;
int one_file_per_frame;
unsigned fps[2];
const char *filename;
int framenum;
};
#define MAX_NUM_MUXERS 4
......@@ -86,8 +91,9 @@ int output_open(MuxerContext **const c_out,
int res;
if (name) {
const int name_offset = 5 * !strncmp(name, "frame", 5);
for (i = 0; i < num_muxers; i++) {
if (!strcmp(muxers[i]->name, name)) {
if (!strcmp(muxers[i]->name, &name[name_offset])) {
impl = muxers[i];
break;
}
......@@ -122,7 +128,37 @@ int output_open(MuxerContext **const c_out,
}
c->impl = impl;
c->data = (MuxerPriv *) &c[1];
if (impl->write_header && (res = impl->write_header(c->data, filename, p, fps)) < 0) {
enum {
PATTERN_HAVE_NONE = 0,
PATTERN_HAVE_WIDTH = 1 << 0,
PATTERN_HAVE_HEIGHT = 1 << 1,
PATTERN_HAVE_FRAMENUM = 1 << 2,
PATTERN_HAVE_ALL = (1 << 3) - 1,
} pattern = PATTERN_HAVE_NONE;
for (const char *ptr = filename ? strchr(filename, '%') : NULL;
ptr; ptr = strchr(ptr, '%'))
{
ptr++; // skip '%'
while (*ptr >= '0' && *ptr <= '9')
ptr++; // skip length indicators
switch (*ptr) {
case 'w': pattern |= PATTERN_HAVE_WIDTH; break;
case 'h': pattern |= PATTERN_HAVE_HEIGHT; break;
case 'n': pattern |= PATTERN_HAVE_FRAMENUM; break;
default: break;
}
}
c->one_file_per_frame = (name && !strncmp(name, "frame", 5)) ||
pattern == PATTERN_HAVE_ALL;
if (c->one_file_per_frame) {
c->fps[0] = fps[0];
c->fps[1] = fps[1];
c->filename = filename;
c->framenum = 0;
} else if (impl->write_header &&
(res = impl->write_header(c->data, filename, p, fps)) < 0)
{
free(c);
return res;
}
......@@ -131,17 +167,97 @@ int output_open(MuxerContext **const c_out,
return 0;
}
static void safe_strncat(char *const dst, const int dst_len,
const char *const src, const int src_len)
{
if (!src_len) return;
const int dst_fill = (int) strlen(dst);
assert(dst_fill < dst_len);
const int to_copy = imin(src_len, dst_len - dst_fill - 1);
memcpy(dst + dst_fill, src, to_copy);
dst[dst_fill + to_copy] = 0;
}
static void assemble_field(char *const dst, const int dst_len,
const char *const fmt, const int fmt_len,
const int field)
{
char fmt_copy[32];
assert(fmt[0] == '%');
fmt_copy[0] = '%';
if (fmt[1] >= '1' && fmt[1] <= '9') {
fmt_copy[1] = '0'; // pad with zeroes, not spaces
fmt_copy[2] = 0;
} else {
fmt_copy[1] = 0;
}
safe_strncat(fmt_copy, sizeof(fmt_copy), &fmt[1], fmt_len - 1);
safe_strncat(fmt_copy, sizeof(fmt_copy), "d", 1);
char tmp[32];
snprintf(tmp, sizeof(tmp), fmt_copy, field);
safe_strncat(dst, dst_len, tmp, 0xff);
}
static void assemble_filename(MuxerContext *const ctx, char *const filename,
const int filename_size,
const Dav1dPictureParameters *const p)
{
filename[0] = 0;
const int framenum = ctx->framenum++;
assert(ctx->filename);
const char *ptr = ctx->filename, *iptr;
while ((iptr = strchr(ptr, '%'))) {
safe_strncat(filename, filename_size, ptr, (int) (iptr - ptr));
ptr = iptr;
const char *iiptr = &iptr[1]; // skip '%'
while (*iiptr >= '0' && *iiptr <= '9')
iiptr++; // skip length indicators
switch (*iiptr) {
case 'w':
assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->w);
break;
case 'h':
assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), p->h);
break;
case 'n':
assemble_field(filename, filename_size, ptr, (int) (iiptr - ptr), framenum);
break;
default:
safe_strncat(filename, filename_size, "%", 1);
ptr = &iptr[1];
continue;
}
ptr = &iiptr[1];
}
safe_strncat(filename, filename_size, ptr, 0xff);
}
int output_write(MuxerContext *const ctx, Dav1dPicture *const p) {
int res;
if (ctx->one_file_per_frame && ctx->impl->write_header) {
char filename[1024];
assemble_filename(ctx, filename, sizeof(filename), &p->p);
res = ctx->impl->write_header(ctx->data, filename, &p->p, ctx->fps);
if (res < 0)
return res;
}
if ((res = ctx->impl->write_picture(ctx->data, p)) < 0)
return res;
if (ctx->one_file_per_frame && ctx->impl->write_trailer)
ctx->impl->write_trailer(ctx->data);
return 0;
}
void output_close(MuxerContext *const ctx) {
if (ctx->impl->write_trailer)
if (!ctx->one_file_per_frame && ctx->impl->write_trailer)
ctx->impl->write_trailer(ctx->data);
free(ctx);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment