diff --git a/av/attachments/__init__.py b/av/attachments/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/av/attachments/stream.pxd b/av/attachments/stream.pxd deleted file mode 100644 index 81f788b77..000000000 --- a/av/attachments/stream.pxd +++ /dev/null @@ -1,5 +0,0 @@ -from av.stream cimport Stream - - -cdef class AttachmentStream(Stream): - pass diff --git a/av/attachments/stream.pyi b/av/attachments/stream.pyi deleted file mode 100644 index 3d660e4a0..000000000 --- a/av/attachments/stream.pyi +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Literal - -from av.stream import Stream - -class AttachmentStream(Stream): - type: Literal["attachment"] - @property - def mimetype(self) -> str | None: ... diff --git a/av/attachments/stream.pyx b/av/attachments/stream.pyx deleted file mode 100644 index de7d10119..000000000 --- a/av/attachments/stream.pyx +++ /dev/null @@ -1,26 +0,0 @@ -from av.stream cimport Stream - - -cdef class AttachmentStream(Stream): - """ - An :class:`AttachmentStream` represents a stream of attachment data within a media container. - Typically used to attach font files that are referenced in ASS/SSA Subtitle Streams. - """ - - @property - def name(self): - """ - Returns the file name of the attachment. - - :rtype: str | None - """ - return self.metadata.get("filename") - - @property - def mimetype(self): - """ - Returns the MIME type of the attachment. - - :rtype: str | None - """ - return self.metadata.get("mimetype") diff --git a/av/container/output.pyi b/av/container/output.pyi index 568345cd2..b370095de 100644 --- a/av/container/output.pyi +++ b/av/container/output.pyi @@ -3,8 +3,8 @@ from typing import Sequence, TypeVar, Union, overload from av.audio import _AudioCodecName from av.audio.stream import AudioStream -from av.data.stream import DataStream from av.packet import Packet +from av.stream import DataStream from av.subtitles.stream import SubtitleStream from av.video import _VideoCodecName from av.video.stream import VideoStream diff --git a/av/container/streams.pyi b/av/container/streams.pyi index fbaf1b67f..52b98818f 100644 --- a/av/container/streams.pyi +++ b/av/container/streams.pyi @@ -1,9 +1,7 @@ from typing import Iterator, Literal, overload -from av.attachments.stream import AttachmentStream from av.audio.stream import AudioStream -from av.data.stream import DataStream -from av.stream import Stream +from av.stream import AttachmentStream, DataStream, Stream from av.subtitles.stream import SubtitleStream from av.video.stream import VideoStream diff --git a/av/data/__init__.pxd b/av/data/__init__.pxd deleted file mode 100644 index e69de29bb..000000000 diff --git a/av/data/__init__.py b/av/data/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/av/data/stream.pxd b/av/data/stream.pxd deleted file mode 100644 index 012792a4a..000000000 --- a/av/data/stream.pxd +++ /dev/null @@ -1,5 +0,0 @@ -from av.stream cimport Stream - - -cdef class DataStream(Stream): - pass diff --git a/av/data/stream.pyi b/av/data/stream.pyi deleted file mode 100644 index 45a669d4f..000000000 --- a/av/data/stream.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from av.frame import Frame -from av.packet import Packet -from av.stream import Stream - -class DataStream(Stream): - name: str | None diff --git a/av/data/stream.pyx b/av/data/stream.pyx deleted file mode 100644 index 4cc957762..000000000 --- a/av/data/stream.pyx +++ /dev/null @@ -1,16 +0,0 @@ -cimport libav as lib - - -cdef class DataStream(Stream): - def __repr__(self): - return ( - f"'} at 0x{id(self):x}>" - ) - - @property - def name(self): - cdef const lib.AVCodecDescriptor *desc = lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) - if desc == NULL: - return None - return desc.name diff --git a/av/stream.pxd b/av/stream.pxd index c847f641e..9aa6616e5 100644 --- a/av/stream.pxd +++ b/av/stream.pxd @@ -19,8 +19,14 @@ cdef class Stream: # Private API. cdef _init(self, Container, lib.AVStream*, CodecContext) cdef _finalize_for_output(self) - cdef _set_time_base(self, value) cdef _set_id(self, value) cdef Stream wrap_stream(Container, lib.AVStream*, CodecContext) + + +cdef class DataStream(Stream): + pass + +cdef class AttachmentStream(Stream): + pass diff --git a/av/stream.pyx b/av/stream.py similarity index 72% rename from av/stream.pyx rename to av/stream.py index 90f10d038..b5c22588b 100644 --- a/av/stream.pyx +++ b/av/stream.py @@ -1,10 +1,10 @@ -cimport libav as lib - from enum import Flag -from av.error cimport err_check -from av.packet cimport Packet -from av.utils cimport ( +import cython +from cython.cimports import libav as lib +from cython.cimports.av.error import err_check +from cython.cimports.av.packet import Packet +from cython.cimports.av.utils import ( avdict_to_dict, avrational_to_fraction, dict_to_avdict, @@ -34,35 +34,38 @@ class Disposition(Flag): multilayer = 1 << 21 -cdef object _cinit_bypass_sentinel = object() +_cinit_bypass_sentinel = cython.declare(object, object()) -cdef Stream wrap_stream(Container container, lib.AVStream *c_stream, CodecContext codec_context): - """Build an av.Stream for an existing AVStream. - The AVStream MUST be fully constructed and ready for use before this is - called. +@cython.cfunc +def wrap_stream( + container: Container, + c_stream: cython.pointer[lib.AVStream], + codec_context: CodecContext, +) -> Stream: + """Build an av.Stream for an existing AVStream. + The AVStream MUST be fully constructed and ready for use before this is called. """ # This better be the right one... assert container.ptr.streams[c_stream.index] == c_stream - cdef Stream py_stream + py_stream: Stream + + from av.audio.stream import AudioStream + from av.subtitles.stream import SubtitleStream + from av.video.stream import VideoStream if c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO: - from av.video.stream import VideoStream py_stream = VideoStream.__new__(VideoStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO: - from av.audio.stream import AudioStream py_stream = AudioStream.__new__(AudioStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE: - from av.subtitles.stream import SubtitleStream py_stream = SubtitleStream.__new__(SubtitleStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_ATTACHMENT: - from av.attachments.stream import AttachmentStream py_stream = AttachmentStream.__new__(AttachmentStream, _cinit_bypass_sentinel) elif c_stream.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA: - from av.data.stream import DataStream py_stream = DataStream.__new__(DataStream, _cinit_bypass_sentinel) else: py_stream = Stream.__new__(Stream, _cinit_bypass_sentinel) @@ -71,7 +74,8 @@ class Disposition(Flag): return py_stream -cdef class Stream: +@cython.cclass +class Stream: """ A single stream of audio, video or subtitles within a :class:`.Container`. @@ -93,7 +97,13 @@ def __cinit__(self, name): return raise RuntimeError("cannot manually instantiate Stream") - cdef _init(self, Container container, lib.AVStream *stream, CodecContext codec_context): + @cython.cfunc + def _init( + self, + container: Container, + stream: cython.pointer[lib.AVStream], + codec_context: CodecContext, + ): self.container = container self.ptr = stream @@ -121,18 +131,19 @@ def __setattr__(self, name, value): if name == "disposition": self.ptr.disposition = value return + if name == "time_base": + to_avrational(value, cython.address(self.ptr.time_base)) + return # Convenience setter for codec context properties. if self.codec_context is not None: setattr(self.codec_context, name, value) - if name == "time_base": - self._set_time_base(value) - - cdef _finalize_for_output(self): - + @cython.cfunc + def _finalize_for_output(self): dict_to_avdict( - &self.ptr.metadata, self.metadata, + cython.address(self.ptr.metadata), + self.metadata, encoding=self.container.metadata_encoding, errors=self.container.metadata_errors, ) @@ -140,9 +151,12 @@ def __setattr__(self, name, value): if not self.ptr.time_base.num: self.ptr.time_base = self.codec_context.ptr.time_base - # It prefers if we pass it parameters via this other object. - # Lets just copy what we want. - err_check(lib.avcodec_parameters_from_context(self.ptr.codecpar, self.codec_context.ptr)) + # It prefers if we pass it parameters via this other object. Let's just copy what we want. + err_check( + lib.avcodec_parameters_from_context( + self.ptr.codecpar, self.codec_context.ptr + ) + ) @property def id(self): @@ -154,10 +168,8 @@ def id(self): """ return self.ptr.id - cdef _set_id(self, value): - """ - Setter used by __setattr__ for the id property. - """ + @cython.cfunc + def _set_id(self, value): if value is None: self.ptr.id = 0 else: @@ -196,7 +208,6 @@ def index(self): """ return self.ptr.index - @property def time_base(self): """ @@ -205,13 +216,7 @@ def time_base(self): :type: fractions.Fraction | None """ - return avrational_to_fraction(&self.ptr.time_base) - - cdef _set_time_base(self, value): - """ - Setter used by __setattr__ for the time_base property. - """ - to_avrational(value, &self.ptr.time_base) + return avrational_to_fraction(cython.address(self.ptr.time_base)) @property def start_time(self): @@ -267,3 +272,47 @@ def type(self): :type: Literal["audio", "video", "subtitle", "data", "attachment"] """ return lib.av_get_media_type_string(self.ptr.codecpar.codec_type) + + +@cython.cclass +class DataStream(Stream): + def __repr__(self): + return ( + f"'} at 0x{id(self):x}>" + ) + + @property + def name(self): + desc: cython.pointer[cython.const[lib.AVCodecDescriptor]] = ( + lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) + ) + if desc == cython.NULL: + return None + return desc.name + + +@cython.cclass +class AttachmentStream(Stream): + """ + An :class:`AttachmentStream` represents a stream of attachment data within a media container. + Typically used to attach font files that are referenced in ASS/SSA Subtitle Streams. + """ + + @property + def name(self): + """ + Returns the file name of the attachment. + + :rtype: str | None + """ + return self.metadata.get("filename") + + @property + def mimetype(self): + """ + Returns the MIME type of the attachment. + + :rtype: str | None + """ + return self.metadata.get("mimetype") diff --git a/av/stream.pyi b/av/stream.pyi index 88dc7c00b..18e6fdca9 100644 --- a/av/stream.pyi +++ b/av/stream.pyi @@ -1,6 +1,6 @@ from enum import Flag from fractions import Fraction -from typing import Literal, cast +from typing import Any, Literal, cast from .codec import Codec, CodecContext from .container import Container @@ -36,6 +36,7 @@ class Stream: profiles: list[str] profile: str | None index: int + options: dict[str, Any] time_base: Fraction | None average_rate: Fraction | None base_rate: Fraction | None @@ -46,3 +47,12 @@ class Stream: frames: int language: str | None type: Literal["video", "audio", "data", "subtitle", "attachment"] + +class DataStream(Stream): + type: Literal["data"] + name: str | None + +class AttachmentStream(Stream): + type: Literal["attachment"] + @property + def mimetype(self) -> str | None: ...