add read me
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .core import Container, Flags, open
|
||||
from .input import InputContainer
|
||||
from .output import OutputContainer
|
||||
@@ -0,0 +1,3 @@
|
||||
from .core import *
|
||||
from .input import *
|
||||
from .output import *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
51
venv/lib/python3.12/site-packages/av/container/core.pxd
Normal file
51
venv/lib/python3.12/site-packages/av/container/core.pxd
Normal file
@@ -0,0 +1,51 @@
|
||||
cimport libav as lib
|
||||
|
||||
from av.codec.hwaccel cimport HWAccel
|
||||
from av.container.pyio cimport PyIOFile
|
||||
from av.container.streams cimport StreamContainer
|
||||
from av.dictionary cimport _Dictionary
|
||||
from av.format cimport ContainerFormat
|
||||
from av.stream cimport Stream
|
||||
|
||||
# Interrupt callback information, times are in seconds.
|
||||
ctypedef struct timeout_info:
|
||||
double start_time
|
||||
double timeout
|
||||
|
||||
|
||||
cdef class Container:
|
||||
|
||||
cdef readonly bint writeable
|
||||
cdef lib.AVFormatContext *ptr
|
||||
|
||||
cdef readonly object name
|
||||
cdef readonly str metadata_encoding
|
||||
cdef readonly str metadata_errors
|
||||
|
||||
cdef readonly PyIOFile file
|
||||
cdef int buffer_size
|
||||
cdef bint input_was_opened
|
||||
cdef readonly object io_open
|
||||
cdef readonly object open_files
|
||||
|
||||
cdef readonly ContainerFormat format
|
||||
|
||||
cdef readonly dict options
|
||||
cdef readonly dict container_options
|
||||
cdef readonly list stream_options
|
||||
|
||||
cdef HWAccel hwaccel
|
||||
|
||||
cdef readonly StreamContainer streams
|
||||
cdef readonly dict metadata
|
||||
|
||||
# Private API.
|
||||
cdef _assert_open(self)
|
||||
cdef int err_check(self, int value) except -1
|
||||
|
||||
# Timeouts
|
||||
cdef readonly object open_timeout
|
||||
cdef readonly object read_timeout
|
||||
cdef timeout_info interrupt_callback_info
|
||||
cdef set_timeout(self, object)
|
||||
cdef start_timeout(self)
|
||||
167
venv/lib/python3.12/site-packages/av/container/core.pyi
Normal file
167
venv/lib/python3.12/site-packages/av/container/core.pyi
Normal file
@@ -0,0 +1,167 @@
|
||||
from enum import Flag, IntEnum
|
||||
from fractions import Fraction
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import Any, Callable, ClassVar, Literal, Type, TypedDict, cast, overload
|
||||
|
||||
from av.codec.hwaccel import HWAccel
|
||||
from av.format import ContainerFormat
|
||||
|
||||
from .input import InputContainer
|
||||
from .output import OutputContainer
|
||||
from .streams import StreamContainer
|
||||
|
||||
Real = int | float | Fraction
|
||||
|
||||
class Flags(Flag):
|
||||
gen_pts = cast(ClassVar[Flags], ...)
|
||||
ign_idx = cast(ClassVar[Flags], ...)
|
||||
non_block = cast(ClassVar[Flags], ...)
|
||||
ign_dts = cast(ClassVar[Flags], ...)
|
||||
no_fillin = cast(ClassVar[Flags], ...)
|
||||
no_parse = cast(ClassVar[Flags], ...)
|
||||
no_buffer = cast(ClassVar[Flags], ...)
|
||||
custom_io = cast(ClassVar[Flags], ...)
|
||||
discard_corrupt = cast(ClassVar[Flags], ...)
|
||||
flush_packets = cast(ClassVar[Flags], ...)
|
||||
bitexact = cast(ClassVar[Flags], ...)
|
||||
sort_dts = cast(ClassVar[Flags], ...)
|
||||
fast_seek = cast(ClassVar[Flags], ...)
|
||||
shortest = cast(ClassVar[Flags], ...)
|
||||
auto_bsf = cast(ClassVar[Flags], ...)
|
||||
|
||||
class AudioCodec(IntEnum):
|
||||
none = cast(int, ...)
|
||||
pcm_alaw = cast(int, ...)
|
||||
pcm_bluray = cast(int, ...)
|
||||
pcm_dvd = cast(int, ...)
|
||||
pcm_f16le = cast(int, ...)
|
||||
pcm_f24le = cast(int, ...)
|
||||
pcm_f32be = cast(int, ...)
|
||||
pcm_f32le = cast(int, ...)
|
||||
pcm_f64be = cast(int, ...)
|
||||
pcm_f64le = cast(int, ...)
|
||||
pcm_lxf = cast(int, ...)
|
||||
pcm_mulaw = cast(int, ...)
|
||||
pcm_s16be = cast(int, ...)
|
||||
pcm_s16be_planar = cast(int, ...)
|
||||
pcm_s16le = cast(int, ...)
|
||||
pcm_s16le_planar = cast(int, ...)
|
||||
pcm_s24be = cast(int, ...)
|
||||
pcm_s24daud = cast(int, ...)
|
||||
pcm_s24le = cast(int, ...)
|
||||
pcm_s24le_planar = cast(int, ...)
|
||||
pcm_s32be = cast(int, ...)
|
||||
pcm_s32le = cast(int, ...)
|
||||
pcm_s32le_planar = cast(int, ...)
|
||||
pcm_s64be = cast(int, ...)
|
||||
pcm_s64le = cast(int, ...)
|
||||
pcm_s8 = cast(int, ...)
|
||||
pcm_s8_planar = cast(int, ...)
|
||||
pcm_u16be = cast(int, ...)
|
||||
pcm_u16le = cast(int, ...)
|
||||
pcm_u24be = cast(int, ...)
|
||||
pcm_u24le = cast(int, ...)
|
||||
pcm_u32be = cast(int, ...)
|
||||
pcm_u32le = cast(int, ...)
|
||||
pcm_u8 = cast(int, ...)
|
||||
pcm_vidc = cast(int, ...)
|
||||
|
||||
class Chapter(TypedDict):
|
||||
id: int
|
||||
start: int
|
||||
end: int
|
||||
time_base: Fraction | None
|
||||
metadata: dict[str, str]
|
||||
|
||||
class Container:
|
||||
writeable: bool
|
||||
name: str
|
||||
metadata_encoding: str
|
||||
metadata_errors: str
|
||||
file: Any
|
||||
buffer_size: int
|
||||
input_was_opened: bool
|
||||
io_open: Any
|
||||
open_files: Any
|
||||
format: ContainerFormat
|
||||
options: dict[str, str]
|
||||
container_options: dict[str, str]
|
||||
stream_options: list[dict[str, str]]
|
||||
streams: StreamContainer
|
||||
metadata: dict[str, str]
|
||||
open_timeout: Real | None
|
||||
read_timeout: Real | None
|
||||
flags: int
|
||||
def __enter__(self) -> Container: ...
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> bool: ...
|
||||
def set_timeout(self, timeout: Real | None) -> None: ...
|
||||
def start_timeout(self) -> None: ...
|
||||
def chapters(self) -> list[Chapter]: ...
|
||||
def set_chapters(self, chapters: list[Chapter]) -> None: ...
|
||||
|
||||
@overload
|
||||
def open(
|
||||
file: Any,
|
||||
mode: Literal["r"],
|
||||
format: str | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
container_options: dict[str, str] | None = None,
|
||||
stream_options: list[str] | None = None,
|
||||
metadata_encoding: str = "utf-8",
|
||||
metadata_errors: str = "strict",
|
||||
buffer_size: int = 32768,
|
||||
timeout: Real | None | tuple[Real | None, Real | None] = None,
|
||||
io_open: Callable[..., Any] | None = None,
|
||||
hwaccel: HWAccel | None = None,
|
||||
) -> InputContainer: ...
|
||||
@overload
|
||||
def open(
|
||||
file: str | Path,
|
||||
mode: Literal["r"] | None = None,
|
||||
format: str | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
container_options: dict[str, str] | None = None,
|
||||
stream_options: list[str] | None = None,
|
||||
metadata_encoding: str = "utf-8",
|
||||
metadata_errors: str = "strict",
|
||||
buffer_size: int = 32768,
|
||||
timeout: Real | None | tuple[Real | None, Real | None] = None,
|
||||
io_open: Callable[..., Any] | None = None,
|
||||
hwaccel: HWAccel | None = None,
|
||||
) -> InputContainer: ...
|
||||
@overload
|
||||
def open(
|
||||
file: Any,
|
||||
mode: Literal["w"],
|
||||
format: str | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
container_options: dict[str, str] | None = None,
|
||||
stream_options: list[str] | None = None,
|
||||
metadata_encoding: str = "utf-8",
|
||||
metadata_errors: str = "strict",
|
||||
buffer_size: int = 32768,
|
||||
timeout: Real | None | tuple[Real | None, Real | None] = None,
|
||||
io_open: Callable[..., Any] | None = None,
|
||||
hwaccel: HWAccel | None = None,
|
||||
) -> OutputContainer: ...
|
||||
@overload
|
||||
def open(
|
||||
file: Any,
|
||||
mode: Literal["r", "w"] | None = None,
|
||||
format: str | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
container_options: dict[str, str] | None = None,
|
||||
stream_options: list[str] | None = None,
|
||||
metadata_encoding: str = "utf-8",
|
||||
metadata_errors: str = "strict",
|
||||
buffer_size: int = 32768,
|
||||
timeout: Real | None | tuple[Real | None, Real | None] = None,
|
||||
io_open: Callable[..., Any] | None = None,
|
||||
hwaccel: HWAccel | None = None,
|
||||
) -> InputContainer | OutputContainer: ...
|
||||
494
venv/lib/python3.12/site-packages/av/container/core.pyx
Executable file
494
venv/lib/python3.12/site-packages/av/container/core.pyx
Executable file
@@ -0,0 +1,494 @@
|
||||
from cython.operator cimport dereference
|
||||
from libc.stdint cimport int64_t
|
||||
|
||||
import os
|
||||
import time
|
||||
from enum import Flag, IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
cimport libav as lib
|
||||
|
||||
from av.codec.hwaccel cimport HWAccel
|
||||
from av.container.core cimport timeout_info
|
||||
from av.container.input cimport InputContainer
|
||||
from av.container.output cimport OutputContainer
|
||||
from av.container.pyio cimport pyio_close_custom_gil, pyio_close_gil
|
||||
from av.error cimport err_check, stash_exception
|
||||
from av.format cimport build_container_format
|
||||
from av.utils cimport (
|
||||
avdict_to_dict,
|
||||
avrational_to_fraction,
|
||||
dict_to_avdict,
|
||||
to_avrational,
|
||||
)
|
||||
|
||||
from av.dictionary import Dictionary
|
||||
from av.logging import Capture as LogCapture
|
||||
|
||||
|
||||
cdef object _cinit_sentinel = object()
|
||||
|
||||
|
||||
# We want to use the monotonic clock if it is available.
|
||||
cdef object clock = getattr(time, "monotonic", time.time)
|
||||
|
||||
cdef int interrupt_cb (void *p) noexcept nogil:
|
||||
cdef timeout_info info = dereference(<timeout_info*> p)
|
||||
if info.timeout < 0: # timeout < 0 means no timeout
|
||||
return 0
|
||||
|
||||
cdef double current_time
|
||||
with gil:
|
||||
current_time = clock()
|
||||
|
||||
# Check if the clock has been changed.
|
||||
if current_time < info.start_time:
|
||||
# Raise this when we get back to Python.
|
||||
stash_exception((RuntimeError, RuntimeError("Clock has been changed to before timeout start"), None))
|
||||
return 1
|
||||
|
||||
if current_time > info.start_time + info.timeout:
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
cdef int pyav_io_open(lib.AVFormatContext *s,
|
||||
lib.AVIOContext **pb,
|
||||
const char *url,
|
||||
int flags,
|
||||
lib.AVDictionary **options) noexcept nogil:
|
||||
with gil:
|
||||
return pyav_io_open_gil(s, pb, url, flags, options)
|
||||
|
||||
|
||||
cdef int pyav_io_open_gil(lib.AVFormatContext *s,
|
||||
lib.AVIOContext **pb,
|
||||
const char *url,
|
||||
int flags,
|
||||
lib.AVDictionary **options) noexcept:
|
||||
cdef Container container
|
||||
cdef object file
|
||||
cdef PyIOFile pyio_file
|
||||
try:
|
||||
container = <Container>dereference(s).opaque
|
||||
|
||||
if options is not NULL:
|
||||
options_dict = avdict_to_dict(
|
||||
dereference(<lib.AVDictionary**>options),
|
||||
encoding=container.metadata_encoding,
|
||||
errors=container.metadata_errors
|
||||
)
|
||||
else:
|
||||
options_dict = {}
|
||||
|
||||
file = container.io_open(
|
||||
<str>url if url is not NULL else "",
|
||||
flags,
|
||||
options_dict
|
||||
)
|
||||
|
||||
pyio_file = PyIOFile(
|
||||
file,
|
||||
container.buffer_size,
|
||||
(flags & lib.AVIO_FLAG_WRITE) != 0
|
||||
)
|
||||
|
||||
# Add it to the container to avoid it being deallocated
|
||||
container.open_files[<int64_t>pyio_file.iocontext.opaque] = pyio_file
|
||||
|
||||
pb[0] = pyio_file.iocontext
|
||||
return 0
|
||||
|
||||
except Exception:
|
||||
return stash_exception()
|
||||
|
||||
|
||||
cdef int pyav_io_close(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept nogil:
|
||||
with gil:
|
||||
return pyav_io_close_gil(s, pb)
|
||||
|
||||
cdef int pyav_io_close_gil(lib.AVFormatContext *s, lib.AVIOContext *pb) noexcept:
|
||||
cdef Container container
|
||||
cdef int result = 0
|
||||
try:
|
||||
container = <Container>dereference(s).opaque
|
||||
|
||||
if container.open_files is not None and <int64_t>pb.opaque in container.open_files:
|
||||
result = pyio_close_custom_gil(pb)
|
||||
|
||||
# Remove it from the container so that it can be deallocated
|
||||
del container.open_files[<int64_t>pb.opaque]
|
||||
else:
|
||||
result = pyio_close_gil(pb)
|
||||
|
||||
except Exception:
|
||||
stash_exception()
|
||||
result = lib.AVERROR_UNKNOWN # Or another appropriate error code
|
||||
|
||||
return result
|
||||
|
||||
cdef void _free_chapters(lib.AVFormatContext *ctx) noexcept nogil:
|
||||
cdef int i
|
||||
if ctx.chapters != NULL:
|
||||
for i in range(ctx.nb_chapters):
|
||||
if ctx.chapters[i] != NULL:
|
||||
if ctx.chapters[i].metadata != NULL:
|
||||
lib.av_dict_free(&ctx.chapters[i].metadata)
|
||||
lib.av_freep(<void **>&ctx.chapters[i])
|
||||
lib.av_freep(<void **>&ctx.chapters)
|
||||
ctx.nb_chapters = 0
|
||||
|
||||
|
||||
class Flags(Flag):
|
||||
gen_pts: "Generate missing pts even if it requires parsing future frames." = lib.AVFMT_FLAG_GENPTS
|
||||
ign_idx: "Ignore index." = lib.AVFMT_FLAG_IGNIDX
|
||||
non_block: "Do not block when reading packets from input." = lib.AVFMT_FLAG_NONBLOCK
|
||||
ign_dts: "Ignore DTS on frames that contain both DTS & PTS." = lib.AVFMT_FLAG_IGNDTS
|
||||
no_fillin: "Do not infer any values from other values, just return what is stored in the container." = lib.AVFMT_FLAG_NOFILLIN
|
||||
no_parse: "Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fill in code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled." = lib.AVFMT_FLAG_NOPARSE
|
||||
no_buffer: "Do not buffer frames when possible." = lib.AVFMT_FLAG_NOBUFFER
|
||||
custom_io: "The caller has supplied a custom AVIOContext, don't avio_close() it." = lib.AVFMT_FLAG_CUSTOM_IO
|
||||
discard_corrupt: "Discard frames marked corrupted." = lib.AVFMT_FLAG_DISCARD_CORRUPT
|
||||
flush_packets: "Flush the AVIOContext every packet." = lib.AVFMT_FLAG_FLUSH_PACKETS
|
||||
bitexact: "When muxing, try to avoid writing any random/volatile data to the output. This includes any random IDs, real-time timestamps/dates, muxer version, etc. This flag is mainly intended for testing." = lib.AVFMT_FLAG_BITEXACT
|
||||
sort_dts: "Try to interleave outputted packets by dts (using this flag can slow demuxing down)." = lib.AVFMT_FLAG_SORT_DTS
|
||||
fast_seek: "Enable fast, but inaccurate seeks for some formats." = lib.AVFMT_FLAG_FAST_SEEK
|
||||
auto_bsf: "Add bitstream filters as requested by the muxer." = lib.AVFMT_FLAG_AUTO_BSF
|
||||
|
||||
class AudioCodec(IntEnum):
|
||||
"""Enumeration for audio codec IDs."""
|
||||
none = lib.AV_CODEC_ID_NONE # No codec.
|
||||
pcm_alaw = lib.AV_CODEC_ID_PCM_ALAW # PCM A-law.
|
||||
pcm_bluray = lib.AV_CODEC_ID_PCM_BLURAY # PCM Blu-ray.
|
||||
pcm_dvd = lib.AV_CODEC_ID_PCM_DVD # PCM DVD.
|
||||
pcm_f16le = lib.AV_CODEC_ID_PCM_F16LE # PCM F16 little-endian.
|
||||
pcm_f24le = lib.AV_CODEC_ID_PCM_F24LE # PCM F24 little-endian.
|
||||
pcm_f32be = lib.AV_CODEC_ID_PCM_F32BE # PCM F32 big-endian.
|
||||
pcm_f32le = lib.AV_CODEC_ID_PCM_F32LE # PCM F32 little-endian.
|
||||
pcm_f64be = lib.AV_CODEC_ID_PCM_F64BE # PCM F64 big-endian.
|
||||
pcm_f64le = lib.AV_CODEC_ID_PCM_F64LE # PCM F64 little-endian.
|
||||
pcm_lxf = lib.AV_CODEC_ID_PCM_LXF # PCM LXF.
|
||||
pcm_mulaw = lib.AV_CODEC_ID_PCM_MULAW # PCM μ-law.
|
||||
pcm_s16be = lib.AV_CODEC_ID_PCM_S16BE # PCM signed 16-bit big-endian.
|
||||
pcm_s16be_planar = lib.AV_CODEC_ID_PCM_S16BE_PLANAR # PCM signed 16-bit big-endian planar.
|
||||
pcm_s16le = lib.AV_CODEC_ID_PCM_S16LE # PCM signed 16-bit little-endian.
|
||||
pcm_s16le_planar = lib.AV_CODEC_ID_PCM_S16LE_PLANAR # PCM signed 16-bit little-endian planar.
|
||||
pcm_s24be = lib.AV_CODEC_ID_PCM_S24BE # PCM signed 24-bit big-endian.
|
||||
pcm_s24daud = lib.AV_CODEC_ID_PCM_S24DAUD # PCM signed 24-bit D-Cinema audio.
|
||||
pcm_s24le = lib.AV_CODEC_ID_PCM_S24LE # PCM signed 24-bit little-endian.
|
||||
pcm_s24le_planar = lib.AV_CODEC_ID_PCM_S24LE_PLANAR # PCM signed 24-bit little-endian planar.
|
||||
pcm_s32be = lib.AV_CODEC_ID_PCM_S32BE # PCM signed 32-bit big-endian.
|
||||
pcm_s32le = lib.AV_CODEC_ID_PCM_S32LE # PCM signed 32-bit little-endian.
|
||||
pcm_s32le_planar = lib.AV_CODEC_ID_PCM_S32LE_PLANAR # PCM signed 32-bit little-endian planar.
|
||||
pcm_s64be = lib.AV_CODEC_ID_PCM_S64BE # PCM signed 64-bit big-endian.
|
||||
pcm_s64le = lib.AV_CODEC_ID_PCM_S64LE # PCM signed 64-bit little-endian.
|
||||
pcm_s8 = lib.AV_CODEC_ID_PCM_S8 # PCM signed 8-bit.
|
||||
pcm_s8_planar = lib.AV_CODEC_ID_PCM_S8_PLANAR # PCM signed 8-bit planar.
|
||||
pcm_u16be = lib.AV_CODEC_ID_PCM_U16BE # PCM unsigned 16-bit big-endian.
|
||||
pcm_u16le = lib.AV_CODEC_ID_PCM_U16LE # PCM unsigned 16-bit little-endian.
|
||||
pcm_u24be = lib.AV_CODEC_ID_PCM_U24BE # PCM unsigned 24-bit big-endian.
|
||||
pcm_u24le = lib.AV_CODEC_ID_PCM_U24LE # PCM unsigned 24-bit little-endian.
|
||||
pcm_u32be = lib.AV_CODEC_ID_PCM_U32BE # PCM unsigned 32-bit big-endian.
|
||||
pcm_u32le = lib.AV_CODEC_ID_PCM_U32LE # PCM unsigned 32-bit little-endian.
|
||||
pcm_u8 = lib.AV_CODEC_ID_PCM_U8 # PCM unsigned 8-bit.
|
||||
pcm_vidc = lib.AV_CODEC_ID_PCM_VIDC # PCM VIDC.
|
||||
|
||||
|
||||
cdef class Container:
|
||||
def __cinit__(self, sentinel, file_, format_name, options,
|
||||
container_options, stream_options, hwaccel,
|
||||
metadata_encoding, metadata_errors,
|
||||
buffer_size, open_timeout, read_timeout,
|
||||
io_open):
|
||||
|
||||
if sentinel is not _cinit_sentinel:
|
||||
raise RuntimeError("cannot construct base Container")
|
||||
|
||||
self.writeable = isinstance(self, OutputContainer)
|
||||
if not self.writeable and not isinstance(self, InputContainer):
|
||||
raise RuntimeError("Container cannot be directly extended.")
|
||||
|
||||
if isinstance(file_, str):
|
||||
self.name = file_
|
||||
else:
|
||||
self.name = str(getattr(file_, "name", "<none>"))
|
||||
|
||||
self.options = dict(options or ())
|
||||
self.container_options = dict(container_options or ())
|
||||
self.stream_options = [dict(x) for x in stream_options or ()]
|
||||
|
||||
self.hwaccel = hwaccel
|
||||
|
||||
self.metadata_encoding = metadata_encoding
|
||||
self.metadata_errors = metadata_errors
|
||||
|
||||
self.open_timeout = open_timeout
|
||||
self.read_timeout = read_timeout
|
||||
|
||||
self.buffer_size = buffer_size
|
||||
self.io_open = io_open
|
||||
|
||||
acodec = None # no audio codec specified
|
||||
if format_name is not None:
|
||||
if ":" in format_name:
|
||||
format_name, acodec = format_name.split(":")
|
||||
self.format = ContainerFormat(format_name)
|
||||
|
||||
self.input_was_opened = False
|
||||
cdef int res
|
||||
|
||||
cdef bytes name_obj = os.fsencode(self.name)
|
||||
cdef char *name = name_obj
|
||||
|
||||
cdef lib.AVOutputFormat *ofmt
|
||||
if self.writeable:
|
||||
|
||||
ofmt = self.format.optr if self.format else lib.av_guess_format(NULL, name, NULL)
|
||||
if ofmt == NULL:
|
||||
raise ValueError("Could not determine output format")
|
||||
|
||||
with nogil:
|
||||
# This does not actually open the file.
|
||||
res = lib.avformat_alloc_output_context2(
|
||||
&self.ptr,
|
||||
ofmt,
|
||||
NULL,
|
||||
name,
|
||||
)
|
||||
self.err_check(res)
|
||||
|
||||
else:
|
||||
# We need the context before we open the input AND setup Python IO.
|
||||
self.ptr = lib.avformat_alloc_context()
|
||||
|
||||
# Setup interrupt callback
|
||||
if self.open_timeout is not None or self.read_timeout is not None:
|
||||
self.ptr.interrupt_callback.callback = interrupt_cb
|
||||
self.ptr.interrupt_callback.opaque = &self.interrupt_callback_info
|
||||
|
||||
if acodec is not None:
|
||||
self.ptr.audio_codec_id = getattr(AudioCodec, acodec)
|
||||
|
||||
self.ptr.flags |= lib.AVFMT_FLAG_GENPTS
|
||||
self.ptr.opaque = <void*>self
|
||||
|
||||
# Setup Python IO.
|
||||
self.open_files = {}
|
||||
if not isinstance(file_, basestring):
|
||||
self.file = PyIOFile(file_, buffer_size, self.writeable)
|
||||
self.ptr.pb = self.file.iocontext
|
||||
|
||||
if io_open is not None:
|
||||
self.ptr.io_open = pyav_io_open
|
||||
self.ptr.io_close2 = pyav_io_close
|
||||
self.ptr.flags |= lib.AVFMT_FLAG_CUSTOM_IO
|
||||
|
||||
cdef lib.AVInputFormat *ifmt
|
||||
cdef _Dictionary c_options
|
||||
if not self.writeable:
|
||||
ifmt = self.format.iptr if self.format else NULL
|
||||
c_options = Dictionary(self.options, self.container_options)
|
||||
|
||||
self.set_timeout(self.open_timeout)
|
||||
self.start_timeout()
|
||||
with nogil:
|
||||
res = lib.avformat_open_input(&self.ptr, name, ifmt, &c_options.ptr)
|
||||
self.set_timeout(None)
|
||||
self.err_check(res)
|
||||
self.input_was_opened = True
|
||||
|
||||
if format_name is None:
|
||||
self.format = build_container_format(self.ptr.iformat, self.ptr.oformat)
|
||||
|
||||
def __dealloc__(self):
|
||||
with nogil:
|
||||
lib.avformat_free_context(self.ptr)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<av.{self.__class__.__name__} {self.file or self.name!r}>"
|
||||
|
||||
cdef int err_check(self, int value) except -1:
|
||||
return err_check(value, filename=self.name)
|
||||
|
||||
def dumps_format(self):
|
||||
self._assert_open()
|
||||
with LogCapture() as logs:
|
||||
lib.av_dump_format(self.ptr, 0, "", isinstance(self, OutputContainer))
|
||||
return "".join(log[2] for log in logs)
|
||||
|
||||
cdef set_timeout(self, timeout):
|
||||
if timeout is None:
|
||||
self.interrupt_callback_info.timeout = -1.0
|
||||
else:
|
||||
self.interrupt_callback_info.timeout = timeout
|
||||
|
||||
cdef start_timeout(self):
|
||||
self.interrupt_callback_info.start_time = clock()
|
||||
|
||||
cdef _assert_open(self):
|
||||
if self.ptr == NULL:
|
||||
raise AssertionError("Container is not open")
|
||||
|
||||
@property
|
||||
def flags(self):
|
||||
self._assert_open()
|
||||
return self.ptr.flags
|
||||
|
||||
@flags.setter
|
||||
def flags(self, int value):
|
||||
self._assert_open()
|
||||
self.ptr.flags = value
|
||||
|
||||
def chapters(self):
|
||||
self._assert_open()
|
||||
cdef list result = []
|
||||
cdef int i
|
||||
|
||||
for i in range(self.ptr.nb_chapters):
|
||||
ch = self.ptr.chapters[i]
|
||||
result.append({
|
||||
"id": ch.id,
|
||||
"start": ch.start,
|
||||
"end": ch.end,
|
||||
"time_base": avrational_to_fraction(&ch.time_base),
|
||||
"metadata": avdict_to_dict(ch.metadata, self.metadata_encoding, self.metadata_errors),
|
||||
})
|
||||
return result
|
||||
|
||||
def set_chapters(self, chapters):
|
||||
self._assert_open()
|
||||
|
||||
cdef int count = len(chapters)
|
||||
cdef int i
|
||||
cdef lib.AVChapter **ch_array
|
||||
cdef lib.AVChapter *ch
|
||||
cdef dict entry
|
||||
|
||||
with nogil:
|
||||
_free_chapters(self.ptr)
|
||||
|
||||
ch_array = <lib.AVChapter **>lib.av_malloc(count * sizeof(lib.AVChapter *))
|
||||
if ch_array == NULL:
|
||||
raise MemoryError("av_malloc failed for chapters")
|
||||
|
||||
for i in range(count):
|
||||
entry = chapters[i]
|
||||
ch = <lib.AVChapter *>lib.av_malloc(sizeof(lib.AVChapter))
|
||||
if ch == NULL:
|
||||
raise MemoryError("av_malloc failed for chapter")
|
||||
ch.id = entry["id"]
|
||||
ch.start = <int64_t>entry["start"]
|
||||
ch.end = <int64_t>entry["end"]
|
||||
to_avrational(entry["time_base"], &ch.time_base)
|
||||
ch.metadata = NULL
|
||||
if "metadata" in entry:
|
||||
dict_to_avdict(&ch.metadata, entry["metadata"], self.metadata_encoding, self.metadata_errors)
|
||||
ch_array[i] = ch
|
||||
|
||||
self.ptr.nb_chapters = count
|
||||
self.ptr.chapters = ch_array
|
||||
|
||||
def open(
|
||||
file,
|
||||
mode=None,
|
||||
format=None,
|
||||
options=None,
|
||||
container_options=None,
|
||||
stream_options=None,
|
||||
metadata_encoding="utf-8",
|
||||
metadata_errors="strict",
|
||||
buffer_size=32768,
|
||||
timeout=None,
|
||||
io_open=None,
|
||||
hwaccel=None
|
||||
):
|
||||
"""open(file, mode='r', **kwargs)
|
||||
|
||||
Main entrypoint to opening files/streams.
|
||||
|
||||
:param str file: The file to open, which can be either a string or a file-like object.
|
||||
:param str mode: ``"r"`` for reading and ``"w"`` for writing.
|
||||
:param str format: Specific format to use. Defaults to autodect.
|
||||
:param dict options: Options to pass to the container and all streams.
|
||||
:param dict container_options: Options to pass to the container.
|
||||
:param list stream_options: Options to pass to each stream.
|
||||
:param str metadata_encoding: Encoding to use when reading or writing file metadata.
|
||||
Defaults to ``"utf-8"``.
|
||||
:param str metadata_errors: Specifies how to handle encoding errors; behaves like
|
||||
``str.encode`` parameter. Defaults to ``"strict"``.
|
||||
:param int buffer_size: Size of buffer for Python input/output operations in bytes.
|
||||
Honored only when ``file`` is a file-like object. Defaults to 32768 (32k).
|
||||
:param timeout: How many seconds to wait for data before giving up, as a float, or a
|
||||
``(open timeout, read timeout)`` tuple.
|
||||
:param callable io_open: Custom I/O callable for opening files/streams.
|
||||
This option is intended for formats that need to open additional
|
||||
file-like objects to ``file`` using custom I/O.
|
||||
The callable signature is ``io_open(url: str, flags: int, options: dict)``, where
|
||||
``url`` is the url to open, ``flags`` is a combination of AVIO_FLAG_* and
|
||||
``options`` is a dictionary of additional options. The callable should return a
|
||||
file-like object.
|
||||
:param HWAccel hwaccel: Optional settings for hardware-accelerated decoding.
|
||||
:rtype: Container
|
||||
|
||||
For devices (via ``libavdevice``), pass the name of the device to ``format``,
|
||||
e.g.::
|
||||
|
||||
>>> # Open webcam on MacOS.
|
||||
>>> av.open('0', format='avfoundation') # doctest: +SKIP
|
||||
|
||||
For DASH and custom I/O using ``io_open``, add a protocol prefix to the ``file`` to
|
||||
prevent the DASH encoder defaulting to the file protocol and using temporary files.
|
||||
The custom I/O callable can be used to remove the protocol prefix to reveal the actual
|
||||
name for creating the file-like object. E.g.::
|
||||
|
||||
>>> av.open("customprotocol://manifest.mpd", "w", io_open=custom_io) # doctest: +SKIP
|
||||
|
||||
.. seealso:: :ref:`garbage_collection`
|
||||
|
||||
More information on using input and output devices is available on the
|
||||
`FFmpeg website <https://www.ffmpeg.org/ffmpeg-devices.html>`_.
|
||||
"""
|
||||
|
||||
if not (mode is None or (isinstance(mode, str) and mode == "r" or mode == "w")):
|
||||
raise ValueError(f"mode must be 'r', 'w', or None, got: {mode}")
|
||||
|
||||
if isinstance(file, str):
|
||||
pass
|
||||
elif isinstance(file, Path):
|
||||
file = f"{file}"
|
||||
elif mode is None:
|
||||
mode = getattr(file, "mode", None)
|
||||
|
||||
if mode is None:
|
||||
mode = "r"
|
||||
|
||||
if isinstance(timeout, tuple):
|
||||
if not len(timeout) == 2:
|
||||
raise ValueError("timeout must be `float` or `tuple[float, float]`")
|
||||
|
||||
open_timeout, read_timeout = timeout
|
||||
else:
|
||||
open_timeout = timeout
|
||||
read_timeout = timeout
|
||||
|
||||
if mode.startswith("r"):
|
||||
return InputContainer(_cinit_sentinel, file, format, options,
|
||||
container_options, stream_options, hwaccel, metadata_encoding, metadata_errors,
|
||||
buffer_size, open_timeout, read_timeout, io_open,
|
||||
)
|
||||
|
||||
if stream_options:
|
||||
raise ValueError(
|
||||
"Provide stream options via Container.add_stream(..., options={})."
|
||||
)
|
||||
return OutputContainer(_cinit_sentinel, file, format, options,
|
||||
container_options, stream_options, None, metadata_encoding, metadata_errors,
|
||||
buffer_size, open_timeout, read_timeout, io_open,
|
||||
)
|
||||
Binary file not shown.
9
venv/lib/python3.12/site-packages/av/container/input.pxd
Normal file
9
venv/lib/python3.12/site-packages/av/container/input.pxd
Normal file
@@ -0,0 +1,9 @@
|
||||
cimport libav as lib
|
||||
|
||||
from av.container.core cimport Container
|
||||
from av.stream cimport Stream
|
||||
|
||||
|
||||
cdef class InputContainer(Container):
|
||||
|
||||
cdef flush_buffers(self)
|
||||
49
venv/lib/python3.12/site-packages/av/container/input.pyi
Normal file
49
venv/lib/python3.12/site-packages/av/container/input.pyi
Normal file
@@ -0,0 +1,49 @@
|
||||
from typing import Any, Iterator, overload
|
||||
|
||||
from av.audio.frame import AudioFrame
|
||||
from av.audio.stream import AudioStream
|
||||
from av.packet import Packet
|
||||
from av.stream import Stream
|
||||
from av.subtitles.stream import SubtitleStream
|
||||
from av.subtitles.subtitle import SubtitleSet
|
||||
from av.video.frame import VideoFrame
|
||||
from av.video.stream import VideoStream
|
||||
|
||||
from .core import Container
|
||||
|
||||
class InputContainer(Container):
|
||||
start_time: int
|
||||
duration: int | None
|
||||
bit_rate: int
|
||||
size: int
|
||||
|
||||
def __enter__(self) -> InputContainer: ...
|
||||
def close(self) -> None: ...
|
||||
def demux(self, *args: Any, **kwargs: Any) -> Iterator[Packet]: ...
|
||||
@overload
|
||||
def decode(self, video: int) -> Iterator[VideoFrame]: ...
|
||||
@overload
|
||||
def decode(self, audio: int) -> Iterator[AudioFrame]: ...
|
||||
@overload
|
||||
def decode(self, subtitles: int) -> Iterator[SubtitleSet]: ...
|
||||
@overload
|
||||
def decode(self, *args: VideoStream) -> Iterator[VideoFrame]: ...
|
||||
@overload
|
||||
def decode(self, *args: AudioStream) -> Iterator[AudioFrame]: ...
|
||||
@overload
|
||||
def decode(self, *args: SubtitleStream) -> Iterator[SubtitleSet]: ...
|
||||
@overload
|
||||
def decode(
|
||||
self, *args: Any, **kwargs: Any
|
||||
) -> Iterator[VideoFrame | AudioFrame | SubtitleSet]: ...
|
||||
def seek(
|
||||
self,
|
||||
offset: int,
|
||||
*,
|
||||
backward: bool = True,
|
||||
any_frame: bool = False,
|
||||
stream: Stream | VideoStream | AudioStream | None = None,
|
||||
unsupported_frame_offset: bool = False,
|
||||
unsupported_byte_offset: bool = False,
|
||||
) -> None: ...
|
||||
def flush_buffers(self) -> None: ...
|
||||
290
venv/lib/python3.12/site-packages/av/container/input.pyx
Normal file
290
venv/lib/python3.12/site-packages/av/container/input.pyx
Normal file
@@ -0,0 +1,290 @@
|
||||
from libc.stdint cimport int64_t
|
||||
from libc.stdlib cimport free, malloc
|
||||
|
||||
from av.codec.context cimport CodecContext, wrap_codec_context
|
||||
from av.container.streams cimport StreamContainer
|
||||
from av.dictionary cimport _Dictionary
|
||||
from av.error cimport err_check
|
||||
from av.packet cimport Packet
|
||||
from av.stream cimport Stream, wrap_stream
|
||||
from av.utils cimport avdict_to_dict
|
||||
|
||||
from av.dictionary import Dictionary
|
||||
|
||||
|
||||
cdef close_input(InputContainer self):
|
||||
self.streams = StreamContainer()
|
||||
if self.input_was_opened:
|
||||
with nogil:
|
||||
# This causes `self.ptr` to be set to NULL.
|
||||
lib.avformat_close_input(&self.ptr)
|
||||
self.input_was_opened = False
|
||||
|
||||
|
||||
cdef class InputContainer(Container):
|
||||
def __cinit__(self, *args, **kwargs):
|
||||
cdef CodecContext py_codec_context
|
||||
cdef unsigned int i
|
||||
cdef lib.AVStream *stream
|
||||
cdef lib.AVCodec *codec
|
||||
cdef lib.AVCodecContext *codec_context
|
||||
|
||||
# If we have either the global `options`, or a `stream_options`, prepare
|
||||
# a mashup of those options for each stream.
|
||||
cdef lib.AVDictionary **c_options = NULL
|
||||
cdef _Dictionary base_dict, stream_dict
|
||||
if self.options or self.stream_options:
|
||||
base_dict = Dictionary(self.options)
|
||||
c_options = <lib.AVDictionary**>malloc(self.ptr.nb_streams * sizeof(void*))
|
||||
for i in range(self.ptr.nb_streams):
|
||||
c_options[i] = NULL
|
||||
if i < len(self.stream_options) and self.stream_options:
|
||||
stream_dict = base_dict.copy()
|
||||
stream_dict.update(self.stream_options[i])
|
||||
lib.av_dict_copy(&c_options[i], stream_dict.ptr, 0)
|
||||
else:
|
||||
lib.av_dict_copy(&c_options[i], base_dict.ptr, 0)
|
||||
|
||||
self.set_timeout(self.open_timeout)
|
||||
self.start_timeout()
|
||||
with nogil:
|
||||
# This peeks are the first few frames to:
|
||||
# - set stream.disposition from codec.audio_service_type (not exposed);
|
||||
# - set stream.codec.bits_per_coded_sample;
|
||||
# - set stream.duration;
|
||||
# - set stream.start_time;
|
||||
# - set stream.r_frame_rate to average value;
|
||||
# - open and closes codecs with the options provided.
|
||||
ret = lib.avformat_find_stream_info(
|
||||
self.ptr,
|
||||
c_options
|
||||
)
|
||||
self.set_timeout(None)
|
||||
self.err_check(ret)
|
||||
|
||||
# Cleanup all of our options.
|
||||
if c_options:
|
||||
for i in range(self.ptr.nb_streams):
|
||||
lib.av_dict_free(&c_options[i])
|
||||
free(c_options)
|
||||
|
||||
at_least_one_accelerated_context = False
|
||||
|
||||
self.streams = StreamContainer()
|
||||
for i in range(self.ptr.nb_streams):
|
||||
stream = self.ptr.streams[i]
|
||||
codec = lib.avcodec_find_decoder(stream.codecpar.codec_id)
|
||||
if codec:
|
||||
# allocate and initialise decoder
|
||||
codec_context = lib.avcodec_alloc_context3(codec)
|
||||
err_check(lib.avcodec_parameters_to_context(codec_context, stream.codecpar))
|
||||
codec_context.pkt_timebase = stream.time_base
|
||||
py_codec_context = wrap_codec_context(codec_context, codec, self.hwaccel)
|
||||
if py_codec_context.is_hwaccel:
|
||||
at_least_one_accelerated_context = True
|
||||
else:
|
||||
# no decoder is available
|
||||
py_codec_context = None
|
||||
self.streams.add_stream(wrap_stream(self, stream, py_codec_context))
|
||||
|
||||
if self.hwaccel and not self.hwaccel.allow_software_fallback and not at_least_one_accelerated_context:
|
||||
raise RuntimeError("Hardware accelerated decode requested but no stream is compatible")
|
||||
|
||||
self.metadata = avdict_to_dict(self.ptr.metadata, self.metadata_encoding, self.metadata_errors)
|
||||
|
||||
def __dealloc__(self):
|
||||
close_input(self)
|
||||
|
||||
@property
|
||||
def start_time(self):
|
||||
self._assert_open()
|
||||
if self.ptr.start_time != lib.AV_NOPTS_VALUE:
|
||||
return self.ptr.start_time
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
self._assert_open()
|
||||
if self.ptr.duration != lib.AV_NOPTS_VALUE:
|
||||
return self.ptr.duration
|
||||
|
||||
@property
|
||||
def bit_rate(self):
|
||||
self._assert_open()
|
||||
return self.ptr.bit_rate
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
self._assert_open()
|
||||
return lib.avio_size(self.ptr.pb)
|
||||
|
||||
def close(self):
|
||||
close_input(self)
|
||||
|
||||
def demux(self, *args, **kwargs):
|
||||
"""demux(streams=None, video=None, audio=None, subtitles=None, data=None)
|
||||
|
||||
Yields a series of :class:`.Packet` from the given set of :class:`.Stream`::
|
||||
|
||||
for packet in container.demux():
|
||||
# Do something with `packet`, often:
|
||||
for frame in packet.decode():
|
||||
# Do something with `frame`.
|
||||
|
||||
.. seealso:: :meth:`.StreamContainer.get` for the interpretation of
|
||||
the arguments.
|
||||
|
||||
.. note:: The last packets are dummy packets that when decoded will flush the buffers.
|
||||
|
||||
"""
|
||||
self._assert_open()
|
||||
|
||||
# For whatever reason, Cython does not like us directly passing kwargs
|
||||
# from one method to another. Without kwargs, it ends up passing a
|
||||
# NULL reference, which segfaults. So we force it to do something with it.
|
||||
# This is likely a bug in Cython; see https://github.com/cython/cython/issues/2166
|
||||
# (and others).
|
||||
id(kwargs)
|
||||
|
||||
streams = self.streams.get(*args, **kwargs)
|
||||
|
||||
cdef bint *include_stream = <bint*>malloc(self.ptr.nb_streams * sizeof(bint))
|
||||
if include_stream == NULL:
|
||||
raise MemoryError()
|
||||
|
||||
cdef unsigned int i
|
||||
cdef Packet packet
|
||||
cdef int ret
|
||||
|
||||
self.set_timeout(self.read_timeout)
|
||||
try:
|
||||
for i in range(self.ptr.nb_streams):
|
||||
include_stream[i] = False
|
||||
for stream in streams:
|
||||
i = stream.index
|
||||
if i >= self.ptr.nb_streams:
|
||||
raise ValueError(f"stream index {i} out of range")
|
||||
include_stream[i] = True
|
||||
|
||||
while True:
|
||||
packet = Packet()
|
||||
try:
|
||||
self.start_timeout()
|
||||
with nogil:
|
||||
ret = lib.av_read_frame(self.ptr, packet.ptr)
|
||||
self.err_check(ret)
|
||||
except EOFError:
|
||||
break
|
||||
|
||||
if include_stream[packet.ptr.stream_index]:
|
||||
# If AVFMTCTX_NOHEADER is set in ctx_flags, then new streams
|
||||
# may also appear in av_read_frame().
|
||||
# http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html
|
||||
# TODO: find better way to handle this
|
||||
if packet.ptr.stream_index < len(self.streams):
|
||||
packet._stream = self.streams[packet.ptr.stream_index]
|
||||
# Keep track of this so that remuxing is easier.
|
||||
packet.ptr.time_base = packet._stream.ptr.time_base
|
||||
yield packet
|
||||
|
||||
# Flush!
|
||||
for i in range(self.ptr.nb_streams):
|
||||
if include_stream[i]:
|
||||
packet = Packet()
|
||||
packet._stream = self.streams[i]
|
||||
packet.ptr.time_base = packet._stream.ptr.time_base
|
||||
yield packet
|
||||
|
||||
finally:
|
||||
self.set_timeout(None)
|
||||
free(include_stream)
|
||||
|
||||
def decode(self, *args, **kwargs):
|
||||
"""decode(streams=None, video=None, audio=None, subtitles=None, data=None)
|
||||
|
||||
Yields a series of :class:`.Frame` from the given set of streams::
|
||||
|
||||
for frame in container.decode():
|
||||
# Do something with `frame`.
|
||||
|
||||
.. seealso:: :meth:`.StreamContainer.get` for the interpretation of
|
||||
the arguments.
|
||||
|
||||
"""
|
||||
self._assert_open()
|
||||
id(kwargs) # Avoid Cython bug; see demux().
|
||||
for packet in self.demux(*args, **kwargs):
|
||||
for frame in packet.decode():
|
||||
yield frame
|
||||
|
||||
def seek(
|
||||
self, offset, *, bint backward=True, bint any_frame=False, Stream stream=None,
|
||||
bint unsupported_frame_offset=False, bint unsupported_byte_offset=False
|
||||
):
|
||||
"""seek(offset, *, backward=True, any_frame=False, stream=None)
|
||||
|
||||
Seek to a (key)frame nearsest to the given timestamp.
|
||||
|
||||
:param int offset: Time to seek to, expressed in``stream.time_base`` if ``stream``
|
||||
is given, otherwise in :data:`av.time_base`.
|
||||
:param bool backward: If there is not a (key)frame at the given offset,
|
||||
look backwards for it.
|
||||
:param bool any_frame: Seek to any frame, not just a keyframe.
|
||||
:param Stream stream: The stream who's ``time_base`` the ``offset`` is in.
|
||||
|
||||
:param bool unsupported_frame_offset: ``offset`` is a frame
|
||||
index instead of a time; not supported by any known format.
|
||||
:param bool unsupported_byte_offset: ``offset`` is a byte
|
||||
location in the file; not supported by any known format.
|
||||
|
||||
After seeking, packets that you demux should correspond (roughly) to
|
||||
the position you requested.
|
||||
|
||||
In most cases, the defaults of ``backwards = True`` and ``any_frame = False``
|
||||
are the best course of action, followed by you demuxing/decoding to
|
||||
the position that you want. This is because to properly decode video frames
|
||||
you need to start from the previous keyframe.
|
||||
|
||||
.. seealso:: :ffmpeg:`avformat_seek_file` for discussion of the flags.
|
||||
|
||||
"""
|
||||
self._assert_open()
|
||||
|
||||
# We used to take floats here and assume they were in seconds. This
|
||||
# was super confusing, so lets go in the complete opposite direction
|
||||
# and reject non-ints.
|
||||
if not isinstance(offset, int):
|
||||
raise TypeError("Container.seek only accepts integer offset.", type(offset))
|
||||
|
||||
cdef int64_t c_offset = offset
|
||||
|
||||
cdef int flags = 0
|
||||
cdef int ret
|
||||
|
||||
if backward:
|
||||
flags |= lib.AVSEEK_FLAG_BACKWARD
|
||||
if any_frame:
|
||||
flags |= lib.AVSEEK_FLAG_ANY
|
||||
|
||||
# If someone really wants (and to experiment), expose these.
|
||||
if unsupported_frame_offset:
|
||||
flags |= lib.AVSEEK_FLAG_FRAME
|
||||
if unsupported_byte_offset:
|
||||
flags |= lib.AVSEEK_FLAG_BYTE
|
||||
|
||||
cdef int stream_index = stream.index if stream else -1
|
||||
with nogil:
|
||||
ret = lib.av_seek_frame(self.ptr, stream_index, c_offset, flags)
|
||||
err_check(ret)
|
||||
|
||||
self.flush_buffers()
|
||||
|
||||
cdef flush_buffers(self):
|
||||
self._assert_open()
|
||||
|
||||
cdef Stream stream
|
||||
cdef CodecContext codec_context
|
||||
|
||||
for stream in self.streams:
|
||||
codec_context = stream.codec_context
|
||||
if codec_context:
|
||||
codec_context.flush_buffers()
|
||||
Binary file not shown.
12
venv/lib/python3.12/site-packages/av/container/output.pxd
Normal file
12
venv/lib/python3.12/site-packages/av/container/output.pxd
Normal file
@@ -0,0 +1,12 @@
|
||||
cimport libav as lib
|
||||
|
||||
from av.container.core cimport Container
|
||||
from av.stream cimport Stream
|
||||
|
||||
|
||||
cdef class OutputContainer(Container):
|
||||
cdef bint _started
|
||||
cdef bint _done
|
||||
cdef lib.AVPacket *packet_ptr
|
||||
|
||||
cpdef start_encoding(self)
|
||||
474
venv/lib/python3.12/site-packages/av/container/output.py
Normal file
474
venv/lib/python3.12/site-packages/av/container/output.py
Normal file
@@ -0,0 +1,474 @@
|
||||
import os
|
||||
from fractions import Fraction
|
||||
|
||||
import cython
|
||||
from cython.cimports import libav as lib
|
||||
from cython.cimports.av.codec.codec import Codec
|
||||
from cython.cimports.av.codec.context import CodecContext, wrap_codec_context
|
||||
from cython.cimports.av.container.streams import StreamContainer
|
||||
from cython.cimports.av.dictionary import _Dictionary
|
||||
from cython.cimports.av.error import err_check
|
||||
from cython.cimports.av.packet import Packet
|
||||
from cython.cimports.av.stream import Stream, wrap_stream
|
||||
from cython.cimports.av.utils import dict_to_avdict, to_avrational
|
||||
|
||||
from av.dictionary import Dictionary
|
||||
|
||||
|
||||
@cython.cfunc
|
||||
def close_output(self: OutputContainer):
|
||||
self.streams = StreamContainer()
|
||||
if self._started and not self._done:
|
||||
# We must only ever call av_write_trailer *once*, otherwise we get a
|
||||
# segmentation fault. Therefore no matter whether it succeeds or not
|
||||
# we must absolutely set self._done.
|
||||
try:
|
||||
self.err_check(lib.av_write_trailer(self.ptr))
|
||||
finally:
|
||||
if self.file is None and not (self.ptr.oformat.flags & lib.AVFMT_NOFILE):
|
||||
lib.avio_closep(cython.address(self.ptr.pb))
|
||||
self._done = True
|
||||
|
||||
|
||||
@cython.cclass
|
||||
class OutputContainer(Container):
|
||||
def __cinit__(self, *args, **kwargs):
|
||||
self.streams = StreamContainer()
|
||||
self.metadata = {}
|
||||
with cython.nogil:
|
||||
self.packet_ptr = lib.av_packet_alloc()
|
||||
|
||||
def __dealloc__(self):
|
||||
close_output(self)
|
||||
with cython.nogil:
|
||||
lib.av_packet_free(cython.address(self.packet_ptr))
|
||||
|
||||
def add_stream(self, codec_name, rate=None, options: dict | None = None, **kwargs):
|
||||
"""add_stream(codec_name, rate=None)
|
||||
|
||||
Creates a new stream from a codec name and returns it.
|
||||
Supports video, audio, and subtitle streams.
|
||||
|
||||
:param codec_name: The name of a codec.
|
||||
:type codec_name: str
|
||||
:param dict options: Stream options.
|
||||
:param \\**kwargs: Set attributes for the stream.
|
||||
:rtype: The new :class:`~av.stream.Stream`.
|
||||
|
||||
"""
|
||||
|
||||
codec_obj: Codec = Codec(codec_name, "w")
|
||||
codec: cython.pointer[cython.const[lib.AVCodec]] = codec_obj.ptr
|
||||
|
||||
# Assert that this format supports the requested codec.
|
||||
if not lib.avformat_query_codec(
|
||||
self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL
|
||||
):
|
||||
raise ValueError(
|
||||
f"{self.format.name!r} format does not support {codec_obj.name!r} codec"
|
||||
)
|
||||
|
||||
# Create new stream in the AVFormatContext, set AVCodecContext values.
|
||||
stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(self.ptr, codec)
|
||||
ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3(codec)
|
||||
|
||||
# Now lets set some more sane video defaults
|
||||
if codec.type == lib.AVMEDIA_TYPE_VIDEO:
|
||||
ctx.pix_fmt = lib.AV_PIX_FMT_YUV420P
|
||||
ctx.width = kwargs.pop("width", 640)
|
||||
ctx.height = kwargs.pop("height", 480)
|
||||
ctx.bit_rate = kwargs.pop("bit_rate", 0)
|
||||
ctx.bit_rate_tolerance = kwargs.pop("bit_rate_tolerance", 128000)
|
||||
try:
|
||||
to_avrational(kwargs.pop("time_base"), cython.address(ctx.time_base))
|
||||
except KeyError:
|
||||
pass
|
||||
to_avrational(rate or 24, cython.address(ctx.framerate))
|
||||
|
||||
stream.avg_frame_rate = ctx.framerate
|
||||
stream.time_base = ctx.time_base
|
||||
|
||||
# Some sane audio defaults
|
||||
elif codec.type == lib.AVMEDIA_TYPE_AUDIO:
|
||||
ctx.sample_fmt = codec.sample_fmts[0]
|
||||
ctx.bit_rate = kwargs.pop("bit_rate", 0)
|
||||
ctx.bit_rate_tolerance = kwargs.pop("bit_rate_tolerance", 32000)
|
||||
try:
|
||||
to_avrational(kwargs.pop("time_base"), cython.address(ctx.time_base))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if rate is None:
|
||||
ctx.sample_rate = 48000
|
||||
elif type(rate) is int:
|
||||
ctx.sample_rate = rate
|
||||
else:
|
||||
raise TypeError("audio stream `rate` must be: int | None")
|
||||
stream.time_base = ctx.time_base
|
||||
lib.av_channel_layout_default(cython.address(ctx.ch_layout), 2)
|
||||
|
||||
# Some formats want stream headers to be separate
|
||||
if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER:
|
||||
ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER
|
||||
|
||||
# Initialise stream codec parameters to populate the codec type.
|
||||
#
|
||||
# Subsequent changes to the codec context will be applied just before
|
||||
# encoding starts in `start_encoding()`.
|
||||
err_check(lib.avcodec_parameters_from_context(stream.codecpar, ctx))
|
||||
|
||||
# Construct the user-land stream
|
||||
py_codec_context: CodecContext = wrap_codec_context(ctx, codec, None)
|
||||
py_stream: Stream = wrap_stream(self, stream, py_codec_context)
|
||||
self.streams.add_stream(py_stream)
|
||||
|
||||
if options:
|
||||
py_stream.options.update(options)
|
||||
|
||||
for k, v in kwargs.items():
|
||||
setattr(py_stream, k, v)
|
||||
|
||||
return py_stream
|
||||
|
||||
def add_stream_from_template(
|
||||
self, template: Stream, opaque: bool | None = None, **kwargs
|
||||
):
|
||||
"""
|
||||
Creates a new stream from a template. Supports video, audio, subtitle, data and attachment streams.
|
||||
|
||||
:param template: Copy codec from another :class:`~av.stream.Stream` instance.
|
||||
:param opaque: If True, copy opaque data from the template's codec context.
|
||||
:param \\**kwargs: Set attributes for the stream.
|
||||
:rtype: The new :class:`~av.stream.Stream`.
|
||||
"""
|
||||
if opaque is None:
|
||||
opaque = template.type != "video"
|
||||
|
||||
if template.codec_context is None:
|
||||
return self._add_stream_without_codec_from_template(template, **kwargs)
|
||||
|
||||
codec_obj: Codec
|
||||
if opaque: # Copy ctx from template.
|
||||
codec_obj = template.codec_context.codec
|
||||
else: # Construct new codec object.
|
||||
codec_obj = Codec(template.codec_context.codec.name, "w")
|
||||
|
||||
codec: cython.pointer[cython.const[lib.AVCodec]] = codec_obj.ptr
|
||||
|
||||
# Assert that this format supports the requested codec.
|
||||
if not lib.avformat_query_codec(
|
||||
self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL
|
||||
):
|
||||
raise ValueError(
|
||||
f"{self.format.name!r} format does not support {codec_obj.name!r} codec"
|
||||
)
|
||||
|
||||
# Create new stream in the AVFormatContext, set AVCodecContext values.
|
||||
stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(self.ptr, codec)
|
||||
ctx: cython.pointer[lib.AVCodecContext] = lib.avcodec_alloc_context3(codec)
|
||||
|
||||
err_check(lib.avcodec_parameters_to_context(ctx, template.ptr.codecpar))
|
||||
# Reset the codec tag assuming we are remuxing.
|
||||
ctx.codec_tag = 0
|
||||
|
||||
# Some formats want stream headers to be separate
|
||||
if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER:
|
||||
ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER
|
||||
|
||||
# Copy flags If we're creating a new codec object. This fixes some muxing issues.
|
||||
# Overwriting `ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER` is intentional.
|
||||
if not opaque:
|
||||
ctx.flags = template.codec_context.flags
|
||||
|
||||
# Initialize stream codec parameters to populate the codec type. Subsequent changes to
|
||||
# the codec context will be applied just before encoding starts in `start_encoding()`.
|
||||
err_check(lib.avcodec_parameters_from_context(stream.codecpar, ctx))
|
||||
|
||||
# Construct the user-land stream
|
||||
py_codec_context: CodecContext = wrap_codec_context(ctx, codec, None)
|
||||
py_stream: Stream = wrap_stream(self, stream, py_codec_context)
|
||||
self.streams.add_stream(py_stream)
|
||||
|
||||
for k, v in kwargs.items():
|
||||
setattr(py_stream, k, v)
|
||||
|
||||
return py_stream
|
||||
|
||||
def _add_stream_without_codec_from_template(
|
||||
self, template: Stream, **kwargs
|
||||
) -> Stream:
|
||||
codec_type: cython.int = template.ptr.codecpar.codec_type
|
||||
if codec_type not in {lib.AVMEDIA_TYPE_ATTACHMENT, lib.AVMEDIA_TYPE_DATA}:
|
||||
raise ValueError(
|
||||
f"template stream of type {template.type} has no codec context"
|
||||
)
|
||||
|
||||
stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(
|
||||
self.ptr, cython.NULL
|
||||
)
|
||||
if stream == cython.NULL:
|
||||
raise MemoryError("Could not allocate stream")
|
||||
|
||||
err_check(lib.avcodec_parameters_copy(stream.codecpar, template.ptr.codecpar))
|
||||
|
||||
# Mirror basic properties that are not derived from a codec context.
|
||||
stream.time_base = template.ptr.time_base
|
||||
stream.start_time = template.ptr.start_time
|
||||
stream.duration = template.ptr.duration
|
||||
stream.disposition = template.ptr.disposition
|
||||
|
||||
py_stream: Stream = wrap_stream(self, stream, None)
|
||||
self.streams.add_stream(py_stream)
|
||||
|
||||
py_stream.metadata = dict(template.metadata)
|
||||
|
||||
for k, v in kwargs.items():
|
||||
setattr(py_stream, k, v)
|
||||
|
||||
return py_stream
|
||||
|
||||
def add_attachment(self, name: str, mimetype: str, data: bytes):
|
||||
"""
|
||||
Create an attachment stream and embed its payload into the container header.
|
||||
|
||||
- Only supported by formats that support attachments (e.g. Matroska).
|
||||
- No per-packet muxing is required; attachments are written at header time.
|
||||
"""
|
||||
# Create stream with no codec (attachments are codec-less).
|
||||
stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(
|
||||
self.ptr, cython.NULL
|
||||
)
|
||||
if stream == cython.NULL:
|
||||
raise MemoryError("Could not allocate stream")
|
||||
|
||||
stream.codecpar.codec_type = lib.AVMEDIA_TYPE_ATTACHMENT
|
||||
stream.codecpar.codec_id = lib.AV_CODEC_ID_NONE
|
||||
|
||||
# Allocate and copy payload into codecpar.extradata.
|
||||
payload_size: cython.size_t = len(data)
|
||||
if payload_size:
|
||||
buf = cython.cast(cython.p_uchar, lib.av_malloc(payload_size + 1))
|
||||
if buf == cython.NULL:
|
||||
raise MemoryError("Could not allocate attachment data")
|
||||
# Copy bytes.
|
||||
for i in range(payload_size):
|
||||
buf[i] = data[i]
|
||||
buf[payload_size] = 0
|
||||
stream.codecpar.extradata = cython.cast(cython.p_uchar, buf)
|
||||
stream.codecpar.extradata_size = payload_size
|
||||
|
||||
# Wrap as user-land stream.
|
||||
meta_ptr = cython.address(stream.metadata)
|
||||
err_check(lib.av_dict_set(meta_ptr, b"filename", name.encode(), 0))
|
||||
mime_bytes = mimetype.encode()
|
||||
err_check(lib.av_dict_set(meta_ptr, b"mimetype", mime_bytes, 0))
|
||||
|
||||
py_stream: Stream = wrap_stream(self, stream, None)
|
||||
self.streams.add_stream(py_stream)
|
||||
return py_stream
|
||||
|
||||
def add_data_stream(self, codec_name=None, options: dict | None = None):
|
||||
"""add_data_stream(codec_name=None)
|
||||
|
||||
Creates a new data stream and returns it.
|
||||
|
||||
:param codec_name: Optional name of the data codec (e.g. 'klv')
|
||||
:type codec_name: str | None
|
||||
:param dict options: Stream options.
|
||||
:rtype: The new :class:`~av.data.stream.DataStream`.
|
||||
"""
|
||||
codec: cython.pointer[cython.const[lib.AVCodec]] = cython.NULL
|
||||
|
||||
if codec_name is not None:
|
||||
codec = lib.avcodec_find_encoder_by_name(codec_name.encode())
|
||||
if codec == cython.NULL:
|
||||
raise ValueError(f"Unknown data codec: {codec_name}")
|
||||
|
||||
# Assert that this format supports the requested codec
|
||||
if not lib.avformat_query_codec(
|
||||
self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL
|
||||
):
|
||||
raise ValueError(
|
||||
f"{self.format.name!r} format does not support {codec_name!r} codec"
|
||||
)
|
||||
|
||||
# Create new stream in the AVFormatContext
|
||||
stream: cython.pointer[lib.AVStream] = lib.avformat_new_stream(self.ptr, codec)
|
||||
if stream == cython.NULL:
|
||||
raise MemoryError("Could not allocate stream")
|
||||
|
||||
# Set up codec context if we have a codec
|
||||
ctx: cython.pointer[lib.AVCodecContext] = cython.NULL
|
||||
if codec != cython.NULL:
|
||||
ctx = lib.avcodec_alloc_context3(codec)
|
||||
if ctx == cython.NULL:
|
||||
raise MemoryError("Could not allocate codec context")
|
||||
|
||||
# Some formats want stream headers to be separate
|
||||
if self.ptr.oformat.flags & lib.AVFMT_GLOBALHEADER:
|
||||
ctx.flags |= lib.AV_CODEC_FLAG_GLOBAL_HEADER
|
||||
|
||||
# Initialize stream codec parameters
|
||||
err_check(lib.avcodec_parameters_from_context(stream.codecpar, ctx))
|
||||
else:
|
||||
# For raw data streams, just set the codec type
|
||||
stream.codecpar.codec_type = lib.AVMEDIA_TYPE_DATA
|
||||
|
||||
# Construct the user-land stream
|
||||
py_codec_context: CodecContext | None = None
|
||||
if ctx != cython.NULL:
|
||||
py_codec_context = wrap_codec_context(ctx, codec, None)
|
||||
|
||||
py_stream: Stream = wrap_stream(self, stream, py_codec_context)
|
||||
self.streams.add_stream(py_stream)
|
||||
|
||||
if options:
|
||||
py_stream.options.update(options)
|
||||
|
||||
return py_stream
|
||||
|
||||
@cython.ccall
|
||||
def start_encoding(self):
|
||||
"""Write the file header! Called automatically."""
|
||||
if self._started:
|
||||
return
|
||||
|
||||
# TODO: This does NOT handle options coming from 3 sources.
|
||||
# This is only a rough approximation of what would be cool to do.
|
||||
used_options: set = set()
|
||||
stream: Stream
|
||||
|
||||
# Finalize and open all streams.
|
||||
for stream in self.streams:
|
||||
ctx = stream.codec_context
|
||||
# Skip codec context handling for streams without codecs (e.g. data/attachments).
|
||||
if ctx is None:
|
||||
if stream.type not in {"data", "attachment"}:
|
||||
raise ValueError(f"Stream {stream.index} has no codec context")
|
||||
else:
|
||||
if not ctx.is_open:
|
||||
for k, v in self.options.items():
|
||||
ctx.options.setdefault(k, v)
|
||||
ctx.open()
|
||||
|
||||
# Track option consumption.
|
||||
for k in self.options:
|
||||
if k not in ctx.options:
|
||||
used_options.add(k)
|
||||
|
||||
stream._finalize_for_output()
|
||||
|
||||
# Open the output file, if needed.
|
||||
name_obj: bytes = os.fsencode(self.name if self.file is None else "")
|
||||
name: cython.p_char = name_obj
|
||||
if self.ptr.pb == cython.NULL and not self.ptr.oformat.flags & lib.AVFMT_NOFILE:
|
||||
err_check(
|
||||
lib.avio_open(cython.address(self.ptr.pb), name, lib.AVIO_FLAG_WRITE)
|
||||
)
|
||||
|
||||
# Copy the metadata dict.
|
||||
dict_to_avdict(
|
||||
cython.address(self.ptr.metadata),
|
||||
self.metadata,
|
||||
encoding=self.metadata_encoding,
|
||||
errors=self.metadata_errors,
|
||||
)
|
||||
|
||||
all_options: _Dictionary = Dictionary(self.options, self.container_options)
|
||||
options: _Dictionary = all_options.copy()
|
||||
self.err_check(lib.avformat_write_header(self.ptr, cython.address(options.ptr)))
|
||||
|
||||
# Track option usage...
|
||||
for k in all_options:
|
||||
if k not in options:
|
||||
used_options.add(k)
|
||||
|
||||
# ... and warn if any weren't used.
|
||||
unused_options = {
|
||||
k: v for k, v in self.options.items() if k not in used_options
|
||||
}
|
||||
if unused_options:
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.warning("Some options were not used: %s" % unused_options)
|
||||
|
||||
self._started = True
|
||||
|
||||
@property
|
||||
def supported_codecs(self):
|
||||
"""
|
||||
Returns a set of all codecs this format supports.
|
||||
"""
|
||||
result: set = set()
|
||||
codec: cython.pointer[cython.const[lib.AVCodec]] = cython.NULL
|
||||
opaque: cython.p_void = cython.NULL
|
||||
|
||||
while True:
|
||||
codec = lib.av_codec_iterate(cython.address(opaque))
|
||||
if codec == cython.NULL:
|
||||
break
|
||||
|
||||
if (
|
||||
lib.avformat_query_codec(
|
||||
self.ptr.oformat, codec.id, lib.FF_COMPLIANCE_NORMAL
|
||||
)
|
||||
== 1
|
||||
):
|
||||
result.add(codec.name)
|
||||
|
||||
return result
|
||||
|
||||
@property
|
||||
def default_video_codec(self):
|
||||
"""
|
||||
Returns the default video codec this container recommends.
|
||||
"""
|
||||
return lib.avcodec_get_name(self.format.optr.video_codec)
|
||||
|
||||
@property
|
||||
def default_audio_codec(self):
|
||||
"""
|
||||
Returns the default audio codec this container recommends.
|
||||
"""
|
||||
return lib.avcodec_get_name(self.format.optr.audio_codec)
|
||||
|
||||
@property
|
||||
def default_subtitle_codec(self):
|
||||
"""
|
||||
Returns the default subtitle codec this container recommends.
|
||||
"""
|
||||
return lib.avcodec_get_name(self.format.optr.subtitle_codec)
|
||||
|
||||
def close(self):
|
||||
close_output(self)
|
||||
|
||||
def mux(self, packets):
|
||||
# We accept either a Packet, or a sequence of packets. This should smooth out
|
||||
# the transition to the new encode API which returns a sequence of packets.
|
||||
if isinstance(packets, Packet):
|
||||
self.mux_one(packets)
|
||||
else:
|
||||
for packet in packets:
|
||||
self.mux_one(packet)
|
||||
|
||||
def mux_one(self, packet: Packet):
|
||||
self.start_encoding()
|
||||
|
||||
# Assert the packet is in stream time.
|
||||
if (
|
||||
packet.ptr.stream_index < 0
|
||||
or cython.cast(cython.uint, packet.ptr.stream_index) >= self.ptr.nb_streams
|
||||
):
|
||||
raise ValueError("Bad Packet stream_index.")
|
||||
|
||||
stream: cython.pointer[lib.AVStream] = self.ptr.streams[packet.ptr.stream_index]
|
||||
packet._rebase_time(stream.time_base)
|
||||
|
||||
# Make another reference to the packet, as `av_interleaved_write_frame()`
|
||||
# takes ownership of the reference.
|
||||
self.err_check(lib.av_packet_ref(self.packet_ptr, packet.ptr))
|
||||
|
||||
with cython.nogil:
|
||||
ret: cython.int = lib.av_interleaved_write_frame(self.ptr, self.packet_ptr)
|
||||
self.err_check(ret)
|
||||
62
venv/lib/python3.12/site-packages/av/container/output.pyi
Normal file
62
venv/lib/python3.12/site-packages/av/container/output.pyi
Normal file
@@ -0,0 +1,62 @@
|
||||
from fractions import Fraction
|
||||
from typing import Sequence, TypeVar, Union, overload
|
||||
|
||||
from av.audio import _AudioCodecName
|
||||
from av.audio.stream import AudioStream
|
||||
from av.packet import Packet
|
||||
from av.stream import AttachmentStream, DataStream, Stream
|
||||
from av.subtitles.stream import SubtitleStream
|
||||
from av.video import _VideoCodecName
|
||||
from av.video.stream import VideoStream
|
||||
|
||||
from .core import Container
|
||||
|
||||
_StreamT = TypeVar("_StreamT", bound=Stream)
|
||||
|
||||
class OutputContainer(Container):
|
||||
def __enter__(self) -> OutputContainer: ...
|
||||
@overload
|
||||
def add_stream(
|
||||
self,
|
||||
codec_name: _AudioCodecName,
|
||||
rate: int | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
**kwargs,
|
||||
) -> AudioStream: ...
|
||||
@overload
|
||||
def add_stream(
|
||||
self,
|
||||
codec_name: _VideoCodecName,
|
||||
rate: Fraction | int | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
**kwargs,
|
||||
) -> VideoStream: ...
|
||||
@overload
|
||||
def add_stream(
|
||||
self,
|
||||
codec_name: str,
|
||||
rate: Fraction | int | None = None,
|
||||
options: dict[str, str] | None = None,
|
||||
**kwargs,
|
||||
) -> VideoStream | AudioStream | SubtitleStream: ...
|
||||
def add_stream_from_template(
|
||||
self, template: _StreamT, opaque: bool | None = None, **kwargs
|
||||
) -> _StreamT: ...
|
||||
def add_attachment(
|
||||
self, name: str, mimetype: str, data: bytes
|
||||
) -> AttachmentStream: ...
|
||||
def add_data_stream(
|
||||
self, codec_name: str | None = None, options: dict[str, str] | None = None
|
||||
) -> DataStream: ...
|
||||
def start_encoding(self) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
def mux(self, packets: Packet | Sequence[Packet]) -> None: ...
|
||||
def mux_one(self, packet: Packet) -> None: ...
|
||||
@property
|
||||
def default_video_codec(self) -> str: ...
|
||||
@property
|
||||
def default_audio_codec(self) -> str: ...
|
||||
@property
|
||||
def default_subtitle_codec(self) -> str: ...
|
||||
@property
|
||||
def supported_codecs(self) -> set[str]: ...
|
||||
Binary file not shown.
24
venv/lib/python3.12/site-packages/av/container/pyio.pxd
Normal file
24
venv/lib/python3.12/site-packages/av/container/pyio.pxd
Normal file
@@ -0,0 +1,24 @@
|
||||
cimport libav as lib
|
||||
from libc.stdint cimport int64_t, uint8_t
|
||||
|
||||
|
||||
cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) noexcept nogil
|
||||
cdef int pyio_write(void *opaque, const uint8_t *buf, int buf_size) noexcept nogil
|
||||
cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil
|
||||
cdef int pyio_close_gil(lib.AVIOContext *pb)
|
||||
cdef int pyio_close_custom_gil(lib.AVIOContext *pb)
|
||||
|
||||
cdef class PyIOFile:
|
||||
# File-like source.
|
||||
cdef readonly object file
|
||||
cdef object fread
|
||||
cdef object fwrite
|
||||
cdef object fseek
|
||||
cdef object ftell
|
||||
cdef object fclose
|
||||
|
||||
# Custom IO for above.
|
||||
cdef lib.AVIOContext *iocontext
|
||||
cdef unsigned char *buffer
|
||||
cdef long pos
|
||||
cdef bint pos_is_valid
|
||||
169
venv/lib/python3.12/site-packages/av/container/pyio.pyx
Normal file
169
venv/lib/python3.12/site-packages/av/container/pyio.pyx
Normal file
@@ -0,0 +1,169 @@
|
||||
cimport libav as lib
|
||||
from libc.string cimport memcpy
|
||||
|
||||
from av.error cimport stash_exception
|
||||
|
||||
ctypedef int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) noexcept nogil
|
||||
|
||||
|
||||
cdef class PyIOFile:
|
||||
def __cinit__(self, file, buffer_size, writeable=None):
|
||||
self.file = file
|
||||
|
||||
cdef seek_func_t seek_func = NULL
|
||||
|
||||
readable = getattr(self.file, "readable", None)
|
||||
writable = getattr(self.file, "writable", None)
|
||||
seekable = getattr(self.file, "seekable", None)
|
||||
self.fread = getattr(self.file, "read", None)
|
||||
self.fwrite = getattr(self.file, "write", None)
|
||||
self.fseek = getattr(self.file, "seek", None)
|
||||
self.ftell = getattr(self.file, "tell", None)
|
||||
self.fclose = getattr(self.file, "close", None)
|
||||
|
||||
# To be seekable the file object must have `seek` and `tell` methods.
|
||||
# If it also has a `seekable` method, it must return True.
|
||||
if (
|
||||
self.fseek is not None
|
||||
and self.ftell is not None
|
||||
and (seekable is None or seekable())
|
||||
):
|
||||
seek_func = pyio_seek
|
||||
|
||||
if writeable is None:
|
||||
writeable = self.fwrite is not None
|
||||
|
||||
if writeable:
|
||||
if self.fwrite is None or (writable is not None and not writable()):
|
||||
raise ValueError("File object has no write() method, or writable() returned False.")
|
||||
else:
|
||||
if self.fread is None or (readable is not None and not readable()):
|
||||
raise ValueError("File object has no read() method, or readable() returned False.")
|
||||
|
||||
self.pos = 0
|
||||
self.pos_is_valid = True
|
||||
|
||||
# This is effectively the maximum size of reads.
|
||||
self.buffer = <unsigned char*>lib.av_malloc(buffer_size)
|
||||
|
||||
self.iocontext = lib.avio_alloc_context(
|
||||
self.buffer,
|
||||
buffer_size,
|
||||
writeable,
|
||||
<void*>self, # User data.
|
||||
pyio_read,
|
||||
pyio_write,
|
||||
seek_func
|
||||
)
|
||||
|
||||
if seek_func:
|
||||
self.iocontext.seekable = lib.AVIO_SEEKABLE_NORMAL
|
||||
self.iocontext.max_packet_size = buffer_size
|
||||
|
||||
def __dealloc__(self):
|
||||
with nogil:
|
||||
# FFmpeg will not release custom input, so it's up to us to free it.
|
||||
# Do not touch our original buffer as it may have been freed and replaced.
|
||||
if self.iocontext:
|
||||
lib.av_freep(&self.iocontext.buffer)
|
||||
lib.av_freep(&self.iocontext)
|
||||
|
||||
# We likely errored badly if we got here, and so are still
|
||||
# responsible for our buffer.
|
||||
else:
|
||||
lib.av_freep(&self.buffer)
|
||||
|
||||
|
||||
cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) noexcept nogil:
|
||||
with gil:
|
||||
return pyio_read_gil(opaque, buf, buf_size)
|
||||
|
||||
cdef int pyio_read_gil(void *opaque, uint8_t *buf, int buf_size) noexcept:
|
||||
cdef PyIOFile self
|
||||
cdef bytes res
|
||||
try:
|
||||
self = <PyIOFile>opaque
|
||||
res = self.fread(buf_size)
|
||||
memcpy(buf, <void*><char*>res, len(res))
|
||||
self.pos += len(res)
|
||||
if not res:
|
||||
return lib.AVERROR_EOF
|
||||
return len(res)
|
||||
except Exception:
|
||||
return stash_exception()
|
||||
|
||||
|
||||
cdef int pyio_write(void *opaque, const uint8_t *buf, int buf_size) noexcept nogil:
|
||||
with gil:
|
||||
return pyio_write_gil(opaque, buf, buf_size)
|
||||
|
||||
cdef int pyio_write_gil(void *opaque, const uint8_t *buf, int buf_size) noexcept:
|
||||
cdef PyIOFile self
|
||||
cdef bytes bytes_to_write
|
||||
cdef int bytes_written
|
||||
try:
|
||||
self = <PyIOFile>opaque
|
||||
bytes_to_write = buf[:buf_size]
|
||||
ret_value = self.fwrite(bytes_to_write)
|
||||
bytes_written = ret_value if isinstance(ret_value, int) else buf_size
|
||||
self.pos += bytes_written
|
||||
return bytes_written
|
||||
except Exception:
|
||||
return stash_exception()
|
||||
|
||||
|
||||
cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) noexcept nogil:
|
||||
# Seek takes the standard flags, but also a ad-hoc one which means that
|
||||
# the library wants to know how large the file is. We are generally
|
||||
# allowed to ignore this.
|
||||
if whence == lib.AVSEEK_SIZE:
|
||||
return -1
|
||||
with gil:
|
||||
return pyio_seek_gil(opaque, offset, whence)
|
||||
|
||||
cdef int64_t pyio_seek_gil(void *opaque, int64_t offset, int whence):
|
||||
cdef PyIOFile self
|
||||
try:
|
||||
self = <PyIOFile>opaque
|
||||
res = self.fseek(offset, whence)
|
||||
|
||||
# Track the position for the user.
|
||||
if whence == 0:
|
||||
self.pos = offset
|
||||
elif whence == 1:
|
||||
self.pos += offset
|
||||
else:
|
||||
self.pos_is_valid = False
|
||||
if res is None:
|
||||
if self.pos_is_valid:
|
||||
res = self.pos
|
||||
else:
|
||||
res = self.ftell()
|
||||
return res
|
||||
except Exception:
|
||||
return stash_exception()
|
||||
|
||||
|
||||
cdef int pyio_close_gil(lib.AVIOContext *pb):
|
||||
try:
|
||||
return lib.avio_close(pb)
|
||||
|
||||
except Exception:
|
||||
stash_exception()
|
||||
|
||||
|
||||
cdef int pyio_close_custom_gil(lib.AVIOContext *pb):
|
||||
cdef PyIOFile self
|
||||
try:
|
||||
self = <PyIOFile>pb.opaque
|
||||
|
||||
# Flush bytes in the AVIOContext buffers to the custom I/O
|
||||
lib.avio_flush(pb)
|
||||
|
||||
if self.fclose is not None:
|
||||
self.fclose()
|
||||
|
||||
return 0
|
||||
|
||||
except Exception:
|
||||
stash_exception()
|
||||
Binary file not shown.
21
venv/lib/python3.12/site-packages/av/container/streams.pxd
Normal file
21
venv/lib/python3.12/site-packages/av/container/streams.pxd
Normal file
@@ -0,0 +1,21 @@
|
||||
cimport libav as lib
|
||||
|
||||
from av.stream cimport Stream
|
||||
|
||||
from .core cimport Container
|
||||
|
||||
|
||||
cdef class StreamContainer:
|
||||
cdef list _streams
|
||||
|
||||
# For the different types.
|
||||
cdef readonly tuple video
|
||||
cdef readonly tuple audio
|
||||
cdef readonly tuple subtitles
|
||||
cdef readonly tuple attachments
|
||||
cdef readonly tuple data
|
||||
cdef readonly tuple other
|
||||
|
||||
cdef add_stream(self, Stream stream)
|
||||
cdef int _get_best_stream_index(self, Container container, lib.AVMediaType type_enum, Stream related) noexcept
|
||||
|
||||
35
venv/lib/python3.12/site-packages/av/container/streams.pyi
Normal file
35
venv/lib/python3.12/site-packages/av/container/streams.pyi
Normal file
@@ -0,0 +1,35 @@
|
||||
from typing import Iterator, Literal, overload
|
||||
|
||||
from av.audio.stream import AudioStream
|
||||
from av.stream import AttachmentStream, DataStream, Stream
|
||||
from av.subtitles.stream import SubtitleStream
|
||||
from av.video.stream import VideoStream
|
||||
|
||||
class StreamContainer:
|
||||
video: tuple[VideoStream, ...]
|
||||
audio: tuple[AudioStream, ...]
|
||||
subtitles: tuple[SubtitleStream, ...]
|
||||
attachments: tuple[AttachmentStream, ...]
|
||||
data: tuple[DataStream, ...]
|
||||
other: tuple[Stream, ...]
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __iter__(self) -> Iterator[Stream]: ...
|
||||
@overload
|
||||
def __getitem__(self, index: int) -> Stream: ...
|
||||
@overload
|
||||
def __getitem__(self, index: slice) -> list[Stream]: ...
|
||||
@overload
|
||||
def __getitem__(self, index: int | slice) -> Stream | list[Stream]: ...
|
||||
def get(
|
||||
self,
|
||||
*args: int | Stream | dict[str, int | tuple[int, ...]],
|
||||
**kwargs: int | tuple[int, ...],
|
||||
) -> list[Stream]: ...
|
||||
def best(
|
||||
self,
|
||||
type: Literal["video", "audio", "subtitle", "data", "attachment"],
|
||||
/,
|
||||
related: Stream | None = None,
|
||||
) -> Stream | None: ...
|
||||
170
venv/lib/python3.12/site-packages/av/container/streams.pyx
Normal file
170
venv/lib/python3.12/site-packages/av/container/streams.pyx
Normal file
@@ -0,0 +1,170 @@
|
||||
cimport libav as lib
|
||||
|
||||
|
||||
def _flatten(input_):
|
||||
for x in input_:
|
||||
if isinstance(x, (tuple, list)):
|
||||
for y in _flatten(x):
|
||||
yield y
|
||||
else:
|
||||
yield x
|
||||
|
||||
cdef lib.AVMediaType _get_media_type_enum(str type):
|
||||
if type == "video":
|
||||
return lib.AVMEDIA_TYPE_VIDEO
|
||||
elif type == "audio":
|
||||
return lib.AVMEDIA_TYPE_AUDIO
|
||||
elif type == "subtitle":
|
||||
return lib.AVMEDIA_TYPE_SUBTITLE
|
||||
elif type == "attachment":
|
||||
return lib.AVMEDIA_TYPE_ATTACHMENT
|
||||
elif type == "data":
|
||||
return lib.AVMEDIA_TYPE_DATA
|
||||
else:
|
||||
raise ValueError(f"Invalid stream type: {type}")
|
||||
|
||||
cdef class StreamContainer:
|
||||
"""
|
||||
|
||||
A tuple-like container of :class:`Stream`.
|
||||
|
||||
::
|
||||
|
||||
# There are a few ways to pulling out streams.
|
||||
first = container.streams[0]
|
||||
video = container.streams.video[0]
|
||||
audio = container.streams.get(audio=(0, 1))
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __cinit__(self):
|
||||
self._streams = []
|
||||
self.video = ()
|
||||
self.audio = ()
|
||||
self.subtitles = ()
|
||||
self.data = ()
|
||||
self.attachments = ()
|
||||
self.other = ()
|
||||
|
||||
cdef add_stream(self, Stream stream):
|
||||
|
||||
assert stream.ptr.index == len(self._streams)
|
||||
self._streams.append(stream)
|
||||
|
||||
if stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO:
|
||||
self.video = self.video + (stream, )
|
||||
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO:
|
||||
self.audio = self.audio + (stream, )
|
||||
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE:
|
||||
self.subtitles = self.subtitles + (stream, )
|
||||
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_ATTACHMENT:
|
||||
self.attachments = self.attachments + (stream, )
|
||||
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA:
|
||||
self.data = self.data + (stream, )
|
||||
else:
|
||||
self.other = self.other + (stream, )
|
||||
|
||||
# Basic tuple interface.
|
||||
def __len__(self):
|
||||
return len(self._streams)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._streams)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, int):
|
||||
return self.get(index)[0]
|
||||
else:
|
||||
return self.get(index)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""get(streams=None, video=None, audio=None, subtitles=None, data=None)
|
||||
|
||||
Get a selection of :class:`.Stream` as a ``list``.
|
||||
|
||||
Positional arguments may be ``int`` (which is an index into the streams),
|
||||
or ``list`` or ``tuple`` of those::
|
||||
|
||||
# Get the first channel.
|
||||
streams.get(0)
|
||||
|
||||
# Get the first two audio channels.
|
||||
streams.get(audio=(0, 1))
|
||||
|
||||
Keyword arguments (or dicts as positional arguments) as interpreted
|
||||
as ``(stream_type, index_value_or_set)`` pairs::
|
||||
|
||||
# Get the first video channel.
|
||||
streams.get(video=0)
|
||||
# or
|
||||
streams.get({'video': 0})
|
||||
|
||||
:class:`.Stream` objects are passed through untouched.
|
||||
|
||||
If nothing is selected, then all streams are returned.
|
||||
|
||||
"""
|
||||
|
||||
selection = []
|
||||
|
||||
for x in _flatten((args, kwargs)):
|
||||
if x is None:
|
||||
pass
|
||||
|
||||
elif isinstance(x, Stream):
|
||||
selection.append(x)
|
||||
|
||||
elif isinstance(x, int):
|
||||
selection.append(self._streams[x])
|
||||
|
||||
elif isinstance(x, dict):
|
||||
for type_, indices in x.items():
|
||||
if type_ == "streams": # For compatibility with the pseudo signature
|
||||
streams = self._streams
|
||||
else:
|
||||
streams = getattr(self, type_)
|
||||
if not isinstance(indices, (tuple, list)):
|
||||
indices = [indices]
|
||||
for i in indices:
|
||||
selection.append(streams[i])
|
||||
|
||||
else:
|
||||
raise TypeError("Argument must be Stream or int.", type(x))
|
||||
|
||||
return selection or self._streams[:]
|
||||
|
||||
cdef int _get_best_stream_index(self, Container container, lib.AVMediaType type_enum, Stream related) noexcept:
|
||||
cdef int stream_index
|
||||
|
||||
if related is None:
|
||||
stream_index = lib.av_find_best_stream(container.ptr, type_enum, -1, -1, NULL, 0)
|
||||
else:
|
||||
stream_index = lib.av_find_best_stream(container.ptr, type_enum, -1, related.ptr.index, NULL, 0)
|
||||
|
||||
return stream_index
|
||||
|
||||
def best(self, str type, /, Stream related = None):
|
||||
"""best(type: Literal["video", "audio", "subtitle", "attachment", "data"], /, related: Stream | None)
|
||||
Finds the "best" stream in the file. Wraps :ffmpeg:`av_find_best_stream`. Example::
|
||||
|
||||
stream = container.streams.best("video")
|
||||
|
||||
:param type: The type of stream to find
|
||||
:param related: A related stream to use as a reference (optional)
|
||||
:return: The best stream of the specified type
|
||||
:rtype: Stream | None
|
||||
"""
|
||||
cdef type_enum = _get_media_type_enum(type)
|
||||
|
||||
if len(self._streams) == 0:
|
||||
return None
|
||||
|
||||
cdef container = self._streams[0].container
|
||||
|
||||
cdef int stream_index = self._get_best_stream_index(container, type_enum, related)
|
||||
|
||||
if stream_index < 0:
|
||||
return None
|
||||
|
||||
return self._streams[stream_index]
|
||||
Reference in New Issue
Block a user