Skip to content

Commit 4d603ec

Browse files
authored
Implement set_chapters method
1 parent 1060894 commit 4d603ec

File tree

3 files changed

+70
-3
lines changed

3 files changed

+70
-3
lines changed

av/container/core.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class AudioCodec(IntEnum):
6767
pcm_u8 = cast(int, ...)
6868
pcm_vidc = cast(int, ...)
6969

70-
class _Chapter(TypedDict):
70+
class Chapter(TypedDict):
7171
id: int
7272
start: int
7373
end: int
@@ -102,7 +102,8 @@ class Container:
102102
) -> bool: ...
103103
def set_timeout(self, timeout: Real | None) -> None: ...
104104
def start_timeout(self) -> None: ...
105-
def chapters(self) -> list[_Chapter]: ...
105+
def chapters(self) -> list[Chapter]: ...
106+
def set_chapters(self, chapters: list[Chapter]) -> None: ...
106107

107108
@overload
108109
def open(

av/container/core.pyx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ 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, avrational_to_fraction
18+
from av.utils cimport (
19+
avdict_to_dict,
20+
avrational_to_fraction,
21+
dict_to_avdict,
22+
to_avrational,
23+
)
1924

2025
from av.dictionary import Dictionary
2126
from av.logging import Capture as LogCapture
@@ -123,6 +128,17 @@ cdef int pyav_io_close_gil(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept
123128

124129
return result
125130

131+
cdef void _free_chapters(lib.AVFormatContext *ctx) noexcept nogil:
132+
cdef int i
133+
if ctx.chapters != NULL:
134+
for i in range(ctx.nb_chapters):
135+
if ctx.chapters[i] != NULL:
136+
if ctx.chapters[i].metadata != NULL:
137+
lib.av_dict_free(&ctx.chapters[i].metadata)
138+
lib.av_freep(<void **>&ctx.chapters[i])
139+
lib.av_freep(<void **>&ctx.chapters)
140+
ctx.nb_chapters = 0
141+
126142

127143
class Flags(Flag):
128144
gen_pts: "Generate missing pts even if it requires parsing future frames." = lib.AVFMT_FLAG_GENPTS
@@ -346,6 +362,39 @@ cdef class Container:
346362
})
347363
return result
348364

365+
def set_chapters(self, chapters):
366+
self._assert_open()
367+
368+
cdef int count = len(chapters)
369+
cdef int i
370+
cdef lib.AVChapter **ch_array
371+
cdef lib.AVChapter *ch
372+
cdef dict entry
373+
374+
with nogil:
375+
_free_chapters(self.ptr)
376+
377+
ch_array = <lib.AVChapter **>lib.av_malloc(count * sizeof(lib.AVChapter *))
378+
if ch_array == NULL:
379+
raise MemoryError("av_malloc failed for chapters")
380+
381+
for i in range(count):
382+
entry = chapters[i]
383+
ch = <lib.AVChapter *>lib.av_malloc(sizeof(lib.AVChapter))
384+
if ch == NULL:
385+
raise MemoryError("av_malloc failed for chapter")
386+
ch.id = entry["id"]
387+
ch.start = <int64_t>entry["start"]
388+
ch.end = <int64_t>entry["end"]
389+
to_avrational(entry["time_base"], &ch.time_base)
390+
ch.metadata = NULL
391+
if "metadata" in entry:
392+
dict_to_avdict(&ch.metadata, entry["metadata"], self.metadata_encoding, self.metadata_errors)
393+
ch_array[i] = ch
394+
395+
self.ptr.nb_chapters = count
396+
self.ptr.chapters = ch_array
397+
349398
def open(
350399
file,
351400
mode=None,

tests/test_chapters.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,20 @@ def test_chapters() -> None:
3939
path = fate_suite("vorbis/vorbis_chapter_extension_demo.ogg")
4040
with av.open(path) as container:
4141
assert container.chapters() == expected
42+
43+
44+
def test_set_chapters() -> None:
45+
chapters: list[av.container.Chapter] = [
46+
{
47+
"id": 1,
48+
"start": 0,
49+
"end": 5000,
50+
"time_base": Fraction(1, 1000),
51+
"metadata": {"title": "start"},
52+
}
53+
]
54+
55+
path = fate_suite("h264/interlaced_crop.mp4")
56+
with av.open(path) as container:
57+
container.set_chapters(chapters)
58+
assert container.chapters() == chapters

0 commit comments

Comments
 (0)