Commit 94cba3150c for ffmpeg

commit 94cba3150c6b83057ed48b833689adc806715cc0
Author: James Almer <jamrial@gmail.com>
Date:   Sat Oct 17 15:25:26 2026 -0300

    avcodec/bsf/jpegapp: use an AVOption of binary type for the sidecar

    Opening a file for this is hacky and the tests added for it are broken on Windows.

    Signed-off-by: James Almer <jamrial@gmail.com>

diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi
index 47bb75892b..cc1cd453a7 100644
--- a/doc/bitstream_filters.texi
+++ b/doc/bitstream_filters.texi
@@ -635,7 +635,7 @@ the two bytes immediately following SOI, that APP0 segment is dropped.
 APP0 segments preceded by fill bytes or another segment are retained.
 An APP1-only payload keeps the encoder APP0: SOI, APP1, APP0, DQT, ...

-The filter requires @option{filename}. Extra unused sidecar bytes are
+The filter requires @option{sidecar}. Extra unused sidecar bytes are
 ignored; a packet without a matching record is an error.
 Records follow packets in arrival order. Flushing the filter does not
 rewind the sidecar, so a reused context needs a sidecar positioned for
@@ -645,12 +645,9 @@ by that filter context. To resume, create a new context with a sidecar
 matching the remaining packets. Flushing does not clear this error.

 @example
-ffmpeg -i in.mp4 -c:v mjpeg -bsf:v jpegapp=filename=app.bin frame_%d.jpg
+ffmpeg -i in.mp4 -c:v mjpeg -bsf:v jpegapp=sidecar=00000004DEADBEEF frame_%d.jpg
 @end example

-On Windows, escape the drive colon in the BSF option:
-@code{filename='E\:/path/app.bin'}.
-
 @section mjpeg2jpeg

 Convert MJPEG/AVI1 packets to full JPEG/JFIF packets.
diff --git a/libavcodec/bsf/jpegapp.c b/libavcodec/bsf/jpegapp.c
index c75822dfb6..8024b77d46 100644
--- a/libavcodec/bsf/jpegapp.c
+++ b/libavcodec/bsf/jpegapp.c
@@ -56,16 +56,16 @@

 #include "libavcodec/bsf.h"
 #include "libavcodec/bsf_internal.h"
+#include "libavcodec/bytestream.h"
 #include "libavcodec/mjpeg.h"

 #define JPEGAPP_MAX_RECORD (16 * 1024 * 1024)

 typedef struct JPEGAppContext {
     const AVClass *class;
-    char *filename;
-    FILE *file;
-    uint8_t *buf;
-    int buf_size;
+    GetByteContext gb;
+    uint8_t *sidecar;
+    int sidecar_len;
     int read_error;
     int64_t packet_index;
 } JPEGAppContext;
@@ -99,84 +99,43 @@ static int jpegapp_init(AVBSFContext *ctx)
 {
     JPEGAppContext *s = ctx->priv_data;

-    if (!s->filename || !s->filename[0]) {
-        av_log(ctx, AV_LOG_ERROR, "Option filename is required\n");
+    if (s->sidecar_len <= 0) {
+        av_log(ctx, AV_LOG_ERROR, "Option sidecar is required\n");
         return AVERROR(EINVAL);
     }

-    s->file = avpriv_fopen_utf8(s->filename, "rb");
-    if (!s->file) {
-        int err = errno ? AVERROR(errno) : AVERROR(EIO);
-        av_log(ctx, AV_LOG_ERROR, "Could not open '%s': %s\n",
-               s->filename, av_err2str(err));
-        return err;
-    }
-    return 0;
-}
+    bytestream2_init(&s->gb, s->sidecar, s->sidecar_len);

-static void jpegapp_close(AVBSFContext *ctx)
-{
-    JPEGAppContext *s = ctx->priv_data;
-
-    if (s->file) {
-        fclose(s->file);
-        s->file = NULL;
-    }
-    av_freep(&s->buf);
-    s->buf_size = 0;
+    return 0;
 }

 static int jpegapp_read_record(AVBSFContext *ctx, int *out_size)
 {
     JPEGAppContext *s = ctx->priv_data;
-    uint8_t hdr[4];
     uint32_t n;
-    size_t got;
     int ret;

-    errno = 0;
-    got = fread(hdr, 1, 4, s->file);
-    ret = errno;
-    if (got == 0 && feof(s->file) && !ferror(s->file)) {
+    if (!bytestream2_get_bytes_left(&s->gb)) {
         av_log(ctx, AV_LOG_ERROR,
                "jpegapp: no sidecar record for packet %"PRId64"\n",
                s->packet_index);
         return AVERROR_INVALIDDATA;
     }
-    if (got != 4) {
-        if (ferror(s->file)) {
-            av_log(ctx, AV_LOG_ERROR, "jpegapp: sidecar read failed\n");
-            return ret ? AVERROR(ret) : AVERROR(EIO);
-        }
+    if (bytestream2_get_bytes_left(&s->gb) < 4) {
         av_log(ctx, AV_LOG_ERROR, "jpegapp: truncated length prefix\n");
         return AVERROR_INVALIDDATA;
     }
-    n = AV_RB32(hdr);
+    n = bytestream2_get_be32(&s->gb);
     if (n > JPEGAPP_MAX_RECORD) {
         av_log(ctx, AV_LOG_ERROR, "jpegapp: record length %u too large\n", n);
         return AVERROR_INVALIDDATA;
     }
-    if (n > (uint32_t)s->buf_size) {
-        ret = av_reallocp(&s->buf, n);
-        if (ret < 0) {
-            s->buf_size = 0;
-            return ret;
-        }
-        s->buf_size = n;
-    }
     if (n) {
-        errno = 0;
-        got = fread(s->buf, 1, n, s->file);
-        ret = errno;
-        if (got != n) {
-            if (ferror(s->file)) {
-                av_log(ctx, AV_LOG_ERROR, "jpegapp: sidecar read failed\n");
-                return ret ? AVERROR(ret) : AVERROR(EIO);
-            }
+        if (n > bytestream2_get_bytes_left(&s->gb)) {
             av_log(ctx, AV_LOG_ERROR, "jpegapp: truncated record payload\n");
             return AVERROR_INVALIDDATA;
         }
-        ret = jpegapp_validate_payload(s->buf, n);
+        ret = jpegapp_validate_payload(s->gb.buffer, n);
         if (ret < 0) {
             av_log(ctx, AV_LOG_ERROR,
                    "jpegapp: payload is not a sequence of APP0..APP15 segments\n");
@@ -213,7 +172,7 @@ static int jpegapp_filter(AVBSFContext *ctx, AVPacket *out)
     }

     src_off = 2;
-    if (rec_size >= 2 && s->buf[0] == 0xff && s->buf[1] == APP0 &&
+    if (rec_size >= 2 && s->gb.buffer[0] == 0xff && s->gb.buffer[1] == APP0 &&
         in->size >= 4 && in->data[2] == 0xff && in->data[3] == APP0) {
         int skip = jpeg_app_segment_len(in->data + 2, in->size - 2);
         if (skip < 0) {
@@ -243,7 +202,7 @@ static int jpegapp_filter(AVBSFContext *ctx, AVPacket *out)
         goto fail;

     memcpy(out->data, in->data, 2);
-    memcpy(out->data + 2, s->buf, rec_size);
+    bytestream2_get_buffer(&s->gb, out->data + 2, rec_size);
     memcpy(out->data + 2 + rec_size, in->data + src_off, in->size - src_off);
     av_packet_free(&in);
     return 0;
@@ -258,10 +217,8 @@ fail:
 #define OFFSET(x) offsetof(JPEGAppContext, x)
 #define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM)
 static const AVOption jpegapp_options[] = {
-    { "filename", "sidecar of u32be-length + APP-segment records", OFFSET(filename),
-      AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
-    { "file",     "sidecar of u32be-length + APP-segment records", OFFSET(filename),
-      AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
+    { "sidecar", "sidecar of u32be-length + APP-segment records", OFFSET(sidecar),
+      AV_OPT_TYPE_BINARY, .flags = FLAGS },
     { NULL },
 };

@@ -282,6 +239,5 @@ const FFBitStreamFilter ff_jpegapp_bsf = {
     .p.priv_class   = &jpegapp_class,
     .priv_data_size = sizeof(JPEGAppContext),
     .init           = jpegapp_init,
-    .close          = jpegapp_close,
     .filter         = jpegapp_filter,
 };
diff --git a/libavcodec/version.h b/libavcodec/version.h
index d0980e28de..ef9018e70b 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -30,7 +30,7 @@
 #include "version_major.h"

 #define LIBAVCODEC_VERSION_MINOR  20
-#define LIBAVCODEC_VERSION_MICRO 100
+#define LIBAVCODEC_VERSION_MICRO 101

 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \
diff --git a/tests/fate/jpegapp.mak b/tests/fate/jpegapp.mak
index 995189af11..1ec4b41d26 100644
--- a/tests/fate/jpegapp.mak
+++ b/tests/fate/jpegapp.mak
@@ -5,34 +5,28 @@

 FATE_JPEGAPP-$(call ALLYES, SMJPEG_DEMUXER JPEGAPP_BSF MJPEG_MUXER FILE_PROTOCOL) += fate-bsf-jpegapp
 fate-bsf-jpegapp: CMD = \
-    cleanfiles="$${cleanfiles} $${outfile}.bin"; \
-    printf "\\000\\000\\000\\010\\377\\341\\000\\006FATE\\000\\000\\000\\000\\000\\000\\000\\010\\377\\340\\000\\006FATE" > "$${outfile}.bin"; \
     md5 -i $(TARGET_SAMPLES)/smjpeg/scenwin.mjpg -map 0:v:0 -frames:v 3 -c:v copy \
-        -bsf:v "jpegapp=filename=$$(target_path "$${outfile}.bin" | sed "s/:/\\\\\\\\:/g")" -fflags +bitexact -f mjpeg
+        -bsf:v "jpegapp=sidecar=00000008FFE10006464154450000000000000008FFE0000646415445" -fflags +bitexact -f mjpeg

 # One record for three packets: the second packet must fail instead of
 # silently passing through without its metadata.
-FATE_JPEGAPP-$(call ALLYES, SMJPEG_DEMUXER JPEGAPP_BSF MJPEG_MUXER FILE_PROTOCOL) += fate-bsf-jpegapp-missing
+FATE_JPEGAPP-$(call ALLYES, SMJPEG_DEMUXER JPEGAPP_BSF NULL_MUXER FILE_PROTOCOL) += fate-bsf-jpegapp-missing
 fate-bsf-jpegapp-missing: CMP = grep
 fate-bsf-jpegapp-missing: REF = jpegapp: no sidecar record for packet 1
 fate-bsf-jpegapp-missing: CMD = \
-    cleanfiles="$${cleanfiles} $${outfile}.bin $${outfile}.mjpeg"; \
-    printf "\\000\\000\\000\\000" > "$${outfile}.bin"; \
     ffmpeg -xerror -i $(TARGET_SAMPLES)/smjpeg/scenwin.mjpg -map 0:v:0 -frames:v 3 -c:v copy \
-        -bsf:v "jpegapp=filename=$$(target_path "$${outfile}.bin" | sed "s/:/\\\\\\\\:/g")" \
-        -fflags +bitexact -f mjpeg -y "$$(target_path "$${outfile}.mjpeg")" ; \
+        -bsf:v "jpegapp=sidecar=00000000" \
+        -fflags +bitexact -f null -; \
     test $$? -ne 0

-# A record longer than the 16 MiB limit must be rejected.
-FATE_JPEGAPP-$(call ALLYES, SMJPEG_DEMUXER JPEGAPP_BSF MJPEG_MUXER FILE_PROTOCOL) += fate-bsf-jpegapp-oversize
+# A record longer than the 16 MiB limit zmust be rejected.
+FATE_JPEGAPP-$(call ALLYES, SMJPEG_DEMUXER JPEGAPP_BSF NULL_MUXER FILE_PROTOCOL) += fate-bsf-jpegapp-oversize
 fate-bsf-jpegapp-oversize: CMP = grep
 fate-bsf-jpegapp-oversize: REF = jpegapp: record length 16777217 too large
 fate-bsf-jpegapp-oversize: CMD = \
-    cleanfiles="$${cleanfiles} $${outfile}.bin $${outfile}.mjpeg"; \
-    printf "\\001\\000\\000\\001\\377\\341\\000\\006FATE" > "$${outfile}.bin"; \
     ffmpeg -xerror -i $(TARGET_SAMPLES)/smjpeg/scenwin.mjpg -map 0:v:0 -frames:v 3 -c:v copy \
-        -bsf:v "jpegapp=filename=$$(target_path "$${outfile}.bin" | sed "s/:/\\\\\\\\:/g")" \
-        -fflags +bitexact -f mjpeg -y "$$(target_path "$${outfile}.mjpeg")" ; \
+        -bsf:v "jpegapp=sidecar=01000001FFE1000646415445" \
+        -fflags +bitexact -f null -; \
     test $$? -ne 0

 # Without -xerror, subsequent packets must still fail after an oversized
@@ -40,9 +34,7 @@ fate-bsf-jpegapp-oversize: CMD = \
 # after the oversized prefix would form valid records if reading resumed.
 FATE_JPEGAPP-$(call ALLYES, SMJPEG_DEMUXER JPEGAPP_BSF MJPEG_MUXER FILE_PROTOCOL) += fate-bsf-jpegapp-oversize-continuation
 fate-bsf-jpegapp-oversize-continuation: CMD = \
-    cleanfiles="$${cleanfiles} $${outfile}.bin"; \
-    printf "\\000\\000\\000\\000\\001\\000\\000\\001\\000\\000\\000\\010\\377\\341\\000\\006FATE\\000\\000\\000\\000" > "$${outfile}.bin"; \
     md5 -i $(TARGET_SAMPLES)/smjpeg/scenwin.mjpg -map 0:v:0 -frames:v 4 -c:v copy \
-        -bsf:v "jpegapp=filename=$$(target_path "$${outfile}.bin" | sed "s/:/\\\\\\\\:/g")" -fflags +bitexact -f mjpeg
+        -bsf:v "jpegapp=sidecar=000000000100000100000008ffe100064641544500000000" -fflags +bitexact -f mjpeg

 FATE_SAMPLES_FFMPEG += $(FATE_JPEGAPP-yes)