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 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 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 fillin 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 shortest: "Stop muxing when the shortest stream stops." = lib.AVFMT_FLAG_SHORTEST 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 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, )
Memory