150 lines
4.2 KiB
Python
150 lines
4.2 KiB
Python
|
|
import cython
|
||
|
|
import cython.cimports.libav as lib
|
||
|
|
from cython.cimports.libc.stdint import int64_t
|
||
|
|
|
||
|
|
_cinit_bypass_sentinel = cython.declare(object, object())
|
||
|
|
|
||
|
|
|
||
|
|
@cython.cfunc
|
||
|
|
def wrap_index_entry(ptr: cython.pointer[cython.const[lib.AVIndexEntry]]) -> IndexEntry:
|
||
|
|
obj: IndexEntry = IndexEntry(_cinit_bypass_sentinel)
|
||
|
|
obj._init(ptr)
|
||
|
|
return obj
|
||
|
|
|
||
|
|
|
||
|
|
@cython.final
|
||
|
|
@cython.cclass
|
||
|
|
class IndexEntry:
|
||
|
|
"""A single entry from a stream's index.
|
||
|
|
|
||
|
|
This is a thin wrapper around FFmpeg's ``AVIndexEntry``.
|
||
|
|
|
||
|
|
The exact meaning of the fields depends on the container/demuxer.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __cinit__(self, sentinel):
|
||
|
|
if sentinel is not _cinit_bypass_sentinel:
|
||
|
|
raise RuntimeError("cannot manually instantiate IndexEntry")
|
||
|
|
|
||
|
|
@cython.cfunc
|
||
|
|
def _init(self, ptr: cython.pointer[cython.const[lib.AVIndexEntry]]):
|
||
|
|
self.ptr = ptr
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return (
|
||
|
|
f"<av.IndexEntry pos={self.pos} timestamp={self.timestamp} flags={self.flags} "
|
||
|
|
f"size={self.size} min_distance={self.min_distance}>"
|
||
|
|
)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def pos(self):
|
||
|
|
return self.ptr.pos
|
||
|
|
|
||
|
|
@property
|
||
|
|
def timestamp(self):
|
||
|
|
return self.ptr.timestamp
|
||
|
|
|
||
|
|
@property
|
||
|
|
def flags(self):
|
||
|
|
return self.ptr.flags
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_keyframe(self):
|
||
|
|
return bool(self.ptr.flags & lib.AVINDEX_KEYFRAME)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_discard(self):
|
||
|
|
return bool(self.ptr.flags & lib.AVINDEX_DISCARD_FRAME)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def size(self):
|
||
|
|
return self.ptr.size
|
||
|
|
|
||
|
|
@property
|
||
|
|
def min_distance(self):
|
||
|
|
return self.ptr.min_distance
|
||
|
|
|
||
|
|
|
||
|
|
@cython.cfunc
|
||
|
|
def wrap_index_entries(ptr: cython.pointer[lib.AVStream]) -> IndexEntries:
|
||
|
|
obj: IndexEntries = IndexEntries(_cinit_bypass_sentinel)
|
||
|
|
obj._init(ptr)
|
||
|
|
return obj
|
||
|
|
|
||
|
|
|
||
|
|
@cython.final
|
||
|
|
@cython.cclass
|
||
|
|
class IndexEntries:
|
||
|
|
"""A sequence-like view of FFmpeg's per-stream index entries.
|
||
|
|
|
||
|
|
Exposed as :attr:`~av.stream.Stream.index_entries`.
|
||
|
|
|
||
|
|
The index is provided by the demuxer and may be empty or incomplete depending
|
||
|
|
on the container format. This is useful for fast multi-seek loops (e.g., decoding
|
||
|
|
at a lower-than-native framerate).
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __cinit__(self, sentinel):
|
||
|
|
if sentinel is _cinit_bypass_sentinel:
|
||
|
|
return
|
||
|
|
raise RuntimeError("cannot manually instantiate IndexEntries")
|
||
|
|
|
||
|
|
@cython.cfunc
|
||
|
|
def _init(self, ptr: cython.pointer[lib.AVStream]):
|
||
|
|
self.stream_ptr = ptr
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return f"<av.IndexEntries[{len(self)}]>"
|
||
|
|
|
||
|
|
def __len__(self) -> int:
|
||
|
|
with cython.nogil:
|
||
|
|
return lib.avformat_index_get_entries_count(self.stream_ptr)
|
||
|
|
|
||
|
|
def __iter__(self):
|
||
|
|
for i in range(len(self)):
|
||
|
|
yield self[i]
|
||
|
|
|
||
|
|
def __getitem__(self, index):
|
||
|
|
if isinstance(index, int):
|
||
|
|
n = len(self)
|
||
|
|
if index < 0:
|
||
|
|
index += n
|
||
|
|
if index < 0 or index >= n:
|
||
|
|
raise IndexError(f"Index entries {index} out of bounds for size {n}")
|
||
|
|
|
||
|
|
c_idx: cython.int = index
|
||
|
|
entry: cython.pointer[cython.const[lib.AVIndexEntry]]
|
||
|
|
with cython.nogil:
|
||
|
|
entry = lib.avformat_index_get_entry(self.stream_ptr, c_idx)
|
||
|
|
if entry == cython.NULL:
|
||
|
|
raise IndexError("index entry not found")
|
||
|
|
|
||
|
|
return wrap_index_entry(entry)
|
||
|
|
elif isinstance(index, slice):
|
||
|
|
start, stop, step = index.indices(len(self))
|
||
|
|
return [self[i] for i in range(start, stop, step)]
|
||
|
|
else:
|
||
|
|
raise TypeError("Index must be an integer or a slice")
|
||
|
|
|
||
|
|
def search_timestamp(
|
||
|
|
self, timestamp, *, backward: bool = True, any_frame: bool = False
|
||
|
|
):
|
||
|
|
"""Search the underlying index for ``timestamp``.
|
||
|
|
|
||
|
|
This wraps FFmpeg's ``av_index_search_timestamp``.
|
||
|
|
|
||
|
|
Returns an index into this object, or ``-1`` if no match is found.
|
||
|
|
"""
|
||
|
|
c_timestamp: int64_t = timestamp
|
||
|
|
flags: cython.int = 0
|
||
|
|
|
||
|
|
if backward:
|
||
|
|
flags |= lib.AVSEEK_FLAG_BACKWARD
|
||
|
|
if any_frame:
|
||
|
|
flags |= lib.AVSEEK_FLAG_ANY
|
||
|
|
|
||
|
|
with cython.nogil:
|
||
|
|
idx = lib.av_index_search_timestamp(self.stream_ptr, c_timestamp, flags)
|
||
|
|
|
||
|
|
return idx
|