Skip to content

Commit f462b75

Browse files
authored
Expose AVIndexEntry (#2136)
1 parent 3e86381 commit f462b75

File tree

11 files changed

+295
-0
lines changed

11 files changed

+295
-0
lines changed

av/indexentries.pxd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cimport libav as lib
2+
3+
4+
cdef class IndexEntries:
5+
cdef lib.AVStream *stream_ptr
6+
cdef _init(self, lib.AVStream *ptr)
7+
8+
9+
cdef IndexEntries wrap_index_entries(lib.AVStream *ptr)

av/indexentries.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import cython
2+
import cython.cimports.libav as lib
3+
from cython.cimports.av.indexentry import wrap_index_entry
4+
from cython.cimports.libc.stdint import int64_t
5+
6+
_cinit_bypass_sentinel = cython.declare(object, object())
7+
8+
9+
@cython.cfunc
10+
def wrap_index_entries(ptr: cython.pointer[lib.AVStream]) -> IndexEntries:
11+
obj: IndexEntries = IndexEntries(_cinit_bypass_sentinel)
12+
obj._init(ptr)
13+
return obj
14+
15+
16+
@cython.cclass
17+
class IndexEntries:
18+
"""A sequence-like view of FFmpeg's per-stream index entries.
19+
20+
Exposed as :attr:`~av.stream.Stream.index_entries`.
21+
22+
The index is provided by the demuxer and may be empty or incomplete depending
23+
on the container format. This is useful for fast multi-seek loops (e.g., decoding
24+
at a lower-than-native framerate).
25+
"""
26+
27+
def __cinit__(self, sentinel):
28+
if sentinel is _cinit_bypass_sentinel:
29+
return
30+
raise RuntimeError("cannot manually instantiate IndexEntries")
31+
32+
@cython.cfunc
33+
def _init(self, ptr: cython.pointer[lib.AVStream]):
34+
self.stream_ptr = ptr
35+
36+
def __repr__(self):
37+
return f"<av.IndexEntries[{len(self)}]>"
38+
39+
def __len__(self) -> int:
40+
with cython.nogil:
41+
return lib.avformat_index_get_entries_count(self.stream_ptr)
42+
43+
def __iter__(self):
44+
for i in range(len(self)):
45+
yield self[i]
46+
47+
def __getitem__(self, index):
48+
if isinstance(index, int):
49+
n = len(self)
50+
if index < 0:
51+
index += n
52+
if index < 0 or index >= n:
53+
raise IndexError(f"Index entries {index} out of bounds for size {n}")
54+
55+
c_idx = cython.declare(cython.int, index)
56+
with cython.nogil:
57+
entry = lib.avformat_index_get_entry(self.stream_ptr, c_idx)
58+
59+
if entry == cython.NULL:
60+
raise IndexError("index entry not found")
61+
62+
return wrap_index_entry(entry)
63+
64+
elif isinstance(index, slice):
65+
start, stop, step = index.indices(len(self))
66+
return [self[i] for i in range(start, stop, step)]
67+
68+
else:
69+
raise TypeError("Index must be an integer or a slice")
70+
71+
def search_timestamp(
72+
self, timestamp, *, backward: bool = True, any_frame: bool = False
73+
):
74+
"""Search the underlying index for ``timestamp``.
75+
76+
This wraps FFmpeg's ``av_index_search_timestamp``.
77+
78+
Returns an index into this object, or ``-1`` if no match is found.
79+
"""
80+
c_timestamp = cython.declare(int64_t, timestamp)
81+
flags = cython.declare(cython.int, 0)
82+
83+
if backward:
84+
flags |= lib.AVSEEK_FLAG_BACKWARD
85+
if any_frame:
86+
flags |= lib.AVSEEK_FLAG_ANY
87+
88+
with cython.nogil:
89+
idx = lib.av_index_search_timestamp(self.stream_ptr, c_timestamp, flags)
90+
91+
return idx

av/indexentries.pyi

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from typing import Iterator, overload
2+
3+
from av.indexentry import IndexEntry
4+
5+
class IndexEntries:
6+
def __len__(self) -> int: ...
7+
def __iter__(self) -> Iterator[IndexEntry]: ...
8+
@overload
9+
def __getitem__(self, index: int) -> IndexEntry: ...
10+
@overload
11+
def __getitem__(self, index: slice) -> list[IndexEntry]: ...
12+
def search_timestamp(
13+
self, timestamp, *, backward: bool = True, any_frame: bool = False
14+
) -> int: ...

av/indexentry.pxd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cimport libav as lib
2+
3+
4+
cdef class IndexEntry:
5+
cdef lib.AVIndexEntry *ptr
6+
cdef _init(self, lib.AVIndexEntry *ptr)
7+
8+
9+
cdef IndexEntry wrap_index_entry(lib.AVIndexEntry *ptr)

av/indexentry.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import cython
2+
import cython.cimports.libav as lib
3+
4+
_cinit_bypass_sentinel = cython.declare(object, object())
5+
6+
7+
@cython.cfunc
8+
def wrap_index_entry(ptr: cython.pointer[lib.AVIndexEntry]) -> IndexEntry:
9+
obj: IndexEntry = IndexEntry(_cinit_bypass_sentinel)
10+
obj._init(ptr)
11+
return obj
12+
13+
14+
@cython.cclass
15+
class IndexEntry:
16+
"""A single entry from a stream's index.
17+
18+
This is a thin wrapper around FFmpeg's ``AVIndexEntry``.
19+
20+
The exact meaning of the fields depends on the container/demuxer.
21+
"""
22+
23+
def __cinit__(self, sentinel):
24+
if sentinel is not _cinit_bypass_sentinel:
25+
raise RuntimeError("cannot manually instantiate IndexEntry")
26+
27+
@cython.cfunc
28+
def _init(self, ptr: cython.pointer[lib.AVIndexEntry]):
29+
self.ptr = ptr
30+
31+
def __repr__(self):
32+
return (
33+
f"<av.IndexEntry pos={self.pos} timestamp={self.timestamp} flags={self.flags} "
34+
f"size={self.size} min_distance={self.min_distance}>"
35+
)
36+
37+
@property
38+
def pos(self):
39+
return self.ptr.pos
40+
41+
@property
42+
def timestamp(self):
43+
return self.ptr.timestamp
44+
45+
@property
46+
def flags(self):
47+
return self.ptr.flags
48+
49+
@property
50+
def is_keyframe(self):
51+
return bool(self.ptr.flags & lib.AVINDEX_KEYFRAME)
52+
53+
@property
54+
def is_discard(self):
55+
return bool(self.ptr.flags & lib.AVINDEX_DISCARD_FRAME)
56+
57+
@property
58+
def size(self):
59+
return self.ptr.size
60+
61+
@property
62+
def min_distance(self):
63+
return self.ptr.min_distance

av/indexentry.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class IndexEntry:
2+
pos: int
3+
timestamp: int
4+
flags: int
5+
is_keyframe: bool
6+
is_discard: bool
7+
size: int
8+
min_distance: int

av/stream.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ cimport libav as lib
33
from av.codec.context cimport CodecContext
44
from av.container.core cimport Container
55
from av.frame cimport Frame
6+
from av.indexentries cimport IndexEntries
67
from av.packet cimport Packet
78

89

@@ -16,6 +17,8 @@ cdef class Stream:
1617
# CodecContext attributes.
1718
cdef readonly CodecContext codec_context
1819

20+
cdef readonly IndexEntries index_entries
21+
1922
# Private API.
2023
cdef _init(self, Container, lib.AVStream*, CodecContext)
2124
cdef _finalize_for_output(self)

av/stream.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import cython
44
from cython.cimports import libav as lib
55
from cython.cimports.av.error import err_check
6+
from cython.cimports.av.indexentries import wrap_index_entries
67
from cython.cimports.av.packet import Packet
78
from cython.cimports.av.utils import (
89
avdict_to_dict,
@@ -106,6 +107,7 @@ def _init(
106107
):
107108
self.container = container
108109
self.ptr = stream
110+
self.index_entries = wrap_index_entries(self.ptr)
109111

110112
self.codec_context = codec_context
111113
if self.codec_context:

av/stream.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ from typing import Literal, cast
44

55
from .codec import Codec, CodecContext
66
from .container import Container
7+
from .indexentries import IndexEntries
78

89
class Disposition(Flag):
910
default = cast(int, ...)
@@ -32,6 +33,7 @@ class Stream:
3233
codec: Codec
3334
codec_context: CodecContext
3435
metadata: dict[str, str]
36+
index_entries: IndexEntries
3537
id: int
3638
profiles: list[str]
3739
profile: str | None

include/libavformat/avformat.pxd

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,18 @@ cdef extern from "libavformat/avformat.h" nogil:
331331
# custom
332332

333333
cdef set pyav_get_available_formats()
334+
335+
cdef struct AVIndexEntry:
336+
int64_t pos
337+
int64_t timestamp
338+
int flags
339+
int size
340+
int min_distance
341+
342+
cdef enum:
343+
AVINDEX_KEYFRAME
344+
AVINDEX_DISCARD_FRAME
345+
346+
cdef AVIndexEntry *avformat_index_get_entry(AVStream *st, int idx)
347+
cdef int avformat_index_get_entries_count(AVStream *st)
348+
cdef int av_index_search_timestamp(AVStream *st, int64_t timestamp, int flags)

0 commit comments

Comments
 (0)