mirror of
https://code.videolan.org/videolan/dav1d
synced 2026-06-11 04:03:05 +00:00
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:
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user