Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Thilo Borgmann via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: Thilo Borgmann <thilo.borgmann@mail.de>
Subject: [FFmpeg-devel] [PATCH v13 3/8] avcodec/bsf: Add awebp2webp bitstream filter
Date: Fri, 21 Jun 2024 12:43:18 +0200
Message-ID: <20240621104323.92453-4-thilo.borgmann@mail.de> (raw)
In-Reply-To: <20240621104323.92453-1-thilo.borgmann@mail.de>

From: Thilo Borgmann via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>

Splits a packet containing a webp animations into
one non-compliant packet per frame of the animation.
Skips RIFF and WEBP chunks for those packets except
for the first. Copyies ICC, EXIF and XMP chunks first
into each of the packets except for the first.
---
 configure                      |   1 +
 libavcodec/bitstream_filters.c |   1 +
 libavcodec/bsf/Makefile        |   1 +
 libavcodec/bsf/awebp2webp.c    | 353 +++++++++++++++++++++++++++++++++
 4 files changed, 356 insertions(+)
 create mode 100644 libavcodec/bsf/awebp2webp.c

diff --git a/configure b/configure
index 3bca638459..1b6e56496f 100755
--- a/configure
+++ b/configure
@@ -3437,6 +3437,7 @@ aac_adtstoasc_bsf_select="adts_header mpeg4audio"
 av1_frame_merge_bsf_select="cbs_av1"
 av1_frame_split_bsf_select="cbs_av1"
 av1_metadata_bsf_select="cbs_av1"
+awebp2webp_bsf_select=""
 dts2pts_bsf_select="cbs_h264 h264parse"
 eac3_core_bsf_select="ac3_parser"
 evc_frame_merge_bsf_select="evcparse"
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index 138246c50e..1f6471f4f3 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -28,6 +28,7 @@ extern const FFBitStreamFilter ff_aac_adtstoasc_bsf;
 extern const FFBitStreamFilter ff_av1_frame_merge_bsf;
 extern const FFBitStreamFilter ff_av1_frame_split_bsf;
 extern const FFBitStreamFilter ff_av1_metadata_bsf;
+extern const FFBitStreamFilter ff_awebp2webp_bsf;
 extern const FFBitStreamFilter ff_chomp_bsf;
 extern const FFBitStreamFilter ff_dump_extradata_bsf;
 extern const FFBitStreamFilter ff_dca_core_bsf;
diff --git a/libavcodec/bsf/Makefile b/libavcodec/bsf/Makefile
index fb70ad0c21..48c67dd210 100644
--- a/libavcodec/bsf/Makefile
+++ b/libavcodec/bsf/Makefile
@@ -5,6 +5,7 @@ OBJS-$(CONFIG_AAC_ADTSTOASC_BSF)          += bsf/aac_adtstoasc.o
 OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF)        += bsf/av1_frame_merge.o
 OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF)        += bsf/av1_frame_split.o
 OBJS-$(CONFIG_AV1_METADATA_BSF)           += bsf/av1_metadata.o
+OBJS-$(CONFIG_AWEBP2WEBP_BSF)             += bsf/awebp2webp.o
 OBJS-$(CONFIG_CHOMP_BSF)                  += bsf/chomp.o
 OBJS-$(CONFIG_DCA_CORE_BSF)               += bsf/dca_core.o
 OBJS-$(CONFIG_DTS2PTS_BSF)                += bsf/dts2pts.o
diff --git a/libavcodec/bsf/awebp2webp.c b/libavcodec/bsf/awebp2webp.c
new file mode 100644
index 0000000000..69a0156167
--- /dev/null
+++ b/libavcodec/bsf/awebp2webp.c
@@ -0,0 +1,353 @@
+/*
+ * Animated WebP into non-compliant WebP bitstream filter
+ * Copyright (c) 2024 Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Animated WebP into non-compliant WebP bitstream filter
+ * Splits a packet containing a webp animations into
+ * one non-compliant packet per frame of the animation.
+ * Skips RIFF and WEBP chunks for those packets except
+ * for the first. Copyies ICC, EXIF and XMP chunks first
+ * into each of the packets except for the first.
+ * @author Thilo Borgmann <thilo.borgmann _at_ mail.de>
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "codec_id.h"
+#include "bytestream.h"
+#include "libavutil/error.h"
+#include "libavutil/mem.h"
+
+#include "bsf.h"
+#include "bsf_internal.h"
+#include "packet.h"
+
+#define VP8X_FLAG_ANIMATION             0x02
+#define VP8X_FLAG_XMP_METADATA          0x04
+#define VP8X_FLAG_EXIF_METADATA         0x08
+#define VP8X_FLAG_ALPHA                 0x10
+#define VP8X_FLAG_ICC                   0x20
+
+typedef struct WEBPBSFContext {
+    const AVClass *class;
+    GetByteContext gb;
+
+    AVPacket *last_pkt;
+    uint8_t *last_iccp;
+    uint8_t *last_exif;
+    uint8_t *last_xmp;
+
+    int iccp_size;
+    int exif_size;
+    int xmp_size;
+
+    int add_iccp;
+    int add_exif;
+    int add_xmp;
+
+    uint64_t last_pts;
+} WEBPBSFContext;
+
+static int save_chunk(WEBPBSFContext *ctx, uint8_t **buf, int *buf_size, uint32_t chunk_size)
+{
+    if (*buf || !buf_size || !chunk_size)
+        return 0;
+
+    *buf = av_malloc(chunk_size + 8);
+    if (!*buf)
+        return AVERROR(ENOMEM);
+
+    *buf_size = chunk_size + 8;
+
+    bytestream2_seek(&ctx->gb, -8, SEEK_CUR);
+    bytestream2_get_buffer(&ctx->gb, *buf, chunk_size + 8);
+
+    return 0;
+}
+
+static int awebp2webp_filter(AVBSFContext *ctx, AVPacket *out)
+{
+    WEBPBSFContext *s = ctx->priv_data;
+    AVPacket *in;
+    uint32_t chunk_type;
+    uint32_t chunk_size;
+    int64_t packet_start;
+    int64_t packet_end;
+    int64_t out_off;
+    int ret       = 0;
+    int is_frame  = 0;
+    int key_frame = 0;
+    int delay     = 0;
+    int out_size  = 0;
+    int has_anim  = 0;
+
+    // initialize for new packet
+    if (!bytestream2_size(&s->gb)) {
+        if (s->last_pkt)
+            av_packet_unref(s->last_pkt);
+
+        ret = ff_bsf_get_packet(ctx, &s->last_pkt);
+        if (ret < 0)
+            goto fail;
+
+        bytestream2_init(&s->gb, s->last_pkt->data, s->last_pkt->size);
+
+        av_freep(&s->last_iccp);
+        av_freep(&s->last_exif);
+        av_freep(&s->last_xmp);
+
+        // read packet scanning for metadata && animation
+        while (bytestream2_get_bytes_left(&s->gb) > 0) {
+            chunk_type = bytestream2_get_le32(&s->gb);
+            chunk_size = bytestream2_get_le32(&s->gb);
+
+            if (chunk_size == UINT32_MAX)
+                return AVERROR_INVALIDDATA;
+            chunk_size += chunk_size & 1;
+
+            if (!bytestream2_get_bytes_left(&s->gb) ||
+                 bytestream2_get_bytes_left(&s->gb) < chunk_size)
+                break;
+
+            if (chunk_type == MKTAG('R', 'I', 'F', 'F') && chunk_size > 4) {
+                chunk_size = 4;
+            }
+
+            switch (chunk_type) {
+            case MKTAG('I', 'C', 'C', 'P'):
+                if (!s->last_iccp) {
+                    ret = save_chunk(s, &s->last_iccp, &s->iccp_size, chunk_size);
+                    if (ret < 0)
+                        goto fail;
+                } else {
+                    bytestream2_skip(&s->gb, chunk_size);
+                }
+                break;
+
+            case MKTAG('E', 'X', 'I', 'F'):
+                if (!s->last_exif) {
+                    ret = save_chunk(s, &s->last_exif, &s->exif_size, chunk_size);
+                    if (ret < 0)
+                        goto fail;
+                } else {
+                    bytestream2_skip(&s->gb, chunk_size);
+                }
+                break;
+
+            case MKTAG('X', 'M', 'P', ' '):
+                if (!s->last_xmp) {
+                    ret = save_chunk(s, &s->last_xmp, &s->xmp_size, chunk_size);
+                    if (ret < 0)
+                        goto fail;
+                } else {
+                    bytestream2_skip(&s->gb, chunk_size);
+                }
+                break;
+
+            case MKTAG('A', 'N', 'M', 'F'):
+                has_anim = 1;
+                bytestream2_skip(&s->gb, chunk_size);
+                break;
+
+            default:
+                bytestream2_skip(&s->gb, chunk_size);
+                break;
+            }
+        }
+
+        // if no animation is found, pass-through the packet
+        if (!has_anim) {
+            av_packet_move_ref(out, s->last_pkt);
+            return 0;
+        }
+
+        // reset bytestream to beginning of packet
+        bytestream2_init(&s->gb, s->last_pkt->data, s->last_pkt->size);
+    }
+
+    // packet read completely, reset and ask for next packet
+    if (!bytestream2_get_bytes_left(&s->gb)) {
+        if (s->last_pkt)
+            av_packet_free(&s->last_pkt);
+        // reset to empty buffer for reinit with next real packet
+        bytestream2_init(&s->gb, NULL, 0);
+        return AVERROR(EAGAIN);
+    }
+
+    // start reading from packet until sub packet ready
+    packet_start = bytestream2_tell(&s->gb);
+    s->add_iccp  = 1;
+    s->add_exif  = 1;
+    s->add_xmp   = 1;
+
+    while (bytestream2_get_bytes_left(&s->gb) > 0) {
+        chunk_type = bytestream2_get_le32(&s->gb);
+        chunk_size = bytestream2_get_le32(&s->gb);
+
+        if (chunk_size == UINT32_MAX)
+            return AVERROR_INVALIDDATA;
+        chunk_size += chunk_size & 1;
+
+        if (!bytestream2_get_bytes_left(&s->gb) ||
+             bytestream2_get_bytes_left(&s->gb) < chunk_size)
+            break;
+
+        if (chunk_type == MKTAG('R', 'I', 'F', 'F') && chunk_size > 4) {
+            chunk_size = 4;
+            key_frame = 1;
+        }
+
+        switch (chunk_type) {
+        case MKTAG('I', 'C', 'C', 'P'):
+            s->add_iccp = 0;
+            bytestream2_skip(&s->gb, chunk_size);
+            break;
+
+        case MKTAG('E', 'X', 'I', 'F'):
+            s->add_exif = 0;
+            bytestream2_skip(&s->gb, chunk_size);
+            break;
+
+        case MKTAG('X', 'M', 'P', ' '):
+            s->add_xmp = 0;
+            bytestream2_skip(&s->gb, chunk_size);
+            break;
+
+        case MKTAG('V', 'P', '8', ' '):
+            if (is_frame) {
+                bytestream2_seek(&s->gb, -8, SEEK_CUR);
+                goto flush;
+            }
+            bytestream2_skip(&s->gb, chunk_size);
+            is_frame = 1;
+            break;
+
+        case MKTAG('V', 'P', '8', 'L'):
+            if (is_frame) {
+                bytestream2_seek(&s->gb, -8, SEEK_CUR);
+                goto flush;
+            }
+            bytestream2_skip(&s->gb, chunk_size);
+            is_frame = 1;
+            break;
+
+        case MKTAG('A', 'N', 'M', 'F'):
+            if (is_frame) {
+                bytestream2_seek(&s->gb, -8, SEEK_CUR);
+                goto flush;
+            }
+            bytestream2_skip(&s->gb, 12);
+            delay = bytestream2_get_le24(&s->gb);
+            if (!delay)
+                delay = s->last_pkt->duration;
+            bytestream2_skip(&s->gb, 1);
+            break;
+
+        default:
+            bytestream2_skip(&s->gb, chunk_size);
+            break;
+        }
+
+        packet_end = bytestream2_tell(&s->gb);
+    }
+
+flush:
+    // generate packet from data read so far
+    out_size = packet_end - packet_start;
+    out_off  = 0;
+
+    if (s->add_iccp && s->last_iccp)
+        out_size += s->iccp_size;
+    if (s->add_exif && s->last_exif)
+        out_size += s->exif_size;
+    if (s->add_xmp && s->last_xmp)
+        out_size += s->xmp_size;
+
+    ret = av_new_packet(out, out_size);
+    if (ret < 0)
+        goto fail;
+
+    // copy metadata
+    if (s->add_iccp && s->last_iccp) {
+        memcpy(out->data + out_off, s->last_iccp, s->iccp_size);
+        out_off += s->iccp_size;
+    }
+    if (s->add_exif && s->last_exif) {
+        memcpy(out->data + out_off, s->last_exif, s->exif_size);
+        out_off += s->exif_size;
+    }
+    if (s->add_xmp && s->last_xmp) {
+        memcpy(out->data + out_off, s->last_xmp, s->xmp_size);
+        out_off += s->xmp_size;
+    }
+
+    // copy frame data
+    memcpy(out->data + out_off, s->last_pkt->data + packet_start, packet_end - packet_start);
+
+    if (key_frame)
+        out->flags |= AV_PKT_FLAG_KEY;
+    else
+        out->flags &= ~AV_PKT_FLAG_KEY;
+
+    out->pts          = s->last_pts;
+    out->dts          = out->pts;
+    out->pos          = packet_start;
+    out->duration     = delay;
+    out->stream_index = s->last_pkt->stream_index;
+    out->time_base    = s->last_pkt->time_base;
+
+    s->last_pts += (delay > 0) ? delay : 1;
+
+    key_frame = 0;
+
+    return 0;
+
+fail:
+    if (ret < 0) {
+        av_packet_unref(out);
+        return ret;
+    }
+    av_packet_free(&in);
+
+    return ret;
+}
+
+static void awebp2webp_close(AVBSFContext *ctx)
+{
+    WEBPBSFContext *s = ctx->priv_data;
+    av_freep(&s->last_iccp);
+    av_freep(&s->last_exif);
+    av_freep(&s->last_xmp);
+}
+
+static const enum AVCodecID codec_ids[] = {
+    AV_CODEC_ID_WEBP, AV_CODEC_ID_NONE,
+};
+
+const FFBitStreamFilter ff_awebp2webp_bsf = {
+    .p.name         = "awebp2webp",
+    .p.codec_ids    = codec_ids,
+    .priv_data_size = sizeof(WEBPBSFContext),
+    .filter         = awebp2webp_filter,
+    .close          = awebp2webp_close,
+};
-- 
2.39.3 (Apple Git-146)

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  parent reply	other threads:[~2024-06-21 10:44 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-06-21 10:43 [FFmpeg-devel] [PATCH v13 0/8] [WIP] webp: add support for animated WebP decoding Thilo Borgmann via ffmpeg-devel
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 1/8] avcodec/webp: remove unused definitions Thilo Borgmann via ffmpeg-devel
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 2/8] avcodec/webp: separate VP8 decoding Thilo Borgmann via ffmpeg-devel
2024-06-21 11:52   ` Anton Khirnov
2024-06-21 10:43 ` Thilo Borgmann via ffmpeg-devel [this message]
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 4/8] libavcodec/webp: add support for animated WebP Thilo Borgmann via ffmpeg-devel
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 5/8] avcodec/webp: make init_canvas_frame static Thilo Borgmann via ffmpeg-devel
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 6/8] libavformat/webp: add WebP demuxer Thilo Borgmann via ffmpeg-devel
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 7/8] fate: add test for animated WebP Thilo Borgmann via ffmpeg-devel
2024-06-21 10:43 ` [FFmpeg-devel] [PATCH v13 8/8] avcodec/webp: export XMP metadata Thilo Borgmann via ffmpeg-devel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240621104323.92453-4-thilo.borgmann@mail.de \
    --to=ffmpeg-devel@ffmpeg.org \
    --cc=thilo.borgmann@mail.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git