We are operating with semantic versioning.
Fixes:
- Reading :attr:`.Frame.side_data` no longer holds the frame in a reference cycle, which kept its picture buffer alive until gc ran. By :gh-user:`WyattBlue`.
- :class:`.MotionVectors` no longer memoizes the vectors it hands out, a cache that never evicted and put every one of them in a reference cycle with the side data, retaining 1640 objects for a single 640x360 frame. By :gh-user:`WyattBlue`.
- :attr:`.Stream.index_entries` is now a view built on access rather than an object allocated for every stream whether or not anything reads it, which also takes the stream out of a reference cycle with it. Fixes (:issue:`2425`) by :gh-user:`WyattBlue`.
- :class:`.Filter` and :class:`.FilterContext` pads are now built on access rather than cached, which takes a filter out of a reference cycle with its pads and makes a pad compare equal by what it denotes. By :gh-user:`WyattBlue`.
Major:
- Drop support for Python 3.11. Binary wheels are now built for Python 3.12 and later.
- Remove the undocumented
CodecContext.hwaccelattribute. It held theHWAccelsettings object passed in, not the live device context; useCodecContext.is_hwaccelto check whether hardware acceleration is in use. - Rational attributes (
time_base,average_rate,base_rate,guessed_rate,framerate,rate,sample_aspect_ratio, anddisplay_aspect_ratio) now return :class:`av.AVRational` rather thanfractions.Fraction, and are neverNone: an unset value is the falsyAVRational(0, 1). Test them withif not stream.time_base:instead ofis None. Setters still accept afractions.Fraction. - Closing an :class:`.OutputContainer` now frees its context, so using one afterwards raises
Container is not openinstead of continuing against a finished file. Muxing has noavformat_close_input()to do this for it, soadd_stream(),mux(),start_encoding()and the rest kept working afterclose(), and streams held from it stayed readable. - Remove
Capabilities.hwaccel,Capabilities.hwaccel_vdpau, andCapabilities.neg_linesizes, none of which FFmpeg defines any more. - Remove the
metadata_encodingandmetadata_errorsarguments to :func:`av.open`, and the matching attributes. Metadata is now always read and written as UTF-8 withsurrogateescape, which is byte exact: a tag in another encoding survives as surrogates and is recovered per key withvalue.encode("utf-8", "surrogateescape").decode("cp1251"). Previously one encoding had to be chosen for a whole container, so a file mixing encodings across tags could not be represented at all. - Remove the
stream_optionsargument to :func:`av.open` and the matching attribute. They only ever reachedavformat_find_stream_info(), and only for formats that expose their streams before it runs, so they raised for MPEG and friends; output containers rejected them outright. Passoptionsfor every stream, setstream.codec_context.optionsfor one, andContainer.add_stream(..., options={})when writing.
Features:
av.dump_codecs()now lists every codec FFmpeg knows of rather than only those with an encoder or a decoder, so data and attachment codecs appear, matchingffmpeg -codecs. Its legend gains the..D...and..T...media types.ContainerFormat.fixed_framesizereports whether a format wants fixed size audio frames.- :class:`.CodecContext` exposes more of
AVCodecContext:pkt_timebase,frame_num,active_thread_type,bits_per_raw_sample,compression_level,rc_buffer_size,min_bit_rate, a setter formax_bit_rate, the audioinitial_padding,trailing_padding, andseek_preroll, andstats_in/stats_outfor two-pass encoding.VideoCodecContextgainschroma_sample_location,refs, andmb_decision;AudioCodecContextgainsblock_align. CodecContext.coded_side_dataandCodecContext.decoded_side_dataexpose the context's global side data as dicts ofbytes, keyed by packet side data name and :class:`~av.sidedata.sidedata.Type` respectively. Stream wide HDR metadata, such as mastering display and content light level, arrives indecoded_side_dataonce a frame has been decoded.VideoFrame.chroma_locationexposesAVFrame.chroma_location, the position of the chroma samples relative to the luma samples, and the newChromaLocationenum names its values. Only the codec context side of the field was wrapped, asVideoCodecContext.chroma_sample_location, so the siting a decoder actually reported per frame could not be read at all. Each property mirrors its C field name, which FFmpeg spells differently on the two structs.- Enums gained the members FFmpeg has since added:
Properties.FIELDS,Properties.ENHANCEMENT,PixFmtLoss.EXCESS_RESOLUTION,PixFmtLoss.EXCESS_DEPTH,Flags2.icc_profiles,format.Flags.experimental,Interpolation.STRICT,Interpolation.UNSTABLE,ColorTrc.V_LOG,ColorPrimaries.V_GAMUT, theLCEVC,VIEW_ID,THREE_D_REFERENCE_DISPLAYS, andEXIFmembers ofsidedata.Type, and theexif,dynamic_hdr_smpte_2094_app5, andhevc_confpacket side data names.
Fixes:
Frame.side_dataandPacketSideData.data_typeno longer raise on side data types FFmpeg has added since PyAV last listed them. The packet side data names were missing three, so reading, say, theAV_PKT_DATA_HEVC_CONFan HEVC stream in MP4 or Matroska carries raisedIndexError. A frame side data type that nosidedata.Typemember names, which is anything a newer FFmpeg than PyAV was built against added, now becomes anUNKNOWN_<value>member rather than raisingValueError.CodecContext.bit_rate_tolerancereturns its value instead of alwaysNone; the getter was missing itsreturn.- A rejected
add_stream()oradd_mux_stream()no longer breaks the container. InputContainer.sizereturnsNonewhen the size cannot be determined rather than the negativeAVERRORit was passing through, which read as a plausible byte count. A non-seekable input, such as a pipe, reported-78.av.dump_codecs()no longer drops the canonical namesh264,hevc,av1,dirac, andilbc, each of which was overwritten by the row of whichever encoder it resolved to.FilterLink.inputandFilterLink.outputnow follow the filters the graph auto-inserts while configuring. They cached the pad they first resolved, so reading one beforeGraph.configure()reported the filter the link no longer pointed at.- Fix a segfault when a
FilterLinkoutlives itsGraph. It held the graph by weak reference and dereferencedAVFilterLinkbefore consulting it, solink.inputandlink.outputread freed memory. It now holds the graph, as aFilterContextalready did. - Reading a :class:`.Stream` or its :attr:`~av.stream.Stream.index_entries` after the container is closed now raises instead of reading freed memory, since
avformat_close_input()frees the underlyingAVStream. Holding anindex_entriesalso keeps its container alive, and an :class:`.IndexEntry` is a copy, so it stays readable after the close and is unaffected by the demuxer reallocating the index. - Attaching one object to the
opaqueof more than one frame or packet no longer loses it. The objects were keyed byid(), so every holder shared an entry and whichever was freed first took it away from the rest. Frame.side_datanow satisfies theMappingprotocol it advertises: iteration yields :class:`~av.sidedata.sidedata.Type` keys, soitems(),keys(), andvalues()work instead of raisingKeyError. Values remain reachable positionally by an integer or, for the first time, a slice. Its type stub was aTypedDictwith a single literal key, and is nowSideDataContainer.- Fix crashes from indexes that were turned into C pointer arithmetic without being range checked.
MotionVectors[i]only checked the upper bound, so a negative index read off the front of the buffer (mvs[-1]now returns the last vector, as with any sequence);VideoFormatComponentandAudioPlaneaccepted any index at all; andBitmapSubtitlePlaneandVideoBlockParamswere missing their lower bounds. - Frames returned by flushing a codec context directly (
CodecContext.decode()with no packet) now carry the stream'stime_baseinstead ofNone. VideoFrame.reformat()(and soto_ndarray(format=...),to_rgb(),to_image()) now shares oneSwsContextper thread instead of allocating one per frame. FFmpeg 8's swscale retains megabytes of graph state per context, which showed up as large RSS growth when many frames were alive at once.- Writing to a network URL no longer blocks every other Python thread, and
timeoutnow applies to opening an output container.avio_open(),avformat_write_header(),av_write_trailer(), andavio_closep()held the GIL, so an unreachable RTMP server froze the whole process, and the interrupt callback was only installed for demuxing, so nothing could end the wait. :meth:`.OutputContainer.close` now raises rather than freeing a context another thread is still muxing or closing. By :gh-user:`adrianrfreedman` in (:pr:`2412`). timeoutnow applies to muxing and closing an output container, not just to opening it. Only opening armed the interrupt callback, so a peer that accepted the connection and then stopped reading leftav_interleaved_write_frame()andav_write_trailer()blocked forever. Each mux gets the full timeout, and a close shares one across writing the trailer and flushing, so neither can outlast it. Only unseekable outputs are covered: writing a seekable file takes as long as the file is big, so a deadline meant for a peer would abandon it part-written. By :gh-user:`adrianrfreedman` in (:pr:`2414`).- :meth:`.HWAccel.create` now passes
flagson to the context it creates. They were dropped, soav_hwdevice_ctx_create()always got0and CUDA'sAV_CUDA_USE_PRIMARY_CONTEXTcould not be requested through them.
18.X Changelog <https://pyav.basswood.io/docs/18.1/development/changelog.html>