Skip to content

Commit 5f33896

Browse files
authored
Binding to AVChapter and chapters functions in Container class
1 parent a2a7996 commit 5f33896

File tree

5 files changed

+79
-4
lines changed

5 files changed

+79
-4
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fate-suite:
2828
rsync -vrltLW rsync://fate-suite.ffmpeg.org/fate-suite/ tests/assets/fate-suite/
2929

3030
lint:
31-
$(PIP) install -U ruff isort pillow numpy mypy==1.16.1 pytest
31+
$(PIP) install -U ruff isort pillow numpy mypy==1.17.1 pytest
3232
ruff format --check av examples tests setup.py
3333
isort --check-only --diff av examples tests
3434
mypy av tests

av/container/core.pyi

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from enum import Flag, IntEnum
22
from fractions import Fraction
33
from pathlib import Path
44
from types import TracebackType
5-
from typing import Any, Callable, ClassVar, Literal, Type, cast, overload
5+
from typing import Any, Callable, ClassVar, Literal, Type, TypedDict, cast, overload
66

77
from av.codec.hwaccel import HWAccel
88
from av.format import ContainerFormat
@@ -67,6 +67,13 @@ class AudioCodec(IntEnum):
6767
pcm_u8 = cast(int, ...)
6868
pcm_vidc = cast(int, ...)
6969

70+
class _Chapter(TypedDict):
71+
id: int
72+
start: int
73+
end: int
74+
time_base: Fraction | None
75+
metadata: dict[str, str]
76+
7077
class Container:
7178
writeable: bool
7279
name: str
@@ -86,7 +93,6 @@ class Container:
8693
open_timeout: Real | None
8794
read_timeout: Real | None
8895
flags: int
89-
9096
def __enter__(self) -> Container: ...
9197
def __exit__(
9298
self,
@@ -96,6 +102,7 @@ class Container:
96102
) -> bool: ...
97103
def set_timeout(self, timeout: Real | None) -> None: ...
98104
def start_timeout(self) -> None: ...
105+
def chapters(self) -> list[_Chapter]: ...
99106

100107
@overload
101108
def open(

av/container/core.pyx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ from av.container.output cimport OutputContainer
1515
from av.container.pyio cimport pyio_close_custom_gil, pyio_close_gil
1616
from av.error cimport err_check, stash_exception
1717
from av.format cimport build_container_format
18-
from av.utils cimport avdict_to_dict
18+
from av.utils cimport avdict_to_dict, avrational_to_fraction
1919

2020
from av.dictionary import Dictionary
2121
from av.logging import Capture as LogCapture
@@ -330,6 +330,22 @@ cdef class Container:
330330
self._assert_open()
331331
self.ptr.flags = value
332332

333+
def chapters(self):
334+
self._assert_open()
335+
cdef list result = []
336+
cdef int i
337+
338+
for i in range(self.ptr.nb_chapters):
339+
ch = self.ptr.chapters[i]
340+
result.append({
341+
"id": ch.id,
342+
"start": ch.start,
343+
"end": ch.end,
344+
"time_base": avrational_to_fraction(&ch.time_base),
345+
"metadata": avdict_to_dict(ch.metadata, self.metadata_encoding, self.metadata_errors),
346+
})
347+
return result
348+
333349
def open(
334350
file,
335351
mode=None,

include/libavformat/avformat.pxd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ cdef extern from "libavformat/avformat.h" nogil:
4444
AVRational r_frame_rate
4545
AVRational sample_aspect_ratio
4646

47+
cdef struct AVChapter:
48+
int id
49+
int64_t start
50+
int64_t end
51+
AVRational time_base
52+
AVDictionary *metadata
53+
54+
4755
# http://ffmpeg.org/doxygen/trunk/structAVIOContext.html
4856
cdef struct AVIOContext:
4957
unsigned char* buffer
@@ -173,6 +181,9 @@ cdef extern from "libavformat/avformat.h" nogil:
173181
unsigned int nb_streams
174182
AVStream **streams
175183

184+
unsigned int nb_chapters
185+
AVChapter **chapters
186+
176187
AVInputFormat *iformat
177188
AVOutputFormat *oformat
178189

tests/test_chapters.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from fractions import Fraction
2+
3+
import av
4+
5+
from .common import fate_suite
6+
7+
8+
def test_chapters() -> None:
9+
expected = [
10+
{
11+
"id": 1,
12+
"start": 0,
13+
"end": 5000,
14+
"time_base": Fraction(1, 1000),
15+
"metadata": {"title": "start"},
16+
},
17+
{
18+
"id": 2,
19+
"start": 5000,
20+
"end": 10500,
21+
"time_base": Fraction(1, 1000),
22+
"metadata": {"title": "Five Seconds"},
23+
},
24+
{
25+
"id": 3,
26+
"start": 10500,
27+
"end": 15000,
28+
"time_base": Fraction(1, 1000),
29+
"metadata": {"title": "Ten point 5 seconds"},
30+
},
31+
{
32+
"id": 4,
33+
"start": 15000,
34+
"end": 19849,
35+
"time_base": Fraction(1, 1000),
36+
"metadata": {"title": "15 sec - over soon"},
37+
},
38+
]
39+
path = fate_suite("vorbis/vorbis_chapter_extension_demo.ogg")
40+
with av.open(path) as container:
41+
assert container.chapters() == expected

0 commit comments

Comments
 (0)