Skip to content

Commit 8c2f1d7

Browse files
Max ChristophDE-AI
authored andcommitted
Implemented set_chapters method
1 parent 8105052 commit 8c2f1d7

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

av/container/core.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class Container:
103103
def set_timeout(self, timeout: Real | None) -> None: ...
104104
def start_timeout(self) -> None: ...
105105
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from fractions import Fraction
22

33
import av
4+
from av.container.core import _Chapter
45

56
from .common import fate_suite
67

@@ -39,3 +40,20 @@ def test_chapters() -> None:
3940
path = fate_suite("vorbis/vorbis_chapter_extension_demo.ogg")
4041
with av.open(path) as container:
4142
assert container.chapters() == expected
43+
44+
45+
def test_set_chapters() -> None:
46+
chapters: list[_Chapter] = [
47+
{
48+
"id": 1,
49+
"start": 0,
50+
"end": 5000,
51+
"time_base": Fraction(1, 1000),
52+
"metadata": {"title": "start"},
53+
}
54+
]
55+
56+
path = fate_suite("h264/interlaced_crop.mp4")
57+
with av.open(path) as container:
58+
container.set_chapters(chapters)
59+
assert container.chapters() == chapters

0 commit comments

Comments
 (0)