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.
This commit is contained in:
Ronald S. Bultje
2022-01-06 18:50:09 +00:00
parent f9bddfff7b
commit 36beb8185d
2 changed files with 117 additions and 7 deletions
+2 -2
View File
@@ -116,9 +116,9 @@ 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 (%%n, %%w or %%h will be filled in for per-frame files)\n"
" --demuxer $name: force demuxer type ('ivf', 'section5' or 'annexb'; default: detect from content)\n"
" --muxer $name: force muxer type (" AVAILABLE_MUXERS "; default: detect from extension)\n"
" --muxer $name: force muxer type (" AVAILABLE_MUXERS "; use 'frame' as prefix to write per-frame files; default: detect from extension; if filename contains %%n, will default to writing per-frame files)\n"
" --quiet/-q: disable status messages\n"
" --frametimes $file: dump frame times to file\n"
" --limit/-l $num: stop decoding after $num frames\n"
+115 -5
View File
@@ -34,6 +34,7 @@
#include <string.h>
#include "common/attributes.h"
#include "common/intops.h"
#include "output/output.h"
#include "output/muxer.h"
@@ -41,6 +42,10 @@
struct MuxerContext {
MuxerPriv *data;
const Muxer *impl;
int one_file_per_frame;
unsigned fps[2];
const char *filename;
int framenum;
};
extern const Muxer null_muxer;
@@ -84,10 +89,12 @@ int output_open(MuxerContext **const c_out,
MuxerContext *c;
unsigned i;
int res;
int name_offset = 0;
if (name) {
name_offset = 5 * !strncmp(name, "frame", 5);
for (i = 0; muxers[i]; i++) {
if (!strcmp(muxers[i]->name, name)) {
if (!strcmp(muxers[i]->name, &name[name_offset])) {
impl = muxers[i];
break;
}
@@ -122,7 +129,25 @@ 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) {
int have_num_pattern = 0;
for (const char *ptr = filename ? strchr(filename, '%') : NULL;
!have_num_pattern && ptr; ptr = strchr(ptr, '%'))
{
ptr++; // skip '%'
while (*ptr >= '0' && *ptr <= '9')
ptr++; // skip length indicators
have_num_pattern = *ptr == 'n';
}
c->one_file_per_frame = name_offset || (!name && have_num_pattern);
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,13 +156,98 @@ 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);
if (!to_copy) return;
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, (int) strlen(tmp));
}
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, (int) strlen(ptr));
}
int output_write(MuxerContext *const ctx, Dav1dPicture *const p) {
const int res = ctx->impl->write_picture(ctx->data, p);
return res < 0 ? res : 0;
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);
}