Skip to content

Commit 49b2e96

Browse files
committed
Expose frame metadata
Expose `AVFrame.metadata` as a read-only `Frame.metadata` property and cover metadata-producing audio filters. Closes #2342.
1 parent 61e4aa8 commit 49b2e96

4 files changed

Lines changed: 47 additions & 2 deletions

File tree

av/frame.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import cython
22
from cython.cimports.av.error import err_check
33
from cython.cimports.av.opaque import opaque_container
4-
from cython.cimports.av.utils import avrational_to_fraction, to_avrational
4+
from cython.cimports.av.utils import (
5+
avdict_to_dict,
6+
avrational_to_fraction,
7+
to_avrational,
8+
)
59

610
from av.sidedata.sidedata import SideDataContainer
711

@@ -178,6 +182,11 @@ def side_data(self):
178182
self._side_data = SideDataContainer(self)
179183
return self._side_data
180184

185+
@property
186+
def metadata(self):
187+
"""Metadata attached to the frame by FFmpeg."""
188+
return avdict_to_dict(self.ptr.metadata, "utf-8", "strict")
189+
181190
def make_writable(self):
182191
"""
183192
Ensures that the frame data is writable. Copy the data to new buffer if it is not.

av/frame.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class Frame:
1414
side_data: SideData
1515
opaque: object
1616
@property
17+
def metadata(self) -> dict[str, str]: ...
18+
@property
1719
def time(self) -> float | None: ...
1820
@property
1921
def is_corrupt(self) -> bool: ...

scripts/build-deps

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ echo ./configure
6969
--disable-bsfs \
7070
--enable-bsf=chomp,extract_extradata,h264_mp4toannexb,setts \
7171
--disable-filters \
72-
--enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,loudnorm,lutrgb,overlay,palettegen,scale,testsrc,vflip,volume \
72+
--enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,ebur128,loudnorm,lutrgb,overlay,palettegen,scale,testsrc,vflip,volume \
7373
--enable-sse \
7474
--enable-avx \
7575
--enable-avx2 \

tests/test_filters.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,40 @@ def test_audio_buffer_volume_filter(self):
193193

194194
assert np.allclose(input_data * 0.5, output_data)
195195

196+
def test_audio_frame_metadata(self) -> None:
197+
graph = Graph()
198+
graph.link_nodes(
199+
graph.add_abuffer(
200+
format="fltp",
201+
sample_rate=48000,
202+
layout="stereo",
203+
time_base=Fraction(1, 48000),
204+
),
205+
graph.add("ebur128", "metadata=1"),
206+
graph.add("abuffersink"),
207+
).configure()
208+
209+
frame = AudioFrame.from_ndarray(
210+
np.zeros((2, 4800), dtype=np.float32),
211+
format="fltp",
212+
layout="stereo",
213+
)
214+
frame.sample_rate = 48000
215+
frame.time_base = Fraction(1, 48000)
216+
frame.pts = 0
217+
218+
graph.push(frame)
219+
graph.push(None)
220+
output = graph.pull()
221+
metadata = output.metadata
222+
223+
assert "lavfi.r128.I" in metadata
224+
assert "lavfi.r128.LRA" in metadata
225+
226+
# Frame.metadata returns a copy of the underlying AVDictionary.
227+
metadata.clear()
228+
assert "lavfi.r128.I" in output.metadata
229+
196230
def _test_video_buffer(self, graph):
197231
input_container = av.open(format="lavfi", file="color=c=pink:duration=1:r=30")
198232
input_video_stream = input_container.streams.video[0]

0 commit comments

Comments
 (0)