Compare commits

...
292 Commits
Author SHA1 Message Date
Kacper Michajłow 182fa6ca49 player/discnav: allow window dragging on menu
lint / commit-msg (push) Has been cancelled
lint / swift (push) Has been cancelled
lint / lua (push) Has been cancelled
lint / ruff (push) Has been cancelled
lint / mypy (push) Has been cancelled
lint / editorconfig (push) Has been cancelled
lint / pre-commit (push) Has been cancelled
build / mingw (i686, wine32) (push) Has been cancelled
build / mingw (x86_64, wine64) (push) Has been cancelled
build / win32 (arm64, windows-11-arm, aarch64-pc-windows-msvc) (push) Has been cancelled
build / win32 (x64, windows-2025-vs2026, x86_64-pc-windows-msvc) (push) Has been cancelled
build / macos (arm, macos-14, Xcode_15.0) (push) Has been cancelled
build / macos (arm, macos-15) (push) Has been cancelled
build / macos (arm, macos-26) (push) Has been cancelled
build / macos (macos-15-intel) (push) Has been cancelled
build / macos (test, macos-26, -target arm64-apple-macosx10.15, 10.15) (push) Has been cancelled
build / linux (map[args:-Db_lto=true -Db_lto_mode=thin cc:clang cxx:clang++ ld:lld]) (push) Has been cancelled
build / linux (map[args:-Db_ndebug=true cc:gcc cxx:g++]) (push) Has been cancelled
build / linux-fuzz (push) Has been cancelled
build / linux-ffmpeg-6-1 (push) Has been cancelled
build / openbsd (push) Has been cancelled
build / freebsd (push) Has been cancelled
build / msys2 (pattern, windows-2025-vs2026, clang64) (push) Has been cancelled
build / msys2 (true, windows-2025-vs2026, clang64) (push) Has been cancelled
build / msys2 (uninitialized, windows-2025-vs2026, clang64) (push) Has been cancelled
build / msys2 (windows-11-arm, clangarm64) (push) Has been cancelled
build / msys2 (windows-2025-vs2026, mingw64) (push) Has been cancelled
build / msys2 (windows-2025-vs2026, ucrt64) (push) Has been cancelled
build / msys2 (zero, windows-2025-vs2026, clang64) (push) Has been cancelled
build / Publish git release (push) Has been cancelled
2026-08-26 18:04:17 +02:00
Kacper Michajłow c318236b88 player: drop the enhancement-layer pairing when the media has no EL 2026-08-26 01:47:41 +02:00
Kacper Michajłow e1cf77c0e9 stream_dvdnav: clamp number of buttons to array size
libdvdread doesn't sanitize this value, so we do for corrupted discs.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 93451ef64f demux_disc: mark streams the current program does not carry
The outer streams live for as long as the disc does, but each program
brings its own set. Playing a BD playlist without the Dolby Vision
enhancement layer left the outer EL stream selected with no slave
stream mapped to it.

Normally when there is no EL there is no track either, but since it was
exposed in PMT we still have the track, while VM may decide to unmap it.
This is bit of workaround, but harmless one.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 795cd15438 demux: let a demuxer mark a stream as absent
Media can stop providing a stream mid-playback, for example a disc
program that does not carry the Dolby Vision enhancement layer the
previous one had. Since mpv always try to keep at least 1 packet
buffered in all tracks, it can keep reading it till EOF.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 96b9d84b60 f_output_chain: restore the chain ends manual connections on relink
mp_pin_connect() clears manual_connection on the pins it links, so a
filter that stops being the first or the last one leaves the chain
unable to drive its own ends. Nothing has changed the ends at runtime
so far, so this went unnoticed.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 79186bd167 discnav: poll the disc VM while playback idles
The BD-J VM progresses only on reads. With playback at EOF or on a
held still nothing reads anymore, applet playlist switches never
surface and menus freeze, libbluray documents this polling requirement
for idle states. Drive one nav read per tick and keep the core awake.
The slave demuxer latches its EOF and stops touching the stream, so
the read path peeks the stream itself to run its event loop. Poll the
same way while paused in a menu, highlight redraws would otherwise
wait for the next core wakeup. dvdnav stills release on user input,
not reads, and the pump bypasses the cache limits, so no pumping
outside these states.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 6f6bedec72 demux_disc: reset the slave demuxer in place on BD time seeks
A seek repositions the disc under the live slave demuxer, and mpegts
spliced its buffered PES state together with data of the new position
into broken packets, an AU worth of decode errors per seek. mpegts
already flushes all per-PID packet state when it sees the avio
position jump, so restart the byte position counters on the seek and
resync the avio position to the stream in drop_buffers.
2026-08-26 01:47:41 +02:00
Kacper Michajłow a47452aac0 stream_bluray: treat unannounced repositions as jumps
BD-J applets seek their player and restart playlists without any event
reaching the read loop, menu loop points do this constantly. Compare
bd_tell against the end of the previous read and treat a mismatch as a
jump. Checking before the read keeps the new position's head out of
the old slave demuxer, the forward-only stream cannot re-read it. A
read delivered together with a jump event can already carry data of
the new position, the same check drops it.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 0c87e46c83 demux_disc: gate slave reopens on buffered stream data
While the disc is between positions the open only probes an EOF and
fails, and retrying at the read rate logged a warning every attempt.
Peek before opening, this pumps the stream's event loop and the peeked
bytes stay buffered for the probe.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 15750e1212 stream_bluray: drain queued events before holding a still
The release of a still can already sit in the event queue behind the
event that announced it. Holding at first sight would miss it and
freeze the menu until user input. STILL_TIME is re-emitted on every
read while the still holds and does not count as progress.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 16896f33c0 stream_dvdnav: fix seek positioning
Menu-domain seeks: menu PGCs sequence intro/transition segments and
the looping button segment as programs of one PGC, and the menu
subpicture is a one-shot packet at a cell start. dvdnav's time search
lands mid-cell (erratically so in menu VOBs, which often lack usable
time maps), skipping the subpicture and leaving the menu without
visible buttons until its next loop. Map menu seeks to program skips.

Title seeks: dvdnav_time_search() interpolates the target from cell
durations. On VBR content the landing can fall further short than the
seek step, so repeated relative seeks converge to a fixed point and
never advance. Use dvdnav_jump_to_sector_by_time(), which resolves
through the VTS time maps, with dvdnav_time_search() as the fallback for
titles without one. Accurate landings also let the hr-seek margin
shrink.
2026-08-26 01:47:41 +02:00
Kacper Michajłow dd18d1eabc stream_dvdnav: reset a stale selected button on menu changes
A menu change keeps the VM's selected-button register unless the link
command carried a button number or the new button set forces one via
fosl_btnn. Select the first button in that case.
2026-08-26 01:47:41 +02:00
Kacper Michajłow abbbffe0dd demux_disc: hold BD jump boundaries for the player's resync seek
The resync seek that follows a disc-initiated jump must not reposition
the VM. On BD the jump is handled out-of-band by reopening the slave
demuxer on the packet-read path, and the VM already sits at the jump
destination. Repositioning it with the (stale) seek target time would
hop to the wrong place. Adopting the discontinuity counter here would
leave the old slave demuxer parsing the new playlist's data with the
wrong codec context. Flush our side only, and keep the counter stale so
the read path still performs the reopen.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4a110f39cf demux_disc: map timestamps with per-stream timeline generations
Discs restart the in-stream timestamps at cell/segment boundaries. The
single global base_dts/last_dts pair rebased on any |diff| >= 5s between
consecutive packets of any stream, which broke down in several ways:

- Backward resets smaller than the threshold (e.g. a 1.5s menu
  transition cell followed by the looping button cell) were not detected
  at all, producing non-monotonic playback timestamps.
- The global last_dts ping-ponged between streams that cross a boundary
  at different packet positions (interleave skew), rebasing on nearly
  every packet and spamming the log.
- Menu subpictures timestamped for the next cell were mapped with the
  previous cell's offset and displayed at the wrong time.

Map each segment as a timeline "generation" with its own raw->playback
offset instead. Boundaries are detected per stream, as a backward step
beyond muxing jitter or a forward gap that would stall playback (sparse
still-image streams exempt). The previous generation stays valid until
every stream has moved past the boundary, and the new generation is
placed so the triggering stream continues seamlessly.

DVD subpictures are muxed around cell boundaries and their raw
timestamps alone cannot tell whether they continue the current segment
or belong to the next one. Defer each by one a/v packet (or EOF) and
let the packet that follows settle which generation it belongs to.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 02ec593037 stream: auto-detect .iso DVD-Video, DVD-Audio and Blu-ray images
Passing a disc image directly (e.g. `mpv disc.iso`) previously fell
through to FFmpeg. Route it to the matching disc backend instead,
like the VIDEO_TS.IFO/AUDIO_TS.IFO/BDMV redirectors already do.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fee9b14d46 demux_disc: handle slideshow DVD titles
Audio DVDs commonly author each song as a chapter whose video is a
single (or more) still frame, with only the audio really interleaved.
Detect this at runtime and mark the video still_image. The player then
treats it as sparse, and the starved stream stops forcing eager reads
that would scan through the source hunting for the next still.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4109805d0c demux_disc: follow the disc's initial audio stream selection
The player's default track selection picks the first probed audio
track, but the substream the disc's current PGC actually plays is
decided by the DVD VM and may be a different one. On discs whose menu
domain plays LPCM while the mux also carries an (unreferenced) AC3
substream, mpv selected the AC3 track, which has no data. We have to
keep track of what VM wants to output and what LAVF expects.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c855d13170 stream_dvdnav: resync disc-initiated jumps through the seek path
When the disc VM jumps (menu activation, end of title), the demuxer
and player have to flush and reinitialize exactly at the boundary.

Hold an EOF at the boundary until the demuxer acks it, and resync
the player with a regular seek, which already orders flush, decoder
reset and restart. d_seek recognizes the held boundary and releases
it instead of repositioning the VM.

Also log the VM domain in nav event messages.
2026-08-26 01:47:41 +02:00
Kacper Michajłow daaab42166 player: rejoin the restart for a video frame after a transient EOF
A sparse video stream can report a transient EOF during a restart. The
DVD-Audio still track is read lazily and its first frame may arrive a
moment after the video chain already drained. Treat a frame arriving
while the restart is in progress as the restart's first frame.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b3e3d1774e player: fix timing of sparse still-image video streams
Sparse video with continuous audio broke playback timing in several
ways:

- The first frame after a seek/restart was always shown immediately.
  With sparse video the frame at the seek target may not exist, and the
  next still can lie minutes ahead. Showing it early displays the wrong
  image.
- hr-seeks dropped frames with PTS below the target. For sparse video
  the nearest preceding still is the correct frame to show.
- Frame scheduling used the incremental A/V delay accounting, which is
  unusable across the video-EOF gaps between stills. Schedule sparse
  frames directly against the audio clock instead.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 34c649b864 demux: allow demuxers to update still_image during demuxing
Whether a DVD title's video track consists of sparse still images (a
slideshow) is only observable from the packet timestamps after
streaming started, and demux_disc wants to flip sh->still_image
accordingly, so that the stream stops being read eagerly.
2026-08-26 01:47:41 +02:00
Kacper Michajłow a2448f3b81 stream_dvda: expose DVD-Audio still images as a cover-art video track
DVD-Audio discs carry an Audio Still Video Set (ASVS). An MPEG-2 still
images (cover art, booklet/lyric pages) that the disc associates with
tracks.

A new STREAM_CTRL_GET_STILL returns the still for a given playback time.
demux_disc exposes it as a selectable mpeg2video track and injects the
still as a keyframe at the track's playback time. It changes as playback
crosses track boundaries (and follows onset times when a disc encodes
them).

--dvda-page (with the new STREAM_CTRL_SET_STILL_PAGE) forces a specific
still page instead of the scheduled one. The still state is guarded by
a mutex, runtime page changes arrive from the player thread while the
demuxer thread extracts stills.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 263bea5261 demux: read sparse still-image streams lazily
still_image streams (AV_DISPOSITION_STILL_IMAGE) consist of sparse
frames that can be minutes apart. When such a stream was selected
alongside an eager audio/video stream, its empty packet queue forced
the demuxer to keep reading ahead, buffering the whole file trying to
chase min_secs of "video". Read them lazily when other eager streams
exist, so playback drives read-ahead instead.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 8c6680a868 osdep/w32_register: add a DVD-Audio AutoPlay handler
Register mpv for the PlayDVDAudioOnArrival event, like the existing
CD-Audio/DVD/Blu-ray handlers.
2026-08-26 01:47:41 +02:00
Kacper Michajłow be04215d3f stream_dvda: add DVD-Audio playback support
Add a dvda:// protocol backed by libdvdread. Audio title sets are
enumerated from the ATS IFOs. Each ATS title is exposed as an mpv
title/edition and its tracks as chapters.

Menus are not supported, they would need full VM implementation, and
libdvdnav currently doesn't support DVD-Audio.

Still images (ASVS) may be supported in the future.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 76c0c5de03 stream_dvdnav: lock event processing
The menu/highlight state is filled by event processing on the demuxer
thread and read by nav commands and controls on the player thread. Add
a mutex around it.
2026-08-26 01:47:41 +02:00
Kacper Michajłow bb19b0fc36 stream_bluray: remove BD_EVENT_POPUP and menu-event tracking
BD_EVENT_POPUP was tracked into has_popup, which never grew a
consumer. BD_EVENT_MENU's menu_event_active only fed the menu_active
condition, where it was subsumed by the visible-overlay check. Remove
all three.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c9422ff88f stream: turn the highlight fields into mp_dvdnav_highlight
The highlight rectangle and palette were passed around as loose fields
in three places. Bundle them into a struct, pass the whole nav state to
the sd_lavc control.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c7859fe5b6 sub/osd_state: rename external2 to image_overlay 2026-08-26 01:47:41 +02:00
Kacper Michajłow b17b4371f8 stream_bluray: route logs through mp_log
This is awkward, because libbluray has a single global log callback.
libmpv can spawn multiple players in a single process, and the
instance created last takes over the callback (the previous instance's
log stays alive until its stream closes, so a racing callback never
sees a dangling pointer). This mirrors how ffmpeg logging works.

The callback delivers only a string without severity, so everything is
printed as MSGL_DEBUG, with the libbluray debug mask derived from our
log level.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 15f8a74aa4 stream_dvdnav: route logs from libdvdnav through mp_log
Use dvdnav_open2() with a logger callback instead of letting libdvdnav
print to stderr. This bumps the libdvdnav requirement to 6.1.0. 6.1.0 is
from 2019, it's resonable to request it by now.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 7c13c58ffa stream_dvdnav: keep cached duration in native time base
Store the duration in 90 kHz ticks instead of converting back and
forth. This also fixes STREAM_CTRL_GET_CURRENT_TIME's failure check,
which previously compared the already-converted value against -1 and
could never trigger.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 61416bd8b5 stream_bluray: rename BD_TIME_TO_MP to BD_TIME_TO_S 2026-08-26 01:47:41 +02:00
Kacper Michajłow b48fb6c86c demux_lavf: seek avfc only on seekable streams in drop_buffers
Disc streams are only seekable through the higher-level stream
controls, a byte seek on the slave can only fail and log errors.
avformat_flush() below already resets the demuxer state.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 2c8d954d21 demux_disc: only add the menu edition for streams with menu support 2026-08-26 01:47:41 +02:00
Kacper Michajłow 8356c7f674 demux_disc: keep one-shot DVD menu subpictures deliverable
Keep DVD SPU streams always enabled at the slave level and retain the
last subpicture per substream. Re-deliver the retained copy when a sub
track is (re)selected and when the player resets playback state.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 47a53910a5 demux: parent chapter metadata to the chapter list
demux_disc replaces the chapter list at runtime. Parent each chapter's
tags to the list that contains them, so a replaced list keeps owning
its metadata instead of accumulating it on the demuxer.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b0c4e8cab6 demux_disc: soft-reload the slave demuxer on DVD discontinuities
A full slave reopen makes libavformat re-probe the stream, which eats
packets around the jump (losing the one-shot menu SPU) and re-bases the
timestamps. DVD cannot change codecs across a jump, so flushing the
slave and refreshing duration/chapters/edition is enough. BD keeps the
full reopen because codecs can change across titles.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 582810ac1d stream_dvdnav: emit a one-shot EOF at source jump boundaries
Menu activations, HOP_CHANNEL and VTS changes jump the source position.
On this strictly forward stream the demuxer cannot tell where the old
content ends, so buffered pre-jump data would mix with post-jump data.
Return a single EOF at the boundary so the demuxer drains and resets
before any new content flows.
2026-08-26 01:47:41 +02:00
Kacper Michajłow dbe496e6be demux_lavf: clear sticky avio EOF in drop_buffers
A stream can report a one-shot EOF and then continue producing data
(disc-nav jump boundaries). Also clear a sticky read error, the
underlying source is being reused.
2026-08-26 01:47:41 +02:00
Kacper Michajłow af53a9dc4b stream_bluray: rework fill_buffer around bd_read_ext
Menu support added bd_read_ext for events but kept the old bd_read
path, leaving two read loops. Use bd_read_ext everywhere and handle
all events in one place.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4084e7609f stream_bluray: add support for still frames
Track BD_EVENT_STILL/STILL_TIME. Hold playback on indefinite stills and
skip timed stills only. Activations on a held still bump the
discontinuity counter, the disc VM will jump.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 3897b3a579 stream_dvdnav: add support for still frames
DVDNAV_STILL_FRAME means the VM is holding a frame indefinitely (menu
pages, some logos) or for a timed duration. Report the indefinite case
via nav state so the player holds playback at EOF instead of ending
the file, and skip timed stills, which would otherwise stall playback.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fbcced5bf0 stream_bluray: lock all controls that read cached disc state
We cache some state like title properties, in some cases during deinit
player thread may actually try to read it during demuxer thead read.
Just add a lock, it shouldn't be called frequently to cause too much
synchronization.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 5f19209953 stream_bluray: use 1-based angle, same as in DVD
Internally the angle stays 0-based (matching the libbluray API), only
the user-visible option/property values are 1-based. BD_EVENT_ANGLE
delivers a 1-based value and is converted on arrival.
2026-08-26 01:47:41 +02:00
Kacper Michajłow f0bc30aaf1 demux: adapt caching to interactive disc navigation
The disc VM advances when the stream is read, so in nav mode the cache
must not run ahead of what the user is seeing. Never read ahead (a
looping menu is endless). Never satisfy seeks from the cache either.

This applies to the whole disc session, not only while a menu is on
screen. The VM can jump at any time (BD-J timers, end of title), so
the cache must never run ahead of playback.

Activations still must be able to drive the VM while nothing is
consumed. demux_drive_nav() forces a single read for them.
2026-08-26 01:47:41 +02:00
Kacper Michajłow c625405ddc stream_bluray: add BD-J menu support
Adds support for the ARGB overlay callback from libbluray. libbluray
does not convert between its overlay formats, BD-J planes arrive as
ARGB while HDMV planes arrive RLE-encoded, so both callbacks have to
be supported.

When the Java VM is not available, fall back to non-menu playback.

Also bump libbluray requirement to 1.0.2, it was released in 2017.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 70174945a4 discnav: sync track selection state with VM
The disc VM selects audio and subpicture streams itself (menu SPU
highlights, title-authored stream changes on jumps). Follow the VM's
selection on every hop, and turn subtitles on/off with the disc's
sub_visible flag.

The sync only applies while the corresponding track selection is on
auto. An explicitly chosen track (--aid/--sid or runtime cycling) is
never overridden.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 4725c2e492 demux_disc: set partially_seekable to true
This suppresses refresh seeks (e.g. on track switches). A refresh seek
would reposition the disc VM, which breaks menus and can hop to a
different position entirely. Track switching relies on the always-on
readahead of the disc demuxer instead.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 216e26c871 player/command: add discnav command and bind default keys
User-facing entry point for in-disc menu navigation. The command
forwards an action (up/down/left/right/select/menu/title-menu/popup/
prev/mouse-move/mouse-click) to the current disc stream handler.

The new discnav input section allows binding controls that are only
active while a disc menu is on screen.

Setting the edition property on a disc jumps to the title through the
stream directly, so selecting the currently-playing edition can still
return to it from a menu.
2026-08-26 01:47:41 +02:00
Kacper Michajłow a5682eb2ad player/command: add disc-menu-active property 2026-08-26 01:47:41 +02:00
Kacper Michajłow 5713c30160 player/loadfile: react to runtime chapter/edition list updates
On DEMUX_EVENT_LISTS, re-copy the chapter list and notify the
chapter/edition properties, so a disc title change is reflected in
observed properties and the OSC.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 816eca1fef player/loadfile: auto-select default tracks added after initial open
play_current_file picks default tracks once, then leaves them alone.
That leaves later-surfacing streams without a default selection. This
applies to all demuxers with late-added streams, not only discs.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 652ca81af9 player: add disc-menu state holder and overlay support
The new player/discnav.c owns the player-side state for the in-disc
menu UI, shared between stream_bluray and stream_dvdnav. Once per
playloop iteration it pulls the stream's nav state and

- pushes the BD menu plane to the OSD (OSDTYPE_DISC_MENU) and the DVD
  highlight state to the dvd_subtitle decoders,
- resyncs playback when the disc VM jumped on its own, detected
  through the stream's discontinuity counter,
- mirrors the disc's current title into demuxer->edition,
- force-selects a dvd_subtitle track while a DVD menu is up, so the
  menu graphics decoded by sd_lavc reach the OSD, and
- enables the {discnav} input section while a menu is interactive.

The per-file state is reset on every new playlist entry.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 8d36904e05 sub/osd: add OSDTYPE_DISC_MENU
A dedicated OSD object for the disc menu overlay. It renders above the
subtitle tracks and below mpv's own OSD.
2026-08-26 01:47:41 +02:00
Kacper Michajłow e204c1cfd9 demux_disc: add a synthetic "Disc Menu" edition
Expose the disc menu in the edition list, after the titles, so it can
be reached like any title.
2026-08-26 01:47:41 +02:00
Kacper Michajłow af9a11a8e5 demux_disc: reopen slave demuxer on disc-nav hops
Playing a disc through the menu means crossing playlist boundaries at
runtime: menu -> title (and back), sub-menu -> sub-menu, title -> title.
Each new playlist can have a different stream count and/or a different
codec mix. The previous slave demuxer was probed once at open and never
refreshed, so after a hop the player either kept decoding stale streams
or saw nothing at all. Tear down and re-open the slave on every nav
discontinuity.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 3a8b6995e9 stream_bluray: implement HDMV menu navigation
Add HDMV menu support to stream_bluray, activated by opening with
"bd://menu/..." or the global --disc-menu option.

The IG overlay plane arrives through the libbluray YUV overlay
callback. Decode the palettized RLE into a BGRA plane (premultiplied
alpha) and hand it to the player via STREAM_CTRL_GET_NAV_OVERLAY.
STREAM_CTRL_NAV_CMD maps arrow/select/menu/popup/mouse actions onto
bd_user_input() and bd_mouse_select(). Events relevant to the menu
state (title, playlist, menu on/off) are tracked so
STREAM_CTRL_GET_NAV_STATE can report an active menu, and playlist
changes bump discontinuity_id so the player flushes stale packets.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b17feb1f79 stream_dvdnav: implement DVD menu navigation
Drive libdvdnav's menu state through the new disc-nav protocol so the
player can show and interact with VMGM and VTSM menus.

STREAM_CTRL_NAV_CMD maps the nav actions onto libdvdnav.

STREAM_CTRL_GET_NAV_STATE reports whether a menu with buttons is on
screen, the highlight rectangle and palette of the focused button, and
the ids of the disc-selected audio/SPU streams.

Menu mode is entered by opening with dvd://menu or --disc-menu, which
starts at the disc's root menu instead of a title.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fbd0e047b1 options: add disc-menu option 2026-08-26 01:47:41 +02:00
Kacper Michajłow e2b7a983fa stream: add disc-nav actions and states
Define three new stream controls so disc backends (libdvdnav, libbluray)
can expose their in-disc menu/highlight state to the player.

Not used yet, the next commits add the backends and the player side.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 5db1db2852 demux: allow updating duration, chapters and editions during playback
Disc demuxers switch titles and playlists at runtime, which changes the
duration and replaces the chapter/edition lists. Add
demux_set_duration() and demux_lists_changed() for demuxers to publish
such updates, and DEMUX_EVENT_LISTS for the player to react.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 3ba4f76096 demux_disc: drop "PTS discontinuity" logs to verbose
Discs restart timestamps on every title or menu transition, so these
are expected in normal operation, not a sign of broken input.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 8b583e9a0d sd_lavc: add support for DVD sub highlight
This is connected to standard DVD subs renderer, with additional state
passed for highlight color.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fad4b3f321 stream_dvdnav: read VTS event payload from the right buffer
While here, drop the message to MP_VERBOSE and fix its wording.
"switched to title" is misleading because this is a VTS-change event,
not a title change.
2026-08-26 01:47:41 +02:00
Kacper Michajłow b573edaaa1 sub/osd: change osd_set_external2 into generic osd_set_bitmaps
Let the caller pick the OSD object instead of hardcoding EXTERNAL2, so
other bitmap overlay sources can reuse the same path. No functional
change, EXTERNAL2 is still the only user.
2026-08-26 01:47:41 +02:00
Kacper Michajłow fbca538bfe stream_dvdnav: remove unused mousex/mousey 2026-08-26 01:47:41 +02:00
Kacper Michajłow f73dd13376 demux_lavf: parent mp_codec_params members to it
The members were parented to the demuxer or sh_stream, while they really
commute better with mp_codec_params.
2026-08-26 01:47:41 +02:00
Kacper Michajłow 57b0a0a695 stream_dvdnav,demux_disc: clean up log messages 2026-08-26 01:47:41 +02:00
Kacper Michajłow 654e9382c0 command: add colorspace and luminance arguments to overlay-add
Add primaries, transfer, max_luma and video_colorspace arguments.
Allows tagging the colorspace of the overlay bitmap instead of assuming
sRGB. video_colorspace interprets the bitmap in the video's colorspace,
so it is tone mapped exactly like the video. max_luma overrides the
luminance peak of the bitmap. Currently only vo_gpu_next honors these
parameters. vo_gpu keeps its sRGB assumption regardless of the tag.

Fixes #10678
2026-08-23 18:09:02 +02:00
Kacper Michajłow 1c2ff7d56b vo_gpu_next: support per-part overlay colorspace
Split the parts into runs of consecutive parts that share a colorspace
and render each run as a separate overlay referencing the same texture.
2026-08-23 18:09:02 +02:00
Kacper Michajłow 66ff2efbc7 sub/sd_lavc: zero-initialize sub_bitmap entries 2026-08-23 18:09:02 +02:00
posi 345e783643 vo_gpu_next: use video colorspace for overlays only when requested
Bitmap subtitles muxed in media file are generally the same colorspace
as video. Generic overlays may not be.
2026-08-23 18:09:02 +02:00
Kacper Michajłow e89a491052 sub/packer: preserve video_color_space when packing to RGBA
The flag was lost when replacing the result with the packed image.
2026-08-23 18:09:02 +02:00
Kacper Michajłow 413153fd4e vo_gpu_next: split update_overlays into helper functions
No functional changes.
2026-08-23 18:09:02 +02:00
Kacper Michajłow 65d138fd57 vo_gpu_next: don't read libass.color for non-libass parts
The union member is only valid for SUBBITMAP_LIBASS. The resulting data
is only read for PL_OVERLAY_MONOCHROME, so there was no risk of using
garbage data, but we can make it explicit.
2026-08-23 18:09:02 +02:00
verygoodlee 9f9f8c4dd4 select.lua: fix some incorrect boolean values in the property menu
`false` value in the `value or mp.get_property_native(property)`
expression will trigger `mp.get_property_native`, but some
sub-properties cannot be accessed directly, the obtained value is nil.
2026-08-23 10:45:18 +02:00
Kacper Michajłow b955aa28f3 f_enhancement_pair: keep retained frames within the hw surface budget
Reduce the queue to 8 frames and reserve that many extra surfaces on
both the BL and EL decoders.
2026-08-22 21:00:11 +02:00
Kacper Michajłow 228f3109fd f_decoder_wrapper: add mp_decoder_wrapper_set_extra_hw_frames()
Player-side consumers can now retain decoded frames beyond what the
decoder and VO account for, e.g. the enhancement-layer pairing queue.
2026-08-22 21:00:11 +02:00
Kacper Michajłow 3b4caf0f8b f_enhancement_pair: don't emit BL-only frames before the EL warms up
The queue-full fallback could fire during startup (and after seeks),
before the enhancement-layer decoder produced its first frame.

Gate the fallback on the EL pipeline having delivered at least one
frame since the last reset. Until then a full queue only means the EL
decoder is still spinning up.
2026-08-22 21:00:11 +02:00
Kacper Michajłow 49418246f3 ci: port BSD jobs to new cross-platform-actions syntax
The run input is deprecated. Split the monolithic scripts into the same
step layout as the other jobs. Sync the sources once at VM start and
skip the per-step file synchronization, since everything runs inside the
VM anyway.

This will make reading logs easier.
2026-08-21 13:36:49 +02:00
Kate Markson a87a678573 osdep/subprocess-win: terminate environment block
Terminate the block of environment variables with NUL. Previously the block was left unterminated which could cause subprocess spawn failures depending on the array contents after the last item.
2026-08-21 12:45:11 +02:00
Kacper Michajłow e7191f2a65 demux_lavf: select initial edition only by the program's variant_bitrate
The per-stream variant_bitrate is not a reliable selection key. FFmpeg
attributes the whole variant's bandwidth to individual streams, and for
streams shared by multiple variants the value was bogus or missing
depending on the number of variants sharing the stream. The program
metadata always carries the exact HLS variant BANDWIDTH, so use it as
the only key and drop the per-stream fallback.

Note that with `--flatten-editions` tracks are selected by their own
tags, so they may still use bogus values.

For context, there is currently a bug in lavf where, depending on the
number of variants sharing a stream, the stream may or may not get
`variant_bitrate`. This will be fixed in a future FFmpeg release.
2026-08-17 19:57:13 +02:00
sharpchen b6d3434493 script_format_time: fix typo in comment 2026-08-17 19:52:11 +02:00
dependabot[bot] e034d612cf ci: bump cross-platform-actions/action from 1.3.0 to 1.4.0
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-16 13:43:18 +02:00
Kacper Michajłow e167836802 demux/ebml: interpret zero-length elements as their default value
An EBML element is permitted to have a zero-length data payload. It's
then interpreted as the element's RFC declared default value, or zero.

Before this change all zero-length elements were initialized to empty or
rejected in case of uint.

Fixes: #18351
2026-08-14 23:24:50 +02:00
Kacper Michajłow 52bb166f30 TOOLS/matroska.py: use named attributes for optional element fields
Will be useful for next commit.
2026-08-14 23:24:50 +02:00
Philip Langdale 7b8915bc1d ra_pl: add x2bgr10/x2rgb10 special RA formats
x2bgr10/x2rgb10 are packed formats that do not automatically get mapped by the
existing ra_pl logic. They are more useful than other non-byte-aligned packed
formats because they show up when dealing with > 8bit RGB content, which is
more common in an HDR context.

In practice, x2bgr10 is probably the only one that's going to get used, but
I figured there's no harm in supporting both.
2026-08-13 21:03:11 +02:00
llyyr f4d13e1c2c wayland: handle null output in wl_surface.enter event
This is seemingly possible: https://wayland.freedesktop.org/docs/book/Message_XML.html#allow-nulltrue--false
2026-08-10 04:39:39 +00:00
llyyr 513d3407d4 wayland: wait for initial surface configure before attaching buffer 2026-08-09 04:09:06 +00:00
subotac dd5d17d328 osc.lua: ignore seek ranges without usable duration
seekRangesF() accepts a zero duration and produces NaN/Inf coordinates,
which assdraw cannot format. Ignore seek-range metadata when the native
duration property is unavailable or zero.

Fixes #18344
2026-08-07 22:41:57 +02:00
zhanghongyuan 21277b0ccf bash-completion: fix '=' for *-clr/*-help and --help=
The special-options regex decided which options are completed without
a trailing '=', but it only covered Print options and "not in config
files" flags. As a result, two groups of no-argument options still got
'=' appended:

  (1) *-clr / *-help sub-options (e.g. --af-clr, --vo-help) which are
      mpv list-type sub-options taking no argument.
  (2) --help/--h which is String-typed in --list-options but actually
      accepts a substring filter to narrow down the option listing.

Extend the regex to cover (1). For (2), keep '=' appended (it does take
an argument) and route completion through a new Help branch that mirrors
the zsh help-options state: complete bare option names (no "--"
prefix), excluding no-* negations and list-type sub-options
(-add/-append/-clr/-pre/-set/-remove/-toggle). Bare names are needed
because --help=<substring> matches within the option name body.
2026-08-05 20:01:49 -07:00
Kacper Michajłow 1d15686142 hwdec_d3d11egl: fix EGL_EXT_device_query availability check
After ANGLE commit 56663dbfa780 EGL_EXT_device_query is listed in the
client extension string instead of the display one. The display string
check always failed, silently disabling native d3d11va interop.

This fixes `hwdec` with `gpu-context=angle`.
2026-07-31 18:01:08 +02:00
Kacper Michajłow 55146ecd14 ci/cleanup_caches: prune setup-msys2 package caches
setup-msys2 saves the pacman package cache under a key ending in a hash
of its contents. So with each update it saves more caches, what's more
important it saves it per each PR ref. We don't want that. Keep only the
msys2 cache on main branch and only the latest one per each
configuration. If PR saves new one, it will be dropped, we prefer master
branch case that can be used by all PR, rather then per PR cache of
this. The few packages that will be refetched are ok.

This change is an atempt to keep our cache under 10GB limit, to avoid
evicting more important ccache caches.
2026-07-31 17:58:16 +02:00
Kacper Michajłow 612bf5b6e8 ci/cleanup_caches: rewrite the selection as a single jq program
The nested jq invocations were hard to extend and left no trace of what
they decided. Delete by id rather than by key, since a key is only
unique per ref, and log each deletion.

No change in which caches are selected.
2026-07-31 17:58:16 +02:00
Kacper Michajłow 22c09166d7 ci/cleanup_caches: group all cleanup runs together
A cleanup is not tied to the build that triggered it, it always operates
on the whole cache state. Just cancel them and let the newest one
finish.
2026-07-31 17:58:16 +02:00
sfan5 63ada87ec7 ci/mingw: add second download URL for iconv and freetype
It appears this time GNU broke their ftpmirror, while the primary one is working again.

download.savannah.gnu.org also seems to be having issues.
Never thought I'd see the day where sourceforge is more reliable.
2026-07-31 15:56:00 +02:00
Dudemanguy 56b6200986 build: use --output option for rst2man
Apparently, the separate destination argument was deprecated years ago
and will be removed soon*. Just switch to the "new" way of specifiying
the output. Fixes #18283.

https://sourceforge.net/p/docutils/feature-requests/36/#0709
2026-07-31 13:12:03 +00:00
Nathan Lucas 74356c0fc6 ad_spdif: only warn for required codec parameters
determine_codec_params() warns when it cannot determine a codec profile.
Some passthrough formats use fixed output parameters and do not require
the probed profile or sample rate making the warning misleading.

Warn only when a value used to configure the selected passthrough format
is missing: the profile for DTS-HD, or the sample rate for core DTS and
AC-3.

Signed-off-by: Nathan Lucas <nlucasgit@gmail.com>
2026-07-29 18:18:37 +02:00
Nathan Lucas 1388a45395 ad_spdif: recreate spdif muxer on reset
The spdif decoder uses avformat/spdifenc. Some spdifenc paths keep
muxer state across packets, such as partial TrueHD MAT frames. After a
seek, that state belongs to the old stream position and can cause the
next packet to be muxed with stale timing or buffering state.

On decoder reset, discard the current spdif muxer context. The next
usable input packet will recreate the muxer through the existing
initialization path.

Cache codec parameters from the initial probe so recreating the muxer
does not probe a partial packet after a seek or replace known values
with fallbacks.

A fresh TrueHD spdif muxer needs a TrueHD major sync before it can begin
muxing the stream. Startup or post-seek playback may begin on a TrueHD
access unit that does not have major sync, so drop TrueHD packets until
a major sync access unit is seen. This also gives the downstream decoder
a clean restart point after seeks. Warn periodically if no major sync
has been found.

Partially addresses: https://trac.ffmpeg.org/ticket/9569
Signed-off-by: Nathan Lucas <nlucasgit@gmail.com>
2026-07-29 18:18:37 +02:00
Kacper Michajłow 24c1cc52a3 vd_lavc: derive hwdec surface pool size from VO requirements
Change --hwdec-extra-frames default to auto, which sizes fixed hw frame
pools from the number of frames the VO declares to reference, instead of
a static guess. Fixes pool exhaustion with e.g. --interpolation and a
tscale filter with larger radius.

Fixes: #18205
2026-07-28 15:08:36 +02:00
Kacper Michajłow 7f72f64b7f vo: add number of referenced frames to queue params
VOs can hold more frames than the requested lookahead. vo_gpu_next's
pl_queue retains past frames for the interpolation mixing window. Let
VOs declare the total number of frames they may reference at the same
time, so fixed hwdec surface pools can be sized accordingly.
2026-07-28 15:08:36 +02:00
Kacper Michajłow 48e6c35c0e demux: interrupt blocking reads whose result would be discarded
Add demux_read_interrupted(), true when the demuxer is being canceled,
its thread is terminating, or a seek is queued. Poll it from
demux_lavf's AVIO interrupt callback so blocking network I/O (e.g. the
HLS demuxer waiting on a live playlist reload) doesn't stall queued
seeks or shutdown for multiple seconds. The aborted read's result is
discarded in all of these cases anyway.

In normal operation demuxer reads are fast enough to not notice the
issue. However, in the HLS/DASH demuxers, FFmpeg blocks read() calls
on the playlist reload timeout. This is expected, but can mean multiple
seconds of dummy wait before we can actually proceed. This change
significantly speeds up seeking in HLS playback when it hits a playlist
reload.
2026-07-26 18:15:18 +02:00
Philip Langdale 390ebe2eac hwdec_vulkan: propagate the queue creation flags to ffmpeg
Up until now, we have not bothered to propagate queue creation flags when
setting up the ffmpeg device context, but with libplacebo setting flags like
VK_DEVICE_QUEUE_CREATE_INTERNALLY_SYNCHRONIZED_BIT_KHR and newer nvidia drivers
actually supporting this feature, I've now managed to run into this. If the
flags aren't put into the context, ffmpeg code can end up calling
vkGetDeviceQueue2 with the wrong flags, and the driver will return a NULL
queue instead.

I suspect we got this far without hitting this because the only filter that
can currently end up calling vkGetDeviceQueue2 is vf_libplacebo and then only
with the new `inherit_device=1` where the device we set up _from_ libplacebo
goes back to libplacebo. The oroborus is complete.
2026-07-26 06:05:47 -07:00
Kacper Michajłow b27573a239 demux: fail probing when the stream cannot be rewound to the start
Each probe attempt assumes it reads from position 0. If the rewind
fails, the demuxer would silently misparse the stream from a wrong
position.

On non-seekable streams this may fail when the probe size > buffer size,
which would cause us to drop the buffered data and not allow to rewind
the stream. This can be improved in the future.
2026-07-26 12:59:51 +02:00
Kacper Michajłow 762c5999c2 stream_curl: only offer compression for full unbounded requests
The requested start/end is in file bytes, we cannot use compression in
this case.
2026-07-26 12:59:51 +02:00
Kacper Michajłow 5910e9c2ba stream_curl: validate that the server honored a requested start offset
Error out like lavf/http.c instead of serving bytes at wrong offsets when
the server ignores the range request.
2026-07-26 12:59:51 +02:00
Kacper Michajłow 8849c4963f stream_curl: fix racy read of request_start when setting initial pos
request_start is producer state, a fast transfer can complete and roll it
forward before curl_open gets to read it, mislabeling all stream offsets.
Store the requested offset separately and use it for the initial pos.

Fixes: 289723b41a
2026-07-26 12:59:51 +02:00
nanahi 5659a9a9c8 DOCS/man/options: clarify --playlist expansion behavior
It's not recursive except for directories.
2026-07-26 11:50:05 +02:00
nanahi f76519ebd8 DOCS/man/options: clarify how --shuffle option works
There are many misconceptions about how this option work.
Make it more clear.
2026-07-26 11:50:05 +02:00
nanahi 05d45f4955 DOCS/man: mention when options are marked as file-local 2026-07-26 11:49:30 +02:00
nanahi 42d50061f6 DOCS/man/input: use connect terminology for file-local options
The file-local-options property sets options that are reset when file
change occurs. This is different from per-file options which are set with
--{ commandline arguments and options parameter of the loadfile command.
Per-file options use file-local option mechanism for setting options when
the file is loaded, but they are persistent and stay with the playlist
entries, unlike file-local-options set at runtime.

This corrects the on_load hook documentation about the effect of setting
file-local-options.
2026-07-26 11:49:30 +02:00
nanahi 731229bd2b options: add --playlist-inherit-options option
This allows controlling whether the files in a playlist inherit the
per-file options of the playlist file itself. For playlists created
by --autocreate-playlist, --playlist-inherit-options=current allows
inheriting only for the item the playlist is created from.
2026-07-26 11:49:16 +02:00
nanahi 19215efe61 player/loadfile: inherit file-local options when resolving playlist file
Currently, if a playlist file is played and replaced by its contents,
the per-file options of the playlist file are ignored.

This commit makes these options inherited for its contents so they work
as expected.
2026-07-26 11:49:16 +02:00
nanahi 97db44f89f common/playlist: add playlist_set_params function
Helper function to set file local options for all entries. Will be used
in the next commit.
2026-07-26 11:49:16 +02:00
llyyr 8c67647b50 wayland: don't leak wl_fixes on uninit
I fixed this locally but forgot to push it before the original commit
got merged, oh well

Fixes: 6f6c29b0b4
2026-07-24 12:53:05 +00:00
diniamo 0fb136f685 input/ipc-win: allow Windows handles for --input-ipc-client
Previously, you could only provide a CRT FD, which works great for the
most part, but when using CreateProcess on Windows, it requires the use
of the reserved fields of STARTUPINFO to inherit the FD to a child mpv
process. This becomes a problem for example if you use Go's os/exec API,
which does not expose a way to set those reserved fields. Exposing a way
to allow passing handles directly fixes the mentioned scenario, and
makes --input-ipc-client more flexible at no cost.
2026-07-23 22:06:04 +02:00
llyyr 6f6c29b0b4 wayland: use wl_fixes.ack_global_remove on global_remove
Notifies the compositor that the removed global will not be bound, so
the compositor can destroy it.

Ref: https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/533
2026-07-23 12:29:49 +00:00
Guido Cella 1c4adc9819 console.lua: limit how many completions are printed to the terminal
This prevents mpv from freezing e.g. when completing very large
directories.
2026-07-23 13:04:55 +02:00
Kacper Michajłow 11a9054b1d player/lua: pre-allocate tables of known size 2026-07-23 12:16:59 +02:00
Kacper Michajłow 7ff087a6b0 options/m_property: avoid repacking list nodes if we can just steal it
Removes one redundant deep-copy. For example this makes playlist with
300_000 elements conversion to node from 800 ms to 470 ms. Not a big
improvement, but still it's free to do.
2026-07-23 12:16:59 +02:00
Kacper Michajłow e61d819bf6 console.lua: add coroutine to filter selectable items asynchronously
Make the filtering and user input render asynchronously, such that there
is no latency penalty for user typing.

A single keystroke with hundreds of thousands of selectable items blocks
the script for significant time. Especially filtering by single letter
which would produce a lot of matches. This could be fixed by delaying
filtering by the time user "stops" typing, but this adds artificial
delay on top of the filtering delay itself. Instead we start the
filtering always, but have a deadline of 5 ms, at which point the
coroutine is paused and script is allowed to process user events again.
If user typed or closed the dialog, we simply abort the filtering and
start new one. Instead of waiting for full filtering to finish when the
result is already stale.

The first slice runs synchronously, so anything but huge item lists is
still filtered within the keypress handler exactly as before.

Fixes: #18268
2026-07-23 12:16:59 +02:00
Kacper Michajłow 893ffae982 console.lua: speed up filtering of selectable items
Store just the indices of the matching items in matches. The text is
looked up in selectable_items, and the positions to highlight are
computed in get_matches_to_print() only for the rendered matches, using
the query that produced the current matches. This reduces useless work
of computing highlight positions when only handful of items can be
displayed at the time on screen in practice.

Sort matches without lua comparator, this significantly speeds up
sorting of big lists, where C->Lua round-trip was problematic.

Overall it's 10x-20x times faster in my testcase.
2026-07-23 12:16:59 +02:00
Kacper Michajłow 0583bb892e fzy.lua: use lookup table for precompute_bonus
Replace those conditions with precomputed lookup. It's not huge win, but
it's free, so we can do it.

JIT:
* score() x 44875 matches: 35 -> 25 ms
* filter_range 'p': 50 -> 25 ms
* filter_range 'pf': 61 -> 37 ms

interpreter:
* score() x 44875 matches: 295 -> 254 ms
* filter_range 'p': 412 -> 284 ms
* filter_range 'pf': 531 -> 388 ms

The lookup is dense, to avoid hash tables, which makes things slower,
but in fact the tables are reused, so it's low on memory.
2026-07-23 12:16:59 +02:00
Kacper Michajłow 181090e70d fzy.lua: add filter_range() for interactive filtering of large lists
Add fzy.filter_range() which computes only scores, appends to
caller-provided arrays instead of allocating tables per match, takes
an inclusive range so callers can process a large list in slices while
handling pending input in between, and accepts optional pre-lowercased
haystacks so repeated calls don't allocate a lowercase copy of the whole
list each time.
2026-07-23 12:16:59 +02:00
Kacper Michajłow e2eaf31ce2 fzy.lua: optimize main compute() a little
There could be a lot more done if we target luajit only. Or if we port
this to C. But for now let's keep it this way. It's 2-3x faster than
before, and the issue is mostly only on very big inputs, which are not
that common in practice.

Previously computing scores allocated two matrices of lua tables per
call. With hundreds of thousands of items this made fuzzy matching in
console.lua GC-bound.

When only the score is needed, keep two reused rolling matrix rows,
making repeated scoring allocation-free. positions() still keeps all
rows since tracing back the matched positions requires them.

fzy.filter() of 300000 items drops from 1013 ms to 356 ms (on my setup)
for a worst-case single-character needle, and a standalone score() call
no longer allocates at all.
2026-07-23 12:16:59 +02:00
Kacper Michajłow e4abaaa26b fzy.lua: do byte comparision instead string ones
This are char comparisions, but in lua they are expressed as full string
compare. Compare as bytes instead, this is significantly faster,
especially with luajit compiled code.
2026-07-23 12:16:59 +02:00
Kacper Michajłow 23d39a6048 vo_wlshm: fix leak on in flight buffer on unint 2026-07-23 10:17:00 +02:00
Kacper Michajłow 11e6a50025 vo/context_wayland: fix memory leaks on failed init 2026-07-23 10:17:00 +02:00
Kacper Michajłow 15de3e6193 vo/opengl/context: fix memory leaks on failed init 2026-07-23 10:17:00 +02:00
idontwannagoo 8ab3f8b66d options: add secondary-sub-scale
This allows scaling secondary subtitles independently from primary subtitles. The option defaults to 1 like --sub-scale and follows the other secondary-sub-* options by not inheriting from the primary subtitle setting.

Fixes #14989.
2026-07-22 19:32:01 +00:00
llyyr 25451bcc11 player/command: don't refresh sub stream on UPDATE_SUB option updates
Back when reselect_demux_stream call was added in this path in 700f72f,
sub_redecode_cached_packets only retained the last two packets. The
subtitle decoder cache was changed in 0a4b098 to retain the whole packet
sequence. Therefore sub_redecode_cached_packets by itself can rebuild
the new decoder state entirely from packets held in memory, and we no
longer need the demuxer to refresh the track.

This fixes expensive track refresh for network streams, where changing
some sub-* options during runtime of network streams would cause the
cache to be dropped
2026-07-22 20:51:55 +02:00
Dudemanguy 94335ab87a ci/build-mingw64: remove --enable-libshaderc flag
This was removed upstream.

https://code.ffmpeg.org/FFmpeg/FFmpeg/commit/92f2e6374dfbe9bf1c8e4d879f8810deafff3710
2026-07-14 19:13:03 +00:00
dengbo e5486b96d7 ao_pipewire: treat init timeout as failure instead of success
When wait_for_init_done times out, init_state remains INIT_STATE_NONE.
The original check only handled INIT_STATE_ERROR, allowing init() to
return 0 (success) even though the stream was never properly initialized.
This could lead to an infinite reload loop when the partially initialized
stream later enters an error state.

Fix by checking for INIT_STATE_SUCCESS instead of INIT_STATE_ERROR,
so that any state other than SUCCESS (including NONE due to timeout)
is correctly treated as a failure.
2026-07-10 00:22:51 +02:00
Kacper Michajłow 115b87b521 demux_mkv: init ff-index for backward compatibility
lavf basically always provide tracks in order as they appear, so this
will match it. There is no hard guarantee, but this is documented.

`ff-index` is now optional, but both demux_mkv and demux_lavf set it, so
it should cover most cases where it's used. In all other cases it would
be wrong anyway.
2026-07-09 05:04:11 +02:00
Kacper Michajłow 5b087dfb18 demux: remove falsification of ff-index
The `ff-index` is just a index of in the track order as seen by lavf.
However, there are many cases where tracks are not related to lavf, when
it's not used or there are virtual tracks addded like DOVI EL track or
merged image tile grid. Don't conjure `ff-index` out of nothing.

Some scripts may be confused when this index does not much ffmpeg. Even
though it has always stated in docs.

Fixes #18244
2026-07-09 05:04:11 +02:00
Kacper Michajłow e517fd6043 player/command: make track-list/N/ff-index optional
The `ff-index` is just a index of in the track order, it doesn't carry
much meaning and may be wrong or unavailable when other than lavf
demuxer is used. Additionally some tracks may be virtual like DOVI EL
track or merged image tile grid. In which case those track are not
related to lavf at all.

No deprecation period. It was never guaranteed to match FFmpeg anyway,
so anyone who has a hard dependency on this property should reconsider.
It's not really possible to provide backward compatibility given the
ambiguity of this index. It's better to make it unavailable than making
mismatch. For user it's clear when it's not available, but not possible
to know when it's wrong.
2026-07-09 05:04:11 +02:00
Kacper Michajłow 68387ea859 video/vdpau: throttle reinit in all cases
If `is_preempted` flag is set during init we need to try again, but if
we do it too fast, we never let the init to finish and constantly gets
the flag re-set.

Fix this by throttling the same as error path. It's not perfect, because
we rely on init timing, but was confirmed working and this restores the
previous status quo.

Fixes: d9f27bd0f1
Fixes: #18161
2026-07-08 21:02:26 +02:00
Kacper Michajłow cc763d17dc osc.lua: fix duration when it becomes available
After recent changes OSC become defunct when duration is updated during
playback.

Fixes: 02491a3949
2026-07-08 20:43:09 +02:00
Kacper Michajłow cef62fbc39 player/command: reset playback state of edition changes
When changing MPEG-TS program (edition) it may have completelly
different time base and duration, on live, non-seekable streams we would
just hang trying to resync to revious PTS, which is not valid. Reset the
state on edition change.
2026-07-08 20:42:25 +02:00
Kacper Michajłow 7f93b06797 demux_lavf: sort AVProgram array by id
AVProgram array contains programs in order as they were discovered by
lavf. For MPEG-TS depending on the point where we start demuxing it is
effectivelly random. Sort by the id, so we have consistent order of our
editions, regardless of probing.
2026-07-08 20:42:25 +02:00
Kacper Michajłow 02baec9701 demux_lavf: change demuxer-lavf-{probesize,buffersize} to OPT_BYTE_SIZE
It's way easier to type 10M instead of typing all those zeros.

Our C API uses int64_t for properties and string for setting options, so
it's safe to bump this option type.
2026-07-08 20:42:25 +02:00
Kacper Michajłow ab34ec35d2 vo_gpu_next: use pl_color_transfer_is_hdr consistently
pl_color_space_is_hdr checks also luminance, but we don't do that in
other places, and unlikely that we want it.
2026-07-08 20:20:07 +02:00
Kacper Michajłow 4c220ffd95 vo_gpu_next: disable auto reference white on onlder libplacebo version
To avoid forcing by default values that may be problematic in some
cases, especially with ICC profiles.
2026-07-07 00:00:05 +02:00
Kacper Michajłow 6a0ef86e00 vo_gpu_next: make subtitles / osd also follow system reference white 2026-07-07 00:00:05 +02:00
Kacper Michajłow ba92eb63c4 vo_gpu_next: ignore reference white for screenshots 2026-07-07 00:00:05 +02:00
llyyr 8e88661257 video/out/hwdec_cuda: use mp_msg for ffnvcodec logging
ffnvcodec falls back to fprintf(stderr) unless FFNV_LOG_FUNC and
FFNV_DEBUG_LOG_FUNC are defined by the user
(https://github.com/FFmpeg/nv-codec-headers/blob/master/include/ffnvcodec/dynlink_loader.h#L80-L84).
Define these callbacks so CUDA loader messages go through mpv's normal
logging instead.
2026-07-06 18:26:36 +02:00
der richter 28a7932599 osxbundle: fix vulkan loader search path check
the old check stopped at the first occurrence of a vulkan loader path.
though it's possible different vulkan loaders are installed in different
paths, leading to not found vulkan loaders.

to prevent this, also check if the needed vulkan loader is actually
installed to that path.

specific case is when installing MoltenVK and KosmicKrisp at the same
time. the former is installed in the homebrew prefix under etc/ the
latter under share/. the latter wasn't found previous, since the former
was found first.
2026-07-06 18:25:28 +02:00
der richter ed9c05d06e osxbundle: fix a wrong vulkan search path
the paths in the array are meant to be base paths to the vulkan path.
remove vulkan from it so it won't end up as /usr/share/vulkan/vulkan.
2026-07-06 18:25:28 +02:00
nanahi 9281bccfdf osdep/compiler: don't use __has_builtin for __builtin_mul_overflow
The feature test macro is nonstandard and not available on older GCC
version, which causes build failure despite the compiler does support
__builtin_mul_overflow.

Since there are no fallback when __builtin_mul_overflow is not
available, there is no need to check, so simply remove it.
If __builtin_mul_overflow is not supported compilation will still fail
just like currently.
2026-07-06 18:25:12 +02:00
llyyr c8c7d91a8e ci/freebsd: bump freebsd version to 15.1 2026-07-06 13:17:01 +02:00
Kacper Michajłow 7bf2d9cee6 vo/opengl/context_drm_egl: init fd to -1
Instead of using zero initialized value. Otherwise an early init failure
calls close(0), a descriptor we don't own, breaking whatever else
happens to hold it.

This is bit defensive, but I want to init those just after allocation,
so we don't have to trace whether they are initalized correctly or not.
2026-07-06 13:02:16 +02:00
Kacper Michajłow 318e4e21fb vo/drm_common: init fd to -1
Instead of using zero initialized value. Otherwise an early init failure
calls close(0), a descriptor we don't own, breaking whatever else
happens to hold it.

Also add missing check before close() call.
2026-07-06 13:02:16 +02:00
Kacper Michajłow f6df7dfd8b vo/vulkan/context_display: init fd to -1
Instead of using zero initialized value. Otherwise an early init failure
calls close(0), a descriptor we don't own, breaking whatever else
happens to hold it.

This is bit defensive, but I want to init those just after allocation,
so we don't have to trace whether they are initalized correctly or not.
2026-07-06 13:02:16 +02:00
Dudemanguy 33111f3212 DOCS/man/vo: remove mentions of 'experimental' for vo_dmabuf_wayland
It's fair to say that it's generally a mature and well-working VO at
this point.
2026-07-03 14:00:15 +00:00
Dudemanguy 7a94ec5719 vo_libmpv: use the VO_CAP of the renderer backend instead of the VO
The software renderer backend is not capable of using any of the VO
capabilities set globally by vo_libmpv. This can lead to assertion
errors when trying to render a video with 90 degree rotation metadata.
Instead use the ctx->renderer->driver_caps value so the source frame
doesn't have its params rotated when it's not desired.
2026-07-03 13:59:58 +00:00
Mohammad AlSaleh 7aa8580cab options: store audio/sub delay in doubles instead of floats
An `--audio-file` could be sourced from a stream. A stream could be
 using timestamps that match unix time in their segments. In that
 case, setting `audio-delay` will lead to a big unexpected A/V desync
 due to the limited precision of floats.

 This commit changes `--audio-delay` (and --sub-delay while at it) to be
 stored in `double` instead of `float`.

 `audio_delay` was already up-casted to `double` wherever it was
 evaluated. Same for `sub-delay`, except its own value was getting
 adjusted based on `double` values in one place (bad!):

 player/command.c:6297

    mpctx->opts->subs_shared->sub_delay[track_ind] -= a[0] - refpts;

 A showcase:

    mkfifo /tmp/mpv
    date=$(date +'%s')

 Before:

    mpv --input-ipc-server=/tmp/mpv -
    echo 'print-text '"${date}" | socat - /tmp/mpv # 1782890841
    echo 'set audio-delay '"${date}" | socat - /tmp/mpv
    echo 'set sub-delay '"${date}" | socat - /tmp/mpv
    echo 'print-text ${=audio-delay}' | socat - /tmp/mpv # 1782890880.000000
    echo 'print-text ${=sub-delay}' | socat - /tmp/mpv # 1782890880.000000

 After:

    mpv --input-ipc-server=/tmp/mpv -
    echo 'print-text '"${date}" | socat - /tmp/mpv # 1782890841
    echo 'set audio-delay '"${date}" | socat - /tmp/mpv
    echo 'set sub-delay '"${date}" | socat - /tmp/mpv
    echo 'print-text ${=audio-delay}' | socat - /tmp/mpv # 1782890841.000000
    echo 'print-text ${=sub-delay}' | socat - /tmp/mpv # 1782890841.000000

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2026-07-02 17:38:51 +00:00
Kacper Michajłow c02751a0bf vo: prefer dmabuf_wayland over other fallback VOs 2026-07-02 17:38:28 +00:00
Mohammad AlSaleh dc8101574d audio/filter: implement lavfi-tempo leveraging lavfi's atempo/ascale
Implement `lavfi-tempo` audio filter, which uses `atempo` or `ascale`
 form libavfilter for setting the tempo.

 Users (myself included) seem to be always looking for alternative tempo
 scalers to try, and this provides a faster implementation (or two), and
 may prove useful beyond performance/speed.

 `atempo` is picked as a default, and as a fall-back if `ascale` is not
 available.

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2026-07-02 17:38:07 +00:00
Emmanuel Gil Peyrot 0279c0f07d path-unix: only accept absolute paths
The XDG Base Directory specification says “All paths set in these
environment variables must be absolute. If an implementation encounters
a relative path in any of these variables it should consider the path
invalid and ignore it.”
2026-07-02 17:36:50 +00:00
Thomas Weißschuh c22cac669e stream_env: add new stream to read from environment variables 2026-07-02 17:36:31 +00:00
Thomas Weißschuh 6d64f2867c stream_memory: split out bstr logic
This logic can then be reused by other streams that work on static data.
2026-07-02 17:36:31 +00:00
Thomas Weißschuh 1c467ad4f9 stream: drop unneeded include fcntl.h 2026-07-02 17:36:31 +00:00
Nicolas F a3d4b59197 command: plug another slop faucet
In the 2020s, now known as "the twilight years of modern
civilization", Silicon Valley's two most morbidly obese failsons
finally found their niche: a product that lets complete imbeciles
pretend to be software developers. This was, at least for a
while, a smash hit. Hordes of claudecucked vibechuds began
spamming free software projects with bogus "security bugs" and
associated even worse fixes, both of which they did not know how
to reason about, and definitely did not care to learn how to
reason about. The entire exercise, after all, was to get one's
name into the list of contributors, to get ahead in a highly
competitive job market of economies with equally torturous
education systems.

To the disturbingly moist-looking tech oligarchs recording
Goldman Sachs jerk-off instructons on their podcasts, this was a
profound shift in software development. Now, they no longer had
to pay 6 figures to some idiot who doesn't know how to program so
chooses React, and instead they can pay 6 figures to some
application that doesn't know how to program so chooses React.

For everyone else, it just added to the torment that is life in a
modern globalised service economy, where the fruits of their
labour are siphoned off and directed towards building ever
grander and more opulent paedophile island resorts.

But back to the code. Someone submitted a slop issue about how
there's UB if you run an input command with bogus parameters.
This is hardly a bug, since the commands are trusted, so the fix
is "don't do dumb shit". However, contextless vibechuddies saw
this as an opportunity to fire up their deeply subsidized
planet-burners and shit out some code they had no idea about what
it did.

LLMs, failing to have any real reasoning, tried twice, and failed
twice, to find the real """fix""" here. #18144 decided that
another condition needed to be added to the logical OR, whereas
the subsequent #18211 instead wanted to do an explicit cast to
size_t for no fucking reason. Funnily enough, the unsignedness of
size_t doesn't cause any problems here, because of the earlier
check to make sure `w` is positive.

Both of them missed that if `foo < bar * 4` overflows `bar`, you
can just check for `foo / 4 < bar`, which is an efficient
division as 4 is a power of 2, and the integer division here does
not lead to any problems because the check is a range check.

Do that, just to stop these stupid fucking PRs from popping up,
and deny these dumb shitheads the satisfaction of getting their
name into the contributor stats.

Closes #18128.
2026-07-02 00:30:18 +02:00
Kacper Michajłow 99b4c12ccc demux_mkv: map hvcE into side-data 2026-07-01 04:46:42 +02:00
Kacper Michajłow abfc3b21c8 mp_image: don't use deprecated pl_avdovi_metadata_supported
It's always true now.
2026-07-01 04:46:42 +02:00
Kacper Michajłow 8930fdb132 vo: bump size of pass desc string
To avoid truncated description.
2026-07-01 04:46:42 +02:00
Kacper Michajłow f53e8f3fed demux_mkv: group EL+BL dovi tracks 2026-07-01 04:46:42 +02:00
Kacper Michajłow 103aee7261 demux_disc: mirror lavf demuxer stream groups to parent
This joins Dolby Vision EL track with base one. libbluray current (as of
1.4.1) doesn't even expose this info, it will be available in next
version, but it is less code to ask lavf for this info in fact.
2026-07-01 04:46:42 +02:00
Kacper Michajłow 6b4a205c99 demux_lavf: handle AV_STREAM_GROUP_PARAMS_DOLBY_VISION 2026-07-01 04:46:42 +02:00
Kacper Michajłow 95f8f18d13 demux_mkv: add support for splitting EL track from in-band 2026-07-01 04:46:42 +02:00
Kacper Michajłow 116c8ecf3c demux_lavf: add support for splitting EL track from in-band 2026-07-01 04:46:42 +02:00
Kacper Michajłow 998c20af67 demux/dovi_split: add helper to split interleaved HEVC with BL and EL 2026-07-01 04:46:42 +02:00
Kacper Michajłow 6f724890e0 vo_gpu_next: upload/map EL frame 2026-07-01 04:46:42 +02:00
Kacper Michajłow 29c693c039 vo_gpu_next: parametrize frame upload/map
This is refactor only.
2026-07-01 04:46:42 +02:00
Kacper Michajłow 5058e6e737 f_output_chain: pair BL+EL and output as single frame 2026-07-01 04:46:42 +02:00
Kacper Michajłow 5330cae57e f_enhancement_pair: add filter to match frames of separate video tracks 2026-07-01 04:46:42 +02:00
Kacper Michajłow 81037dae3a mp_image: add no_dovi / no_enhancement_layer params 2026-07-01 04:46:42 +02:00
Kacper Michajłow 0480011ac4 vf_format: add vf=format=enhancement-layer option 2026-07-01 04:46:42 +02:00
Kacper Michajłow 6c4480cbfd mp_image: add enhancement_layer child image 2026-07-01 04:46:42 +02:00
Kacper Michajłow 314e80f2ec loadfile: reselect dependent tracks only when selected by user 2026-07-01 04:46:42 +02:00
Kacper Michajłow 25606307ff demux: propagate track selection only from base track
Don't try to select dependent track as main and feedback to base track.
To avoid situations where tracks are deselected only to select the same
group again.
2026-07-01 04:46:42 +02:00
Kacper Michajłow bc5684c2cf video/img_format: make mp_imgfmt_desc_get_num_comps input param const 2026-07-01 04:46:42 +02:00
sfan5 3f1b23abd0 stream_curl: fix libavformat version checks
Unfortunately ffmpeg missed the backport of <https://code.ffmpeg.org/FFmpeg/FFmpeg/commit/1a7e802c8f970e76807f08c3d3fb3513ae5a4882>,
so custom IO is still segfaulting on the older versions.
2026-06-29 14:58:15 +02:00
Kacper Michajłow 5abce15f1c vf_d3d11vpp: enable NVIDIA RTX Video only for SDR input
This filter only converts SDR to HDR, so disable it if we have already
HDR on input. Note that it also seems to be disabled if HDR is disabled
in Windows, but we don't check that, because filters are not related to
display. Use conditional profile to enable this filter when needed, this
commit is only small convenience change.

Fixes: #17800
2026-06-28 17:22:20 +02:00
Kacper Michajłow 4dbc235dba select.lua: add property_set getter 2026-06-28 16:36:32 +02:00
yangli 523aea99ad {select,console}.lua: release memory of menu items after menu closing
select.lua: `properties` is a large table containing 10k+ elements,
currently it's a global variable, which makes its memory cannot be
reclaimed by the GC after menu closing, so let's simply change it
to a local variable.

console.lua: `selectable_items` has the same issue, however, it cannot
be changed to a local variable because it is used by too many functions,
but at least we can reset it to nil when closing console.
2026-06-28 16:35:54 +02:00
dependabot[bot] 42149e9eac ci: bump cross-platform-actions/action from 1.2.0 to 1.3.0
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 15:57:06 +02:00
dependabot[bot] 508d25590d ci: bump actions/cache from 5 to 6
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-28 15:56:36 +02:00
Kacper Michajłow 62f1494661 vo_gpu_next: follow reference white system setting
This is mostly useful for SDR mode where previously we would output 203
nits, which on Windows is considered HDR, because reference white is
fixed at 80 nits in SDR mode. Which in turn causes additional
tone-mapping when using scRGB output.

Note that for SDR transfers this doesn't change the luminance, but
instead causes more aggressive tone-mapping, might not be fully
expected. But maybe we should follow system setting for better of for
worse.

Fixes: https://github.com/mpv-player/mpv/issues/17850
Not fully, because when ICC profile is embeded in content, libplacebo
will override our luminance, this will be fixed separately.
2026-06-27 23:15:26 +02:00
Kacper Michajłow a01e391851 vulkan/context_win: implement target_ref_luma 2026-06-27 23:15:26 +02:00
Kacper Michajłow be8d1eb3d6 vo/gpu/context: add preferred_ref_luma function 2026-06-27 23:15:26 +02:00
Kacper Michajłow a8a1cdbbbb d3d11/context: implement target_ref_luma 2026-06-27 23:15:26 +02:00
Kacper Michajłow 069a6f004e vo/gpu/context: add target_ref_luma function 2026-06-27 23:15:26 +02:00
Kacper Michajłow 5d15a41b0f vo/win32/d3d11_helpers: add mp_dxgi_sdr_white_level_from_hwnd() 2026-06-27 23:15:26 +02:00
Kacper Michajłow 245652c34c vo/win32/displayconfig: add mp_w32_displayconfig_get_sdr_white_level 2026-06-27 23:15:26 +02:00
Kacper Michajłow fe4eaf7c9d stream_curl: add support for Icecast metadata 2026-06-27 21:47:41 +02:00
Kacper Michajłow 5c97ee2a48 stream_lavf: use common Icecast metadata parser 2026-06-27 21:47:41 +02:00
Kacper Michajłow aa845b965a stream/network: add Icecast metadata parser 2026-06-27 21:47:41 +02:00
Guido Cella 8395d89069 vo_gpu{,_next}: use mp_basename_bstr() 2026-06-27 21:43:18 +02:00
Guido Cella 9dbf5d681d misc/path_utils: rename bstr_strip_ext() to mp_strip_ext() 2026-06-27 21:43:18 +02:00
Guido Cella 089c172dff misc/path_utils: rename bstr_get_ext() to mp_get_ext() 2026-06-27 21:43:18 +02:00
Guido Cella 4fa3c38983 misc/path_utils: remove mp_splitext()
It is no longer used.
2026-06-27 21:43:18 +02:00
Guido Cella 1a09154988 various: replace mp_splitext() with bstr_get_ext()
This will allow removing mp_splitext().
2026-06-27 21:43:18 +02:00
Guido Cella 9ffc0cd861 misc/path_utils: remove mp_strip_ext()
It is no longer used.
2026-06-27 21:43:18 +02:00
Guido Cella df3b76b018 various: replace mp_strip_ext() with bstr_strip_ext()
This will allow removing mp_strip_ext().
2026-06-27 21:43:18 +02:00
Guido Cella 2057e34f45 misc/path_utils: make bstr_strip_ext() and bstr_get_ext() accurate
Move bstr_strip_ext() and bstr_get_ext() to path_utils.c making them use
split_ext(), which is better than the previous bstr.c implementation
because it doesn't consider . in the dirname or at the start of
basenames.
2026-06-27 21:43:18 +02:00
Guido Cella 381e7eb155 misc/path_utils: add split_ext()
This will be used by bstr_get_ext() and bstr_strip_ext() without
allocating memory.
2026-06-27 21:43:18 +02:00
Guido Cella 4eaef66f4c bstr: don't make bstr_splice() add to NULL
If str.start is NULL, don't error on FreeBSD + ASan with "applying zero
offset to null pointer" in bstr_splice().

This will avoid an error in split_ext() in the language test with NULL
as argument.
2026-06-27 21:43:18 +02:00
Guido Cella 29c26b5bc5 misc/path_utils: add mp_basename_bstr()
This will be used by bstr_get_ext() and bstr_split_ext() without
allocating memory.
2026-06-27 21:43:18 +02:00
Guido Cella 9bfdb3d67f vo_gpu{,_next}: use bstr_strip_ext()
Because it is simpler than mp_splitext(), and will allow removing
mp_splitext().
2026-06-27 21:43:18 +02:00
llyyr d8c82c3b16 ao_pipewire: convert pw_time.queued from bytes to frames
pw_time.queued is reported in bytes, while the code here expects it in
nframes.

Note that this doesn't matter at all, because pw_time.queued is always 0
for us since we use it as a pull-based AO. This would be a latent bug if
we ever switched to push model.
2026-06-27 21:06:47 +02:00
Kacper Michajłow 1cf081e66c select.lua: register not yet evaluated properties to item
In case when during parse and initial item creation there is only single
property evaluated, in case like `prop1 or prop2`. Only `prop1` is
evaluated and added to observe that will make item dirty. However, when
item is dirty, we may need to evaluate the rest of the expression,
especially important to add it to observe, so we mark it dirty next
time.

Fixes: #18153
2026-06-27 20:57:24 +02:00
Kacper Michajłow 80865a7d1f f_swresample: guard invalid memory access also in NDEBUG
Mostly to make GCC happy and fix array-bounds warning.
2026-06-27 20:39:35 +02:00
Kacper Michajłow a40319a514 osdep/compiler: make MP_ASSERT_UNREACHABLE no return
On some platforms assert is continuable, we don't want that, so abort()
just after.
2026-06-27 20:39:35 +02:00
Kacper Michajłow fb6c700ecb meson: try vapoursynth-script first
Older versions ship separate `vapoursynth-script.pc` and
`vapoursynth.pc`, we want to use `script` one. Try it first, if not
available use `vapoursynth`, which is the only one new version ships.
2026-06-27 19:16:30 +02:00
Kacper Michajłow ccfe4871db test/avio_crypto: add test for stream decryption 2026-06-27 19:04:03 +02:00
Kacper Michajłow 449944e25f demux/avio_crypto: add AES decryption
This mirrors `crypto+` protocol from ffmpeg, which is used in HLS
demuxer. Unfortunatelly ffmpeg doesn't use custom io_open in their
crypto.c, so we spin our own decryption.
2026-06-27 19:04:03 +02:00
Kacper Michajłow fc26cd23c0 msg: strip raw C1 controls and skip valid UTF-8 in sanitize
Fixes: #17957
2026-06-27 18:55:17 +02:00
arch1t3cht 7da362dfb5 demux_mkv: support demuxing native VC-1 streams
Match lavf's logic for how to parse the extradata.
2026-06-27 18:12:09 +02:00
verygoodlee 6b623e29cd select.lua: make property list more in line with the desc in the manual
1. the index of array-type properties is 0-based. ( e.g. `playlist/N` )
2. boolean values should be formatted as yes/no instead of true/false.
2026-06-27 18:11:48 +02:00
Nicolas F 669f431cf0 demux_edl: fix signed integer overflow in HLS bitrate
Use the new MP_SATURATE_MUL to prevent hls_bitrate from overflowing when
being multiplied by 8.

Fixes: #18120
2026-06-27 18:10:47 +02:00
Nicolas F f5a894de87 common/common: add saturating multiply macro
Add a new function-like macro MP_SATURATE_MUL. It multiplies a * b and
stores it in pointer "result", but sets "result" to its type's maximum
value if it overflows. Evaluates to true if it saturated, otherwise
false.
2026-06-27 18:10:47 +02:00
Nicolas F 8c6ad1b002 osdep/compiler: add MP_CKD_MUL
Add a new MP_CKD_MUL macro which does a multiplication with overflow
checking, backed by either the C23 ckd_mul standard function, or the
GCC/Clang __builtin_mul_overflow otherwise.
2026-06-27 18:10:47 +02:00
Kacper Michajłow ae99e39239 demux_mkv: remove redundant size checks
After previous commit, those sizes are checked in parser.
2026-06-27 18:09:12 +02:00
Kacper Michajłow 0251783b95 TOOLS/matroska.py: add size constraint for fixed size binary data
Those are fixed size per RFC and read as such in demux_mkv.c, fixes OOB
access in some cases that were missing the checks there.
2026-06-27 18:09:12 +02:00
Kacper Michajłow 23b9e96e9b demux/ebml: fix error path on data reject
If we don't read the data it shouldn't set the element as present.
2026-06-27 18:09:12 +02:00
Guido Cella 351291d583 menu.conf: compare aspect overrides more strictly
Fixes #18152.
2026-06-27 18:04:48 +02:00
Kacper Michajłow 0d68a842a0 hwdec_d3d11va: support 4:4:4 and high bit depth zero-copy formats
Replace the static format whitelist with a device-probed table and add
4:4:4 (VUYX/XV30/XV36/XV48) and 4:2:0 12/16-bit (P012/P016) surfaces to
both the zero-copy and copy mapper paths. The packed YUV formats can't be
decomposed by ra_get_imgfmt_desc(), so they are viewed as a single RGBA
texture and the swizzle is recovered from the imgfmt descriptor, which
only works with --vo=gpu-next.
2026-06-27 18:04:15 +02:00
Kacper Michajłow 2a49cc01d6 video/d3d: exclude VP9 on known broken Intel drivers 2026-06-27 18:03:54 +02:00
Kacper Michajłow 1d8cb602ac video/d3d: exclude HEVC on known crashing Intel drivers
The list of failing drivers was taken from a popular browser, which has
a lot of telemetry to determine which versions are not working.

Fixes: #18173
2026-06-27 18:03:54 +02:00
Kacper Michajłow 07c8c16bef vd_lavc: allow disabling codec hwdec per-device 2026-06-27 18:03:54 +02:00
Kacper Michajłow 70894ae039 ytdl_hook: defer loading until the formats become available
yt-dlp can provide time at which formats become available
`available_at`. Sleep until the requested time.

Fixes: #18192
2026-06-27 01:10:41 +02:00
Kacper Michajłow c75b8d2cca stream_curl: strip parameters from content type
It should only be assigned mime-type itself. I'm sure I had this change
locally, but probably lost it during rebase somewhere.

Fixes: #18187
2026-06-26 06:48:16 +02:00
Kacper Michajłow 289723b41a stream_curl: fix initial stream pos when offset is used
This is to match lavf/http.c behaviour and allow proper seeking.
2026-06-26 06:48:16 +02:00
Kacper Michajłow c4207644ba stream_curl: require libcurl 7.88.0
To avoid those awkward definitions. Initially much lower version was
required, but after changes it landed at 7.87.0, so we might as well
bump it one up.
2026-06-26 06:47:50 +02:00
sfan5 2339eb7276 stream_curl: extend version checks that enable curl by default
...to include older ffmpeg patch relases where the nested IO cleanup bug is fixed
2026-06-23 11:03:09 +02:00
Misaki Kasumi 2bd9c3229f osdep: fix MP_NO_ASAN definition 2026-06-22 03:52:21 +02:00
Guido Cella 1bcc17a7be select.lua: fix selecting the default subtitle line
Fixes 53796ca2ea.
2026-06-21 20:34:00 +02:00
dependabot[bot] dde8d8c4a0 ci: bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-21 12:36:13 +02:00
Kacper Michajłow efe8644a93 github/workflows/docs: update package index to fix install failures
When index is outdated some files may not exist, when we try to install
them.
2026-06-20 23:30:14 +02:00
Kacper Michajłow 2d5dfb343a ytdl_hook: use native http-proxy option instead of lavf option
Fixes: #18042
2026-06-18 05:27:17 +02:00
Kacper Michajłow f8150c78d5 demux_mkv: don't apply AVI b-frame delay compensation to VfW tracks
crazy_video_pts_stuff() shifts video PTS back by the codec delay to undo
libavcodec returning frames paired with a later packet's DTS. This is
only correct for genuine AVI, where DTS are decode-order frame indices.

VfW-muxed Matroska looks similar but its block timestamps are real DTS,
already offset by the reorder delay, so the libavcodec shift cancels the
offset and no compensation is needed. Applying it anyway pushed video
one frame ahead of audio (e.g. VC-1, whose Advanced Profile always
reports has_b_frames=1).

Split the `avi_dts` flag. Deep DTS routing for VfW mkv via a new
`block_dts`, and reserve avi_dts for genuine AVI only.

Note that demux_lavf for Matroska was already not affected, because
`avi_dts` wouldn't be set for those.

Fixes #18089
2026-06-17 23:55:51 +02:00
Guido Cella 02491a3949 osc.lua: don't reinit on duration changes
It can be avoided by extracting just the code to update the seekbar and
its chapter markers. This avoids log spam and is more efficient for DVD
and Blu-ray menus which continously update duration and have chapter
markers.

The livemarkers option is removed since updating the chapter markers is
no longer expensive like reiniting.
2026-06-17 13:39:10 +00:00
Guido Cella 715d2b7cf2 DOCS/man/commands: rename COMMANDS to CONSOLE COMMANDS
Avoid confusing it with command interface.
2026-06-17 13:38:57 +00:00
llyyr 3159500884 f_auto_filters: destroy deint filter immediately on format change
When the image format changes (e.g. switching hwdec), immediately
destroy the existing deinterlace filter instead of draining it. Draining
feeds new frames through the old filter, which can cause issues. (e.g.
bwdif_vulkan receiving vaapi frames).

This fixes userdeint filter failure when switching from hwdec=vulkan to
hwdec=vaapi while the filter is applied via --deinterlace=auto/yes
2026-06-17 13:37:57 +00:00
llyyr 4acaa77de5 af_scaletempo: avoid memcpy with disabled overlap
Overlap can be disabled with overlap=0 or very small stride values which
cause calculated overlap size to truncate down to 0.

Fixes: https://github.com/mpv-player/mpv/issues/18107
2026-06-17 13:37:34 +00:00
llyyr 9d828d5e55 vulkan/context: guard for null --vulkan-device param
Fixes: https://github.com/mpv-player/mpv/issues/18106
2026-06-17 13:37:34 +00:00
Dudemanguy 306546f35f bitmap_packer: drop fprintf logging
Ideally we would properly log this with mp_log but the plumbing for that
was deemed too ugly and not really worth it for just this one mysterious
edge case. So instead, drop the fprintf business since we really
shouldn't be doing that.
2026-06-17 13:36:44 +00:00
Dudemanguy 7bc56a77a9 bitmap_packer: return -1 instead of aborting on invalid bitmap size
All current callers of this already handle the -1 case like an error, so
instead of killing everything just return -1.
2026-06-17 13:36:44 +00:00
Misaki Kasumi 688b2738c6 subprocess-posix: skip macro guard in reset_signals_child 2026-06-17 10:47:16 +02:00
Misaki Kasumi 49b06cc337 subprocess-posix: fix uninitialized stacks in fork path 2026-06-17 10:47:16 +02:00
Misaki Kasumi 6b48326ccf subprocess-posix: avoid using mmap 2026-06-17 10:47:16 +02:00
Misaki Kasumi d849fd60d0 subprocess-posix: use rfork_thread on FreeBSD 2026-06-17 10:47:16 +02:00
Misaki Kasumi de1c73a44b subprocess-posix: handle detach inside spawn_process 2026-06-17 10:47:16 +02:00
Misaki Kasumi 5fb6a76ddf subprocess-posix: use clone when available 2026-06-17 10:47:16 +02:00
Pavel 53796ca2ea select.lua: add keep-open argument to select bindings
Adds an optional keep-open argument to all select.lua script bindings
that prevents the menu from closing after a selection.

Menus that display selection state (tracks, audio device) or a current
position indicator (playlist, chapter, edition, subtitle line) are
wrapped in a recursive function so the list is rebuilt after each
selection and the indicators remain accurate. default_item is passed to
preserve the cursor position on the last selected item.

The history confirmation dialog reopens the history menu when No is
selected, and closes when Yes is selected as there is nothing to show
anymore.
2026-06-17 00:30:39 +02:00
dependabot[bot] f88bcd25c7 ci: bump cross-platform-actions/action from 1.1.0 to 1.2.0
Bumps [cross-platform-actions/action](https://github.com/cross-platform-actions/action) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/v1.1.0...v1.2.0)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 16:20:38 +00:00
Wang Yu 9baeefe635 player: fix cover-art-files overriding embedded video tracks
When comparing external cover art images with embedded video tracks,
the external-vs-internal preference should not apply. Without this
check, --cover-art-files causes video files to be treated as audio
by selecting the external image over the embedded video stream.

Only apply the external track preference when both tracks have the
same image type (both images or both non-images).

Fixes #12219
2026-06-16 16:17:48 +00:00
Guido Cella cab6fbe222 context_menu.lua: don't open disabled submenus on hover
A menu item can have a non-empty submenu but still be disabled. This is
the case for mkvs with a single edition. Don't open the submenu on hover
in that case.
2026-06-16 13:02:52 +02:00
verygoodlee 89da9deafe console.lua: do not allow typing newlines when searching for menu items
newline makes no sense in such case, as there're no line breaks in the
selectable items. and it also causes a bug where the cursor may move
outside the background box.
2026-06-16 13:02:11 +02:00
arch1t3cht 65a1852ba3 input.conf: bind right click to context menu 2026-06-15 07:20:37 +02:00
Mikael Magnusson 662d41093c demux_timeline: fix Twitch VOD timestamps being offset by ~60 seconds 2026-06-14 20:14:42 +02:00
llyyr 7d245fd100 video/gpu/lcms: remove stray pointer from video profile handle
cmsHPROFILE is already a pointer type
2026-06-12 19:15:18 +02:00
llyyr bda1dcaa78 video/gpu/lcms,vo_gpu_next: avoid returning bool from pointer functions 2026-06-12 19:15:18 +02:00
llyyr 3d4893a893 test/language: replace accidental octal literal
Surrounding code uses decimal 0, not octal 0, so probably a typo
2026-06-12 19:15:18 +02:00
Kacper Michajłow 304426c390 video/vf_amf: fix use-after-free on AMF_INPUT_FULL 2026-06-09 11:52:29 +02:00
Kacper Michajłow fb5345809d sub/sd_ass: fix animated state indexing for reordered packets 2026-06-09 11:52:29 +02:00
Kacper Michajłow c1c047f8d6 stream/lavf: check child_next return value before dereference 2026-06-09 11:52:29 +02:00
Kacper Michajłow 894c350f83 stream/dvb: initialize parsing buffers for VDR-style configs 2026-06-09 11:52:29 +02:00
Kacper Michajłow 1ad719ff19 osdep/subprocess-posix: restore signal mask on detached fork failure 2026-06-09 11:52:29 +02:00
Kacper Michajłow f232963c61 input/ipc-win: fix uninitialized handle and missing client NULL check 2026-06-09 11:52:29 +02:00
Kacper Michajłow a7d0ebaf76 input: remove unused mp_input_src_feed_cmd_text() 2026-06-09 11:52:29 +02:00
Kacper Michajłow dddafe8b60 filters/f_lavfi: compare height in is_vformat_ok 2026-06-09 11:52:29 +02:00
Kacper Michajłow 5e4b06d977 demux/disc: avoid timestamp shift on unknown duration 2026-06-09 11:52:29 +02:00
Kacper Michajłow 5dfdd39997 demux/packet: fix OOB read in ITU-T T.35 UK parsing 2026-06-09 11:52:29 +02:00
nanahi 6444c05059 filters/frame: use correct check for mp_frame_set_pts
Not effect for current handlers.

Fixes: [Bug] frame.c: Wrong handler check in mp_frame_set_pts
(get_pts instead of set_pts)
2026-06-08 01:26:23 +02:00
nanahi e7f5d8f9c8 filters/filter: fix dropping request logging
Fixes: 76276c9210
2026-06-08 01:26:23 +02:00
nanahi c42393a280 video/filter/vf_amf: fix profile option
Fixes: [Bug] vf_amf.c: Profile always forced to FRC_PROFILE_HIGH
2026-06-08 01:26:23 +02:00
nanahi c9cf510d6a video/filter/vf_format: fix changing gamma check
Fixes: 76276c9210
2026-06-08 01:26:23 +02:00
nanahi 6a5e514509 input/sdl_gamepad: move controller null check to front
No need to call SDL_GameControllerGetJoystick if controller is NULL.
2026-06-08 01:26:23 +02:00
nanahi a24227d7ad stream/stream_bluray: fix chapter range check
Fixes: 62786c0c91
2026-06-08 01:26:23 +02:00
nanahi f29fcafb81 video/filter/vf_gpu_vulkan: parent opts to ctx
And only free explicitly on success. On failure it will be freed by
parent.

Fixes: [Bug] vf_gpu_vulkan: Use-after-free of ctx_opts
2026-06-08 01:26:23 +02:00
nanahi 579a62c2ba video/filter/vf_gpu_vulkan: remove redundant frees
ractx and vk are parented to ctx. No need to free manually.
2026-06-08 01:26:23 +02:00
arch1t3cht 47f81c8d7e lcms: avoid NaN when building BT.1886 transfer curves
After
https://github.com/mm2/Little-CMS/commit/bb60a46e9c50e9d3d18cf6dd81869240e4ebe618
cmsDetectBlackPoint can output nonzero X and Z values, which can end up
slightly negative due to rounding errors.
Bound the resulting src_black values by 0 below to ensure that raising
them to the power of 1 / gamma does not result in NaN, which would
result in a broken video profile.

Fixes #17439.
2026-06-08 00:56:44 +02:00
Hubert Głuchowski 43b14a4c9f sub/sd_sbr: make margin changes trigger redraw
This fixes panning not updating the subtitle overlay.
2026-06-07 16:06:37 +02:00
Hubert Głuchowski 71ebd08406 sub/packer: correctly offset instance bitmap pointer in pack_subrandr
The software scaling code expects this pointer to already be offset
by `src_x` and `src_y` but this function copied a pointer only
offset by the image offset, forgetting about instance offset.
2026-06-05 22:16:45 +02:00
Hubert Głuchowski 85fe21d290 sub/sd_sbr: fix zooming
Previously both the padding and clip rect calculations didn't take into
account zoom (negative margins) which resulted in more or less broken
behavior depending on if `sub-use-margins` was set or not.

This fixes those to properly handle negative margins which:
- Makes us calculate a tighter clip rect if `!sub-use-margins`.
- Unbreaks layout zoomed in and `sub-use-margins`.
2026-06-05 21:37:18 +02:00
Kacper Michajłow f88f423829 meson: consider only 'v0.*' tags when computing the version
This allow creating other tags on the repository and don't interfere
with version string generated.
2026-06-05 07:21:58 +02:00
Amyspark 1ad1322335 ci/win32: enable building without Git Bash shell
Replace `cp` in the subrandr wrap with an inline Python script.
2026-06-05 04:03:04 +02:00
Amyspark 0fc000b10b meson: properly escape backslashes when setting CONFIGURATION macro
This fixes compiling when using Windows-style paths in the Meson
configuration line, e.g.

> '-Dc_args=-IC:\mpv\subprojects\amf-headers'
2026-06-05 04:03:04 +02:00
185 changed files with 10009 additions and 2051 deletions

No files matched your search

+147 -103
View File
@@ -52,14 +52,14 @@ jobs:
- arch: x86_64
wine: wine64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Get time
id: get_time
run: echo "timestamp=`date +%s%N`" >> $GITHUB_OUTPUT
- name: Restore cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ env.TARGET }}-${{ steps.get_time.outputs.timestamp }}
@@ -123,7 +123,7 @@ jobs:
path: artifact/*
- name: Save Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
if: always()
with:
path: ${{ env.CCACHE_DIR }}
@@ -157,7 +157,7 @@ jobs:
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Get time
id: get_time
@@ -165,7 +165,7 @@ jobs:
"timestamp=$((Get-Date).Ticks)" >> $env:GITHUB_OUTPUT
- name: Restore cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ matrix.target }}-${{ steps.get_time.outputs.timestamp }}
@@ -261,7 +261,7 @@ jobs:
build/mpv.pdb
- name: Save Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
if: always()
with:
path: ${{ env.CCACHE_DIR }}
@@ -285,7 +285,7 @@ jobs:
swift: "-target arm64-apple-macosx10.15"
target: "10.15"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Remove stray upstream python binary symlinks under /usr/local
run: |
@@ -381,7 +381,7 @@ jobs:
ld: "lld"
args: "-Db_lto=true -Db_lto_mode=thin"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Build with meson
id: build
@@ -411,7 +411,7 @@ jobs:
CC: "clang"
CXX: "clang++"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Build with meson
id: build
@@ -434,7 +434,7 @@ jobs:
# Musl support is an added bonus.
image: alpine:3.21
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Install dependencies
run: |
apk update
@@ -485,104 +485,148 @@ jobs:
openbsd:
runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385
timeout-minutes: 15 # avoid any weirdness with the VM
defaults:
run:
shell: cpa.sh {0} --sync-files none
steps:
- uses: actions/checkout@v6
- name: Test in OpenBSD VM
uses: cross-platform-actions/action@v1.1.0
with:
operating_system: openbsd
version: '7.8'
- uses: actions/checkout@v7
- name: Start OpenBSD VM
uses: cross-platform-actions/action@v1.4.0
with:
operating_system: openbsd
version: '7.8'
sync_files: runner-to-vm
- name: Install dependencies
run: |
sudo pkg_add -U \
cmake \
curl \
ffmpeg \
git \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libiconv \
libplacebo \
libv4l \
libxkbcommon \
luajit \
meson \
openal \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sdl2 \
shaderc \
spirv-cross \
spirv-headers \
uchardet \
vulkan-loader \
vulkan-headers \
zimg
./ci/build-openbsd.sh
if ! meson test -C build; then
cat ./build/meson-logs/testlog.txt
exit 1
fi
sudo pkg_add -U \
cmake \
curl \
ffmpeg \
git \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libiconv \
libplacebo \
libv4l \
libxkbcommon \
luajit \
meson \
openal \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sdl2 \
shaderc \
spirv-cross \
spirv-headers \
uchardet \
vulkan-loader \
vulkan-headers \
zimg
- name: Build with meson
id: build
run: |
./ci/build-openbsd.sh
- name: Print meson log
if: ${{ failure() && steps.build.outcome == 'failure' }}
run: |
cat ./build/meson-logs/meson-log.txt
- name: Run meson tests
id: tests
run: |
meson test -C build
- name: Print meson test log
if: ${{ failure() && steps.tests.outcome == 'failure' }}
run: |
cat ./build/meson-logs/testlog.txt
freebsd:
runs-on: ubuntu-latest # until https://github.com/actions/runner/issues/385
timeout-minutes: 15 # avoid any weirdness with the VM
defaults:
run:
shell: cpa.sh {0} --sync-files none
steps:
- uses: actions/checkout@v6
- name: Test in FreeBSD VM
uses: cross-platform-actions/action@v1.1.0
with:
operating_system: freebsd
version: '15.0'
- uses: actions/checkout@v7
- name: Start FreeBSD VM
uses: cross-platform-actions/action@v1.4.0
with:
operating_system: freebsd
version: '15.1'
sync_files: runner-to-vm
- name: Install dependencies
run: |
sudo pkg update
sudo pkg install -y \
alsa-lib \
cmake \
curl \
evdev-proto \
ffmpeg \
git \
iconv \
jackit \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libdisplay-info \
libplacebo \
libXinerama \
libxkbcommon \
libXpresent \
libXv \
luajit \
meson \
mujs \
openal-soft \
pipewire \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sekrit-twc-zimg \
sdl2 \
sndio \
uchardet \
v4l_compat \
vulkan-headers \
wayland-protocols
./ci/build-freebsd.sh
if ! meson test -C build; then
cat ./build/meson-logs/testlog.txt
exit 1
fi
sudo pkg update
sudo pkg install -y \
alsa-lib \
cmake \
curl \
evdev-proto \
ffmpeg \
git \
iconv \
jackit \
libarchive \
libbluray \
libcaca \
libcdio-paranoia \
libdvdnav \
libdisplay-info \
libplacebo \
libXinerama \
libxkbcommon \
libXpresent \
libXv \
luajit \
meson \
mujs \
openal-soft \
pipewire \
pkgconf \
pulseaudio \
python3 \
rubberband \
rust \
sekrit-twc-zimg \
sdl2 \
sndio \
uchardet \
v4l_compat \
vulkan-headers \
wayland-protocols
- name: Build with meson
id: build
run: |
./ci/build-freebsd.sh
- name: Print meson log
if: ${{ failure() && steps.build.outcome == 'failure' }}
run: |
cat ./build/meson-logs/meson-log.txt
- name: Run meson tests
id: tests
run: |
meson test -C build
- name: Print meson test log
if: ${{ failure() && steps.tests.outcome == 'failure' }}
run: |
cat ./build/meson-logs/testlog.txt
msys2:
runs-on: ${{ matrix.os }}
@@ -617,7 +661,7 @@ jobs:
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.sys }}
@@ -691,7 +735,7 @@ jobs:
group: publish-git
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -738,7 +782,7 @@ jobs:
}
# Match the version string baked into the build (see common/meson.build).
version=$(git describe --abbrev=9 --tags --dirty)
version=$(git describe --abbrev=9 --tags --dirty --match "v0.*")
notes=$(cat <<EOF
Automated development build of the latest \`master\` branch.
+44 -26
View File
@@ -6,7 +6,7 @@ on:
types: [completed]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
group: ${{ github.workflow }}
cancel-in-progress: true
permissions:
@@ -17,32 +17,50 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: |
gh cache list -L 100 --json id,key,ref -S last_accessed_at -O desc --jq '
map(select(.key | startswith("x86_64-w64-mingw32-") or
startswith("i686-w64-mingw32-") or
startswith("aarch64-pc-windows-msvc-") or
startswith("x86_64-pc-windows-msvc-"))) |
group_by(.ref) |
map({
ref: .[0].ref,
caches: map({
key: .key,
prefix: (.key | capture("^(?<prefix>[\\w_-]+-)\\d+$").prefix)
}) | group_by(.prefix) | map({keys: map(.key)})
}) |
.[]
' |
while read -r group; do
pr=$(echo "$group" | jq -r '.ref | capture("refs/pull/(?<num>[0-9]+)/merge").num')
if [[ -n "$pr" ]] && [ "$(gh pr view $pr --json state --jq '.state')" != "OPEN" ]; then
keys=$(echo "$group" | jq -c '.caches | map(.keys) | .[]')
else
keys=$(echo "$group" | jq -c '.caches | map(.keys[1:]) | .[]')
fi
for key in $(echo "$keys" | jq -r '.[]'); do
gh cache delete "$key"
done
set -o pipefail
gh cache list -L 1000 --json id,key,ref -S last_accessed_at -O desc > caches.json
# Resolve the state of every pull request that owns a cache.
for pr in $(jq -r '.[].ref | select(test("^refs/pull/[0-9]+/merge$")) |
split("/")[2]' caches.json | sort -u); do
jq -n --arg pr "$pr" --arg state "$(gh pr view $pr --json state --jq .state)" \
'{($pr): $state}'
done | jq -s 'add // {}' > prs.json
jq -r --slurpfile prs prs.json --arg default "$DEFAULT_REF" '
$prs[0] as $state |
# Group caches by kind, dropping the timestamp or content hash
# that every save appends to the key.
def family: if startswith("msys2-") then "msys2"
elif test("-[0-9]+$") then "ccache"
else "other" end;
def group: if family == "msys2" then sub("-(files|state):.*$"; "")
else sub("-[0-9]+$"; "") end;
[ .[] | . + { family: (.key | family),
group: (.key | group),
pr: (.ref | if test("^refs/pull/[0-9]+/merge$")
then split("/")[2] else null end) } ]
| map(select(.family != "other")) as $all
# Caches of merged or closed pull requests, and msys2 caches
# outside the default branch, which every ref restores from.
| ( $all | map(select(
(.pr != null and $state[.pr] != "OPEN") or
(.family == "msys2" and .ref != $default)
)) ) as $dead
# Of the rest only the newest cache of each kind per ref is used.
| ( ($all - $dead) | group_by([.ref, .group]) | map(.[1:]) | add // [] ) as $superseded
| ($dead + $superseded)[] | "\(.id)\t\(.ref)\t\(.key)"
' caches.json |
while IFS=$'\t' read -r id ref key; do
echo "deleting $ref $key"
gh cache delete "$id"
done
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEFAULT_REF: refs/heads/${{ github.event.repository.default_branch }}
+2 -1
View File
@@ -21,9 +21,10 @@ jobs:
check-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Docs
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y python3-docutils rst2pdf
./TOOLS/docutils-wrapper.py rst2man --strip-elements-with-class=contents --halt=2 ./DOCS/man/mpv.rst mpv.1
./TOOLS/docutils-wrapper.py rst2html --halt=2 ./DOCS/man/mpv.rst mpv.html
+7 -7
View File
@@ -17,7 +17,7 @@ jobs:
commit-msg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 50
@@ -28,20 +28,20 @@ jobs:
swift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: norio-nomura/action-swiftlint@3.2.1
lua:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: lunarmodules/luacheck@v1
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Check
id: check
uses: astral-sh/ruff-action@v3
@@ -54,7 +54,7 @@ jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: python/mypy@master
with:
install_project_dependencies: no
@@ -64,12 +64,12 @@ jobs:
editorconfig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: editorconfig-checker/action-editorconfig-checker@main
- run: editorconfig-checker -config .editorconfig-checker.json
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: pre-commit/action@v3.0.1
+3
View File
@@ -0,0 +1,3 @@
add `--disc-menu` option
add `disc-menu-active` property
add `discnav` command
+2
View File
@@ -0,0 +1,2 @@
add `dvda://` protocol for DVD-Audio playback
add `--dvda-device` and `--dvda-page` options
+1
View File
@@ -0,0 +1 @@
add `vf=format=enhancement-layer` option
+1
View File
@@ -0,0 +1 @@
`track-list/N/ff-index` may be unavailable if no known mapping exists
@@ -0,0 +1 @@
change `--hwdec-extra-frames` default to `auto`, sized from VO frame retention
@@ -0,0 +1 @@
implement `lavfi-tempo` an alternative speed changing audio filter
@@ -0,0 +1,2 @@
remove `osc-livemarkers` script-opt because chapter markers are now updated
without reiniting the OSC
@@ -0,0 +1,2 @@
add `video_colorspace`, `max_luma`, `primaries` and `transfer` arguments
to the `overlay-add` command
@@ -0,0 +1 @@
add `--playlist-inherit-options` option
@@ -0,0 +1 @@
bind right click to context menu
@@ -0,0 +1,2 @@
add `--secondary-sub-scale` option
`--sub-scale` now only affects primary subtitles
@@ -0,0 +1 @@
add `keep-open` argument support to select.lua script bindings
@@ -0,0 +1 @@
add `--sub-hdr-peak=auto` to follow the reference white and make it the default
+31
View File
@@ -189,6 +189,37 @@ Available filters are:
``window-size=<amount>``
Length in milliseconds of the overlap-and-add window. (default: 12)
``lavfi-tempo[=[filter=]<filter_name>]``
Scales audio tempo using ``atempo`` or ``ascale`` filters from FFmpeg's
libavfilter.
If ``ascale`` (Librempeg only) is not available in the loaded
libavfilter, ``atempo`` will be used as a fall-back.
This can be used in place of ``scaletempo`` and ``scaletempo2``.
.. admonition:: Examples
``mpv --af=lavfi-tempo --speed=1.2 media.ogg``
Would play media at 1.2x normal speed at normal pitch with tempo
scaled by the ``atempo`` filter from lavfi.
``mpv --af=lavfi-tempo=atempo --speed=1.2 media.ogg``
Same as above.
``mpv --af=lavfi-tempo=filter=atempo --speed=1.2 media.ogg``
Same as above.
``mpv --af=lavfi-tempo=ascale --speed=1.2 media.ogg``
Would play media at 1.2x normal speed at normal pitch with tempo
scaled by the ``ascale`` filter from lavfi, unless the filter is
not available, then ``atempo`` will be used as a fall-back.
``mpv --af=lavfi-tempo=filter=ascale --speed=1.2 media.ogg``
Same as above.
``rubberband``
High quality pitch correction with librubberband. This can be used in place
of ``scaletempo`` and ``scaletempo2``, and will be used to adjust audio pitch
+2 -2
View File
@@ -1,5 +1,5 @@
COMMANDS
========
CONSOLE COMMANDS
================
This script allows running and completing input commands in the console
interactively, and also adds mpv's log to the console's log.
+75 -6
View File
@@ -904,7 +904,7 @@ OSD Commands
Show the progress bar, the elapsed time and the total duration of the file
on the OSD. ``no-osd`` has no effect on this command.
``overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride> <dw> <dh>``
``overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride> [<dw> [<dh> [<video_colorspace> [<max_luma> [<primaries> [<transfer>]]]]]]``
Add an OSD overlay sourced from raw data. This might be useful for scripts
and applications controlling mpv, and which want to display things on top
of the video window.
@@ -964,6 +964,24 @@ OSD Commands
in display to ``dw`` and ``dh``. If parameters are not present, the
values for ``w`` and ``h`` are used.
``video_colorspace`` (default: no) interprets the overlay in the video's
colorspace, including HDR metadata, so it is tone mapped exactly like
the video.
``max_luma`` specifies the (optional) luminance peak of the overlay in
cd/m². If unset, it is inferred from the overlay's colorspace, or taken
from the video for ``video_colorspace`` overlays.
``primaries`` and ``transfer`` specify the (optional) colorspace the
overlay is encoded in, and accept the same values as ``--target-prim``
and ``--target-trc``. The default ``auto`` means sRGB (BT.709 primaries
and sRGB transfer). They are ignored if ``video_colorspace`` is set.
Currently only ``--vo=gpu-next`` honors these parameters. ``--vo=gpu``
always assumes the overlay is sRGB and adapts it to the display. Other
VOs ignore these parameters, and either blend the overlay without any
colorspace conversion or pass it to the system compositor.
.. note::
Before mpv 0.42.0, the file was mapped into memory with ``mmap()``,
@@ -1753,6 +1771,46 @@ Miscellaneous Commands
``context-menu``
Show context menu on the video window. See `Context Menu`_ section for details.
``discnav <action> [<x> <y>]``
Send a navigation command to the optical-disc stream that is currently
playing. The command does nothing when no disc is being played.
``<action>`` is one of:
up, down, left, right
Move the on-disc button selection.
select
Activate the currently highlighted button.
menu
Jump to the root menu of the current title set (DVD: VTSM root menu,
BD: HDMV top menu).
title-menu
Jump to the disc's title menu (DVD: VMGM title menu, treated like
``menu`` on BD).
popup
Show / dismiss the Blu-ray popup menu. For DVDs, falls back to the
chapter menu where available.
prev
Return from a menu to playback, or to the previous menu domain.
mouse-move
Forward a mouse position to the disc, so it can update the focused
button under the cursor. With no ``<x> <y>`` arguments, the position
is read from the ``mouse-pos`` property and mapped from window into
source-video coordinates automatically.
mouse-click
Like ``mouse-move`` but also activates the button under the cursor.
``<x>`` and ``<y>`` are optional and only meaningful for the ``mouse-*``
actions. They are normalized coordinates in the range 0..1 (top-left at
``0,0``), and are used instead of the live ``mouse-pos``.
mpv ships default bindings for this command in the ``{discnav}`` input
section (``UP``/``DOWN``/``LEFT``/``RIGHT`` for navigation, ``ENTER`` to
select, ``ESC`` / ``BS`` to leave, ``MBTN_LEFT`` / ``MOUSE_MOVE`` for
mouse). The player enables and disables that section automatically as
the menu appears and disappears, so the bindings only shadow the normal
ones while a menu is actually on screen.
``update-clipboard <type> [timeout]``
Update the clipboard content so that the ``clipboard`` property reflects
up-to-date value. This command is required to update the ``clipboard``
@@ -1986,7 +2044,7 @@ The following hooks are currently defined:
For example, you could read and write the ``stream-open-filename``
property to redirect an URL to something else (consider support for
streaming sites which rarely give the user a direct media URL), or
you could set per-file options with by setting the property
you could set file-local options by setting the property
``file-local-options/<option name>``. The player will wait until all
hooks are run.
@@ -2423,6 +2481,12 @@ Property list
between having no editions and a single edition, which will be reflected by
the property, although in practice it does not matter.)
``disc-menu-active``
``yes`` when the current optical-disc stream (DVD or Blu-ray) is showing
an interactive menu (a DVD menu domain, or a visible Blu-ray menu
overlay), and ``no`` otherwise. Unavailable when the currently playing
source is not an optical disc.
``chapters``
Number of chapters.
@@ -3576,7 +3640,7 @@ Property list
this can be potentially wrong if a demuxer other than libavformat
(``--demuxer=lavf``) is used. For mkv files, the index will usually
match even if the default (builtin) demuxer is used, but there is
no hard guarantee.
no hard guarantee. May be unavailable if no known mapping exists.
``track-list/N/decoder``
If this track is being decoded, the short decoder name,
@@ -4162,9 +4226,14 @@ Property list
``file-local-options/<name>`` (RW)
Similar to ``options/<name>``, but when setting an option through this
property, the option is reset to its old value once the current file has
stopped playing. Trying to write an option while no file is playing (or
is being loaded) results in an error.
property, the option is marked as file-local, which causes it to reset to
its old value once the current file has stopped playing. Trying to write
an option while no file is playing (or is being loaded) results in an error.
If an option is applied through a file specific mechanism such as per-file
options, watch later, ``options`` parameter of the ``loadfile`` command,
or file-specific configuration files, the option is similarly marked as
file-local.
(Note that if an option is marked as file-local, even ``options/`` will
access the local value, and the ``old`` value, which will be restored on
+50 -7
View File
@@ -174,6 +174,15 @@ z and Z
l
Set/clear A-B loop points. See ``ab-loop`` command for details.
Ctrl+m
Jump to the disc menu when playing a DVD or Blu-ray (see the ``discnav``
command). While a menu with a button highlight is on screen, the arrow
keys, ENTER, ESC/BACKSPACE and the mouse control the menu instead of
their usual functions.
Ctrl+M
Show or hide the Blu-ray popup menu.
L
Toggle infinite looping.
@@ -393,7 +402,7 @@ Left double click
Toggle fullscreen on/off.
Right click
Toggle pause on/off.
Show the context menu (see `CONTEXT MENU`_).
Forward/Back button
Skip to next/previous entry in playlist.
@@ -622,7 +631,8 @@ reset when a new file is played.
Sometimes, it is useful to change options per-file. This can be achieved by
adding the special per-file markers ``--{`` and ``--}``. (Note that you must
escape these on some shells.) Example::
escape these on some shells.) When a file is loaded, the associated per-file
options are applied and marked as file-local options. Example::
mpv --a file1.mkv --b --\{ --c file2.mkv --d file3.mkv --e --\} file4.mkv --f
@@ -637,7 +647,8 @@ file4.mkv ``--a --b --f``
Additionally, any file-local option changed at runtime is reset when the current
file stops playing. If option ``--c`` is changed during playback of
``file2.mkv``, it is reset when advancing to ``file3.mkv``. This only affects
``file2.mkv``, it is reset when advancing to ``file3.mkv``, or when restarting
the current file with the ``playlist-play-index`` command. This only affects
file-local options. The option ``--a`` is never reset here.
@@ -816,6 +827,8 @@ file-specific configuration is loaded from ``~/.config/mpv``. In addition, the
``--use-filedir-conf`` option enables directory-specific configuration files.
For this, mpv first tries to load a mpv.conf from the same directory
as the file played and then tries to load any file-specific configuration.
The options loaded in this way are marked as file-local, which are reset when
the current file stops playing.
Profiles
@@ -1266,6 +1279,9 @@ modified after playback began, for example the volume and selected audio/subtitl
and restores their values the next time the file is played. Which options are
saved can be configured with the ``--watch-later-options`` option.
The options applied in this way are marked as file-local, and they are reset when
playback of the file associated with it stops.
When playing multiple playlist entries, mpv checks if one them has a resume
config file associated, and if it finds one it restarts playback from it. For
example, if you use ``quit-watch-later`` on the 5th episode of a show, and
@@ -1320,8 +1336,12 @@ PROTOCOLS
Play a Blu-ray disc. Since libbluray 1.0.1, you can read from ISO files
by passing them to ``--bluray-device``.
A Blu-ray ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened.
``title`` can be: ``longest`` or ``first`` (selects the default
playlist); ``mpls/<number>`` (selects <number>.mpls playlist);
playlist); ``menu`` (starts in the disc menu, see ``--disc-menu`` and the
``discnav`` command); ``mpls/<number>`` (selects <number>.mpls playlist);
``<number>`` (select playlist with the same index). mpv will list
the available playlists on loading.
@@ -1329,13 +1349,32 @@ PROTOCOLS
``dvd://[title][/device]`` ``--dvd-device=PATH``
Play a DVD. DVD menus are not supported. If no title is given, the longest
title is auto-selected. Without ``--dvd-device``, it will probably try
to open an actual optical drive, if available and implemented for the OS.
Play a DVD. If no title is given, the longest title is auto-selected.
``title`` can also be ``menu`` to start in the disc menu (see
``--disc-menu`` and the ``discnav`` command). Without ``--dvd-device``,
it will probably try to open an actual optical drive, if available and
implemented for the OS.
A DVD-Video ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened.
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
thing.
``dvda://[title][/device]`` ``--dvda-device=PATH``
Play the AUDIO_TS zone of a DVD-Audio disc. Titles correspond to the
disc's audio groups, tracks are exposed as chapters. If no title is
given, the longest title is auto-selected. Menus are not supported.
Still images (ASVS), such as cover art or booklet pages a disc associates
with its tracks, are exposed as a video track and shown by default. Can be
disabled with ``--vid=no``.
A DVD-Audio ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened. For hybrid discs, the DVD-Audio zone is preferred over
the DVD-Video zone.
``dvb://[cardnumber@]channel`` ``--dvbin-...``
Digital TV via DVB. (Linux only.)
@@ -1492,6 +1531,10 @@ PROTOCOLS
This will play ``video.mkv`` in the archive file ``file.zip``.
``env://variable``
Read the environment variable ``variable`` as source data.
PSEUDO GUI MODE
===============
+99 -21
View File
@@ -285,8 +285,23 @@ Playback Control
``--pause``
Start the player in paused state.
``--shuffle``
``--shuffle=<yes|no>``
Play files in random order.
This works by shuffling the playlist at the following points:
1. At player startup before playback starts. The files specified on the
command line are shuffled. Note that the directories and
playlist files in these arguments are not expanded at this time, so
their contents are not shuffled until the situation 2 mentioned below
happens. To expand these lists at startup, use ``--playlist``.
2. When loading a directory or playlist file, either with ``loadlist``
command or by playing a playlist file in the current playlist. The
items in the loaded playlist are shuffled before they are added to
the current playlist. Other existing items are not shuffled.
3. When ``--loop-playlist`` is enabled, the player performs a shuffle
after looping.
``--playlist-start=<auto|index>``
Set which file on the internal playlist to start playback with. The index
@@ -312,6 +327,13 @@ Playback Control
different demuxers and will not work with this option. They still can be
played directly, without using this option.
This option differs from specifying playlist files directly as arguments.
The playlists specified by ``--playlist`` are expanded at startup, while
playlist files specified directly as arguments are expanded only when the
list is being played. Note that this expansion is not recursive, except in
the case of ``--playlist=<directory>``, where expansion follows the
``--directory-mode`` option.
By default, mpv doesn't play URLs from playlists which are considered
unsafe. If you trust the playlist file, you can disable any security checks
with ``--load-unsafe-playlists``. Because playlists can load other playlist
@@ -335,6 +357,12 @@ Playback Control
local files, such as special protocols like ``avdevice://`` (which are
inherently unsafe).
``--playlist-inherit-options=<yes|no|current>``
Whether the per-file options of a playlist file are inherited by its items
when the playlist file is resolved and expanded (default: no). The value
``current`` means that for playlists created by ``--autocreate-playlist``,
only the file from which the playlist is created inherits the options.
``--chapter-merge-threshold=<number>``
Threshold for merging almost consecutive ordered chapter parts in
milliseconds (default: 100). Some Matroska files with ordered chapters
@@ -1541,20 +1569,27 @@ Video
Runtime changes to this are ignored (the current option value is used
whenever the renderer is created).
``--hwdec-extra-frames=<N>``
Number of GPU frames hardware decoding should preallocate (default: see
``--list-options`` output). If this is too low, frame allocation may fail
during decoding, and video frames might get dropped and/or corrupted.
Setting it too high simply wastes GPU memory and has no advantages.
``--hwdec-extra-frames=<auto|N>``
Number of extra GPU frames hardware decoding should preallocate, on top of
what the codec itself requires (default: ``auto``).
This value is used only for hardware decoding APIs which require
preallocating surfaces (known examples include ``d3d11va`` and ``vaapi``).
For other APIs, frames are allocated as needed. The details depend on the
libavcodec implementations of the hardware decoders.
The required number of surfaces depends on dynamic runtime situations. The
default is a fixed value that is thought to be sufficient for most uses. But
in certain situations, it may not be enough.
In ``auto`` mode, the number is derived from how many frames the player
and the VO may reference at the same time. For example, enabling
``--interpolation`` increases it in proportion to the ``--tscale`` filter
radius, and VO deinterlacing adds the temporal reference frames it needs.
The value is determined when the decoder is initialized; runtime option
changes that increase frame requirements do not resize the pool, so set a
fixed value if you intend to switch such options during playback.
Setting a fixed value overrides the automatic sizing in both directions.
If it is too low, frame allocation may fail during decoding, and video
frames might get dropped and/or corrupted. Setting it too high simply
wastes GPU memory and has no advantages.
``--hwdec-image-format=<name>``
Set the internal pixel format used by hardware decoding via ``--hwdec``
@@ -1794,6 +1829,10 @@ Video
inserted deinterlacing filters, and that this will make video look worse if
it's not actually interlaced.
Enabling video output deinterlacing at runtime may require setting
``--hwdec-extra-frames``, as the hardware decoder's surface pool is sized
at decoder initialization.
``--deinterlace-field-parity=<tff|bff|auto>``
Specify the field parity/order when deinterlacing (default: auto).
Each frame of an interlaced video is divided into two fields, which are
@@ -2518,6 +2557,10 @@ Subtitles
This affects ASS subtitles as well, and may lead to incorrect subtitle
rendering. Use with care, or use ``--sub-font-size`` instead.
``--secondary-sub-scale=<0-100>``
Factor for the secondary subtitle font size. This is similar to
``--sub-scale`` but for secondary subtitles (default: 1).
``--sub-scale-signs=<yes|no>``
When set to yes, also apply ``--sub-scale`` to typesetting (or "signs").
When this is set to no, ``--sub-scale`` is only applied to dialogue. The
@@ -2815,10 +2858,12 @@ Subtitles
This also affects image subtitle brightness in HDR tone mapping with
``--blend-subtitles=<yes|video>``.
``--sub-hdr-peak=<sdr|10-10000>``
``--sub-hdr-peak=<auto|sdr|10-10000>``
Controls the text subtitle and OSD diffuse white level in cd/m² (nits)
for HDR output (default: sdr). ``sdr`` is 203 cd/m² for standard SDR white.
(``--vo=gpu-next`` only)
for HDR output (default: auto). In ``auto`` mode, subtitles and OSD follow
the reference white (see ``--hdr-reference-white``), matching the diffuse
white level used for SDR content. ``sdr`` forces 203 cd/m² for standard SDR
white. (``--vo=gpu-next`` only)
This also affects text subtitle brightness in HDR tone mapping with
``--blend-subtitles=<yes|video>``.
@@ -3872,6 +3917,12 @@ Window
Disc Devices
------------
``--disc-menu=<yes|no>``
When set, opening ``dvd://`` or ``bd://`` boots into the disc's interactive
menu instead of automatically playing the longest title (default: ``no``).
The menu can also be reached at any time via the synthetic "Disc Menu"
entry in the editions/titles list, or with ``discnav menu`` command.
``--cdda-device=<path>``
Specify the CD device for CDDA playback. The default device path depends on
the OS. See the `OPTICAL DRIVES`_ section.
@@ -3886,6 +3937,21 @@ Disc Devices
``mpv dvd:// --dvd-device=/path/to/dvd/``
``--dvda-device=<path>``
Specify the DVD-Audio device or .iso filename for ``dvda://`` playback.
You can also specify a directory that contains files previously copied
directly from a DVD-Audio disc. The default device path depends on
the OS. See the `OPTICAL DRIVES`_ section.
.. admonition:: Example
``mpv dvda:// --dvda-device=/path/to/dvda/``
``--dvda-page=<-1|0-...>``
Force the still picture page shown during DVD-Audio playback, instead of
the page scheduled for the current playback position (default: -1, use
the scheduled page).
``--bluray-device=<path>``
Specify the Blu-ray disc location. Must be a directory with Blu-ray
structure. The default device path depends on the OS. See the
@@ -4549,14 +4615,16 @@ Input
.. admonition:: Example
``--input-ipc-client=fd://123``
``--input-ipc-client=handle://123``
.. note::
To use this option on Windows, the fd must refer to a wrapped
(created by ``_open_osfhandle``) named pipe server handle with a client
already connected. The named pipe must be created duplex with overlapped
IO and inheritable handles. The program communicates with mpv through
the client handle.
already connected. Alternatively, the Windows HANDLE can be passed by
prefixing it with handle:// if mpv inherited it from the parent. The
named pipe must be created duplex with overlapped IO and inheritable
handles. The program communicates with mpv through the client handle.
.. warning::
@@ -5751,14 +5819,12 @@ TLS/connection diagnostics), set ``--msg-level=curl=trace``.
``--curl-enabled=<yes|no>``
Enable the libcurl-based network backend (default: ``yes``).
Defaults to ``no`` with libavformat < 62.15.101, which has a nested IO
Defaults to ``no`` on known older FFmpeg versions, which have a nested IO
cleanup bug that can cause crashes or memory leaks. The issue happens only
on transfer failures or aborts, can be enabled if you don't mind possible
stability issues.
on transfer failures or aborts.
``--curl-http-version=<auto|1.0|1.1|2|2tls|2-prior-knowledge|3|3only>``
Select the maximum HTTP protocol version libcurl is allowed to negotiate.
If libcurl was built without HTTP/3 support, it will fallback to ``auto``.
(default: ``auto``, i.e. let libcurl pick)
``--curl-max-redirects=<0-100>``
@@ -5950,6 +6016,10 @@ them.
being the smoothest/blurriest and ``oversample`` being the sharpest/least
smooth.
Switching to a filter with a larger radius at runtime may require setting
``--hwdec-extra-frames``, as the hardware decoder's surface pool is sized
at decoder initialization.
``--scale-param1=<value>``, ``--scale-param2=<value>``, ``--cscale-param1=<value>``, ``--cscale-param2=<value>``, ``--dscale-param1=<value>``, ``--dscale-param2=<value>``, ``--tscale-param1=<value>``, ``--tscale-param2=<value>``
Set filter parameters. By default, these are set to the special string
``default``, which maps to a scaler-specific default value. Ignored if the
@@ -6102,6 +6172,9 @@ them.
the video along the temporal axis. The filter used can be controlled using
the ``--tscale`` setting.
Enabling this at runtime may require setting ``--hwdec-extra-frames``, as
the hardware decoder's surface pool is sized at decoder initialization.
``--interpolation-threshold=<0..1,-1>``
Threshold below which frame ratio interpolation gets disabled (default:
``0.01``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
@@ -7534,11 +7607,16 @@ them.
encoding into the target colorspace, so after the application of
``--target-trc``. (Only for ``--vo=gpu-next``)
``--hdr-reference-white=<auto|10-1000000>``
``--hdr-reference-white=<auto|10-10000>``
Specifies the assumed peak brightness of the mastering display for SDR
content, in cd/m² (nits). This is used as HDR diffuse white level for SDR
content. Essentially this is the SDR brightness in HDR container.
Default is 203 cd/m². (Only for ``--vo=gpu-next``)
(Only for ``--vo=gpu-next``)
In ``auto`` mode (default), the reference white luminance is queried from
the system. This is currently only supported on Windows. If the system does
not provide a value, 203 cd/m² is assumed. (``auto`` mode works only
with libplacebo >= 371)
.. note::
-6
View File
@@ -499,12 +499,6 @@ Configurable Options
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
``livemarkers``
Default: yes
Update chapter markers positions on duration changes, e.g. live streams.
The updates are unoptimized - consider disabling it on very low-end systems.
``chapter_fmt``
Default: ``Chapter: %s``
+7
View File
@@ -68,10 +68,17 @@ By default select.lua's script bindings are bound to key sequences starting with
``g`` listed in `Keyboard Control`_. The names of the script bindings listed
below can be used to bind them to different keys.
All script bindings accept an optional ``keep-open`` argument that
prevents the menu from closing after a selection.
.. admonition:: Example to rebind playlist selection in input.conf
Ctrl+p script-binding select/select-playlist
.. admonition:: Example to keep the track menu open in input.conf
g-t script-binding select/select-track keep-open
Available script bindings are:
``select-playlist``
+8 -1
View File
@@ -323,6 +323,12 @@ Available mpv-only filters are:
Whether or not to include HDR10+ metadata (default: yes). If
disabled, any HDR10+ metadata will be stripped from frames.
``<enhancement-layer=yes|no>``
Whether or not to apply the image enhancement layer (default: yes).
If disabled, the enhancement-layer frame paired with each base-layer
frame is discarded. Currently this controls Dolby Vision Profile 7 FEL
application.
``<min-luma>``
Set the minimum luminance value for the mastering display metadata.
This is a float value in nits (cd/m²).
@@ -760,7 +766,8 @@ Available mpv-only filters are:
of the d3d11 frame.
``nvidia-true-hdr``
Enable NVIDIA RTX Video HDR processing.
Enable NVIDIA RTX Video HDR processing. This only converts SDR to HDR,
so it is ignored when the source is already HDR.
``amf_frc``
AMD Frame Rate Conversion filter. Requires AMD hardware and drivers
+3 -3
View File
@@ -293,7 +293,7 @@ Available video output drivers are:
Instruct SDL to switch the monitor video mode when going fullscreen.
``dmabuf-wayland``
Experimental Wayland output driver designed for use with either drm stateless
Special Wayland output driver designed for use with either drm stateless
or VA API hardware decoding. The driver is designed to avoid any GPU to CPU copies,
and to perform scaling and color space conversion using fixed-function hardware,
if available, rather than GPU shaders. This frees up GPU resources for other tasks.
@@ -309,8 +309,8 @@ Available video output drivers are:
This driver is not required for mpv to work on Wayland. ``vo=gpu``
and ``vo=gpu-next`` will switch to the appropriate Wayland context
automatically. This driver is experimental and generally lower quality
than ``gpu``/``gpu-next``.
automatically. This video output is geared towards power efficiency
and has generally lower quality than ``gpu``/``gpu-next``.
``vaapi``
Intel VA API video output driver with support for hardware decoding. Note
+10 -8
View File
@@ -218,30 +218,32 @@ def process_vulkan_loader(binary, loader_name, loader_relative_folder, library_n
os.path.join("/etc", loader_relative_folder),
os.path.join(os.path.expanduser("~"), ".local/share", loader_relative_folder),
os.path.join("/usr/local/share", loader_relative_folder),
os.path.join("/usr/share/vulkan", loader_relative_folder),
os.path.join("/usr/share", loader_relative_folder),
os.path.join(get_homebrew_prefix(), "etc", loader_relative_folder),
os.path.join(get_homebrew_prefix(), "share", loader_relative_folder), # old location
]
loader_system_folder = ""
loader_system_path = ""
for loader_system_search_folder in loader_system_search_folders:
if os.path.exists(loader_system_search_folder):
loader_system_folder = loader_system_search_folder
break
if os.path.exists(os.path.join(loader_system_folder, loader_name)):
loader_system_path = os.path.join(loader_system_folder, loader_name)
break
if not loader_system_folder:
print(">>> could not find loader folder " + loader_relative_folder)
return
loader_bundle_folder = os.path.join(resources_path(binary), loader_relative_folder)
loader_system_path = os.path.join(loader_system_folder, loader_name)
loader_bundle_path = os.path.join(loader_bundle_folder, loader_name)
library_relative_folder = "../../../Frameworks/"
if not os.path.exists(loader_system_path):
if not loader_system_path:
print(">>> could not find loader " + loader_name)
return
loader_bundle_folder = os.path.join(resources_path(binary), loader_relative_folder)
loader_bundle_path = os.path.join(loader_bundle_folder, loader_name)
library_relative_folder = "../../../Frameworks/"
if not os.path.exists(loader_bundle_folder):
os.makedirs(loader_bundle_folder)
+62 -37
View File
@@ -28,13 +28,13 @@ from math import ldexp
elements_ebml = (
"EBML, 1a45dfa3, sub", (
"EBMLVersion, 4286, uint",
"EBMLReadVersion, 42f7, uint",
"EBMLMaxIDLength, 42f2, uint",
"EBMLMaxSizeLength, 42f3, uint",
"EBMLVersion, 4286, uint, default=1",
"EBMLReadVersion, 42f7, uint, default=1",
"EBMLMaxIDLength, 42f2, uint, default=4",
"EBMLMaxSizeLength, 42f3, uint, default=8",
"DocType, 4282, str",
"DocTypeVersion, 4287, uint",
"DocTypeReadVersion, 4285, uint",
"DocTypeVersion, 4287, uint, default=1",
"DocTypeReadVersion, 4285, uint, default=1",
),
"CRC32, bf, binary",
@@ -52,10 +52,10 @@ elements_matroska = (
),
"Info*, 1549a966, sub", (
"SegmentUID, 73a4, binary",
"PrevUID, 3cb923, binary",
"NextUID, 3eb923, binary",
"TimecodeScale, 2ad7b1, uint",
"SegmentUID, 73a4, binary, length=16",
"PrevUID, 3cb923, binary, length=16",
"NextUID, 3eb923, binary, length=16",
"TimecodeScale, 2ad7b1, uint, default=1000000",
"DateUTC, 4461, sint",
"Title, 7ba9, str",
"MuxingApp, 4d80, str",
@@ -72,7 +72,7 @@ elements_matroska = (
"DiscardPadding, 75A2, sint",
"BlockAdditions, 75A1, sub", (
"BlockMore*, A6, sub", (
"BlockAddID, EE, uint",
"BlockAddID, EE, uint, default=1",
"BlockAdditional, A5, binary",
),
),
@@ -85,44 +85,44 @@ elements_matroska = (
"TrackNumber, d7, uint",
"TrackUID, 73c5, uint",
"TrackType, 83, uint",
"FlagEnabled, b9, uint",
"FlagDefault, 88, uint",
"FlagEnabled, b9, uint, default=1",
"FlagDefault, 88, uint, default=1",
"FlagForced, 55aa, uint",
"FlagHearingImpaired, 55ab, uint",
"FlagVisualImpaired, 55ac, uint",
"FlagOriginal, 55ae, uint",
"FlagCommentary, 55af, uint",
"FlagLacing, 9c, uint",
"FlagLacing, 9c, uint, default=1",
"MinCache, 6de7, uint",
"MaxCache, 6df8, uint",
"DefaultDuration, 23e383, uint",
"TrackTimecodeScale, 23314f, float",
"TrackTimecodeScale, 23314f, float, default=1",
"MaxBlockAdditionID, 55ee, uint",
"Name, 536e, str",
"Language, 22b59c, str",
"Language, 22b59c, str, default=eng",
"LanguageBCP47, 22b59d, str",
"CodecID, 86, str",
"CodecPrivate, 63a2, binary",
"CodecName, 258688, str",
"CodecDecodeAll, aa, uint",
"CodecDecodeAll, aa, uint, default=1",
"CodecDelay, 56aa, uint",
"SeekPreRoll, 56bb, uint",
"Video, e0, sub", (
"FlagInterlaced, 9a, uint",
"PixelWidth, b0, uint",
"PixelHeight, ba, uint",
"DisplayWidth, 54b0, uint",
"DisplayHeight, 54ba, uint",
"DisplayWidth, 54b0, uint, default=context",
"DisplayHeight, 54ba, uint, default=context",
"DisplayUnit, 54b2, uint",
"PixelCropTop, 54bb, uint",
"PixelCropLeft, 54cc, uint",
"PixelCropRight, 54dd, uint",
"PixelCropBottom, 54aa, uint",
"FrameRate, 2383e3, float",
"ColourSpace, 2eb524, binary",
"ColourSpace, 2eb524, binary, length=4",
"StereoMode, 53b8, uint",
"Colour, 55b0, sub", (
"MatrixCoefficients, 55B1, uint",
"MatrixCoefficients, 55B1, uint, default=2",
"BitsPerChannel, 55B2, uint",
"ChromaSubsamplingHorz, 55B3, uint",
"ChromaSubsamplingVert, 55B4, uint",
@@ -131,8 +131,8 @@ elements_matroska = (
"ChromaSitingHorz, 55B7, uint",
"ChromaSitingVert, 55B8, uint",
"Range, 55B9, uint",
"TransferCharacteristics, 55BA, uint",
"Primaries, 55BB, uint",
"TransferCharacteristics, 55BA, uint, default=2",
"Primaries, 55BB, uint, default=2",
"MaxCLL, 55BC, uint",
"MaxFALL, 55BD, uint",
"MasteringMetadata, 55D0, sub", (
@@ -157,15 +157,15 @@ elements_matroska = (
),
),
"Audio, e1, sub", (
"SamplingFrequency, b5, float",
"OutputSamplingFrequency, 78b5, float",
"Channels, 9f, uint",
"SamplingFrequency, b5, float, default=8000",
"OutputSamplingFrequency, 78b5, float, default=context",
"Channels, 9f, uint, default=1",
"BitDepth, 6264, uint",
),
"ContentEncodings, 6d80, sub", (
"ContentEncoding*, 6240, sub", (
"ContentEncodingOrder, 5031, uint",
"ContentEncodingScope, 5032, uint",
"ContentEncodingScope, 5032, uint, default=1",
"ContentEncodingType, 5033, uint",
"ContentCompression, 5034, sub", (
"ContentCompAlgo, 4254, uint",
@@ -215,12 +215,12 @@ elements_matroska = (
"ChapterTimeStart, 91, uint",
"ChapterTimeEnd, 92, uint",
"ChapterFlagHidden, 98, uint",
"ChapterFlagEnabled, 4598, uint",
"ChapterSegmentUID, 6e67, binary",
"ChapterFlagEnabled, 4598, uint, default=1",
"ChapterSegmentUID, 6e67, binary, length=16",
"ChapterSegmentEditionUID, 6ebc, uint",
"ChapterDisplay*, 80, sub", (
"ChapString, 85, str",
"ChapLanguage*, 437c, str",
"ChapLanguage*, 437c, str, default=eng",
"ChapLanguageBCP47*, 437d, str",
"ChapCountry*, 437e, str",
),
@@ -230,7 +230,7 @@ elements_matroska = (
"Tags*, 1254c367, sub", (
"Tag*, 7373, sub", (
"Targets, 63c0, sub", (
"TargetTypeValue, 68ca, uint",
"TargetTypeValue, 68ca, uint, default=50",
"TargetType, 63ca, str",
"TargetTrackUID, 63c5, uint",
"TargetEditionUID, 63c9, uint",
@@ -239,10 +239,10 @@ elements_matroska = (
),
"SimpleTag*, 67c8, sub", (
"TagName, 45a3, str",
"TagLanguage, 447a, str",
"TagLanguage, 447a, str, default=und",
"TagLanguageBCP47, 447b, str",
"TagString, 4487, str",
"TagDefault, 4484, uint",
"TagDefault, 4484, uint, default=1",
),
),
),
@@ -266,13 +266,16 @@ def camelcase_to_words(name):
class MatroskaElement:
def __init__(self, name, elid, valtype, namespace):
def __init__(self, name, elid, valtype, namespace, length=None,
default=None):
self.name = name
self.definename = f"{namespace}_ID_{name.upper()}"
self.fieldname = camelcase_to_words(name)
self.structname = "ebml_" + self.fieldname
self.elid = elid
self.valtype = valtype
self.length = int(length or "0")
self.default = default
if valtype == "sub":
self.ebmltype = "EBML_TYPE_SUBELEMENTS"
self.valname = "struct " + self.structname
@@ -291,17 +294,38 @@ class MatroskaElement:
self.subelements = subelements
self.subids = {x[0].elid for x in subelements}
def default_initializer(self):
if self.default is None:
return "false, {0}"
if self.default == "context":
return "true, {0}"
if self.valtype == "uint":
return f"false, {{.u = {int(self.default)}}}"
if self.valtype == "sint":
return f"false, {{.i = {int(self.default)}}}"
if self.valtype == "float":
return f"false, {{.f = {float(self.default)}}}"
if self.valtype == "str":
return f'false, {{.s = "{self.default}"}}'
raise SyntaxError("Default not supported for type " + self.valtype)
elementd: dict[str, MatroskaElement] = {}
elementlist: list[MatroskaElement] = []
def parse_elems(elements, namespace):
subelements = []
for el in elements:
if isinstance(el, str):
name, hexid, eltype = (x.strip() for x in el.split(","))
name, hexid, eltype, *rest = (x.strip() for x in el.split(","))
attrs = {}
for extra in rest:
key, sep, value = extra.partition("=")
if not sep or key not in ("length", "default"):
raise SyntaxError("Invalid element attribute " + extra)
attrs[key] = value
hexid = hexid.lower()
multiple = name.endswith("*")
name = name.strip("*")
new = MatroskaElement(name, hexid, eltype, namespace)
new = MatroskaElement(name, hexid, eltype, namespace, **attrs)
elementd[hexid] = new
elementlist.append(new)
subelements.append((new, multiple))
@@ -363,7 +387,8 @@ def generate_c_definitions(out):
printf(out, "}};")
printf(out, "#undef N")
else:
printf(out, f'E("{el.name}", {el.fieldname}, {el.ebmltype})')
printf(out, f'E("{el.name}", {el.fieldname}, {el.ebmltype}, '
f'{el.length}, {el.default_initializer()})')
def read(s, length):
t = s.read(length)
+98 -18
View File
@@ -22,6 +22,7 @@
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/intreadwrite.h>
#include <libavutil/opt.h>
#include "audio/aframe.h"
@@ -37,6 +38,7 @@
#include "options/options.h"
#define OUTBUF_SIZE 65536
#define TRUEHD_MAJOR_SYNC_WARN_INTERVAL 6000
struct spdifContext {
struct mp_log *log;
@@ -48,6 +50,10 @@ struct spdifContext {
uint8_t out_buffer[OUTBUF_SIZE];
bool need_close;
bool use_dts_hd;
bool codec_params_probed;
int codec_profile;
int codec_rate;
unsigned int dropped_startup_packets;
struct mp_aframe *fmt;
int sstride;
struct mp_aframe_pool *pool;
@@ -74,14 +80,12 @@ static int write_packet(void *p, const uint8_t *buf, int buf_size)
return buf_size;
}
// (called on both filter destruction _and_ if lavf fails to init)
static void ad_spdif_destroy(struct mp_filter *da)
static void close_lavf_context(struct spdifContext *spdif_ctx, bool write_trailer)
{
struct spdifContext *spdif_ctx = da->priv;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
if (lavf_ctx) {
if (spdif_ctx->need_close)
if (write_trailer && spdif_ctx->need_close)
av_write_trailer(lavf_ctx);
if (lavf_ctx->pb)
av_freep(&lavf_ctx->pb->buffer);
@@ -89,9 +93,32 @@ static void ad_spdif_destroy(struct mp_filter *da)
avformat_free_context(lavf_ctx);
spdif_ctx->lavf_ctx = NULL;
}
spdif_ctx->need_close = false;
TA_FREEP(&spdif_ctx->fmt);
}
// (called on both filter destruction _and_ if lavf fails to init)
static void ad_spdif_destroy(struct mp_filter *da)
{
struct spdifContext *spdif_ctx = da->priv;
close_lavf_context(spdif_ctx, true);
mp_free_av_packet(&spdif_ctx->avpkt);
}
static void ad_spdif_reset(struct mp_filter *da)
{
struct spdifContext *spdif_ctx = da->priv;
close_lavf_context(spdif_ctx, false);
spdif_ctx->dropped_startup_packets = 0;
}
static bool truehd_has_major_sync(const AVPacket *pkt)
{
return pkt->size >= 8 && AV_RB32(pkt->data + 4) == 0xf8726fba;
}
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
int *out_profile, int *out_rate)
{
@@ -160,9 +187,32 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
done:
av_frame_free(&frame);
avcodec_free_context(&ctx);
}
if (profile == AV_PROFILE_UNKNOWN)
MP_WARN(da, "Failed to parse codec profile.\n");
static bool dts_profile_maybe_hd(int profile)
{
return profile == AV_PROFILE_DTS_HD_HRA ||
profile == AV_PROFILE_DTS_HD_MA ||
profile == AV_PROFILE_DTS_HD_MA_X ||
profile == AV_PROFILE_DTS_HD_MA_X_IMAX ||
profile == AV_PROFILE_UNKNOWN;
}
// Some codecs do not need every probed value for spdif muxer setup.
static bool codec_params_warning_needed(struct spdifContext *spdif_ctx,
int profile, int rate)
{
switch (spdif_ctx->codec_id) {
case AV_CODEC_ID_DTS:
if (spdif_ctx->use_dts_hd && dts_profile_maybe_hd(profile))
return profile == AV_PROFILE_UNKNOWN;
return rate <= 0;
case AV_CODEC_ID_AC3:
return rate <= 0;
default:
return false;
}
}
static int init_filter(struct mp_filter *da)
@@ -171,10 +221,20 @@ static int init_filter(struct mp_filter *da)
AVPacket *pkt = spdif_ctx->avpkt;
int profile = AV_PROFILE_UNKNOWN;
int c_rate = 0;
determine_codec_params(da, pkt, &profile, &c_rate);
MP_VERBOSE(da, "In: profile=%d samplerate=%d\n", profile, c_rate);
if (!spdif_ctx->codec_params_probed) {
spdif_ctx->codec_profile = AV_PROFILE_UNKNOWN;
determine_codec_params(da, pkt, &spdif_ctx->codec_profile,
&spdif_ctx->codec_rate);
spdif_ctx->codec_params_probed = true;
MP_VERBOSE(da, "In: profile=%d samplerate=%d\n",
spdif_ctx->codec_profile, spdif_ctx->codec_rate);
if (codec_params_warning_needed(spdif_ctx, spdif_ctx->codec_profile,
spdif_ctx->codec_rate))
MP_WARN(da, "Failed to parse codec parameters.\n");
}
int profile = spdif_ctx->codec_profile;
int c_rate = spdif_ctx->codec_rate;
AVFormatContext *lavf_ctx = avformat_alloc_context();
if (!lavf_ctx)
@@ -225,19 +285,13 @@ static int init_filter(struct mp_filter *da)
num_channels = 2;
break;
case AV_CODEC_ID_DTS: {
bool is_hd = profile == AV_PROFILE_DTS_HD_HRA ||
profile == AV_PROFILE_DTS_HD_MA ||
profile == AV_PROFILE_DTS_HD_MA_X ||
profile == AV_PROFILE_DTS_HD_MA_X_IMAX ||
profile == AV_PROFILE_UNKNOWN;
// Apparently, DTS-HD over SPDIF is specified to be 7.1 (8 channels)
// for DTS-HD MA, and stereo (2 channels) for DTS-HD HRA. The bit
// streaming rate as well as the signaled channel count are defined
// based on this value.
int dts_hd_spdif_channel_count = profile == AV_PROFILE_DTS_HD_HRA ?
2 : 8;
if (spdif_ctx->use_dts_hd && is_hd) {
if (spdif_ctx->use_dts_hd && dts_profile_maybe_hd(profile)) {
av_dict_set_int(&format_opts, "dtshd_rate",
dts_hd_spdif_channel_count * 96000, 0);
sample_format = AF_FORMAT_S_DTSHD;
@@ -318,6 +372,7 @@ static void ad_spdif_process(struct mp_filter *da)
struct demux_packet *mpkt = inframe.data;
struct mp_aframe *out = NULL;
double pts = mpkt->pts;
bool drop_packet = false;
if (!spdif_ctx->avpkt) {
spdif_ctx->avpkt = av_packet_alloc();
@@ -326,6 +381,22 @@ static void ad_spdif_process(struct mp_filter *da)
mp_set_av_packet(spdif_ctx->avpkt, mpkt, NULL);
spdif_ctx->avpkt->pts = spdif_ctx->avpkt->dts = 0;
if (!spdif_ctx->lavf_ctx) {
// A fresh TrueHD spdif muxer needs a major sync to initialize its
// stream parameters, so wait for major sync before initializing.
if (spdif_ctx->codec_id == AV_CODEC_ID_TRUEHD &&
!truehd_has_major_sync(spdif_ctx->avpkt))
{
spdif_ctx->dropped_startup_packets++;
if (spdif_ctx->dropped_startup_packets %
TRUEHD_MAJOR_SYNC_WARN_INTERVAL == 0)
{
MP_WARN(da, "Still waiting for TrueHD major sync after "
"dropping %u packets.\n",
spdif_ctx->dropped_startup_packets);
}
drop_packet = true;
goto done;
}
if (init_filter(da) < 0)
goto done;
mp_assert(spdif_ctx->avpkt);
@@ -339,6 +410,12 @@ static void ad_spdif_process(struct mp_filter *da)
MP_ERR(da, "spdif mux error: '%s'\n", mp_strerror(AVUNERROR(ret)));
goto done;
}
if (spdif_ctx->dropped_startup_packets) {
MP_VERBOSE(da, "dropped %u TrueHD packet%s before major sync\n",
spdif_ctx->dropped_startup_packets,
spdif_ctx->dropped_startup_packets == 1 ? "" : "s");
spdif_ctx->dropped_startup_packets = 0;
}
out = mp_aframe_new_ref(spdif_ctx->fmt);
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
@@ -360,6 +437,8 @@ done:
talloc_free(mpkt);
if (out) {
mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
} else if (drop_packet) {
mp_filter_internal_mark_progress(da);
} else {
mp_filter_internal_mark_failed(da);
}
@@ -423,6 +502,7 @@ static const struct mp_filter_info ad_spdif_filter = {
.name = "ad_spdif",
.priv_size = sizeof(struct spdifContext),
.process = ad_spdif_process,
.reset = ad_spdif_reset,
.destroy = ad_spdif_destroy,
};
+232
View File
@@ -0,0 +1,232 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include "common/common.h"
#include "common/msg.h"
#include "audio/aframe.h"
#include "filters/f_lavfi.h"
#include "filters/filter.h"
#include "filters/filter_internal.h"
#include "filters/frame.h"
#include "filters/user_filters.h"
#include "options/m_option.h"
#include "ta/ta_talloc.h"
struct f_opts {
char *filter;
};
struct priv {
struct f_opts *opts;
bool initialized;
bool fall_backed;
bool lavfi_ready;
double speed;
double set_speed;
double in_pts;
struct mp_filter *lavfi_filter;
const struct mp_filter_info *lavfi_filter_info;
};
static bool set_speed(struct mp_filter *f) {
struct priv *p = f->priv;
char *arg = talloc_asprintf(NULL, "%f", p->speed);
struct mp_filter_command cmd = {
.type = MP_FILTER_COMMAND_TEXT,
.target = p->opts->filter,
.cmd = "tempo",
.arg = arg,
};
bool ret = p->lavfi_filter_info->command(p->lavfi_filter, &cmd);
talloc_free(arg);
if (!ret) {
MP_FATAL(f, "failed to set %s=%s for lavfi filter %s\n", cmd.cmd, cmd.arg, p->opts->filter);
return false;
} else {
p->set_speed = p->speed;
return ret;
}
}
static bool init_lavfi_tempo(struct mp_filter *f)
{
struct priv *p = f->priv;
if (strcmp(p->opts->filter, "ascale") && strcmp(p->opts->filter, "atempo")) {
MP_FATAL(f, "'%s' is not recognized in this context, use 'ascale' or 'atempo'.\n",
p->opts->filter);
return false;
}
mp_assert(!p->lavfi_filter);
if (!mp_lavfi_is_usable(p->opts->filter, AVMEDIA_TYPE_AUDIO)) {
MP_WARN(f, "%s filter is not available, using atempo instead.\n", p->opts->filter);
p->opts->filter = "atempo";
p->fall_backed = true;
}
p->lavfi_filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, p->opts->filter, NULL);
if (!p->lavfi_filter) {
MP_FATAL(f, "failed to create lavfi %s filter.\n", p->opts->filter);
return false;
}
p->lavfi_filter_info = mp_filter_get_info(p->lavfi_filter);
p->lavfi_ready = false;
p->initialized = true;
return true;
}
static void af_lavfi_tempo_reset(struct mp_filter *f)
{
struct priv *p = f->priv;
p->in_pts = MP_NOPTS_VALUE;
p->lavfi_ready = false;
p->set_speed = 1.0;
p->lavfi_filter_info->reset(p->lavfi_filter);
}
static void af_lavfi_tempo_process(struct mp_filter *f)
{
struct priv *p = f->priv;
if (!p->initialized && !init_lavfi_tempo(f))
return;
if (p->lavfi_ready && p->set_speed != p->speed) {
set_speed(f);
}
if (mp_pin_can_transfer_data(p->lavfi_filter->pins[0], f->ppins[0])) {
struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
p->in_pts = mp_aframe_get_pts(aframe);
}
if (!mp_pin_in_write(p->lavfi_filter->pins[0], frame)) {
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
} else {
p->lavfi_ready = true;
}
}
if (mp_pin_can_transfer_data(f->ppins[1], p->lavfi_filter->pins[1])) {
struct mp_frame frame = mp_pin_out_read(p->lavfi_filter->pins[1]);
if (frame.type == MP_FRAME_AUDIO) {
struct mp_aframe *aframe = frame.data;
mp_aframe_set_speed(aframe, p->set_speed);
if (p->in_pts != MP_NOPTS_VALUE) {
mp_aframe_set_pts(aframe, p->in_pts);
}
}
if (!mp_pin_in_write(f->ppins[1], frame)) {
MP_FATAL(f, "failed to move frame to internal lavfi filter\n");
} else {
p->lavfi_ready = true;
}
}
return;
}
static bool af_lavfi_tempo_command(struct mp_filter *f, struct mp_filter_command *cmd)
{
struct priv *p = f->priv;
switch (cmd->type) {
case MP_FILTER_COMMAND_SET_SPEED:
if (cmd->speed == p->speed) {
return true;
}
p->speed = cmd->speed;
return p->lavfi_ready ? set_speed(f) : true;
}
return false;
}
static void af_lavfi_tempo_destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
if (p->fall_backed) {
// prevent ta_alloc abort since filter name is const
p->opts->filter = NULL;
}
if (p->lavfi_filter_info) {
p->lavfi_filter_info->destroy(p->lavfi_filter);
}
}
static const struct mp_filter_info af_lavfi_tempo_filter = {
.name = "lavfi_tempo",
.priv_size = sizeof(struct priv),
.process = af_lavfi_tempo_process,
.command = af_lavfi_tempo_command,
.reset = af_lavfi_tempo_reset,
.destroy = af_lavfi_tempo_destroy,
};
static struct mp_filter *af_lavfi_tempo_create(struct mp_filter *parent,
void *options)
{
struct mp_filter *f = mp_filter_create(parent, &af_lavfi_tempo_filter);
if (!f) {
talloc_free(options);
return NULL;
}
mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");
struct priv *p = f->priv;
p->opts = talloc_steal(p, options);
p->speed = 1.0;
p->set_speed = 1.0;
p->in_pts = MP_NOPTS_VALUE;
if (!init_lavfi_tempo(f)) {
return NULL;
}
return f;
}
#define OPT_BASE_STRUCT struct f_opts
const struct mp_user_filter_entry af_lavfi_tempo = {
.desc = {
.description = "Tempo change with lavfi ascale or atempo filters",
.name = "lavfi-tempo",
.priv_size = sizeof(OPT_BASE_STRUCT),
.priv_defaults = &(const OPT_BASE_STRUCT) {
.filter = "atempo",
},
.options = (const struct m_option[]) {
{"filter", OPT_STRING(filter)},
{0}
},
},
.create = af_lavfi_tempo_create,
};
+4 -3
View File
@@ -389,9 +389,10 @@ static void af_scaletempo_process(struct mp_filter *f)
out_offset += s->bytes_stride;
// input stride
memcpy(s->buf_overlap,
s->buf_queue + bytes_off + s->bytes_stride,
s->bytes_overlap);
if (s->buf_overlap && s->bytes_overlap)
memcpy(s->buf_overlap,
s->buf_queue + bytes_off + s->bytes_stride,
s->bytes_overlap);
tf = s->frames_stride_scaled + s->frames_stride_error;
ti = (int)tf;
s->frames_stride_error = tf - ti;
+2 -2
View File
@@ -195,7 +195,7 @@ static void on_process(void *userdata)
int64_t end_time = mp_time_ns();
end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate;
end_time += MP_TIME_S_TO_NS(time.delay) * time.rate.num / time.rate.denom;
end_time += MP_TIME_S_TO_NS(time.queued) / ao->samplerate;
end_time += MP_TIME_S_TO_NS(time.queued / ao->sstride) / ao->samplerate;
end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate;
end_time -= pw_stream_get_nsec(p->stream) - time.now;
@@ -668,7 +668,7 @@ static int init(struct ao *ao)
pw_thread_loop_unlock(p->loop);
if (p->init_state == INIT_STATE_ERROR)
if (p->init_state != INIT_STATE_SUCCESS)
goto error;
return 0;
+11 -7
View File
@@ -5,7 +5,7 @@ mkdir -p "$prefix_dir"
ln -snf . "$prefix_dir/usr"
ln -snf . "$prefix_dir/local"
wget="wget -nc --progress=bar:force"
wget="wget --progress=bar:force"
gitclone="git clone --depth=1 --recursive --shallow-submodules"
if [[ -z "$TARGET" || -z "$RUST_TARGET" ]]; then
@@ -109,13 +109,15 @@ function gettar {
[ -z "$dname" ] && dname="${fname%.tar.*}"
[ -d "$dname" ] && return 0
local cachename="$(md5sum <<<"$1" | cut -d " " -f 1)"
if [[ -n "$DOWNLOAD_CACHE" && -s "$DOWNLOAD_CACHE/$cachename" ]]; then
if [ -s "$fname" ]; then
: # exists
elif [[ -n "$DOWNLOAD_CACHE" && -s "$DOWNLOAD_CACHE/$cachename" ]]; then
cp -v "$DOWNLOAD_CACHE/$cachename" "$fname"
cachename=
else
$wget "$1" -O "$fname"
$wget "$1" -O "$fname" || return 1
fi
tar -xaf "$fname"
tar -xaf "$fname" || return 1
if [ ! -d "$dname" ]; then
echo "Error: expected $fname to extract to $dname but it was not created" >&2
return 2
@@ -146,7 +148,8 @@ function build_if_missing {
_iconv () {
local ver=1.19
gettar "https://ftpmirror.gnu.org/gnu/libiconv/libiconv-${ver}.tar.gz"
gettar "https://ftpmirror.gnu.org/gnu/libiconv/libiconv-${ver}.tar.gz" || \
gettar "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${ver}.tar.gz"
builddir libiconv-${ver}
../configure --host=$TARGET $at_flags
makeplusinstall
@@ -206,7 +209,7 @@ _ffmpeg () {
--disable-{doc,programs}
--enable-muxer=spdif --enable-encoder=mjpeg,png --enable-libdav1d
)
pkg-config vulkan && args+=(--enable-vulkan --enable-libshaderc)
pkg-config vulkan && args+=(--enable-vulkan)
../configure "${args[@]}"
makeplusinstall
popd
@@ -274,7 +277,8 @@ _libplacebo_mark=lib/libplacebo.dll.a
_freetype () {
local ver=2.14.3
gettar "https://download.savannah.gnu.org/releases/freetype/freetype-${ver}.tar.xz"
gettar "https://download.savannah.gnu.org/releases/freetype/freetype-${ver}.tar.xz" || \
gettar "https://sourceforge.net/projects/freetype/files/freetype-${ver}.tar.xz"
builddir freetype-${ver}
meson setup .. --cross-file "$prefix_dir/crossfile"
makeplusinstall
+7 -1
View File
@@ -197,11 +197,17 @@ subrandr_build = custom_target(
],
console: true
)
python = find_program('python3')
subrandr_lib = custom_target(
'subrandr-copy',
input: subrandr_build,
output: 'subrandr.lib',
command: ['cp', meson.current_build_dir() / 'lib' / 'subrandr.lib', '@OUTPUT@']
command: [
python,
'-c',
'import shutil; import sys; shutil.copy2(sys.argv[1], sys.argv[2])',
meson.current_build_dir() / 'lib' / 'subrandr.lib', '@OUTPUT@'
]
)
harfbuzz = dependency('harfbuzz', default_options: ['freetype=enabled'])
dep = declare_dependency(
+22
View File
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#include "config.h"
@@ -65,6 +66,27 @@
// Return a+b, unless a is NOPTS. b must not be NOPTS.
#define MP_ADD_PTS(a, b) ((a) == MP_NOPTS_VALUE ? (a) : ((a) + (b)))
// Return the maximum value representable by integer type of a.
#define MP_MAX_VAL(a) _Generic((a), \
char: SCHAR_MAX, \
unsigned char: UCHAR_MAX, \
short: SHRT_MAX, \
unsigned short: USHRT_MAX, \
int: INT_MAX, \
unsigned int: UINT_MAX, \
long: LONG_MAX, \
unsigned long: ULONG_MAX, \
long long: LLONG_MAX, \
unsigned long long: ULLONG_MAX)
// Multiply a * b and store in result. If result would overflow, saturate at
// its maximum value instead, and return true. Otherwise, return false.
#define MP_SATURATE_MUL(result, a, b) ( \
MP_CKD_MUL(result, a, b) ? \
*result = MP_MAX_VAL(*result), true : \
false \
)
#define CONTROL_OK 1
#define CONTROL_TRUE 1
#define CONTROL_FALSE 0
+1 -1
View File
@@ -12,7 +12,7 @@ if not sys.argv[1]:
sys.exit(1)
git_cmd = [sys.argv[1], "--git-dir=" + os.path.join(sys.argv[2], ".git"),
"--work-tree=" + sys.argv[2]]
describe_cmd = git_cmd + ["describe", "--abbrev=9", "--tags", "--dirty"]
describe_cmd = git_cmd + ["describe", "--abbrev=9", "--tags", "--dirty", "--match", "v0.*"]
try:
ver = check_output(describe_cmd, stderr=DEVNULL, encoding="UTF-8")
except CalledProcessError:
+35 -13
View File
@@ -27,6 +27,7 @@
#include "common/common.h"
#include "common/global.h"
#include "misc/bstr.h"
#include "misc/codepoint_width.h"
#include "options/options.h"
#include "options/path.h"
@@ -585,23 +586,44 @@ void mp_msg_sanitize(bstr *text, bool allow_sgr)
// Nuke everything that is not an SGR sequence
if (!sgr)
text->start[i] = '?';
continue;
}
// Allow only printable > 0x20 and 0x08-0x0D (backspace, tab, newline, ...)
else if (ch < 0x08 || (ch > 0x0D && ch < 0x20) || ch == 0x7F) {
text->start[i] = '?';
int len = bstr_parse_utf8_code_length(ch);
// ASCII: allow only printable plus HT, LF, CR.
if (len == 1) {
if (ch == 0x7F || (ch < 0x20 && ch != '\t' && ch != '\n' && ch != '\r'))
text->start[i] = '?';
continue;
}
// Block UTF-8 encoded C1 controls (U+0080-U+009F = bytes C2 80..C2 9F),
// except PU1 (0x91) and PU2 (0x92) used as internal escape prefixes.
else if (ch == 0xC2 && i + 1 < text->len &&
(unsigned char)text->start[i + 1] >= 0x80 &&
(unsigned char)text->start[i + 1] <= 0x9F &&
(unsigned char)text->start[i + 1] != 0x91 &&
(unsigned char)text->start[i + 1] != 0x92)
{
text->start[i] = '?';
text->start[i + 1] = '?';
// Block UTF-8 C1 controls (C2 80..C2 9F), except PU1/PU2 used internally.
if (ch == 0xC2 && i + 1 < text->len) {
unsigned char c1 = text->start[i + 1];
if (c1 >= 0x80 && c1 <= 0x9F && c1 != 0x91 && c1 != 0x92) {
text->start[i] = '?';
text->start[i + 1] = '?';
}
i++;
continue;
}
// Skip valid multi-byte sequences so continuation bytes aren't seen as
// raw C1. Reject overlong 0xC0/0xC1 and verify the bytes before skipping.
if (len >= 2 && ch >= 0xC2 && i + len <= text->len) {
bool valid = true;
for (int n = 1; n < len; n++)
valid &= (text->start[i + n] & 0xC0) == 0x80;
if (valid) {
i += len - 1;
continue;
}
}
// Block raw 8-bit C1 (0x80-0x9F); leave other invalid bytes alone.
if (ch <= 0x9F)
text->start[i] = '?';
}
}
+9
View File
@@ -52,6 +52,15 @@ void playlist_entry_add_params(struct playlist_entry *e,
playlist_entry_add_param(e, params[n].name, params[n].value);
}
void playlist_set_params(struct playlist *pl, struct playlist_param *params,
int num_params)
{
for (int n = 0; n < pl->num_entries; n++) {
pl->entries[n]->num_params = 0;
playlist_entry_add_params(pl->entries[n], params, num_params);
}
}
static void playlist_update_indexes(struct playlist *pl, int start, int end)
{
start = MPMAX(start, 0);
+2
View File
@@ -84,6 +84,8 @@ void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value);
void playlist_entry_add_params(struct playlist_entry *e,
struct playlist_param *params,
int params_count);
void playlist_set_params(struct playlist *pl, struct playlist_param *params,
int num_params);
struct playlist_entry *playlist_entry_new(const char *filename);
+164
View File
@@ -0,0 +1,164 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <libavformat/avio.h>
#include <libavutil/aes.h>
#include <libavutil/error.h>
#include <libavutil/mem.h>
#include "avio_crypto.h"
#include "common/common.h"
#include "misc/bstr.h"
#include "mpv_talloc.h"
#define BLOCKSIZE 16
// In-place decrypt scratch. Holds unconsumed plaintext followed by at most one
// held-back ciphertext block. Any value >= 2*BLOCKSIZE works, this is just a
// reasonable batch size.
#define IO_BUFFER_SIZE (4 * 1024)
#define AVIO_BUFFER_SIZE (64 * 1024)
struct crypto_priv {
AVIOContext *inner;
struct AVAES *aes;
uint8_t iv[BLOCKSIZE];
// [0 .. plain_pos) consumed plaintext (free space)
// [plain_pos .. plain_end) plaintext to deliver
// [plain_end .. ct_end) held-back ciphertext (< BLOCKSIZE)
uint8_t buf[IO_BUFFER_SIZE];
int plain_pos;
int plain_end;
int ct_end;
bool inner_eof;
};
static void priv_destructor(void *ptr)
{
struct crypto_priv *p = ptr;
av_free(p->aes);
}
static int crypto_read(void *opaque, uint8_t *buf, int size)
{
struct crypto_priv *p = opaque;
if (size <= 0)
return 0;
while (p->plain_pos == p->plain_end) {
// Compact held-back ciphertext to the start of the buffer.
int held = p->ct_end - p->plain_end;
if (held > 0 && p->plain_end > 0)
memmove(p->buf, p->buf + p->plain_end, held);
p->plain_pos = p->plain_end = 0;
p->ct_end = held;
// Refill until we have two blocks pending (so the final one can stay
// held back for PKCS#7 stripping) or hit EOF.
while (!p->inner_eof && p->ct_end < 2 * BLOCKSIZE) {
int n = avio_read(p->inner, p->buf + p->ct_end,
sizeof(p->buf) - p->ct_end);
if (n <= 0) {
p->inner_eof = true;
break;
}
p->ct_end += n;
}
int blocks = p->ct_end / BLOCKSIZE;
if (!p->inner_eof)
blocks--;
if (blocks <= 0)
return AVERROR_EOF;
av_aes_crypt(p->aes, p->buf, p->buf, blocks, p->iv, 1);
p->plain_end = blocks * BLOCKSIZE;
// Final batch: drop PKCS#7 padding bytes from the tail.
if (p->inner_eof && p->plain_end == p->ct_end) {
int padding = p->buf[p->plain_end - 1];
if (padding < 1 || padding > BLOCKSIZE)
return AVERROR_INVALIDDATA;
p->plain_end -= padding;
}
}
int copy = MPMIN(p->plain_end - p->plain_pos, size);
memcpy(buf, p->buf + p->plain_pos, copy);
p->plain_pos += copy;
return copy;
}
int mp_avio_crypto_open(AVIOContext **out_pb, AVIOContext *inner,
bstr key, bstr iv)
{
if (!out_pb || !inner || key.len != BLOCKSIZE || iv.len != BLOCKSIZE)
return AVERROR(EINVAL);
int r;
void *avio_buf = NULL;
struct crypto_priv *p = talloc_zero(NULL, struct crypto_priv);
talloc_set_destructor(p, priv_destructor);
p->inner = inner;
p->aes = av_aes_alloc();
if (!p->aes) {
r = AVERROR(ENOMEM);
goto fail;
}
if (av_aes_init(p->aes, key.start, BLOCKSIZE * 8, 1) < 0) {
r = AVERROR(EINVAL);
goto fail;
}
memcpy(p->iv, iv.start, BLOCKSIZE);
avio_buf = av_malloc(AVIO_BUFFER_SIZE);
if (!avio_buf) {
r = AVERROR(ENOMEM);
goto fail;
}
AVIOContext *pb = avio_alloc_context(avio_buf, AVIO_BUFFER_SIZE, 0, p,
crypto_read, NULL, NULL);
if (!pb) {
r = AVERROR(ENOMEM);
goto fail;
}
pb->seekable = 0;
*out_pb = pb;
return 0;
fail:
av_free(avio_buf);
talloc_free(p);
return r;
}
void mp_avio_crypto_close(AVIOContext **pb)
{
if (!pb || !*pb)
return;
struct crypto_priv *p = (*pb)->opaque;
av_freep(&(*pb)->buffer);
avio_context_free(pb);
talloc_free(p);
}
+32
View File
@@ -0,0 +1,32 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libavformat/avio.h>
struct bstr;
// AES-128-CBC + PKCS#7 decryption layer over `inner`. `key` and `iv` are 16
// bytes each; other lengths return AVERROR(EINVAL). Neither buffer is retained
// past this call.
//
// The returned wrapper is read-only and non-seekable, and does not own
// `inner`.
int mp_avio_crypto_open(AVIOContext **out_pb, AVIOContext *inner,
struct bstr key, struct bstr iv);
void mp_avio_crypto_close(AVIOContext **pb);
+187 -23
View File
@@ -184,7 +184,7 @@ struct demux_internal {
// -- All the following fields are protected by lock.
bool thread_terminate;
atomic_bool thread_terminate; // read unlocked by demux_read_interrupted()
bool threading;
bool shutdown_async;
void (*wakeup_cb)(void *ctx);
@@ -209,6 +209,8 @@ struct demux_internal {
double hyst_secs; // stop reading till there's hyst_secs remaining
size_t hyst_bytes; // stop reading till there's hyst_bytes remaining
bool hyst_active;
bool nav_active; // disc navigation (DVD/BD menus) is active
bool nav_pump; // one-shot request to force a single read
size_t max_bytes;
size_t max_bytes_bw;
bool seekable_cache;
@@ -241,8 +243,10 @@ struct demux_internal {
bool back_any_need_recheck; // at least 1 ds->back_need_recheck set
bool tracks_switched; // thread needs to inform demuxer of this
bool nav_refresh; // thread needs to call desc->nav_refresh
bool seeking; // there's a seek queued
atomic_bool seeking; // there's a seek queued; read unlocked
// by demux_read_interrupted()
int seek_flags; // flags for next seek (if seeking==true)
double seek_pts;
@@ -279,6 +283,15 @@ struct demux_internal {
// demux user state (user thread, somewhat similar to reader/decoder state)
double last_playback_pts; // last playback_pts from demux_update()
bool force_metadata_update;
// Chapter/edition lists staged by the demux thread (demux_lists_changed),
// published into d_user by demux_update() on the user thread.
bool lists_changed;
struct demux_chapter *staged_chapters;
int num_staged_chapters;
struct demux_edition *staged_editions;
int num_staged_editions;
int staged_edition;
int cached_metadata_index; // speed up repeated lookups
struct mp_recorder *dumper;
@@ -878,15 +891,8 @@ static void wakeup_ds(struct demux_stream *ds)
}
}
static void update_stream_selection_state(struct demux_internal *in,
struct demux_stream *ds)
static void update_stream_eager_state(struct demux_internal *in)
{
ds->eof = false;
ds->refreshing = false;
// We still have to go over the whole stream list to update ds->eager for
// other streams too, because they depend on other stream's selections.
bool any_av_streams = false;
bool any_streams = false;
@@ -894,25 +900,63 @@ static void update_stream_selection_state(struct demux_internal *in,
struct demux_stream *s = in->streams[n]->ds;
s->still_image = s->sh->still_image;
s->eager = s->selected && !s->sh->attached_picture;
s->eager = s->selected && !s->sh->absent && !s->sh->attached_picture;
if (s->eager && !s->still_image)
any_av_streams |= s->type != STREAM_SUB;
any_streams |= s->selected;
}
// Subtitles are only eagerly read if there are no other eagerly read
// streams.
// Subtitles and sparse still-image video are only eagerly read if there are
// no other eagerly read streams.
if (any_av_streams) {
for (int n = 0; n < in->num_streams; n++) {
struct demux_stream *s = in->streams[n]->ds;
if (s->type == STREAM_SUB)
if (s->type == STREAM_SUB || s->still_image)
s->eager = false;
}
}
if (!any_streams)
in->blocked = false;
}
void demux_set_stream_still_image(demuxer_t *demuxer, struct sh_stream *sh,
bool still_image)
{
struct demux_internal *in = demuxer->in;
mp_assert(demuxer == in->d_thread);
mp_mutex_lock(&in->lock);
sh->still_image = still_image;
update_stream_eager_state(in);
mp_mutex_unlock(&in->lock);
}
// Set whether the media provides data for this stream at all.
void demux_set_stream_absent(demuxer_t *demuxer, struct sh_stream *sh,
bool absent)
{
struct demux_internal *in = demuxer->in;
mp_assert(demuxer == in->d_thread);
mp_mutex_lock(&in->lock);
if (sh->absent != absent) {
MP_VERBOSE(in, "stream %d is %s\n", sh->index,
absent ? "absent" : "present");
sh->absent = absent;
update_stream_eager_state(in);
}
mp_mutex_unlock(&in->lock);
}
static void update_stream_selection_state(struct demux_internal *in,
struct demux_stream *ds)
{
ds->eof = false;
ds->refreshing = false;
update_stream_eager_state(in);
ds_clear_reader_state(ds, true);
@@ -1003,9 +1047,6 @@ static void demux_add_sh_stream_locked(struct demux_internal *in,
if (!sh->codec->codec)
sh->codec->codec = "";
if (sh->ff_index < 0)
sh->ff_index = sh->index;
MP_TARRAY_APPEND(in, in->streams, in->num_streams, sh);
mp_assert(in->streams[sh->index] == sh);
@@ -1249,6 +1290,33 @@ void demux_start_prefetch(struct demuxer *demuxer)
mp_mutex_unlock(&in->lock);
}
void demux_drive_nav(struct demuxer *demuxer)
{
struct demux_internal *in = demuxer->in;
mp_assert(demuxer == in->d_user);
mp_mutex_lock(&in->lock);
in->nav_pump = true;
in->reading = true;
mp_cond_signal(&in->wakeup);
mp_mutex_unlock(&in->lock);
}
// Re-queue sticky packets (e.g. the DVD menu subpicture) for re-delivery.
void demux_nav_refresh(struct demuxer *demuxer)
{
struct demux_internal *in = demuxer->in;
mp_assert(demuxer == in->d_user);
mp_mutex_lock(&in->lock);
if (in->d_thread->desc->nav_refresh) {
in->nav_refresh = true;
in->reading = true;
mp_cond_signal(&in->wakeup);
}
mp_mutex_unlock(&in->lock);
}
const char *stream_type_name(enum stream_type type)
{
switch (type) {
@@ -2304,6 +2372,13 @@ static bool read_packet(struct demux_internal *in)
prefetch_more |= true;
}
// While interactive disc navigation is active, never read ahead. It would
// advance the disc VM past what the user is watching. Reads still happen
// whenever a selected stream demands data.
if (in->nav_active)
prefetch_more = false;
prefetch_more |= in->nav_pump;
MP_TRACE(in, "bytes=%zd, read_more=%d prefetch_more=%d, refresh_more=%d\n",
(size_t)total_fw_bytes, read_more, prefetch_more, refresh_more);
if (total_fw_bytes >= in->max_bytes) {
@@ -2357,6 +2432,7 @@ static bool read_packet(struct demux_internal *in)
// Actually read a packet. Drop the lock while doing so, because waiting
// for disk or network I/O can take time.
in->reading = true;
in->nav_pump = false;
in->after_seek = false;
in->after_seek_to_start = false;
mp_mutex_unlock(&in->lock);
@@ -2365,7 +2441,7 @@ static bool read_packet(struct demux_internal *in)
struct demux_packet *pkt = NULL;
bool eof = true;
if (demux->desc->read_packet && !demux_cancel_test(demux))
if (demux->desc->read_packet && !demux_read_interrupted(demux))
eof = !demux->desc->read_packet(demux, &pkt);
mp_mutex_lock(&in->lock);
@@ -2521,6 +2597,18 @@ static void execute_trackswitch(struct demux_internal *in)
mp_mutex_lock(&in->lock);
}
static void execute_nav_refresh(struct demux_internal *in)
{
in->nav_refresh = false;
mp_mutex_unlock(&in->lock);
if (in->d_thread->desc->nav_refresh)
in->d_thread->desc->nav_refresh(in->d_thread);
mp_mutex_lock(&in->lock);
}
static void execute_seek(struct demux_internal *in)
{
int flags = in->seek_flags;
@@ -2578,6 +2666,8 @@ static void update_opts(struct demuxer *demuxer)
seekable = 1;
}
in->seekable_cache = seekable == 1;
if (in->d_thread->no_cache_seeking)
in->seekable_cache = false;
in->using_network_cache_opts = is_streaming && use_cache;
if (!in->seekable_cache)
@@ -2637,6 +2727,10 @@ static bool thread_work(struct demux_internal *in)
execute_trackswitch(in);
return true;
}
if (in->nav_refresh) {
execute_nav_refresh(in);
return true;
}
if (in->need_back_seek) {
perform_backward_seek(in);
return true;
@@ -3075,7 +3169,7 @@ static void demux_update_replaygain(demuxer_t *demuxer)
if (!rg)
rg = decode_rgain(demuxer->log, demuxer->metadata);
if (rg)
sh->codec->replaygain_data = talloc_steal(in, rg);
sh->codec->replaygain_data = talloc_steal(sh->codec, rg);
}
}
}
@@ -3181,6 +3275,53 @@ void demux_metadata_changed(demuxer_t *demuxer)
mp_mutex_unlock(&in->lock);
}
// Updates the duration should it need to be changed. Used for demuxers that
// changes titles/playlists at runtime.
void demux_set_duration(demuxer_t *demuxer, double duration)
{
mp_assert(demuxer == demuxer->in->d_thread);
struct demux_internal *in = demuxer->in;
mp_mutex_lock(&in->lock);
in->duration = duration;
in->d_thread->duration = duration;
in->highest_av_pts = MP_NOPTS_VALUE;
in->events |= DEMUX_EVENT_DURATION;
mp_mutex_unlock(&in->lock);
}
// Tell the cache whether interactive disc navigation is active.
void demux_set_nav_active(demuxer_t *demuxer, bool active)
{
struct demux_internal *in = demuxer->in;
mp_assert(demuxer == in->d_thread);
mp_mutex_lock(&in->lock);
if (in->nav_active != active) {
in->nav_active = active;
mp_cond_signal(&in->wakeup);
}
mp_mutex_unlock(&in->lock);
}
// Updates the chapters/editions should it need to be changed. Used for demuxers
// that changes titles/playlists at runtime.
void demux_lists_changed(demuxer_t *demuxer)
{
mp_assert(demuxer == demuxer->in->d_thread);
struct demux_internal *in = demuxer->in;
mp_mutex_lock(&in->lock);
in->lists_changed = true;
in->staged_chapters = in->d_thread->chapters;
in->num_staged_chapters = in->d_thread->num_chapters;
in->staged_editions = in->d_thread->editions;
in->num_staged_editions = in->d_thread->num_editions;
in->staged_edition = in->d_thread->edition;
in->events |= DEMUX_EVENT_LISTS;
mp_mutex_unlock(&in->lock);
}
// Called locked, with user demuxer.
static void update_final_metadata(demuxer_t *demuxer, struct timed_metadata *tm)
{
@@ -3278,6 +3419,14 @@ void demux_update(demuxer_t *demuxer, double pts)
demuxer->events |= in->events;
in->events = 0;
if (in->lists_changed) {
in->lists_changed = false;
demuxer->chapters = in->staged_chapters;
demuxer->num_chapters = in->num_staged_chapters;
demuxer->editions = in->staged_editions;
demuxer->num_editions = in->num_staged_editions;
demuxer->edition = in->staged_edition;
}
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
demux_update_replaygain(demuxer);
if (demuxer->events & DEMUX_EVENT_DURATION)
@@ -3439,8 +3588,12 @@ static struct demuxer *open_given_type(struct mpv_global *global,
mp_dbg(log, "Trying demuxer: %s (force-level: %s)\n",
desc->name, d_level(check));
if (stream)
stream_seek(stream, 0);
if (stream && !stream_seek(stream, 0)) {
mp_err(log, "Failed to rewind stream to the start.\n");
demuxer->stream = NULL;
demux_free(demuxer);
return NULL;
}
in->d_thread->params = params; // temporary during open()
int ret = demuxer->desc->open(in->d_thread, check);
@@ -4145,11 +4298,11 @@ void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
struct demux_internal *in = demuxer->in;
mp_mutex_lock(&in->lock);
bool changed = select_track(in, stream, ref_pts, selected);
if (stream->group) {
if (stream->group && !stream->dependent_track) {
for (int i = 0; i < stream->group->num_members; i++) {
struct sh_stream *m = stream->group->members[i];
mp_assert(m);
if (m != stream)
if (m != stream && m->dependent_track)
changed |= select_track(in, m, ref_pts, selected);
}
}
@@ -4251,6 +4404,7 @@ int demuxer_add_chapter(demuxer_t *demuxer, char *name,
};
mp_tags_set_str(new.metadata, "TITLE", name);
MP_TARRAY_APPEND(demuxer, demuxer->chapters, demuxer->num_chapters, new);
talloc_steal(demuxer->chapters, new.metadata);
return demuxer->num_chapters - 1;
}
@@ -4733,6 +4887,16 @@ bool demux_cancel_test(struct demuxer *demuxer)
return mp_cancel_test(demuxer->cancel);
}
// Returns true if a blocking read should be aborted as soon as possible,
// because its result will be discarded anyway.
bool demux_read_interrupted(struct demuxer *demuxer)
{
struct demux_internal *in = demuxer->in;
return atomic_load_explicit(&in->seeking, memory_order_relaxed) ||
atomic_load_explicit(&in->thread_terminate, memory_order_relaxed) ||
demux_cancel_test(demuxer);
}
struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num)
{
struct demux_chapter *new = talloc_array(NULL, struct demux_chapter, num);
+17
View File
@@ -121,6 +121,7 @@ enum demux_event {
DEMUX_EVENT_STREAMS = 1 << 1, // a stream was added
DEMUX_EVENT_METADATA = 1 << 2, // metadata or stream_metadata changed
DEMUX_EVENT_DURATION = 1 << 3, // duration updated
DEMUX_EVENT_LISTS = 1 << 4, // chapters / editions list changed
DEMUX_EVENT_ALL = 0xFFFF,
};
@@ -146,6 +147,8 @@ typedef struct demuxer_desc {
// will be repeated.
bool (*read_packet)(struct demuxer *demuxer, struct demux_packet **pkt);
void (*drop_buffers)(struct demuxer *demuxer);
// Optional. Re-queue retained sticky packets (DVD menu subpicture).
void (*nav_refresh)(struct demuxer *demuxer);
void (*close)(struct demuxer *demuxer);
void (*seek)(struct demuxer *demuxer, double rel_seek_secs, int flags);
void (*switched_tracks)(struct demuxer *demuxer);
@@ -233,6 +236,10 @@ typedef struct demuxer {
double duration; // -1 if unknown
// File format allows PTS resets (even if the current file is without)
bool ts_resets_possible;
// The underlying source can switch to different content at any time, and
// timestamps restart per title, so cached packet ranges become stale
// without notice and must never be used to satisfy seeks.
bool no_cache_seeking;
// The file data was fully read, and there is no need to keep the stream
// open, keep the cache active, or to run the demuxer thread. Generating
// packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
@@ -320,8 +327,15 @@ void demux_start_thread(struct demuxer *demuxer);
void demux_stop_thread(struct demuxer *demuxer);
void demux_set_wakeup_cb(struct demuxer *demuxer, void (*cb)(void *ctx), void *ctx);
void demux_start_prefetch(struct demuxer *demuxer);
void demux_drive_nav(struct demuxer *demuxer);
void demux_nav_refresh(struct demuxer *demuxer);
void demux_set_stream_still_image(struct demuxer *demuxer,
struct sh_stream *sh, bool still_image);
void demux_set_stream_absent(struct demuxer *demuxer, struct sh_stream *sh,
bool absent);
bool demux_cancel_test(struct demuxer *demuxer);
bool demux_read_interrupted(struct demuxer *demuxer);
void demux_flush(struct demuxer *demuxer);
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
@@ -348,6 +362,9 @@ void demux_stream_tags_changed(struct demuxer *demuxer, struct sh_stream *sh,
void demux_close_stream(struct demuxer *demuxer);
void demux_metadata_changed(demuxer_t *demuxer);
void demux_set_duration(demuxer_t *demuxer, double duration);
void demux_set_nav_active(demuxer_t *demuxer, bool active);
void demux_lists_changed(demuxer_t *demuxer);
void demux_update(demuxer_t *demuxer, double playback_pts);
bool demux_cache_dump_set(struct demuxer *demuxer, double start, double end,
+1 -1
View File
@@ -108,7 +108,7 @@ static bool open_source(struct timeline *tl, char *filename)
// are renamed.
struct bstr cuefile =
bstr_strip_ext(bstr0(mp_basename(tl->demuxer->filename)));
mp_strip_ext(bstr0(mp_basename(tl->demuxer->filename)));
DIR *d = opendir(bstrdup0(ctx, dirname));
if (!d)
+948 -99
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -243,7 +243,7 @@ static struct tl_root *parse_edl(bstr str, struct mp_log *log)
: get_meta(tl, index);
sh->lang = get_param0(&ctx, sh, "lang");
sh->title = get_param0(&ctx, sh, "title");
sh->hls_bitrate = get_param_int(&ctx, "byterate", 0) * 8;
MP_SATURATE_MUL(&sh->hls_bitrate, get_param_int(&ctx, "byterate", 0), 8);
int pid = get_param_int(&ctx, "program_id", -1);
if (pid >= 0)
MP_TARRAY_APPEND(sh, sh->program_ids, sh->num_program_ids, pid);
+180 -49
View File
@@ -52,6 +52,7 @@
#include "stream/stream_curl.h"
#include "demux.h"
#include "dovi_split.h"
#include "stheader.h"
#include "options/m_config.h"
#include "options/m_option.h"
@@ -66,13 +67,18 @@
// libavformat (almost) always reads data in blocks of this size.
#define BIO_BUFFER_SIZE 32768
static void avcodec_par_destructor(void *p)
{
avcodec_parameters_free(p);
}
#define OPT_BASE_STRUCT struct demux_lavf_opts
struct demux_lavf_opts {
int probesize;
int64_t probesize;
int probeinfo;
int probescore;
float analyzeduration;
int buffersize;
int64_t buffersize;
bool allow_mimetype;
char *format;
char **avopts;
@@ -85,14 +91,15 @@ struct demux_lavf_opts {
const struct m_sub_options demux_lavf_conf = {
.opts = (const m_option_t[]) {
{"demuxer-lavf-probesize", OPT_INT(probesize), M_RANGE(32, INT_MAX)},
{"demuxer-lavf-probesize", OPT_BYTE_SIZE(probesize),
M_RANGE(32, M_MAX_MEM_BYTES)},
{"demuxer-lavf-probe-info", OPT_CHOICE(probeinfo,
{"no", 0}, {"yes", 1}, {"auto", -1}, {"nostreams", -2})},
{"demuxer-lavf-format", OPT_STRING(format)},
{"demuxer-lavf-analyzeduration", OPT_FLOAT(analyzeduration),
M_RANGE(0, 3600)},
{"demuxer-lavf-buffersize", OPT_INT(buffersize),
M_RANGE(1, 10 * 1024 * 1024), OPTDEF_INT(BIO_BUFFER_SIZE)},
{"demuxer-lavf-buffersize", OPT_BYTE_SIZE(buffersize),
M_RANGE(1, 10 * 1024 * 1024), OPTDEF_INT64(BIO_BUFFER_SIZE)},
{"demuxer-lavf-allow-mimetype", OPT_BOOL(allow_mimetype)},
{"demuxer-lavf-probescore", OPT_INT(probescore),
M_RANGE(1, AVPROBE_SCORE_MAX)},
@@ -224,12 +231,14 @@ struct stream_info {
double last_key_pts;
double highest_pts;
double ts_offset;
struct mp_dovi_split *dovi_split;
};
typedef struct lavf_priv {
struct stream *stream;
bool own_stream;
bool is_dvd_bd;
bool is_dvd;
char *filename;
struct format_hack format_hack;
const AVInputFormat *avif;
@@ -252,6 +261,8 @@ typedef struct lavf_priv {
int retry_counter;
struct demux_packet *pending_pkt;
AVDictionary *av_opts;
// Proxying nested streams.
@@ -466,7 +477,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
// HLS streams seems to be not well tagged, so matching mime type is not
// enough. Strip URL parameters and match extension.
bstr ext = bstr_get_ext(bstr_split(bstr0(priv->filename), "?#", NULL));
bstr ext = mp_get_ext(bstr_split(bstr0(priv->filename), "?#", NULL));
AVProbeData avpd = {
// Disable file-extension matching with normal checks, except for HLS
.filename = !bstrcasecmp0(ext, "m3u8") || !bstrcasecmp0(ext, "m3u") ||
@@ -600,6 +611,12 @@ static void select_tracks(struct demuxer *demuxer, int start)
AVStream *st = priv->avfc->streams[n];
bool selected = stream && demux_stream_is_selected(stream) &&
!stream->attached_picture;
if (!selected && priv->streams[n]->dovi_split) {
struct sh_stream *el =
mp_dovi_split_el_stream(priv->streams[n]->dovi_split);
if (el && demux_stream_is_selected(el))
selected = true;
}
st->discard = selected ? AVDISCARD_DEFAULT : AVDISCARD_ALL;
}
}
@@ -630,7 +647,7 @@ static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
if (!track_data_available && !album_data_available)
return;
struct replaygain_data *rgain = talloc_ptrtype(demuxer, rgain);
struct replaygain_data *rgain = talloc_ptrtype(sh->codec, rgain);
rgain->track_gain = rgain->album_gain = 0;
rgain->track_peak = rgain->album_peak = 1;
@@ -708,7 +725,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->codec->samplerate = codec->sample_rate;
sh->codec->bitrate = codec->bit_rate;
sh->codec->format_name = talloc_strdup(sh, av_get_sample_fmt_name(codec->format));
sh->codec->format_name = talloc_strdup(sh->codec, av_get_sample_fmt_name(codec->format));
double delay = 0;
if (codec->sample_rate > 0)
@@ -748,7 +765,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->codec->disp_w = codec->width;
sh->codec->disp_h = codec->height;
sh->codec->bitrate = codec->bit_rate;
sh->codec->format_name = talloc_strdup(sh, av_get_pix_fmt_name(codec->format));
sh->codec->format_name = talloc_strdup(sh->codec, av_get_pix_fmt_name(codec->format));
if (st->avg_frame_rate.num)
sh->codec->fps = av_q2d(st->avg_frame_rate);
if (is_image(st, sh->attached_picture, priv->avif)) {
@@ -773,9 +790,10 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->codec->dovi = true;
sh->codec->dv_profile = cfg->dv_profile;
sh->codec->dv_level = cfg->dv_level;
sh->codec->dv_el_present = cfg->bl_present_flag && cfg->el_present_flag;
}
// This also applies to vfw-muxed mkv, but we can't detect these easily.
// AVI uses decode-order indices as DTS and needs the compensation.
sh->codec->avi_dts = matches_avinputformat_name(priv, "avi");
break;
@@ -784,7 +802,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh = demux_alloc_sh_stream(STREAM_SUB);
if (codec->extradata_size) {
sh->codec->extradata = talloc_size(sh, codec->extradata_size);
sh->codec->extradata = talloc_size(sh->codec, codec->extradata_size);
memcpy(sh->codec->extradata, codec->extradata, codec->extradata_size);
sh->codec->extradata_size = codec->extradata_size;
}
@@ -826,9 +844,11 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->ff_index = st->index;
mp_codec_info_from_avcodecpar(codec, sh->codec);
sh->codec->codec_tag = codec->codec_tag;
sh->codec->lav_codecpar = avcodec_parameters_alloc();
if (sh->codec->lav_codecpar)
avcodec_parameters_copy(sh->codec->lav_codecpar, codec);
AVCodecParameters **lavp = talloc_ptrtype(sh->codec, lavp);
talloc_set_destructor(lavp, avcodec_par_destructor);
*lavp = avcodec_parameters_alloc();
if (*lavp && avcodec_parameters_copy(*lavp, codec) >= 0)
sh->codec->lav_codecpar = *lavp;
sh->codec->native_tb_num = st->time_base.num;
sh->codec->native_tb_den = st->time_base.den;
sh->codec->duration = st->duration * av_q2d(st->time_base);
@@ -862,12 +882,19 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->lang = talloc_strdup(sh, lang->value);
sh->hls_bitrate = dict_get_decimal(st->metadata, "variant_bitrate", 0);
AVProgram *prog = NULL;
// In the order as reported by lavf
while ((prog = av_find_program_from_stream(avfc, prog, i)))
MP_TARRAY_APPEND(sh, sh->program_ids, sh->num_program_ids, prog->id);
sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
mp_tags_move_from_av_dictionary(sh->tags, &st->metadata);
demux_add_sh_stream(demuxer, sh);
// DVD routes the menu's button-graphics through an SPU
// substream lavf only discovers once the first SPU PES arrives
// mid-playback. Select it immediately to avoid missing menu highlights.
if (priv->is_dvd && sh->type == STREAM_SUB)
demuxer_select_track(demuxer, sh, MP_NOPTS_VALUE, true);
// Unfortunately, there is no better way to detect PCM codecs, other
// than listing them all manually. (Or other "frameless" codecs. Or
// rather, codecs with frames so small libavformat will put multiple of
@@ -911,8 +938,7 @@ static void update_metadata(demuxer_t *demuxer)
static int interrupt_cb(void *ctx)
{
struct demuxer *demuxer = ctx;
return mp_cancel_test(demuxer->cancel);
return demux_read_interrupted(ctx);
}
static int block_io_open(struct AVFormatContext *s, AVIOContext **pb,
@@ -997,6 +1023,13 @@ static int nested_io_close2(struct AVFormatContext *s, AVIOContext *pb)
return priv->default_io_close2(s, pb);
}
static int cmp_avprogram(const void *const a, const void *const b)
{
int ida = (*(const AVProgram *const *)a)->id;
int idb = (*(const AVProgram *const *)b)->id;
return (ida > idb) - (ida < idb);
}
static void build_editions(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
@@ -1015,9 +1048,15 @@ static void build_editions(demuxer_t *demuxer)
return;
}
// Order programs by their id so the list is stable regardless of the order
// lavf happened to discover them.
AVProgram **progs = talloc_memdup(NULL, avfc->programs, avfc->nb_programs * sizeof(progs[0]));
qsort(progs, avfc->nb_programs, sizeof(progs[0]), cmp_avprogram);
bool *nonempty = talloc_zero_array(progs, bool, avfc->nb_programs);
int first_nonempty = -1;
for (unsigned i = 0; i < avfc->nb_programs; i++) {
AVProgram *prog = avfc->programs[i];
AVProgram *prog = progs[i];
struct demux_edition ed = {
.demuxer_id = prog->id,
@@ -1068,6 +1107,7 @@ static void build_editions(demuxer_t *demuxer)
if (title)
mp_tags_set_str(ed.metadata, "title", title);
nonempty[i] = track_count > 0;
if (track_count > 0 && first_nonempty < 0)
first_nonempty = demuxer->num_editions;
@@ -1080,39 +1120,24 @@ static void build_editions(demuxer_t *demuxer)
if (edition_id >= 0 && edition_id < demuxer->num_editions)
selected = edition_id;
// Select initial edition by best variant bitrate. Prefer the program's
// video stream as the representative, falling back to audio for
// audio-only variants (e.g. HLS audio-only renditions).
// Select initial edition by best variant bitrate from the program's
// metadata (HLS variant BANDWIDTH).
if (selected < 0 && hls_bitrate >= 0) {
int best = -1;
int best_bitrate = 0;
bool best_ok = false;
for (int n = 0; n < demuxer->num_editions; n++) {
AVProgram *prog = avfc->programs[n];
struct sh_stream *rep = NULL;
for (unsigned j = 0; j < prog->nb_stream_indexes; j++) {
unsigned idx = prog->stream_index[j];
if (idx >= priv->num_streams || !priv->streams[idx]->sh)
continue;
struct sh_stream *sh = priv->streams[idx]->sh;
if (sh->hls_bitrate <= 0)
continue;
if (sh->type == STREAM_VIDEO) {
rep = sh;
break;
}
if (sh->type == STREAM_AUDIO && !rep)
rep = sh;
}
if (!rep)
AVProgram *prog = progs[n];
int bitrate = dict_get_decimal(prog->metadata, "variant_bitrate", 0);
if (bitrate <= 0 || !nonempty[n])
continue;
bool ok = rep->hls_bitrate <= hls_bitrate;
bool ok = bitrate <= hls_bitrate;
if (best < 0 || (ok && !best_ok) ||
(ok && best_ok && rep->hls_bitrate > best_bitrate) ||
(!ok && !best_ok && rep->hls_bitrate < best_bitrate))
(ok && best_ok && bitrate > best_bitrate) ||
(!ok && !best_ok && bitrate < best_bitrate))
{
best = n;
best_bitrate = rep->hls_bitrate;
best_bitrate = bitrate;
best_ok = ok;
}
}
@@ -1121,6 +1146,8 @@ static void build_editions(demuxer_t *demuxer)
}
demuxer->edition = selected >= 0 ? selected : first_nonempty >= 0 ? first_nonempty : 0;
talloc_free(progs);
}
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 19, 100)
@@ -1266,6 +1293,42 @@ static void handle_lcevc_group(demuxer_t *demuxer, AVStreamGroup *stg)
}
#endif
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(62, 19, 100)
// Base layer + Enhancement layer separate track stream group
static void handle_layered_video_group(demuxer_t *demuxer, AVStreamGroup *stg)
{
lavf_priv_t *priv = demuxer->priv;
AVStreamGroupLayeredVideo *layered = stg->params.layered_video;
if (stg->nb_streams != 2 || layered->el_index >= stg->nb_streams) {
MP_WARN(demuxer, "Dolby Vision group %u: expected 2 streams with valid "
"el_index, got %u streams and el_index %u\n",
stg->index, stg->nb_streams, layered->el_index);
return;
}
AVStream *el_st = stg->streams[layered->el_index];
AVStream *bl_st = stg->streams[layered->el_index ? 0 : 1];
if ((size_t)el_st->index >= priv->num_streams || (size_t)bl_st->index >= priv->num_streams)
return;
struct sh_stream *el_sh = priv->streams[el_st->index]->sh;
struct sh_stream *bl_sh = priv->streams[bl_st->index]->sh;
if (!el_sh || !bl_sh)
return;
// Group storage is attached to the BL so its lifetime tracks the demuxer.
struct sh_stream_group *group = talloc_zero(bl_sh, struct sh_stream_group);
MP_TARRAY_APPEND(group, group->members, group->num_members, bl_sh);
MP_TARRAY_APPEND(group, group->members, group->num_members, el_sh);
bl_sh->group = group;
el_sh->group = group;
el_sh->dependent_track = true;
}
#endif
static void handle_stream_groups(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
@@ -1297,6 +1360,11 @@ static void handle_stream_groups(demuxer_t *demuxer)
case AV_STREAM_GROUP_PARAMS_LCEVC:
handle_lcevc_group(demuxer, stg);
break;
#endif
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(62, 19, 100)
case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
handle_layered_video_group(demuxer, stg);
break;
#endif
default:
MP_VERBOSE(demuxer, "Unhandled stream group type %d (index %u)\n",
@@ -1307,6 +1375,22 @@ static void handle_stream_groups(demuxer_t *demuxer)
}
#endif
static void detect_dovi_split_streams(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
int snapshot_count = priv->num_streams;
for (int n = 0; n < snapshot_count; n++) {
struct stream_info *info = priv->streams[n];
struct sh_stream *sh = info ? info->sh : NULL;
if (!sh || sh->type != STREAM_VIDEO || !sh->codec ||
!sh->codec->dv_el_present || sh->group)
{
continue;
}
info->dovi_split = mp_dovi_split_create(demuxer, sh);
}
}
static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
{
AVFormatContext *avfc = NULL;
@@ -1333,7 +1417,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
if (lavfdopts->probesize) {
if (av_opt_set_int(avfc, "probesize", lavfdopts->probesize, 0) < 0)
MP_ERR(demuxer, "couldn't set option probesize to %u\n",
MP_ERR(demuxer, "couldn't set option probesize to %"PRId64"\n",
lavfdopts->probesize);
}
@@ -1489,6 +1573,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 19, 100)
handle_stream_groups(demuxer);
#endif
detect_dovi_split_streams(demuxer);
mp_tags_move_from_av_dictionary(demuxer->metadata, &avfc->metadata);
@@ -1554,8 +1639,9 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
if (priv->stream) {
const char *sname = priv->stream->info->name;
priv->is_dvd_bd = strcmp(sname, "dvdnav") == 0 ||
strcmp(sname, "ifo_dvdnav") == 0 ||
priv->is_dvd = strcmp(sname, "dvdnav") == 0 ||
strcmp(sname, "ifo_dvdnav") == 0;
priv->is_dvd_bd = priv->is_dvd ||
strcmp(sname, "bd") == 0 ||
strcmp(sname, "bdnav") == 0 ||
strcmp(sname, "bdmv/bluray") == 0;
@@ -1576,6 +1662,13 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
{
lavf_priv_t *priv = demux->priv;
// Companion EL packet queued by the Dolby Vision splitter on a prior call.
if (priv->pending_pkt) {
*mp_pkt = priv->pending_pkt;
priv->pending_pkt = NULL;
return true;
}
AVPacket *pkt = av_packet_alloc();
MP_HANDLE_OOM(pkt);
int r = av_read_frame(priv->avfc, pkt);
@@ -1584,8 +1677,10 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
av_packet_free(&pkt);
if (r == AVERROR_EOF)
return false;
if (mp_cancel_test(demux->cancel))
if (demux_read_interrupted(demux)) {
MP_VERBOSE(demux, "read interrupted: %s.\n", av_err2str(r));
return false;
}
MP_WARN(demux, "error reading packet: %s.\n", av_err2str(r));
if (priv->retry_counter >= 10) {
MP_ERR(demux, "...treating it as fatal error.\n");
@@ -1604,7 +1699,14 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
struct sh_stream *stream = info->sh;
AVStream *st = priv->avfc->streams[pkt->stream_index];
if (!demux_stream_is_selected(stream)) {
// Keep BL packets flowing to feed the Dolby Vision splitter when its
// virtual EL is selected, even if the BL itself isn't selected. The
// unselected BL dp gets discarded by the demuxer queue downstream.
struct sh_stream *split_el = info->dovi_split
? mp_dovi_split_el_stream(info->dovi_split)
: NULL;
bool need_for_split = split_el && demux_stream_is_selected(split_el);
if (!demux_stream_is_selected(stream) && !need_for_split) {
av_packet_free(&pkt);
return true; // don't signal EOF if skipping a packet
}
@@ -1666,6 +1768,13 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
}
}
// Dispatch the EL view of this packet via the splitter.
if (info->dovi_split) {
struct sh_stream *el = mp_dovi_split_el_stream(info->dovi_split);
if (el && demux_stream_is_selected(el))
priv->pending_pkt = mp_dovi_split_dispatch(info->dovi_split, dp);
}
if (st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
st->event_flags = 0;
struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
@@ -1678,14 +1787,35 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
return true;
}
static void reset_dovi_split_state(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
TA_FREEP(&priv->pending_pkt);
for (int n = 0; n < priv->num_streams; n++) {
if (priv->streams[n] && priv->streams[n]->dovi_split)
mp_dovi_split_reset(priv->streams[n]->dovi_split);
}
}
static void demux_drop_buffers_lavf(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
av_seek_frame(priv->avfc, -1, 0, 1);
if (!priv->stream || priv->stream->seekable)
av_seek_frame(priv->avfc, -1, 0, 1);
demux_flush(demuxer);
stream_drop_buffers(priv->stream);
avio_flush(priv->avfc->pb);
if (priv->stream && !priv->stream->seekable) {
// The stream may have been repositioned externally (disc seeks).
// Resync the avio position so mpegts detects the jump and resets
// its packet state.
avio_seek(priv->avfc->pb, stream_tell(priv->stream), SEEK_SET);
}
avformat_flush(priv->avfc);
// Clear sticky EOF/error to reuse this demuxer.
priv->avfc->pb->eof_reached = 0;
priv->avfc->pb->error = 0;
reset_dovi_split_state(demuxer);
}
static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
@@ -1769,6 +1899,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
av_strerror(r, buf, sizeof(buf));
MP_VERBOSE(demuxer, "Seek failed (%s)\n", buf);
}
reset_dovi_split_state(demuxer);
update_read_stats(demuxer);
}
@@ -1800,9 +1931,9 @@ static void demux_close_lavf(demuxer_t *demuxer)
av_freep(&priv->pb);
for (int n = 0; n < priv->num_streams; n++) {
struct stream_info *info = priv->streams[n];
if (info->sh)
avcodec_parameters_free(&info->sh->codec->lav_codecpar);
TA_FREEP(&info->dovi_split);
}
TA_FREEP(&priv->pending_pkt);
if (priv->own_stream)
free_stream(priv->stream);
if (priv->av_opts)
+1 -1
View File
@@ -89,7 +89,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
while (mp_archive_next_entry(mpa)) {
if (filter && filter[0]) {
bstr ext = bstr_get_ext(bstr0(mpa->entry_filename));
bstr ext = mp_get_ext(bstr0(mpa->entry_filename));
bool pass = false;
if (bstr_in_list0(bstr0("video"), filter))
+135 -46
View File
@@ -55,6 +55,7 @@
#include "video/csputils.h"
#include "video/mp_image.h"
#include "demux.h"
#include "dovi_split.h"
#include "packet_pool.h"
#include "stheader.h"
#include "ebml.h"
@@ -145,6 +146,9 @@ typedef struct mkv_track {
bool require_keyframes;
// VfW mode: block timestamps are real DTS, route them to the packet DTS.
bool block_dts;
/* stuff for realaudio braincancer */
double ra_pts; /* previous audio timestamp */
uint32_t sub_packet_size; ///< sub packet size, per stream
@@ -163,6 +167,8 @@ typedef struct mkv_track {
size_t last_index_entry;
AVDOVIDecoderConfigurationRecord *dovi_config;
bstr hvce;
struct mp_dovi_split *dovi_split;
} mkv_track_t;
typedef struct mkv_index {
@@ -464,17 +470,11 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
}
if (info.n_segment_uid) {
size_t len = info.segment_uid.len;
if (len != sizeof(demuxer->matroska_data.uid.segment)) {
MP_INFO(demuxer, "segment uid invalid length %zu\n", len);
} else {
memcpy(demuxer->matroska_data.uid.segment, info.segment_uid.start,
len);
MP_DBG(demuxer, "| + segment uid");
for (size_t i = 0; i < len; i++)
MP_DBG(demuxer, " %02x",
demuxer->matroska_data.uid.segment[i]);
MP_DBG(demuxer, "\n");
}
memcpy(demuxer->matroska_data.uid.segment, info.segment_uid.start, len);
MP_DBG(demuxer, "| + segment uid");
for (size_t i = 0; i < len; i++)
MP_DBG(demuxer, " %02x", demuxer->matroska_data.uid.segment[i]);
MP_DBG(demuxer, "\n");
}
if (demuxer->params && demuxer->params->matroska_wanted_uids) {
if (info.n_segment_uid) {
@@ -744,7 +744,7 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
track->v_height = video->pixel_height;
MP_DBG(demuxer, "| + Pixel height: %"PRIu32"\n", track->v_height);
}
if (video->n_colour_space && video->colour_space.len == 4) {
if (video->n_colour_space) {
uint8_t *d = (uint8_t *)&video->colour_space.start[0];
track->colorspace = d[0] | ((uint32_t)d[1] << 8) | ((uint32_t)d[2] << 16) | ((uint32_t)d[3] << 24);
MP_DBG(demuxer, "| + Colorspace: %#"PRIx32"\n", track->colorspace);
@@ -837,9 +837,13 @@ static void parse_block_addition_mapping(struct demuxer *demuxer,
switch (block_addition_mapping->block_add_id_type) {
case MATROSKA_BLOCK_ADD_ID_TYPE_ITU_T_T35:
break;
case MKBETAG('a','v','c','E'):
case MKBETAG('h','v','c','E'):
MP_WARN(demuxer, "Dolby Vision enhancement-layer playback is not supported.\n");
if (block_addition_mapping->n_block_add_id_extra_data)
track->hvce = bstrdup(track, block_addition_mapping->block_add_id_extra_data);
break;
case MKBETAG('a','v','c','E'):
MP_WARN(demuxer, "Dolby Vision enhancement-layer playback for AVC "
"is not supported.\n");
break;
case MKBETAG('d','v','c','C'):
case MKBETAG('d','v','v','C'):
@@ -867,6 +871,7 @@ static void demux_mkv_free_trackentry(mkv_track_t *track)
{
talloc_free(track->parser_tmp);
av_freep(&track->dovi_config);
TA_FREEP(&track->dovi_split);
talloc_free(track);
}
@@ -1232,22 +1237,15 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
if (ca->n_chapter_segment_uid) {
chapter.has_segment_uid = true;
int len = ca->chapter_segment_uid.len;
if (len != sizeof(chapter.uid.segment))
MP_MSG(demuxer, warn_level,
"Chapter segment uid bad length %d\n", len);
else {
memcpy(chapter.uid.segment, ca->chapter_segment_uid.start,
len);
if (ca->n_chapter_segment_edition_uid)
chapter.uid.edition = ca->chapter_segment_edition_uid;
else
chapter.uid.edition = 0;
MP_DBG(demuxer, "Chapter segment uid ");
for (int n = 0; n < len; n++)
MP_DBG(demuxer, "%02x ",
chapter.uid.segment[n]);
MP_DBG(demuxer, "\n");
}
memcpy(chapter.uid.segment, ca->chapter_segment_uid.start, len);
if (ca->n_chapter_segment_edition_uid)
chapter.uid.edition = ca->chapter_segment_edition_uid;
else
chapter.uid.edition = 0;
MP_DBG(demuxer, "Chapter segment uid ");
for (int n = 0; n < len; n++)
MP_DBG(demuxer, "%02x ", chapter.uid.segment[n]);
MP_DBG(demuxer, "\n");
}
MP_DBG(demuxer, "Chapter %u from %02d:%02d:%02d.%09d "
@@ -1551,13 +1549,14 @@ static void add_coverart(struct demuxer *demuxer)
}
static void init_track(demuxer_t *demuxer, mkv_track_t *track,
struct sh_stream *sh)
struct sh_stream *sh, int idx)
{
track->stream = sh;
if (track->language && (strcmp(track->language, "und") != 0))
sh->lang = track->language;
sh->ff_index = idx; // Emulate lavf track index. For backward compatibility.
sh->demuxer_id = track->tnum;
sh->title = track->name;
sh->default_track = track->default_track;
@@ -1568,9 +1567,9 @@ static void init_track(demuxer_t *demuxer, mkv_track_t *track,
sh->commentary_track = track->commentary_track;
}
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track);
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track);
static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track);
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track, int idx);
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track, int idx);
static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track, int idx);
static void display_create_tracks(demuxer_t *demuxer)
{
@@ -1579,13 +1578,13 @@ static void display_create_tracks(demuxer_t *demuxer)
for (int i = 0; i < mkv_d->num_tracks; i++) {
switch (mkv_d->tracks[i]->type) {
case MATROSKA_TRACK_VIDEO:
demux_mkv_open_video(demuxer, mkv_d->tracks[i]);
demux_mkv_open_video(demuxer, mkv_d->tracks[i], i);
break;
case MATROSKA_TRACK_AUDIO:
demux_mkv_open_audio(demuxer, mkv_d->tracks[i]);
demux_mkv_open_audio(demuxer, mkv_d->tracks[i], i);
break;
case MATROSKA_TRACK_SUBTITLE:
demux_mkv_open_sub(demuxer, mkv_d->tracks[i]);
demux_mkv_open_sub(demuxer, mkv_d->tracks[i], i);
break;
}
}
@@ -1621,12 +1620,12 @@ static void avcodec_par_destructor(void *p)
avcodec_parameters_free(p);
}
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track, int idx)
{
unsigned char *extradata = NULL;
unsigned int extradata_size = 0;
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
init_track(demuxer, track, sh);
init_track(demuxer, track, sh, idx);
struct mp_codec_params *sh_v = sh->codec;
sh_v->bits_per_coded_sample = 24;
@@ -1647,7 +1646,7 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
extradata = track->private_data + 40;
extradata_size = track->private_size - 40;
mp_set_codec_from_tag(sh_v);
sh_v->avi_dts = true;
track->block_dts = true;
} else if (track->private_size >= RVPROPERTIES_SIZE
&& (!strcmp(track->codec_id, "V_REAL/RV10")
|| !strcmp(track->codec_id, "V_REAL/RV20")
@@ -1688,6 +1687,12 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
extradata = track->private_data;
extradata_size = track->private_size;
}
} else if (strcmp(track->codec_id, "V_VC1") == 0) {
if (track->private_size >= 7 && (track->private_data[0] & 0xf0) == 0xc0) {
extradata = track->private_data + 7;
extradata_size = track->private_size - 7;
}
sh_v->codec = "vc1";
} else {
for (int i = 0; mkv_video_tags[i][0]; i++) {
if (!strcmp(mkv_video_tags[i][0], track->codec_id)) {
@@ -1802,11 +1807,33 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
sh_v->dovi = true;
sh_v->dv_level = track->dovi_config->dv_level;
sh_v->dv_profile = track->dovi_config->dv_profile;
sh_v->dv_el_present = track->dovi_config->bl_present_flag &&
track->dovi_config->el_present_flag;
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(62, 35, 100)
if (track->hvce.len > 0) {
void *data = av_memdup(track->hvce.start, track->hvce.len);
MP_HANDLE_OOM(data);
if (!av_packet_side_data_add(&sh_v->lav_codecpar->coded_side_data,
&sh_v->lav_codecpar->nb_coded_side_data,
AV_PKT_DATA_HEVC_CONF,
data, track->hvce.len, 0))
{
MP_ERR(demuxer, "Failed to attach hvcE configuration record to "
"codec parameters for track %d!\n", track->tnum);
av_free(data);
}
}
#endif
done:
demux_add_sh_stream(demuxer, sh);
// Profile 7 NALU-interleaved
if (sh_v->dv_el_present)
track->dovi_split = mp_dovi_split_create(demuxer, sh);
return 0;
}
@@ -1899,10 +1926,10 @@ static const char *const mkv_audio_tags[][2] = {
{ NULL },
};
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track, int idx)
{
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
init_track(demuxer, track, sh);
init_track(demuxer, track, sh, idx);
struct mp_codec_params *sh_a = sh->codec;
if (track->private_size > 0x1000000)
@@ -2182,7 +2209,7 @@ static const char *const mkv_sub_tag[][2] = {
{0}
};
static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track, int idx)
{
const char *subtitle_type = NULL;
for (int n = 0; mkv_sub_tag[n][0]; n++) {
@@ -2196,7 +2223,7 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
return 1;
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB);
init_track(demuxer, track, sh);
init_track(demuxer, track, sh, idx);
sh->codec->codec = subtitle_type;
bstr in = (bstr){track->private_data, track->private_size};
@@ -2250,6 +2277,51 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
return 0;
}
static void pair_dovi_tracks(demuxer_t *demuxer)
{
mkv_demuxer_t *mkv_d = demuxer->priv;
mkv_track_t *bl_track = NULL, *el_track = NULL;
for (int i = 0; i < mkv_d->num_tracks; i++) {
mkv_track_t *track = mkv_d->tracks[i];
if (!track->stream || track->stream->type != STREAM_VIDEO ||
!track->codec_id || strcmp(track->codec_id, "V_MPEGH/ISO/HEVC"))
continue;
AVDOVIDecoderConfigurationRecord *dovi = track->dovi_config;
if (dovi && dovi->dv_profile == 7 && dovi->el_present_flag) {
// bl_present_flag is not checked, because the files in the
// wild set it to 1 for EL stream, while the expectation, based
// on Dolby spec for MPEG-TS would be that it's set to 0.
// Ignore this, if we have EL track and single other video track
// it's safe to assume it's BL.
if (el_track)
return;
el_track = track;
continue;
}
if (bl_track)
return;
bl_track = track;
}
if (!el_track || !bl_track)
return;
struct sh_stream *bl_sh = bl_track->stream;
struct sh_stream *el_sh = el_track->stream;
// Group storage is attached to the BL so its lifetime tracks the demuxer.
struct sh_stream_group *group = talloc_zero(bl_sh, struct sh_stream_group);
MP_TARRAY_APPEND(group, group->members, group->num_members, bl_sh);
MP_TARRAY_APPEND(group, group->members, group->num_members, el_sh);
bl_sh->group = group;
el_sh->group = group;
el_sh->dependent_track = true;
}
// Workaround for broken files that don't set attached_picture
static void probe_if_image(demuxer_t *demuxer)
{
@@ -2531,6 +2603,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
MP_VERBOSE(demuxer, "All headers are parsed!\n");
display_create_tracks(demuxer);
pair_dovi_tracks(demuxer);
add_coverart(demuxer);
process_tags(demuxer);
@@ -2749,6 +2822,7 @@ static void mkv_seek_reset(demuxer_t *demuxer)
av_parser_close(track->av_parser);
track->av_parser = NULL;
avcodec_free_context(&track->av_parser_codec);
mp_dovi_split_reset(track->dovi_split);
}
for (int n = 0; n < mkv_d->num_blocks; n++)
@@ -2896,7 +2970,16 @@ static void mkv_parse_and_add_packet(demuxer_t *demuxer, mkv_track_t *track,
}
if (!track->parse || !track->av_parser || !track->av_parser_codec) {
struct demux_packet *el_dp = NULL;
struct sh_stream *el_sh = NULL;
if (track->dovi_split) {
el_sh = mp_dovi_split_el_stream(track->dovi_split);
if (el_sh && demux_stream_is_selected(el_sh))
el_dp = mp_dovi_split_dispatch(track->dovi_split, dp);
}
add_packet(demuxer, stream, dp);
if (el_dp)
add_packet(demuxer, el_sh, el_dp);
return;
}
@@ -3034,7 +3117,13 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
struct sh_stream *stream = track->stream;
bool use_this_block = tc >= mkv_d->skip_to_timecode;
if (!demux_stream_is_selected(stream))
// Keep BL blocks flowing to feed the Dolby Vision splitter when its
// virtual EL is selected, even if the BL itself isn't selected.
struct sh_stream *split_el = track->dovi_split
? mp_dovi_split_el_stream(track->dovi_split)
: NULL;
bool need_for_split = split_el && demux_stream_is_selected(split_el);
if (!demux_stream_is_selected(stream) && !need_for_split)
return 0;
current_pts = tc / 1e9 - track->codec_delay;
@@ -3108,7 +3197,7 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
dp->pts = current_pts + i * track->default_duration;
dp->keyframe = keyframe;
}
if (stream->codec->avi_dts)
if (track->block_dts)
MPSWAP(double, dp->pts, dp->dts);
if (i == 0 && block_info->duration_known)
dp->duration = block_duration / 1e9;
+5 -5
View File
@@ -249,14 +249,14 @@ static int parse_m3u(struct pl_parser *p)
if (p->probing && !bstr_equals0(line, "#EXTM3U")) {
// Last resort: if the file extension is m3u, it might be headerless.
if (p->check_level == DEMUX_CHECK_UNSAFE) {
char *ext = mp_splitext(p->real_stream->url, NULL);
bstr ext = mp_get_ext(bstr0(p->real_stream->url));
char probe[PROBE_SIZE];
int len = stream_read_peek(p->real_stream, probe, sizeof(probe));
bstr data = {probe, len};
if (ext && data.len >= 2 && maybe_text(data)) {
if (ext.len && data.len >= 2 && maybe_text(data)) {
const char *exts[] = {"m3u", "m3u8", "strm", NULL};
for (int n = 0; exts[n]; n++) {
if (strcasecmp(ext, exts[n]) == 0)
if (bstrcasecmp(ext, bstr0(exts[n])) == 0)
goto ok;
}
}
@@ -426,7 +426,7 @@ static bool test_path(struct pl_parser *p, char *path, int autocreate)
if (!strcmp(path, p->real_stream->path))
return true;
bstr ext = bstr_get_ext(bstr0(path));
bstr ext = mp_get_ext(bstr0(path));
if (autocreate & AUTO_VIDEO && bstr_in_list0(ext, p->mp_opts->video_exts))
return true;
if (autocreate & AUTO_AUDIO && bstr_in_list0(ext, p->mp_opts->audio_exts))
@@ -535,7 +535,7 @@ static int parse_dir(struct pl_parser *p)
p->pl->playlist_dir = NULL;
if (p->autocreate_playlist && p->real_stream->is_local_fs && p->real_stream->is_regular &&
!p->real_stream->is_directory) {
bstr ext = bstr_get_ext(bstr0(p->real_stream->url));
bstr ext = mp_get_ext(bstr0(p->real_stream->url));
switch (p->autocreate_playlist) {
case 1: // filter
autocreate = get_directory_filter(p);
+6
View File
@@ -59,6 +59,7 @@ struct virtual_source {
struct timeline_par *tl;
bool dash, no_clip, delay_open;
bool rebase;
struct segment **segments;
int num_segments;
@@ -250,6 +251,8 @@ static void switch_segment(struct demuxer *demuxer, struct virtual_source *src,
reselect_streams(demuxer);
if (!src->no_clip)
demux_set_ts_offset(new->d, new->start - new->d_start);
else if (src->rebase)
demux_set_ts_offset(new->d, new->start - new->d->start_time);
if (!src->no_clip || !init)
demux_seek(new->d, start_pts, flags);
@@ -546,6 +549,7 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
{
struct priv *p = demuxer->priv;
struct MPOpts *mp_opts = mp_get_config_group(NULL, demuxer->global, &mp_opt_root);
struct virtual_source *src = talloc_ptrtype(p, src);
*src = (struct virtual_source){
.tl = tl,
@@ -553,7 +557,9 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
.delay_open = tl->delay_open,
.no_clip = tl->no_clip || tl->dash,
.dts = MP_NOPTS_VALUE,
.rebase = tl->delay_open && mp_opts->rebase_start_time,
};
TA_FREEP(&mp_opts);
if (!tl->num_parts)
return false;
+203
View File
@@ -0,0 +1,203 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavutil/dovi_meta.h>
#include <libavutil/opt.h>
#include "common/av_common.h"
#include "common/common.h"
#include "common/msg.h"
#include "demux.h"
#include "demux/packet.h"
#include "demux/packet_pool.h"
#include "demux/stheader.h"
#include "dovi_split.h"
#include "mpv_talloc.h"
struct mp_dovi_split {
struct mp_log *log;
struct demuxer *demuxer;
struct sh_stream *bl;
struct sh_stream *el;
AVBSFContext *bsf;
AVPacket *staging;
};
static void mp_dovi_split_destructor(void *p)
{
struct mp_dovi_split *s = p;
av_packet_free(&s->staging);
av_bsf_free(&s->bsf);
}
struct mp_dovi_split *mp_dovi_split_create(struct demuxer *demuxer,
struct sh_stream *bl)
{
if (!bl || bl->type != STREAM_VIDEO || !bl->codec ||
!bl->codec->codec || strcmp(bl->codec->codec, "hevc") != 0)
return NULL;
const AVBitStreamFilter *def = av_bsf_get_by_name("dovi_split");
if (!def) {
MP_WARN(demuxer, "Dolby Vision EL: 'dovi_split' BSF not available in "
"libavcodec; rendering base layer only.\n");
return NULL;
}
struct mp_dovi_split *s = talloc_zero(demuxer, struct mp_dovi_split);
talloc_set_destructor(s, mp_dovi_split_destructor);
s->log = demuxer->log;
s->demuxer = demuxer;
s->bl = bl;
s->staging = av_packet_alloc();
if (!s->staging)
goto fail;
int ret = av_bsf_alloc(def, &s->bsf);
if (ret < 0)
goto fail;
AVCodecParameters *par = mp_codec_params_to_av(bl->codec);
if (par) {
avcodec_parameters_copy(s->bsf->par_in, par);
avcodec_parameters_free(&par);
}
s->bsf->time_base_in = mp_get_codec_timebase(bl->codec);
if (av_opt_set(s->bsf, "mode", "el", AV_OPT_SEARCH_CHILDREN) < 0)
goto fail;
if (av_bsf_init(s->bsf) < 0)
goto fail;
const AVCodecParameters *par_out = s->bsf->par_out;
// Allocate the virtual EL sh_stream.
struct sh_stream *el = demux_alloc_sh_stream(STREAM_VIDEO);
el->codec->codec = "hevc";
el->codec->native_tb_num = bl->codec->native_tb_num;
el->codec->native_tb_den = bl->codec->native_tb_den;
el->codec->fps = bl->codec->fps;
el->codec->disp_w = par_out->width;
el->codec->disp_h = par_out->height;
if (par_out->extradata_size > 0) {
el->codec->extradata = talloc_memdup(el->codec, par_out->extradata,
par_out->extradata_size);
el->codec->extradata_size = par_out->extradata_size;
}
for (int i = 0; i < par_out->nb_coded_side_data; i++) {
const AVPacketSideData *sd = &par_out->coded_side_data[i];
if (sd->type != AV_PKT_DATA_DOVI_CONF)
continue;
const AVDOVIDecoderConfigurationRecord *cfg = (const void *)sd->data;
el->codec->dovi = true;
el->codec->dv_profile = cfg->dv_profile;
el->codec->dv_level = cfg->dv_level;
el->codec->dv_el_present = cfg->el_present_flag;
break;
}
el->title = talloc_strdup(el, "Dolby Vision enhancement layer");
el->dependent_track = true;
demux_add_sh_stream(demuxer, el);
s->el = el;
// Bind BL and EL into a sh_stream_group.
struct sh_stream_group *group = talloc_zero(bl, struct sh_stream_group);
MP_TARRAY_APPEND(group, group->members, group->num_members, bl);
MP_TARRAY_APPEND(group, group->members, group->num_members, el);
bl->group = group;
el->group = group;
MP_VERBOSE(demuxer, "Dolby Vision Profile 7 splitter: BL stream %d, "
"virtual EL stream %d (dependent_track).\n",
bl->index, el->index);
return s;
fail:
talloc_free(s);
return NULL;
}
void mp_dovi_split_reset(struct mp_dovi_split *s)
{
if (!s || !s->bsf)
return;
av_bsf_flush(s->bsf);
}
struct sh_stream *mp_dovi_split_el_stream(struct mp_dovi_split *s)
{
return s ? s->el : NULL;
}
struct demux_packet *mp_dovi_split_dispatch(struct mp_dovi_split *s,
struct demux_packet *bl_dp)
{
if (!s || !s->bsf || !bl_dp || !bl_dp->buffer || bl_dp->len <= 0)
return NULL;
// av_bsf_send_packet takes ownership of the packet's buffer, so copy it,
// to not steal it from caller.
AVPacket *copy = av_packet_alloc();
if (!copy)
return NULL;
int ret = av_new_packet(copy, bl_dp->len);
if (ret < 0) {
av_packet_free(&copy);
return NULL;
}
memcpy(copy->data, bl_dp->buffer, bl_dp->len);
copy->flags = bl_dp->keyframe ? AV_PKT_FLAG_KEY : 0;
ret = av_bsf_send_packet(s->bsf, copy);
av_packet_free(&copy);
if (ret < 0) {
MP_VERBOSE(s->demuxer, "dovi_split: BSF send failed: %s\n",
mp_strerror(AVUNERROR(ret)));
return NULL;
}
ret = av_bsf_receive_packet(s->bsf, s->staging);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
// No EL NALs in this AU, nothing to emit.
return NULL;
}
if (ret < 0) {
MP_VERBOSE(s->demuxer, "dovi_split: BSF receive failed: %s; flushing.\n",
mp_strerror(AVUNERROR(ret)));
av_bsf_flush(s->bsf);
return NULL;
}
struct demux_packet *dp =
new_demux_packet_from_avpacket(s->demuxer->packet_pool, s->staging);
if (dp) {
// Mirror the BL packet's timing so the pairing filter can match by PTS.
dp->pts = bl_dp->pts;
dp->dts = bl_dp->dts;
dp->duration = bl_dp->duration;
dp->keyframe = bl_dp->keyframe;
dp->stream = s->el->index;
}
av_packet_unref(s->staging);
return dp;
}
+51
View File
@@ -0,0 +1,51 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
struct demuxer;
struct sh_stream;
struct demux_packet;
// Dolby Vision Profile 7 enhancement-layer splitter for HEVC streams that
// carry the EL bitstream interleaved as HEVC_NAL_UNSPEC63.
//
// Creates a virtual EL sh_stream and binds it to the BL via sh_stream_group
// so downstream code can treat same as separate track BL+EL.
struct mp_dovi_split;
// Create a splitter on `bl`. Adds the virtual EL sh_stream to `demuxer`.
//
// Returns NULL if the BSF is unavailable or initialization fails. The
// returned context is talloc-attached to `demuxer`.
struct mp_dovi_split *mp_dovi_split_create(struct demuxer *demuxer,
struct sh_stream *bl);
void mp_dovi_split_reset(struct mp_dovi_split *s);
// Return the virtual EL sh_stream created by mp_dovi_split_create. The
// returned pointer remains valid for the lifetime of the splitter.
struct sh_stream *mp_dovi_split_el_stream(struct mp_dovi_split *s);
// Apply the BSF to `bl_dp` and produce a companion EL demux_packet, if any.
// Caller owns the returned packet. Returns NULL if the access unit contained
// no EL NALs or on any non-fatal BSF error.
//
// The emitted packet inherits pts/dts/duration/keyframe from `bl_dp` so it
// lines up with the matching BL packet for PTS-based pairing downstream.
struct demux_packet *mp_dovi_split_dispatch(struct mp_dovi_split *s,
struct demux_packet *bl_dp);
+29 -8
View File
@@ -210,8 +210,11 @@ int ebml_resync_cluster(struct mp_log *log, stream_t *s)
static_assert(sizeof(((struct ebml_elem_desc *)0)->defval) == sizeof(((struct ebml_elem_desc *)0)->defval.u),
"defval.u must cover the entire union for {0} initialization");
#define EVALARGS(F, ...) F(__VA_ARGS__)
#define E(str, N, type) const struct ebml_elem_desc ebml_ ## N ## _desc = { str, type };
#define E(str, N, type, length, context, ...) const struct ebml_elem_desc ebml_ ## N ## _desc = { str, type, length, .context_default = context, .defval = __VA_ARGS__ };
#define E_SN(str, count, N) const struct ebml_elem_desc ebml_ ## N ## _desc = { str, EBML_TYPE_SUBELEMENTS, sizeof(struct ebml_ ## N), count, (const struct ebml_field_desc[]){
#define E_S(str, count) EVALARGS(E_SN, str, count, N)
#define FN(id, name, multiple, N) { id, multiple, offsetof(struct ebml_ ## N, name), offsetof(struct ebml_ ## N, n_ ## name), &ebml_##name##_desc},
@@ -510,11 +513,13 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
else \
subelptr = (fieldtype *) fieldptr
GETPTR(uintptr, uint64_t);
if (length < 1 || length > 8) {
if (length > 8) {
MP_ERR(ctx, "uint invalid length %"PRIu64"\n", length);
goto error;
}
*uintptr = ebml_parse_uint(data, length);
if (!length && ed->context_default)
goto skip;
*uintptr = length ? ebml_parse_uint(data, length) : ed->defval.u;
MP_TRACE(ctx, "uint %"PRIu64"\n", *uintptr);
break;
@@ -525,7 +530,9 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
MP_ERR(ctx, "sint invalid length %"PRIu64"\n", length);
goto error;
}
*sintptr = ebml_parse_sint(data, length);
if (!length && ed->context_default)
goto skip;
*sintptr = length ? ebml_parse_sint(data, length) : ed->defval.i;
MP_TRACE(ctx, "sint %"PRId64"\n", *sintptr);
break;
@@ -536,25 +543,38 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
MP_ERR(ctx, "float invalid length %"PRIu64"\n", length);
goto error;
}
*floatptr = ebml_parse_float(data, length);
if (!length && ed->context_default)
goto skip;
*floatptr = length ? ebml_parse_float(data, length) : ed->defval.f;
MP_DBG(ctx, "float %f\n", *floatptr);
break;
case EBML_TYPE_STR:
if (length > 1024 * 1024) {
MP_ERR(ctx, "Not reading overly long string element.\n");
break;
goto error;
}
char **strptr;
GETPTR(strptr, char *);
*strptr = talloc_strndup(ctx->talloc_ctx, data, length);
if (!length && ed->context_default)
goto skip;
if (!length && ed->defval.s) {
*strptr = talloc_strdup(ctx->talloc_ctx, ed->defval.s);
} else {
*strptr = talloc_strndup(ctx->talloc_ctx, data, length);
}
MP_TRACE(ctx, "string \"%s\"\n", *strptr);
break;
case EBML_TYPE_BINARY:;
if (length > 0x80000000) {
MP_ERR(ctx, "Not reading overly long EBML element.\n");
break;
goto error;
}
if (ed->size && length != ed->size) {
MP_WARN(ctx, "%s invalid length %"PRIu64", ignoring\n",
ed->name, length);
goto error;
}
struct bstr *binptr;
GETPTR(binptr, struct bstr);
@@ -578,6 +598,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
}
*countptr += 1;
error:
skip:
data += length;
}
}
+7
View File
@@ -54,6 +54,13 @@ struct ebml_elem_desc {
int size;
int field_count;
const struct ebml_field_desc *fields;
bool context_default; // default derives from other elements
union {
uint64_t u;
int64_t i;
double f;
const char *s;
} defval; // default value of a zero-length element
};
struct ebml_parse_ctx {
+2
View File
@@ -376,6 +376,8 @@ int demux_packet_add_blockadditional(struct demux_packet *dp, uint64_t id,
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 17, 100)
case ITU_T_T35_COUNTRY_CODE_UK: {
if (remaining < 1)
break;
SKIP(1); // skip t35_uk_country_code_second_octet
if (remaining < 2)
break;
+19
View File
@@ -68,6 +68,7 @@ struct sh_stream {
bool commentary_track; // container flag
bool image; // video stream is an image
bool still_image; // video consists of multiple sparse still images
bool absent; // the media provides no data for this stream
int hls_bitrate;
int *program_ids;
int num_program_ids;
@@ -99,6 +100,23 @@ static inline bool sh_stream_has_program(const struct sh_stream *sh, int program
return false;
}
// Return the dependent twin of the given base track (e.g. a Dolby Vision
// enhancement-layer stream paired with the base layer), or NULL if none. Only
// twin-track groups (exactly 2 members) of matching type are supported.
static inline struct sh_stream *sh_stream_dependent_sibling(struct sh_stream *bl)
{
if (!bl || !bl->group || bl->dependent_track)
return NULL;
if (bl->group->num_members != 2)
return NULL;
for (int i = 0; i < bl->group->num_members; i++) {
struct sh_stream *m = bl->group->members[i];
if (m && m != bl && m->dependent_track && m->type == bl->type)
return m->absent ? NULL : m;
}
return NULL;
}
struct mp_codec_params {
enum stream_type type;
@@ -158,6 +176,7 @@ struct mp_codec_params {
bool dovi;
uint8_t dv_profile;
uint8_t dv_level;
bool dv_el_present; // BL and EL interleaved in this stream (Profile 7)
// STREAM_VIDEO + STREAM_AUDIO
int bits_per_coded_sample;
+17 -1
View File
@@ -30,7 +30,7 @@
#MBTN_LEFT ignore # don't do anything
#MBTN_LEFT_DBL cycle fullscreen # toggle fullscreen
#MBTN_RIGHT cycle pause # toggle pause/playback mode
#MBTN_RIGHT script-binding select/context-menu # open context menu
#MBTN_BACK playlist-prev # skip to the previous file
#MBTN_FORWARD playlist-next # skip to the next file
#Ctrl+MBTN_LEFT script-binding positioning/drag-to-pan # pan around the clicked point
@@ -234,6 +234,22 @@
#Ctrl+KP_UP add video-align-y -0.01 # align video up
#Ctrl+KP_PGUP add video-align-x 0.01; add video-align-y -0.01 # align video right and up
#
# The "{discnav}" section is enabled only while a disc menu with a button
# highlight is actually on screen.
#
#UP {discnav} discnav up
#DOWN {discnav} discnav down
#LEFT {discnav} discnav left
#RIGHT {discnav} discnav right
#ENTER {discnav} discnav select
#ESC {discnav} discnav prev
#BS {discnav} discnav prev
#MOUSE_MOVE {discnav} discnav mouse-move
#MBTN_LEFT {discnav} discnav mouse-click
#Ctrl+m discnav menu # jump to the disc's root menu
#Ctrl+M discnav popup # show/hide Blu-ray popup menu
#
# Legacy bindings (may or may not be removed in the future)
#
+2 -2
View File
@@ -61,8 +61,8 @@ P&layback
&100% set video-zoom 0 checked=video_zoom == 0
&200% set video-zoom 1 checked=video_zoom == 1
&Aspect ratio
&16:9 set video-aspect-override 16:9 checked=math.abs(video_aspect_override - 1.7) < 0.1
&4:3 set video-aspect-override 4:3 checked=math.abs(video_aspect_override - 1.3) < 0.1
&16:9 set video-aspect-override 16:9 checked=math.abs(video_aspect_override - 16/9) < 1e-12
&4:3 set video-aspect-override 4:3 checked=math.abs(video_aspect_override - 4/3) < 1e-12
&2.35:1 set video-aspect-override 2.35:1 checked=video_aspect_override == 2.35
&Default set video-aspect-override no checked=video_aspect_override == -2
&Center no-osd set video-pan-x 0; no-osd set video-pan-y 0; no-osd set video-align-x 0; no-osd set video-align-y 0 disabled=video_pan_x == 0 and video_pan_y == 0 and video_align_x == 0 and video_align_y == 0
+15 -2
View File
@@ -34,6 +34,9 @@ _mpv_get_args()
type="ShowProfile"
elif [ "$1" = "--profile" ]; then
type="Profile"
elif [ "$1" = "--help" -o "$1" = "--h" ]; then
# --help=<substring> filters by option name, not String values
type="Help"
fi
declare -a candidates
@@ -70,6 +73,13 @@ _mpv_get_args()
ShowProfile)
candidates=($(mpv $1= | grep -v ':' | awk '{print $1;}'))
;;
Help)
# Mirror zsh help-options: bare option names (no "--"),
# excluding no-* and *-(add|append|clr|pre|set|remove|toggle).
candidates=($(_mpv_options | awk '{print $1;}' | grep '^--' | \
grep -vE '^--no-' | grep -vE '^--.*-(add|append|clr|pre|set|remove|toggle)$' | \
sed 's/^--//'))
;;
*)
# There are other categories; some of which we could do something smarter
# about, with enough work.
@@ -81,8 +91,11 @@ _mpv_get_args()
fi
}
# This regex detects special options where we don't want an '=' appended
_mpv_special_regex='\s(Flag.*\[not in config files\]|Print)'
# Options where '=' is not appended:
# - Print options and "not in config files" flags
# - list-type sub-options with no argument (e.g. --af-clr, --vo-help)
# --help/--h are intentionally excluded (they take a substring filter).
_mpv_special_regex='\s(Flag.*\[not in config files\]|Print)|^\s+--[[:alnum:]-]+-(clr|help)\s*$'
_mpv_skip_regex='\sremoved \[deprecated\]'
_mpv_regular_options()
+1
View File
@@ -11,6 +11,7 @@
# changed in mpv 0.42.0
MBTN_RIGHT cycle pause
MENU script-binding select/menu
# changed in mpv 0.40.0
+7 -4
View File
@@ -63,12 +63,15 @@ static void deint_process(struct mp_filter *f)
bool filter_needed = opts->deinterlace == 1 ||
(opts->deinterlace == -1 && (p->interlaced_frame || p->sub.filter));
// If the image format changed, or if we no longer need a filter,
// destroy any existing filter.
if (img->imgfmt != p->prev_imgfmt || (p->sub.filter && !filter_needed)) {
// If the image format changed, destroy any existing filter immediately since
// it may not support the new format. If we no longer need a filter, drain
// and destroy it gracefully.
if (img->imgfmt != p->prev_imgfmt) {
mp_subfilter_destroy(&p->sub);
p->prev_imgfmt = img->imgfmt;
} else if (p->sub.filter && !filter_needed) {
if (!mp_subfilter_drain_destroy(&p->sub))
return;
p->prev_imgfmt = img->imgfmt;
}
// If no filter is needed or if the filter is already inserted and we reach
+29 -2
View File
@@ -233,6 +233,7 @@ struct priv {
bool pts_reset;
int attempt_framedrops; // try dropping this many frames
int dropped_frames; // total frames _probably_ dropped
int extra_hw_frames; // extra surfaces retained outside the decoder
// --- Decoder group.
// The group of decoders this wrapper manages. Decodes dependent streams
@@ -495,6 +496,16 @@ static bool reinit_decoder(struct priv *p)
p->codec->codec ? p->codec->codec : "<?>");
}
if (p->decoder && p->decoder->control) {
mp_mutex_lock(&p->cache_lock);
int extra_hw_frames = p->extra_hw_frames;
mp_mutex_unlock(&p->cache_lock);
if (extra_hw_frames > 0) {
p->decoder->control(p->decoder->f, VDCTRL_SET_EXTRA_HW_FRAMES,
&extra_hw_frames);
}
}
update_cached_values(p);
talloc_free(list);
@@ -523,6 +534,19 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
return res;
}
void mp_decoder_wrapper_set_extra_hw_frames(struct mp_decoder_wrapper *d, int n)
{
struct priv *p = d->f->priv;
if (p->is_group) {
for (int i = 0; i < p->num_children; i++)
mp_decoder_wrapper_set_extra_hw_frames(p->children[i], n);
return;
}
mp_mutex_lock(&p->cache_lock);
p->extra_hw_frames = n;
mp_mutex_unlock(&p->cache_lock);
}
void mp_decoder_wrapper_set_frame_drops(struct mp_decoder_wrapper *d, int num)
{
struct priv *p = d->f->priv;
@@ -1406,13 +1430,14 @@ struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
decf_reset(p->decf);
struct mp_pin *out_pin;
if (p->queue) {
struct mp_filter *f_in =
mp_async_queue_create_filter(public_f, MP_PIN_OUT, p->queue);
struct mp_filter *f_out =
mp_async_queue_create_filter(p->decf, MP_PIN_IN, p->queue);
mp_pin_connect(public_f->ppins[0], f_in->pins[0]);
mp_pin_connect(f_out->pins[0], p->decf->pins[0]);
out_pin = f_in->pins[0];
p->dec_thread_valid = true;
if (mp_thread_create(&p->dec_thread, dec_thread, p)) {
@@ -1420,9 +1445,11 @@ struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
goto error;
}
} else {
mp_pin_connect(public_f->ppins[0], p->decf->pins[0]);
out_pin = p->decf->pins[0];
}
mp_pin_connect(public_f->ppins[0], out_pin);
public_f_reset(public_f);
return &p->public;
+6
View File
@@ -43,6 +43,10 @@ struct mp_decoder_wrapper {
struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
struct sh_stream *src);
// Number of extra hw surfaces the player retains on top of the default budget.
// Video only.
void mp_decoder_wrapper_set_extra_hw_frames(struct mp_decoder_wrapper *d, int n);
// Legacy decoder framedrop control.
void mp_decoder_wrapper_set_frame_drops(struct mp_decoder_wrapper *d, int num);
int mp_decoder_wrapper_get_frames_dropped(struct mp_decoder_wrapper *d);
@@ -74,6 +78,8 @@ enum dec_ctrl {
VDCTRL_GET_BFRAMES,
// framedrop mode: 0=none, 1=standard, 2=hrseek
VDCTRL_SET_FRAMEDROP,
// int*: extra hw surfaces retained
VDCTRL_SET_EXTRA_HW_FRAMES,
VDCTRL_CHECK_FORCED_EOF,
};
+241
View File
@@ -0,0 +1,241 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libavutil/buffer.h>
#include "common/common.h"
#include "common/msg.h"
#include "demux/stheader.h"
#include "f_decoder_wrapper.h"
#include "f_enhancement_pair.h"
#include "filter_internal.h"
#include "video/mp_image.h"
// PTS-match tolerance, in seconds.
#define PTS_MATCH_TOLERANCE 1e-6
// Number of frames hold for matching.
#define QUEUE_MAX MP_ENHANCEMENT_PAIR_QUEUE_MAX
struct priv {
struct mp_decoder_wrapper *el_dec;
struct mp_pin *el_in; // el_dec->f->pins[0]
// BL/EL frames decoded but not yet emitted as a pair.
struct mp_image **bl_pending;
int num_bl_pending;
struct mp_image **el_pending;
int num_el_pending;
bool bl_eof;
bool el_eof;
bool el_seen;
};
static int pts_cmp(double a, double b)
{
if (a == MP_NOPTS_VALUE || b == MP_NOPTS_VALUE)
return 0;
if (a < b - PTS_MATCH_TOLERANCE) return -1;
if (a > b + PTS_MATCH_TOLERANCE) return 1;
return 0;
}
// Pull available frames from `pin` into `queue` until either no data is
// ready or the queue is full. Sets *eof if the upstream signaled EOF.
static void drain_pin(struct mp_filter *f, struct mp_pin *pin,
struct mp_image ***queue, int *num, bool *eof)
{
while (!*eof && *num < QUEUE_MAX) {
if (!mp_pin_out_request_data(pin))
return;
struct mp_frame fr = mp_pin_out_read(pin);
if (fr.type == MP_FRAME_EOF) {
*eof = true;
return;
}
if (fr.type != MP_FRAME_VIDEO) {
mp_frame_unref(&fr);
continue;
}
MP_TARRAY_APPEND(f->priv, *queue, *num, fr.data);
}
}
static void pop_head(struct mp_image ***queue, int *num)
{
talloc_free((*queue)[0]);
int remain = *num - 1;
if (remain > 0)
memmove(&(*queue)[0], &(*queue)[1], remain * sizeof((*queue)[0]));
*num = remain;
}
static struct mp_image *take_head(struct mp_image ***queue, int *num)
{
struct mp_image *img = (*queue)[0];
int remain = *num - 1;
if (remain > 0)
memmove(&(*queue)[0], &(*queue)[1], remain * sizeof((*queue)[0]));
*num = remain;
return img;
}
// Downstream code expects the DV RPU and the related color/repr/HDR fields on
// the BL frame. In dual-track sources these are carried on the EL stream, so
// mirror them onto the BL here. This keeps DV bookkeeping local.
static void inherit_dovi_from_el(struct mp_image *bl, struct mp_image *el)
{
if (bl->params.no_dovi || bl->dovi || !el->dovi)
return;
bl->dovi = av_buffer_ref(el->dovi);
if (!bl->dovi)
return;
bl->params.repr.dovi = (void *)bl->dovi->data;
bl->params.repr.sys = el->params.repr.sys;
bl->params.color.primaries = el->params.color.primaries;
bl->params.color.transfer = el->params.color.transfer;
bl->params.color.hdr.min_luma = el->params.color.hdr.min_luma;
bl->params.color.hdr.max_luma = el->params.color.hdr.max_luma;
bl->params.color.hdr.max_pq_y = el->params.color.hdr.max_pq_y;
bl->params.color.hdr.avg_pq_y = el->params.color.hdr.avg_pq_y;
}
static void pair_process(struct mp_filter *f)
{
struct priv *p = f->priv;
struct mp_pin *in = f->ppins[0];
struct mp_pin *out = f->ppins[1];
drain_pin(f, in, &p->bl_pending, &p->num_bl_pending, &p->bl_eof);
drain_pin(f, p->el_in, &p->el_pending, &p->num_el_pending, &p->el_eof);
if (p->num_el_pending > 0)
p->el_seen = true;
while (mp_pin_in_needs_data(out)) {
if (p->num_bl_pending == 0) {
if (p->bl_eof) {
while (p->num_el_pending)
pop_head(&p->el_pending, &p->num_el_pending);
mp_pin_in_write(out, MP_EOF_FRAME);
}
return;
}
struct mp_image *bl = p->bl_pending[0];
int cmp = p->num_el_pending > 0
? pts_cmp(p->el_pending[0]->pts, bl->pts) : 0;
// EL older than BL: its BL partner already left or never arrived.
if (p->num_el_pending > 0 && cmp < 0) {
MP_VERBOSE(f, "dropping stale EL %.6f (oldest BL %.6f)\n",
p->el_pending[0]->pts, bl->pts);
pop_head(&p->el_pending, &p->num_el_pending);
continue;
}
if (p->num_el_pending > 0 && cmp == 0) {
struct mp_image *el = take_head(&p->el_pending, &p->num_el_pending);
take_head(&p->bl_pending, &p->num_bl_pending);
inherit_dovi_from_el(bl, el);
if (bl->params.no_enhancement_layer) {
talloc_free(el);
} else {
bl->enhancement_layer = el;
}
mp_pin_in_write(out, MAKE_FRAME(MP_FRAME_VIDEO, bl));
continue;
}
// No EL match for the oldest BL. Hold BL unless we have affirmative
// evidence no EL is coming.
bool give_up = p->el_eof ||
(p->num_el_pending > 0 && cmp > 0) ||
(p->num_bl_pending >= QUEUE_MAX && p->el_seen);
if (!give_up)
return;
MP_VERBOSE(f, "emitting BL %.6f alone (%s; bl_pending=%d el_pending=%d"
" el head %.6f)\n", bl->pts,
p->el_eof ? "el_eof" :
(p->num_el_pending > 0 && cmp > 0) ? "el_newer" : "queue_full",
p->num_bl_pending, p->num_el_pending,
p->num_el_pending > 0 ? p->el_pending[0]->pts : -1.0);
take_head(&p->bl_pending, &p->num_bl_pending);
bl->enhancement_layer = NULL;
mp_pin_in_write(out, MAKE_FRAME(MP_FRAME_VIDEO, bl));
}
}
static void pair_reset(struct mp_filter *f)
{
struct priv *p = f->priv;
while (p->num_bl_pending)
pop_head(&p->bl_pending, &p->num_bl_pending);
while (p->num_el_pending)
pop_head(&p->el_pending, &p->num_el_pending);
p->bl_eof = false;
p->el_eof = false;
p->el_seen = false;
}
static void pair_destroy(struct mp_filter *f)
{
struct priv *p = f->priv;
while (p->num_bl_pending)
pop_head(&p->bl_pending, &p->num_bl_pending);
while (p->num_el_pending)
pop_head(&p->el_pending, &p->num_el_pending);
// el_dec->f is a child filter, freed by the framework after this returns.
}
static const struct mp_filter_info pair_filter = {
.name = "enhancement_pair",
.priv_size = sizeof(struct priv),
.process = pair_process,
.reset = pair_reset,
.destroy = pair_destroy,
};
struct mp_filter *mp_enhancement_pair_create(struct mp_filter *parent,
struct sh_stream *el_sh)
{
if (!el_sh)
return NULL;
struct mp_filter *f = mp_filter_create(parent, &pair_filter);
if (!f)
return NULL;
mp_filter_add_pin(f, MP_PIN_IN, "in");
mp_filter_add_pin(f, MP_PIN_OUT, "out");
struct priv *p = f->priv;
p->el_dec = mp_decoder_wrapper_create(f, el_sh);
if (p->el_dec)
mp_decoder_wrapper_set_extra_hw_frames(p->el_dec, QUEUE_MAX);
if (!p->el_dec || !mp_decoder_wrapper_reinit(p->el_dec)) {
MP_WARN(f, "Failed to set up enhancement-layer decoder; "
"rendering base layer only.\n");
talloc_free(f);
return NULL;
}
p->el_in = p->el_dec->f->pins[0];
mp_pin_set_manual_connection_for(p->el_in, f);
return f;
}
+41
View File
@@ -0,0 +1,41 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "filter.h"
struct sh_stream;
// Maximum number of decoded frames the pairing filter retains while matching.
#define MP_ENHANCEMENT_PAIR_QUEUE_MAX 8
// Enhancement-layer pairing filter.
//
// Reads base-layer (BL) mp_image frames from its input pin, decodes the
// enhancement-layer (EL) stream `el_sh` via an internal mp_decoder_wrapper,
// and attaches each decoded EL frame as `mpi->enhancement_layer` of the BL
// frame with the matching PTS. Unmatched BL frames are forwarded unchanged
// (BL-only fallback).
//
// `el_sh` must be a dependent sibling of the BL stream via sh_stream_group,
// it is auto-selected by the demuxer when the BL is selected.
//
// 1 input pin (BL frames), 1 output pin (paired BL frames). Returns NULL
// on init failure. The caller should fall back to BL-only rendering.
struct mp_filter *mp_enhancement_pair_create(struct mp_filter *parent,
struct sh_stream *el_sh);
+1 -1
View File
@@ -342,7 +342,7 @@ static bool is_aformat_ok(struct mp_aframe *a, struct mp_aframe *b)
static bool is_vformat_ok(struct mp_image *a, struct mp_image *b)
{
return a->imgfmt == b->imgfmt &&
a->w == b->w && a->h && b->h &&
a->w == b->w && a->h == b->h &&
a->params.p_w == b->params.p_w && a->params.p_h == b->params.p_h &&
a->nominal_fps == b->nominal_fps;
}
+49
View File
@@ -1,6 +1,7 @@
#include "audio/aframe.h"
#include "audio/out/ao.h"
#include "common/global.h"
#include "common/msg.h"
#include "options/m_config.h"
#include "options/m_option.h"
#include "video/out/vo.h"
@@ -9,6 +10,7 @@
#include "f_autoconvert.h"
#include "f_auto_filters.h"
#include "f_enhancement_pair.h"
#include "f_lavfi.h"
#include "f_output_chain.h"
#include "f_utils.h"
@@ -42,6 +44,11 @@ struct chain {
struct mp_user_filter *input, *output, *convert_wrapper;
struct mp_autoconvert *convert;
// Enhancement-layer pair filter wrapper (tail of post_filters). NULL when
// no EL is paired.
struct mp_user_filter *el_pair;
struct sh_stream *el_sh;
struct vo *vo;
struct ao *ao;
@@ -289,6 +296,9 @@ static void relink_filter_list(struct chain *p)
mp_pin_connect(f->pins[0], p->filters_out);
p->filters_out = f->pins[1];
}
mp_pin_set_manual_connection_for(p->filters_in, p->f);
mp_pin_set_manual_connection_for(p->filters_out, p->f);
}
static void output_chain_process(struct mp_filter *f)
@@ -392,6 +402,45 @@ void mp_output_chain_set_vo(struct mp_output_chain *c, struct vo *vo)
update_output_caps(p);
}
void mp_output_chain_set_el_stream(struct mp_output_chain *c,
struct sh_stream *el_sh)
{
struct chain *p = c->f->priv;
mp_assert(p->type == MP_OUTPUT_CHAIN_VIDEO);
if (p->el_sh == el_sh && (!el_sh || p->el_pair))
return;
if (p->el_pair) {
for (int n = 0; n < p->num_post_filters; n++) {
if (p->post_filters[n] == p->el_pair) {
MP_TARRAY_REMOVE_AT(p->post_filters, p->num_post_filters, n);
break;
}
}
talloc_free(p->el_pair->wrapper);
p->el_pair = NULL;
p->el_sh = NULL;
}
if (el_sh) {
struct mp_user_filter *u = create_wrapper_filter(p);
u->name = "el_pair";
u->f = mp_enhancement_pair_create(u->wrapper, el_sh);
if (!u->f) {
MP_WARN(p, "Failed to set up enhancement-layer pairing.\n");
talloc_free(u->wrapper);
} else {
MP_TARRAY_APPEND(p, p->post_filters, p->num_post_filters, u);
p->el_pair = u;
p->el_sh = el_sh;
}
}
relink_filter_list(p);
}
void mp_output_chain_set_ao(struct mp_output_chain *c, struct ao *ao)
{
struct chain *p = c->f->priv;
+6
View File
@@ -88,3 +88,9 @@ double mp_output_get_measured_total_delay(struct mp_output_chain *p);
// Check if deinterlace user filter is inserted
bool mp_output_chain_deinterlace_active(struct mp_output_chain *p);
// Add an enhancement-layer pairing filter at the tail of the chain.
// Pass el_sh=NULL to remove. No-op if the same el_sh is already installed.
struct sh_stream;
void mp_output_chain_set_el_stream(struct mp_output_chain *p,
struct sh_stream *el_sh);
+2 -1
View File
@@ -279,7 +279,8 @@ static bool reorder_planes(struct mp_aframe *mpa, int *reorder,
if (num_planes && !planes)
return false;
uint8_t *old_planes[MP_NUM_CHANNELS];
mp_assert(num_planes <= MP_NUM_CHANNELS);
mp_require(num_planes <= MP_NUM_CHANNELS);
num_planes = MPMIN(num_planes, MP_NUM_CHANNELS);
for (int n = 0; n < num_planes; n++)
old_planes[n] = planes[n];
+1 -1
View File
@@ -485,11 +485,11 @@ static void deinit_connection(struct mp_pin *p)
p->within_conn = p->other->within_conn = false;
mp_assert(!p->other->data_requested); // unused for in pins
mp_assert(!p->other->data.type); // unused for in pins
p->data_requested = false;
if (p->data.type)
MP_VERBOSE(p->owner, "dropping frame due to pin disconnect\n");
if (p->data_requested)
MP_VERBOSE(p->owner, "dropping request due to pin disconnect\n");
p->data_requested = false;
mp_frame_unref(&p->data);
p = p->other->user_conn;
}
+1 -1
View File
@@ -169,7 +169,7 @@ double mp_frame_get_pts(struct mp_frame frame)
void mp_frame_set_pts(struct mp_frame frame, double pts)
{
if (frame_handlers[frame.type].get_pts)
if (frame_handlers[frame.type].set_pts)
frame_handlers[frame.type].set_pts(frame.data, pts);
}
+1
View File
@@ -35,6 +35,7 @@ const struct mp_user_filter_entry *af_list[] = {
&af_lavfi_bridge,
&af_scaletempo,
&af_scaletempo2,
&af_lavfi_tempo,
&af_format,
#if HAVE_RUBBERBAND
&af_rubberband,
+1
View File
@@ -22,6 +22,7 @@ extern const struct mp_user_filter_entry af_lavfi;
extern const struct mp_user_filter_entry af_lavfi_bridge;
extern const struct mp_user_filter_entry af_scaletempo;
extern const struct mp_user_filter_entry af_scaletempo2;
extern const struct mp_user_filter_entry af_lavfi_tempo;
extern const struct mp_user_filter_entry af_format;
extern const struct mp_user_filter_entry af_rubberband;
extern const struct mp_user_filter_entry af_lavcac3enc;
+1 -1
View File
@@ -23,7 +23,7 @@
#include "misc/bstr.h"
#include "options/m_option.h"
#define MP_CMD_DEF_MAX_ARGS 11
#define MP_CMD_DEF_MAX_ARGS 15
#define MP_CMD_OPT_ARG M_OPT_OPTIONAL_PARAM
struct mp_log;
+1 -1
View File
@@ -29,7 +29,7 @@ static bool might_be_subtitle_file(mpv_node *sub_exts, char *file)
mpv_node sub_ext = sub_exts->u.list->values[i];
if (sub_ext.format != MPV_FORMAT_STRING)
continue;
if (!bstrcasecmp0(bstr_get_ext(bstr0(file)), sub_ext.u.string))
if (!bstrcasecmp0(mp_get_ext(bstr0(file)), sub_ext.u.string))
return true;
}
return false;
-37
View File
@@ -2056,43 +2056,6 @@ int mp_input_add_thread_src(struct input_ctx *ictx, void *ctx,
return 0;
}
#define CMD_BUFFER (4 * 4096)
void mp_input_src_feed_cmd_text(struct mp_input_src *src, char *buf, size_t len)
{
struct mp_input_src_internal *in = src->in;
if (!in->cmd_buffer)
in->cmd_buffer = talloc_size(in, CMD_BUFFER);
while (len) {
char *next = memchr(buf, '\n', len);
bool term = !!next;
next = next ? next + 1 : buf + len;
size_t copy = next - buf;
bool overflow = copy > CMD_BUFFER - in->cmd_buffer_size;
if (overflow || in->drop) {
in->cmd_buffer_size = 0;
in->drop = overflow || !term;
MP_WARN(src, "Dropping overlong line.\n");
} else {
memcpy(in->cmd_buffer + in->cmd_buffer_size, buf, copy);
in->cmd_buffer_size += copy;
buf += copy;
len -= copy;
if (term) {
bstr s = {in->cmd_buffer, in->cmd_buffer_size};
s = bstr_strip(s);
struct mp_cmd *cmd = mp_input_parse_cmd_str(src->log, s, "<>");
if (cmd) {
input_lock(src->input_ctx);
queue_cmd(src->input_ctx, cmd);
input_unlock(src->input_ctx);
}
in->cmd_buffer_size = 0;
}
}
}
}
void mp_input_set_repeat_info(struct input_ctx *ictx, int rate, int delay)
{
input_lock(ictx);
-3
View File
@@ -78,9 +78,6 @@ int mp_input_add_thread_src(struct input_ctx *ictx, void *ctx,
// Set src->cancel and src->uninit (if needed) before calling this.
void mp_input_src_init_done(struct mp_input_src *src);
// Feed text data, which will be split into lines of commands.
void mp_input_src_feed_cmd_text(struct mp_input_src *src, char *buf, size_t len);
// Process keyboard input. code is a key code from keycodes.h, possibly
// with modifiers applied. MP_INPUT_RELEASE_ALL is also a valid value.
void mp_input_put_key(struct input_ctx *ictx, int code);
+30 -22
View File
@@ -17,6 +17,7 @@
#include <windows.h>
#include <sddl.h>
#include <stdint.h>
#include "osdep/io.h"
#include "osdep/threads.h"
@@ -55,7 +56,7 @@ static char *get_user_sid(void)
{
char *ssid = NULL;
TOKEN_USER *info = NULL;
HANDLE t;
HANDLE t = INVALID_HANDLE_VALUE;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t))
goto done;
@@ -72,7 +73,7 @@ static char *get_user_sid(void)
ConvertSidToStringSidA(info->User.Sid, &ssid);
done:
if (t)
if (t != INVALID_HANDLE_VALUE)
CloseHandle(t);
talloc_free(info);
return ssid;
@@ -83,7 +84,7 @@ static char *get_integrity_sid(void)
{
char *ssid = NULL;
TOKEN_MANDATORY_LABEL *info = NULL;
HANDLE t;
HANDLE t = INVALID_HANDLE_VALUE;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &t))
goto done;
@@ -100,7 +101,7 @@ static char *get_integrity_sid(void)
ConvertSidToStringSidA(info->Label.Sid, &ssid);
done:
if (t)
if (t != INVALID_HANDLE_VALUE)
CloseHandle(t);
talloc_free(info);
return ssid;
@@ -311,16 +312,21 @@ done:
static void ipc_start_client(struct mp_ipc_ctx *ctx, struct client_arg *client)
{
client->client = mp_new_client(ctx->client_api, client->client_name),
client->log = mp_client_get_log(client->client);
client->client = mp_new_client(ctx->client_api, client->client_name);
if (!client->client)
goto err;
client->log = mp_client_get_log(client->client);
mp_thread client_thr;
if (mp_thread_create(&client_thr, client_thread, client)) {
mpv_destroy(client->client);
CloseHandle(client->client_h);
talloc_free(client);
}
if (mp_thread_create(&client_thr, client_thread, client))
goto err;
mp_thread_detach(client_thr);
return;
err:
mpv_destroy(client->client);
CloseHandle(client->client_h);
talloc_free(client);
}
static void ipc_start_client_json(struct mp_ipc_ctx *ctx, int id, HANDLE h)
@@ -466,21 +472,23 @@ struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
};
if (opts->ipc_client && opts->ipc_client[0]) {
int fd = -1;
HANDLE h = INVALID_HANDLE_VALUE;
bstr str = bstr0(opts->ipc_client);
if (bstr_eatstart0(&str, "fd://") && str.len) {
long long ll = bstrtoll(str, &str, 0);
if (!str.len && ll >= 0 && ll <= INT_MAX)
fd = ll;
long long fd = bstrtoll(str, &str, 0);
if (!str.len && fd >= 0 && fd <= INT_MAX)
h = (HANDLE)_get_osfhandle(fd);
} else if (bstr_eatstart0(&str, "handle://") && str.len) {
long long handle = bstrtoll(str, &str, 0);
if (!str.len && handle >= 0 && handle <= UINT32_MAX)
h = (HANDLE)(uintptr_t)handle;
}
if (fd < 0) {
MP_ERR(arg, "Invalid IPC client argument: '%s'\n", opts->ipc_client);
if (h && h != INVALID_HANDLE_VALUE && (intptr_t)h != -2) {
ipc_start_client_json(arg, -1, h);
} else {
HANDLE h = (HANDLE)_get_osfhandle(fd);
if (h && h != INVALID_HANDLE_VALUE && (intptr_t)h != -2)
ipc_start_client_json(arg, -1, h);
else
MP_ERR(arg, "Invalid IPC client fd: '%d'\n", fd);
MP_ERR(arg, "Invalid IPC client argument: '%s'\n", opts->ipc_client);
}
}
+3 -1
View File
@@ -187,10 +187,12 @@ static void remove_gamepad(struct mp_input_src *src, int id)
{
struct gamepad_priv *p = src->priv;
SDL_GameController *controller = p->controller;
if (!controller)
return;
SDL_Joystick* j = SDL_GameControllerGetJoystick(controller);
SDL_JoystickID jid = SDL_JoystickInstanceID(j);
if (controller && jid == id) {
if (jid == id) {
const char *name = SDL_GameControllerName(controller);
MP_INFO(src, "removed controller: %s\n", name);
SDL_GameControllerClose(controller);
+30 -7
View File
@@ -65,6 +65,7 @@ sources = files(
'audio/filter/af_drop.c',
'audio/filter/af_format.c',
'audio/filter/af_lavcac3enc.c',
'audio/filter/af_lavfi_tempo.c',
'audio/filter/af_scaletempo.c',
'audio/filter/af_scaletempo2.c',
'audio/filter/af_scaletempo2_internals.c',
@@ -106,6 +107,7 @@ sources = files(
'demux/demux_playlist.c',
'demux/demux_raw.c',
'demux/demux_timeline.c',
'demux/dovi_split.c',
'demux/ebml.c',
'demux/packet.c',
'demux/packet_pool.c',
@@ -117,6 +119,7 @@ sources = files(
'filters/f_auto_filters.c',
'filters/f_decoder_wrapper.c',
'filters/f_demux_in.c',
'filters/f_enhancement_pair.c',
'filters/f_hwtransfer.c',
'filters/f_lavfi.c',
'filters/f_output_chain.c',
@@ -167,6 +170,7 @@ sources = files(
'player/client.c',
'player/command.c',
'player/configfiles.c',
'player/discnav.c',
'player/external_files.c',
'player/loadfile.c',
'player/main.c',
@@ -197,6 +201,8 @@ sources = files(
'stream/stream_mpv.c',
'stream/stream_null.c',
'stream/stream_slice.c',
'stream/stream_bstr.c',
'stream/stream_env.c',
## Subtitles
'sub/ass_mp.c',
@@ -465,6 +471,8 @@ features += {'ppoll': cc.has_function('ppoll', args: '-D_GNU_SOURCE',
prefix: '#include <poll.h>')}
features += {'memrchr': cc.has_function('memrchr', args: '-D_GNU_SOURCE',
prefix: '#include <string.h>')}
features += {'clone': cc.has_header_symbol('sched.h', 'clone', args: '-D_GNU_SOURCE')}
features += {'rfork': cc.has_header_symbol('unistd.h', 'rfork_thread')}
optical_devices = {
'windows': 'D:',
@@ -621,11 +629,12 @@ if darwin
subdir(join_paths('TOOLS', 'osxbundle'))
endif
libcurl = dependency('libcurl', version: '>= 7.87.0', required: get_option('libcurl'))
libcurl = dependency('libcurl', version: '>= 7.88.0', required: get_option('libcurl'))
features += {'libcurl': libcurl.found()}
if features['libcurl']
dependencies += [libcurl]
sources += files('stream/stream_curl.c')
sources += files('demux/avio_crypto.c',
'stream/stream_curl.c')
endif
cdda_opt = get_option('cdda').require(
@@ -651,11 +660,22 @@ if features['dvbin']
'stream/stream_dvb.c')
endif
dvda_opt = get_option('dvda').require(
get_option('gpl'),
error_message: 'the build is not GPL!',
)
dvdread = dependency('dvdread', version: '>= 7.1.1', required: dvda_opt)
features += {'dvda': dvdread.found()}
if features['dvda']
dependencies += dvdread
sources += files('stream/stream_dvda.c')
endif
dvdnav_opt = get_option('dvdnav').require(
get_option('gpl'),
error_message: 'the build is not GPL!',
)
dvdnav = dependency('dvdnav', version: '>= 4.2.0', required: dvdnav_opt)
dvdnav = dependency('dvdnav', version: '>= 6.1.0', required: dvdnav_opt)
features += {'dvdnav': dvdnav.found()}
if features['dvdnav']
dependencies += dvdnav
@@ -697,7 +717,7 @@ if features['libavdevice']
dependencies += libavdevice
endif
libbluray = dependency('libbluray', version: '>= 0.5.0', required: get_option('libbluray'))
libbluray = dependency('libbluray', version: '>= 1.0.2', required: get_option('libbluray'))
features += {'libbluray': libbluray.found()}
if features['libbluray']
dependencies += libbluray
@@ -780,7 +800,10 @@ if features['uchardet']
dependencies += uchardet
endif
vapoursynth = dependency('vapoursynth', version: '>= 56', required: get_option('vapoursynth'))
vapoursynth = dependency('vapoursynth-script', version: '>= 56', required: false)
if not vapoursynth.found()
vapoursynth = dependency('vapoursynth', version: '>= 56', required: get_option('vapoursynth'))
endif
features += {'vapoursynth': vapoursynth.found()}
if features['vapoursynth']
dependencies += vapoursynth.partial_dependency(compile_args: true, includes: true)
@@ -1701,7 +1724,7 @@ if rst2man.found()
docutils_wrapper, rst2man,
'--record-dependencies', '@DEPFILE@',
'--strip-elements-with-class=contents',
'@INPUT@', '@OUTPUT@'],
'--output=@OUTPUT@', '@INPUT@'],
depfile: 'mpv.1.dep',
install: true,
install_tag: 'man',
@@ -1748,7 +1771,7 @@ endif
# Set config.h
conf_data = configuration_data()
conf_data.set_quoted('CONFIGURATION', meson.build_options())
conf_data.set_quoted('CONFIGURATION', meson.build_options().strip().replace('\\', '\\\\'))
conf_data.set_quoted('DEFAULT_OPTICAL_DEVICE', optical_device)
# Loop over all features in the build, create a define and add them to config.h
+1
View File
@@ -11,6 +11,7 @@ option('disable-packet-pool', type: 'boolean', value: false, description: 'disab
option('cdda', type: 'feature', value: 'auto', description: 'cdda support (libcdio)')
option('cplugins', type: 'feature', value: 'auto', description: 'C plugins')
option('dvbin', type: 'feature', value: 'auto', description: 'DVB input module')
option('dvda', type: 'feature', value: 'auto', description: 'DVD-Audio support (libdvdread)')
option('dvdnav', type: 'feature', value: 'auto', description: 'dvdnav support')
option('iconv', type: 'feature', value: 'auto', description: 'iconv')
option('javascript', type: 'feature', value: 'auto', description: 'Javascript (MuJS backend)')
+3 -16
View File
@@ -157,6 +157,9 @@ bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right)
struct bstr bstr_splice(struct bstr str, int start, int end)
{
if (!str.len)
return str;
if (start < 0)
start += str.len;
if (end < 0)
@@ -421,22 +424,6 @@ bool bstr_case_endswith(struct bstr s, struct bstr suffix)
return end.len == suffix.len && bstrcasecmp(end, suffix) == 0;
}
struct bstr bstr_strip_ext(struct bstr str)
{
int dotpos = bstrrchr(str, '.');
if (dotpos < 0)
return str;
return (struct bstr){str.start, dotpos};
}
struct bstr bstr_get_ext(struct bstr s)
{
int dotpos = bstrrchr(s, '.');
if (dotpos < 0)
return (struct bstr){NULL, 0};
return bstr_splice(s, dotpos + 1, s.len);
}
static int h_to_i(unsigned char c)
{
if (c >= '0' && c <= '9')
-2
View File
@@ -222,8 +222,6 @@ bool bstr_eatend(struct bstr *s, struct bstr prefix);
bool bstr_case_startswith(struct bstr s, struct bstr prefix);
bool bstr_case_endswith(struct bstr s, struct bstr suffix);
struct bstr bstr_strip_ext(struct bstr str);
struct bstr bstr_get_ext(struct bstr s);
static inline struct bstr bstr_cut(struct bstr str, int n)
{
+2 -1
View File
@@ -22,6 +22,7 @@
#include "common/common.h"
#include "misc/ctype.h"
#include "misc/path_utils.h"
#define L(s) { #s, sizeof(#s) - 1 }
@@ -299,7 +300,7 @@ done:
bstr mp_guess_lang_from_filename(bstr name, int *lang_start, enum track_flags *flags)
{
name = bstr_strip(bstr_strip_ext(name));
name = bstr_strip(mp_strip_ext(name));
if (lang_start)
*lang_start = -1;
+40 -23
View File
@@ -39,22 +39,29 @@
#include <pathcch.h>
#endif
const char *mp_basename(const char *path)
bstr mp_basename_bstr(bstr path)
{
const char *s;
int separator_pos;
#if HAVE_DOS_PATHS
if (!mp_is_url(bstr0(path))) {
s = strrchr(path, '\\');
if (s)
path = s + 1;
s = strrchr(path, ':');
if (s)
path = s + 1;
if (!mp_is_url(path)) {
separator_pos = bstrrchr(path, '\\');
if (separator_pos >= 0)
path = bstr_splice(path, separator_pos + 1, path.len);
separator_pos = bstrrchr(path, ':');
if (separator_pos >= 0)
path = bstr_splice(path, separator_pos + 1, path.len);
}
#endif
s = strrchr(path, '/');
return s ? s + 1 : path;
separator_pos = bstrrchr(path, '/');
if (separator_pos < 0)
return path;
return bstr_splice(path, separator_pos + 1, path.len);
}
const char *mp_basename(const char *path)
{
return mp_basename_bstr(bstr0(path)).start;
}
struct bstr mp_dirname(const char *path)
@@ -82,27 +89,37 @@ void mp_path_strip_trailing_separator(char *path)
path[len - 1] = '\0';
}
char *mp_splitext(const char *path, bstr *root)
/* Return the file extension, excluding the '.'. If root is not NULL, set it to
* the part of the path without extension. So: path == root + "." + extension
* Return NULL if there is no file extension and don't set *root in this case.
*/
static bstr split_ext(bstr path, bstr *root)
{
mp_assert(path);
const char *bn = mp_basename(path);
bstr basename = mp_basename_bstr(path);
// Skip all leading dots, not just for "hidden" unix files, otherwise we
// end up splitting a part of the filename sans leading dot.
bn += strspn(bn, ".");
basename = bstr_splice(basename, bstrspn(basename, "."), basename.len);
const char *split = strrchr(bn, '.');
if (!split || !split[1])
return NULL;
int dot_pos = bstrrchr(basename, '.');
if (dot_pos < 0 || dot_pos == basename.len - 1)
return (bstr){0};
bstr ext = bstr_splice(basename, dot_pos + 1, basename.len);
if (root)
*root = (bstr){(char *)path, split - path};
return (char *)split + 1;
*root = (bstr){path.start, ext.start - 1 - path.start};
return ext;
}
char *mp_strip_ext(void *talloc_ctx, const char *s)
bstr mp_strip_ext(bstr path)
{
bstr root;
return mp_splitext(s, &root) ? bstrto0(talloc_ctx, root) : talloc_strdup(talloc_ctx, s);
bstr root = {0};
split_ext(path, &root);
return root.len ? root : path;
}
bstr mp_get_ext(bstr path)
{
return split_ext(path, NULL);
}
bool mp_path_is_absolute(struct bstr path)
+3 -9
View File
@@ -25,16 +25,10 @@
// Return pointer to filename part of path
const char *mp_basename(const char *path);
bstr mp_basename_bstr(bstr path);
/* Return file extension, excluding the '.'. If root is not NULL, set it to the
* part of the path without extension. So: path == root + "." + extension
* Don't consider it a file extension if the only '.' is the first character.
* Return NULL if no extension and don't set *root in this case.
*/
char *mp_splitext(const char *path, bstr *root);
// This is a shorthand to remove the extension
char *mp_strip_ext(void *talloc_ctx, const char *s);
bstr mp_strip_ext(bstr path);
bstr mp_get_ext(bstr path);
/* Return struct bstr referencing directory part of path, or if that
* would be empty, ".".
+8 -3
View File
@@ -33,6 +33,7 @@
#include "m_property.h"
#include "common/msg.h"
#include "common/common.h"
#include "player/client.h"
static int do_action(const struct m_property *prop_list, const char *name,
int action, void *arg, void *ctx)
@@ -485,7 +486,8 @@ int m_property_read_sub(const struct m_sub_property *props, int action, void *ar
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE};
return M_PROPERTY_OK;
case M_PROPERTY_GET: {
case M_PROPERTY_GET:
case M_PROPERTY_GET_NODE: {
struct mpv_node node;
node.format = MPV_FORMAT_NODE_MAP;
node.u.list = talloc_zero(NULL, mpv_node_list);
@@ -571,7 +573,8 @@ int m_property_read_list(int action, void *arg, int count,
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE};
return M_PROPERTY_OK;
case M_PROPERTY_GET: {
case M_PROPERTY_GET:
case M_PROPERTY_GET_NODE: {
struct mpv_node node;
node.format = MPV_FORMAT_NODE_ARRAY;
node.u.list = talloc_zero(NULL, mpv_node_list);
@@ -582,7 +585,9 @@ int m_property_read_list(int action, void *arg, int count,
sub->format = MPV_FORMAT_NONE;
int r;
r = get_item(n, M_PROPERTY_GET_NODE, sub, ctx);
if (r == M_PROPERTY_NOT_IMPLEMENTED) {
if (r >= 0) {
talloc_steal(node.u.list, node_get_alloc(sub));
} else if (r == M_PROPERTY_NOT_IMPLEMENTED) {
struct m_option opt = {0};
r = get_item(n, M_PROPERTY_GET_TYPE, &opt, ctx);
if (r != M_PROPERTY_OK)
+15 -5
View File
@@ -94,6 +94,7 @@ extern const struct m_obj_list vo_obj_list;
extern const struct m_sub_options ao_conf;
extern const struct m_sub_options dvd_conf;
extern const struct m_sub_options dvda_conf;
extern const struct m_sub_options clipboard_conf;
extern const struct m_sub_options curl_conf;
@@ -316,7 +317,6 @@ const struct m_sub_options mp_subtitle_sub_opts = {
{"sub-gauss", OPT_FLOAT(sub_gauss), M_RANGE(0.0, 3.0)},
{"sub-gray", OPT_BOOL(sub_gray)},
{"sub-ass", OPT_BOOL(ass_enabled), .flags = UPDATE_SUB_HARD},
{"sub-scale", OPT_FLOAT(sub_scale), M_RANGE(0, 100)},
{"sub-scale-signs", OPT_BOOL(sub_scale_signs)},
{"sub-line-spacing", OPT_FLOAT(sub_line_spacing), M_RANGE(-1000, 1000)},
{"sub-ass-line-spacing", OPT_REPLACED("sub-line-spacing")},
@@ -366,7 +366,6 @@ const struct m_sub_options mp_subtitle_sub_opts = {
.sub_scale_with_window = true,
.ass_prune_delay = -1.0,
.teletext_page = 0,
.sub_scale = 1,
.ass_vsfilter_color_compat = 1,
.ass_use_video_data = 2,
.ass_video_aspect = 0,
@@ -381,10 +380,12 @@ const struct m_sub_options mp_subtitle_sub_opts = {
const struct m_sub_options mp_subtitle_shared_sub_opts = {
.opts = (const struct m_option[]){
{"sub-delay", OPT_FLOAT(sub_delay[0])},
{"secondary-sub-delay", OPT_FLOAT(sub_delay[1])},
{"sub-delay", OPT_DOUBLE(sub_delay[0])},
{"secondary-sub-delay", OPT_DOUBLE(sub_delay[1])},
{"sub-pos", OPT_FLOAT(sub_pos[0]), M_RANGE(0.0, 150.0)},
{"secondary-sub-pos", OPT_FLOAT(sub_pos[1]), M_RANGE(0.0, 150.0)},
{"sub-scale", OPT_FLOAT(sub_scale[0]), M_RANGE(0, 100)},
{"secondary-sub-scale", OPT_FLOAT(sub_scale[1]), M_RANGE(0, 100)},
{"sub-visibility", OPT_BOOL(sub_visibility[0])},
{"secondary-sub-visibility", OPT_BOOL(sub_visibility[1])},
{"sub-ass-override", OPT_CHOICE(ass_style_override[0],
@@ -408,6 +409,8 @@ const struct m_sub_options mp_subtitle_shared_sub_opts = {
.sub_visibility[0] = true,
.sub_visibility[1] = true,
.sub_pos[0] = 100,
.sub_scale[0] = 1,
.sub_scale[1] = 1,
.ass_style_override[0] = ASS_STYLE_OVERRIDE_SCALE,
.ass_style_override[1] = ASS_STYLE_OVERRIDE_STRIP,
},
@@ -587,9 +590,13 @@ static const m_option_t mp_opts[] = {
#if HAVE_DVDNAV
{"dvd", OPT_SUBSTRUCT(dvd_opts, dvd_conf)},
#endif
#if HAVE_DVDA
{"dvda", OPT_SUBSTRUCT(dvda_opts, dvda_conf)},
#endif
{"edition", OPT_CHOICE(edition_id, {"auto", -1}), M_RANGE(0, 8190)},
{"flatten-editions", OPT_BOOL(flatten_editions)},
{"disc-menu", OPT_BOOL(disc_menu)},
{"show-dependent-tracks", OPT_BOOL(show_dependent_tracks)},
#if HAVE_LIBBLURAY
{"bluray", OPT_SUBSTRUCT(stream_bluray_opts, stream_bluray_conf)},
@@ -615,6 +622,8 @@ static const m_option_t mp_opts[] = {
{"playlist-start", OPT_CHOICE(playlist_pos, {"auto", -1}, {"no", -1}),
M_RANGE(0, INT_MAX)},
{"playlist-inherit-options", OPT_CHOICE(playlist_inherit_options,
{"no", 0}, {"yes", 1}, {"current", 2})},
{"pause", OPT_BOOL(pause)},
{"keep-open", OPT_CHOICE(keep_open,
@@ -692,7 +701,7 @@ static const m_option_t mp_opts[] = {
{"audio-pitch-correction", OPT_BOOL(pitch_correction)},
// set a-v distance
{"audio-delay", OPT_FLOAT(audio_delay)},
{"audio-delay", OPT_DOUBLE(audio_delay)},
// ------------------------- codec/vfilter options --------------------
@@ -1190,6 +1199,7 @@ static const struct MPOpts mp_default_opts = {
"secondary-sid",
"secondary-sub-delay",
"secondary-sub-pos",
"secondary-sub-scale",
"secondary-sub-ass-override",
"secondary-sub-visibility",
"ab-loop-a",
+11 -3
View File
@@ -110,7 +110,6 @@ struct mp_subtitle_opts {
bool sub_scale_with_window;
bool ass_scale_with_window;
struct osd_style_opts *sub_style;
float sub_scale;
bool sub_scale_signs;
float sub_gauss;
bool sub_gray;
@@ -139,8 +138,9 @@ struct mp_subtitle_opts {
// Options for both primary and secondary subs.
struct mp_subtitle_shared_opts {
float sub_delay[2];
double sub_delay[2];
float sub_pos[2];
float sub_scale[2];
bool sub_visibility[2];
int ass_style_override[2];
};
@@ -164,6 +164,11 @@ struct mp_bluray_opts {
int angle;
};
struct dvda_opts {
char *device;
int page;
};
typedef struct MPOpts {
bool property_print_help;
bool use_terminal;
@@ -255,6 +260,7 @@ typedef struct MPOpts {
int hls_bitrate;
int edition_id;
bool flatten_editions;
bool disc_menu;
bool show_dependent_tracks;
bool initial_audio_sync;
double sync_max_video_change;
@@ -263,7 +269,7 @@ typedef struct MPOpts {
int hr_seek;
float hr_seek_demuxer_offset;
bool hr_seek_framedrop;
float audio_delay;
double audio_delay;
float default_max_pts_correction;
int autosync;
int frame_dropping;
@@ -283,6 +289,7 @@ typedef struct MPOpts {
char **input_commands;
bool consolecontrols;
int playlist_pos;
int playlist_inherit_options;
struct m_rel_time play_start;
struct m_rel_time play_end;
struct m_rel_time play_length;
@@ -419,6 +426,7 @@ typedef struct MPOpts {
struct wingl_opts *wingl_opts;
struct cuda_opts *cuda_opts;
struct dvd_opts *dvd_opts;
struct dvda_opts *dvda_opts;
struct vaapi_opts *vaapi_opts;
struct sws_opts *sws_opts;
struct zimg_opts *zimg_opts;
+14 -1
View File
@@ -7,6 +7,7 @@
#define MP_EXPAND_ARGS(...) __VA_ARGS__
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#include <stdckdint.h>
#define MP_NORETURN [[noreturn]]
#define MP_FALLTHROUGH [[fallthrough]]
#define MP_WARN_UNUSED_RESULT [[nodiscard]]
@@ -41,8 +42,14 @@
#define MP_NONSTRING
#endif
#if __has_attribute(no_sanitize)
#define MP_NO_ASAN __attribute__((no_sanitize("address")))
#else
#define MP_NO_ASAN
#endif
#ifndef NDEBUG
#define MP_ASSERT_UNREACHABLE() assert(!"unreachable")
#define MP_ASSERT_UNREACHABLE() (assert(!"unreachable"), abort())
#elif __has_builtin(__builtin_unreachable)
#define MP_ASSERT_UNREACHABLE() __builtin_unreachable()
#elif defined(_MSC_VER)
@@ -78,4 +85,10 @@
#define MP_SCANF_ATTRIBUTE(a1, a2)
#endif
#if defined(__STDC_VERSION_STDCKDINT_H__) && __STDC_VERSION_STDCKDINT_H__ >= 202311L
#define MP_CKD_MUL(result, a, b) ckd_mul(result, a, b)
#else
#define MP_CKD_MUL(result, a, b) __builtin_mul_overflow(a, b, result)
#endif
#endif
+1 -1
View File
@@ -35,7 +35,7 @@ static void path_init(void)
char *home = getenv("HOME");
char *xdg_config = getenv("XDG_CONFIG_HOME");
if (xdg_config && xdg_config[0]) {
if (xdg_config && xdg_config[0] == '/') {
snprintf(mpv_home, sizeof(mpv_home), "%s/mpv", xdg_config);
} else if (home && home[0]) {
snprintf(mpv_home, sizeof(mpv_home), "%s/.config/mpv", home);
+4 -4
View File
@@ -44,7 +44,7 @@ static void path_init(void)
char *xdg_state = getenv("XDG_STATE_HOME");
bool err = false;
if (xdg_config && xdg_config[0]) {
if (xdg_config && xdg_config[0] == '/') {
err = err || MKPATH(mpv_home, "%s/mpv", xdg_config);
} else if (home && home[0]) {
err = err || MKPATH(mpv_home, "%s/.config/mpv", home);
@@ -56,20 +56,20 @@ static void path_init(void)
err = err || MKPATH(old_cache, "%s/.mpv/cache", home);
}
if (xdg_cache && xdg_cache[0]) {
if (xdg_cache && xdg_cache[0] == '/') {
err = err || MKPATH(mpv_cache, "%s/mpv", xdg_cache);
} else if (home && home[0]) {
err = err || MKPATH(mpv_cache, "%s/.cache/mpv", home);
}
if (xdg_state && xdg_state[0]) {
if (xdg_state && xdg_state[0] == '/') {
err = err || MKPATH(mpv_state, "%s/mpv", xdg_state);
} else if (home && home[0]) {
err = err || MKPATH(mpv_state, "%s/.local/state/mpv", home);
}
char xdg_user_dirs[CONF_MAX];
if (xdg_config && xdg_config[0]) {
if (xdg_config && xdg_config[0] == '/') {
err = err || MKPATH(xdg_user_dirs, "%s/user-dirs.dirs", xdg_config);
} else if (home && home[0]) {
err = err || MKPATH(xdg_user_dirs, "%s/.config/user-dirs.dirs", home);
+140 -63
View File
@@ -32,6 +32,10 @@
extern char **environ;
#if HAVE_CLONE
#include <sched.h>
#endif
#ifdef SIGRTMAX
#define SIGNAL_MAX SIGRTMAX
#else
@@ -84,17 +88,86 @@ static void reset_signals_child(void)
sigprocmask(SIG_SETMASK, &sigmask, NULL);
}
// Returns 0 on any error, valid PID on success.
// This function must be async-signal-safe, as it may be called from a fork().
static pid_t spawn_process(const char *path, struct mp_subprocess_opts *opts,
int src_fds[])
{
int p[2] = {-1, -1};
pid_t fres = 0;
sigset_t sigmask, oldmask;
sigfillset(&sigmask);
pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask);
struct child_args {
const char *path;
struct mp_subprocess_opts *opts;
int *src_fds;
void *child_stack;
int pipe_end;
bool failed;
bool detach;
};
static pid_t spawn_process_inner(const char *path, struct mp_subprocess_opts *opts,
int src_fds[], bool detach, void *stacks[]);
// This function is called from a clone(CLONE_VM)/rfork_thread context where
// the child shares the parent's address space. Use MP_NO_ASAN to avoid false
// positives from ASan when the child writes to shared memory.
MP_NO_ASAN static int child_main(void* args)
{
struct child_args *child_args = args;
const char *path = child_args->path;
struct mp_subprocess_opts *opts = child_args->opts;
int *src_fds = child_args->src_fds;
void *child_stack = child_args->child_stack;
int pipe_end = child_args->pipe_end;
bool detach = child_args->detach;
if (detach) {
setsid();
if (!spawn_process_inner(path, opts, src_fds, false, &child_stack))
goto child_failed;
return 0;
}
reset_signals_child();
for (int n = 0; n < opts->num_fds; n++) {
if (src_fds[n] == opts->fds[n].fd) {
int flags = fcntl(opts->fds[n].fd, F_GETFD);
if (flags == -1)
goto child_failed;
flags &= ~(unsigned)FD_CLOEXEC;
if (fcntl(opts->fds[n].fd, F_SETFD, flags) == -1)
goto child_failed;
} else if (dup2(src_fds[n], opts->fds[n].fd) < 0) {
goto child_failed;
}
}
as_execvpe(path, opts->exe, opts->args, opts->env ? opts->env : environ);
child_failed:
child_args->failed = true;
if (pipe_end >= 0)
(void)write(pipe_end, &(char){1}, 1); // shouldn't be able to fail
return 1;
}
static pid_t spawn_process_inner(const char *path, struct mp_subprocess_opts *opts,
int src_fds[], bool detach, void *stacks[])
{
pid_t fres = 0;
int r = 0;
struct child_args child_args = {
.path = path,
.opts = opts,
.src_fds = src_fds,
.child_stack = stacks[1],
.pipe_end = -1,
.failed = false,
.detach = detach,
};
int p[2] = {-1, -1};
#if HAVE_RFORK
fres = rfork_thread(RFSPAWN, stacks[0], child_main, &child_args);
#elif HAVE_CLONE
fres = clone(child_main, stacks[0], CLONE_VM | CLONE_VFORK | SIGCHLD, &child_args);
#else
// We setup a communication pipe to signal failure. Since the child calls
// exec() and becomes the calling process, we don't know if or when the
// child process successfully ran exec() just from the PID.
@@ -107,57 +180,83 @@ static pid_t spawn_process(const char *path, struct mp_subprocess_opts *opts,
int p_flags = fcntl(p[0], F_GETFD);
if (p_flags == -1 || !FD_CLOEXEC || !(p_flags & FD_CLOEXEC))
goto done; // require CLOEXEC; unknown if fallback would be worth it
child_args.pipe_end = p[1];
fres = fork();
#endif
if (fres < 0) {
fres = 0;
goto done;
}
#if !HAVE_CLONE && !HAVE_RFORK
if (fres == 0) {
// child
reset_signals_child();
for (int n = 0; n < opts->num_fds; n++) {
if (src_fds[n] == opts->fds[n].fd) {
int flags = fcntl(opts->fds[n].fd, F_GETFD);
if (flags == -1)
goto child_failed;
flags &= ~(unsigned)FD_CLOEXEC;
if (fcntl(opts->fds[n].fd, F_SETFD, flags) == -1)
goto child_failed;
} else if (dup2(src_fds[n], opts->fds[n].fd) < 0) {
goto child_failed;
}
}
as_execvpe(path, opts->exe, opts->args, opts->env ? opts->env : environ);
child_failed:
(void)write(p[1], &(char){1}, 1); // shouldn't be able to fail
_exit(1);
_exit(child_main(&child_args));
}
SAFE_CLOSE(p[1]);
int r;
do {
r = read(p[0], &(char){0}, 1);
} while (r < 0 && errno == EINTR);
#endif
// If exec()ing child failed, collect it immediately.
if (r != 0) {
while (waitpid(fres, &(int){0}, 0) < 0 && errno == EINTR) {}
fres = 0;
if (detach || child_args.failed || r != 0) {
int child_status = 0;
while (waitpid(fres, &child_status, 0) < 0 && errno == EINTR) {}
if (r != 0 || !WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0)
fres = 0;
}
done:
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
SAFE_CLOSE(p[0]);
SAFE_CLOSE(p[1]);
return fres;
}
// Returns 0 on any error, valid PID on success.
static pid_t spawn_process(const char *path, struct mp_subprocess_opts *opts,
int src_fds[])
{
bool detach = opts->detach;
void *stacks[2] = {0};
void *ctx = NULL;
#if HAVE_CLONE || HAVE_RFORK
// Pre-allocate stacks so spawn_process_inner() remains async-signal-safe.
// The child only runs a few async-signal-safe calls before execve(), so a
// fixed 32 KiB stack is sufficient; we forgo mmap(MAP_STACK) (and thus the
// auto-growth / guard-page behavior it enables on FreeBSD) in favor of
// plain talloc, which is simpler and async-signal-safe at this call site.
const size_t stack_size = 0x8000;
ctx = talloc_new(NULL);
// stack should be aligned to 16 bytes, which is guaranteed by malloc
stacks[0] = (char *)talloc_size(ctx, stack_size) + stack_size;
if (detach)
stacks[1] = (char *)talloc_size(ctx, stack_size) + stack_size;
#endif
#if !HAVE_RFORK
sigset_t sigmask, oldmask;
sigfillset(&sigmask);
pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask);
#endif
pid_t fres = spawn_process_inner(path, opts, src_fds, detach, stacks);
#if !HAVE_RFORK
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
#endif
talloc_free(ctx);
return fres;
}
void mp_subprocess2(struct mp_subprocess_opts *opts,
struct mp_subprocess_result *res)
{
@@ -216,33 +315,11 @@ void mp_subprocess2(struct mp_subprocess_opts *opts,
src_fds[n] = src_fd;
}
if (opts->detach) {
// If we run it detached, we fork a child to start the process; then
// it exits immediately, letting PID 1 inherit it. So we don't need
// anything else to collect these child PIDs.
sigset_t sigmask, oldmask;
sigfillset(&sigmask);
pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask);
pid_t fres = fork();
if (fres < 0)
goto done;
if (fres == 0) {
// child
setsid();
if (!spawn_process(path, opts, src_fds))
_exit(1);
_exit(0);
}
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
int child_status = 0;
while (waitpid(fres, &child_status, 0) < 0 && errno == EINTR) {}
if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0)
goto done;
} else {
pid = spawn_process(path, opts, src_fds);
if (!pid)
goto done;
}
pid = spawn_process(path, opts, src_fds);
if (!pid)
goto done;
if (opts->detach)
pid = 0;
spawned = true;
+1
View File
@@ -211,6 +211,7 @@ static wchar_t *convert_environ(void *ctx, char **env)
abort();
pos += count;
}
ret[pos] = L'\0';
return ret;
}
+9
View File
@@ -383,6 +383,11 @@ static void w32_register(struct MPContext *mpctx)
REGISTER_AUTOPLAY_HANDLER("dvd", "DVDMovie", "Play DVD movie");
#endif
#if HAVE_DVDA
// Register DVD-Audio handler
REGISTER_AUTOPLAY_HANDLER("dvda", "DVDAudio", "Play DVD-Audio");
#endif
#if HAVE_LIBBLURAY
// Register Blu-ray handler
REGISTER_AUTOPLAY_HANDLER("bluray", "BluRay", "Play Blu-ray movie");
@@ -444,6 +449,7 @@ static void w32_unregister(struct MPContext *mpctx)
reg_del(log, root, KEY_MPV_PROG_ID("bluray"), NULL);
reg_del(log, root, KEY_MPV_PROG_ID("cdda"), NULL);
reg_del(log, root, KEY_MPV_PROG_ID("dvd"), NULL);
reg_del(log, root, KEY_MPV_PROG_ID("dvda"), NULL);
reg_del(log, root, KEY_MPV_PROG_ID("file"), NULL);
reg_del(log, root, KEY_MPV_PROG_ID("url"), NULL);
@@ -453,6 +459,9 @@ static void w32_unregister(struct MPContext *mpctx)
reg_del(log, root, KEY_AUTOPLAY L"\\Handlers\\MpvPlayDVDMovieOnArrival", NULL);
reg_del(log, root, KEY_AUTOPLAY L"\\EventHandlers\\PlayDVDMovieOnArrival",
L"MpvPlayDVDMovieOnArrival");
reg_del(log, root, KEY_AUTOPLAY L"\\Handlers\\MpvPlayDVDAudioOnArrival", NULL);
reg_del(log, root, KEY_AUTOPLAY L"\\EventHandlers\\PlayDVDAudioOnArrival",
L"MpvPlayDVDAudioOnArrival");
reg_del(log, root, KEY_AUTOPLAY L"\\Handlers\\MpvPlayBluRayOnArrival", NULL);
reg_del(log, root, KEY_AUTOPLAY L"\\EventHandlers\\PlayBluRayOnArrival",
L"MpvPlayBluRayOnArrival");
+153 -10
View File
@@ -145,6 +145,10 @@ struct overlay {
struct mp_image *source;
int x, y;
int dw, dh;
enum pl_color_primaries primaries;
enum pl_color_transfer transfer;
float max_luma;
bool video_colorspace;
};
struct hook_handler {
@@ -536,7 +540,7 @@ static int mp_property_filename(void *ctx, struct m_property *prop,
if (strcmp(ka->key, "no-ext") == 0) {
action = ka->action;
arg = ka->arg;
f = mp_strip_ext(filename, f);
f = bstrto0(filename, mp_strip_ext(bstr0(f)));
}
}
int r = m_property_strdup_ro(action, arg, f);
@@ -1130,6 +1134,19 @@ static int mp_property_current_edition(void *ctx, struct m_property *prop,
return m_property_int_ro(action, arg, demuxer->edition);
}
static int mp_property_disc_menu_active(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
struct stream *s = disc_nav_get_stream(mpctx);
if (!s)
return M_PROPERTY_UNAVAILABLE;
struct stream_nav_state st = {0};
if (stream_control(s, STREAM_CTRL_GET_NAV_STATE, &st) < 1)
return M_PROPERTY_UNAVAILABLE;
return m_property_bool_ro(action, arg, st.menu_active);
}
static int mp_property_edition(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -1165,6 +1182,26 @@ static int mp_property_edition(void *ctx, struct m_property *prop,
}
return M_PROPERTY_OK;
}
case M_PROPERTY_SET: {
// For disc demuxers, jump the title directly, we sometimes need to
// react even if the actual edition "value" doesn't change.
if (disc_nav_get_stream(mpctx)) {
int new_ed = *(int *)arg;
if (new_ed < 0 || new_ed >= demuxer->num_editions)
return M_PROPERTY_ERROR;
unsigned new_title = new_ed;
if (stream_control(demuxer->stream, STREAM_CTRL_SET_CURRENT_TITLE,
&new_title) < 1)
return M_PROPERTY_ERROR;
mpctx->opts->edition_id = new_ed;
m_config_notify_change_opt_ptr(mpctx->mconfig,
&mpctx->opts->edition_id);
mp_notify_property(mpctx, "edition");
mp_wakeup_core(mpctx);
return M_PROPERTY_OK;
}
return mp_property_generic_option(mpctx, prop, action, arg);
}
default:
return mp_property_generic_option(mpctx, prop, action, arg);
}
@@ -2115,7 +2152,8 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
{"main-selection", SUB_PROP_INT(order), .unavailable = order < 0},
{"external-filename", SUB_PROP_STR(track->external_filename),
.unavailable = !track->external_filename},
{"ff-index", SUB_PROP_INT(track->ff_index)},
{"ff-index", SUB_PROP_INT(track->ff_index),
.unavailable = track->ff_index == -1},
{"hls-bitrate", SUB_PROP_INT(track->hls_bitrate),
.unavailable = !track->hls_bitrate},
{"program-id", SUB_PROP_INT(sh && sh->num_program_ids ? sh->program_ids[0] : -1),
@@ -4642,6 +4680,7 @@ static const struct m_property mp_properties_base[] = {
{"chapter", mp_property_chapter},
{"edition", mp_property_edition},
{"current-edition", mp_property_current_edition},
{"disc-menu-active", mp_property_disc_menu_active},
{"chapters", mp_property_chapters},
{"editions", mp_property_editions},
{"metadata", mp_property_metadata},
@@ -5097,6 +5136,7 @@ static const struct property_osd_display {
"${?secondary-sub-visibility==yes:visible${?secondary-sid==no: (but no secondary subtitles selected)}}"},
{"sub-forced-events-only", "Forced sub only"},
{"sub-scale", "Sub Scale"},
{"secondary-sub-scale", "Secondary sub scale"},
{"sub-ass-use-video-data", "Subtitle using video properties"},
{"sub-ass-video-aspect-override", "Subtitle aspect override"},
{"sub-ass-override", "ASS subtitle style override"},
@@ -5301,6 +5341,12 @@ static void recreate_overlays(struct MPContext *mpctx)
.h = s->h, .dh = o->dh,
.x = o->x,
.y = o->y,
.bgra = {
.primaries = o->primaries,
.transfer = o->transfer,
.max_luma = o->max_luma,
.video_color_space = o->video_colorspace,
},
};
MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, b);
}
@@ -5362,7 +5408,7 @@ done:
new->num_parts = 0;
}
osd_set_external2(mpctx->osd, new);
osd_set_bitmaps(mpctx->osd, OSDTYPE_EXTERNAL2, new);
mp_wakeup_core(mpctx);
cmd->overlay_osd_current = overlay_next;
}
@@ -5418,6 +5464,13 @@ static void cmd_overlay_add(void *pcmd)
char *fmt = cmd->args[5].v.s;
int w = cmd->args[6].v.i, h = cmd->args[7].v.i, stride = cmd->args[8].v.i;
int dw = cmd->args[9].v.i, dh = cmd->args[10].v.i;
bool video_colorspace = cmd->args[11].v.b;
float max_luma = cmd->args[12].v.f;
int primaries = cmd->args[13].v.i, transfer = cmd->args[14].v.i;
// video_colorspace overrides explicit tags
if (video_colorspace)
primaries = transfer = 0;
if (dw <= 0)
dw = w;
@@ -5431,7 +5484,7 @@ static void cmd_overlay_add(void *pcmd)
MP_ERR(mpctx, "overlay-add: invalid id %d\n", id);
goto error;
}
if (w <= 0 || h <= 0 || stride < w * 4 || (stride % 4) || offset < 0) {
if (w <= 0 || h <= 0 || stride / 4 < w || (stride % 4) || offset < 0) {
MP_ERR(mpctx, "overlay-add: inconsistent parameters\n");
goto error;
}
@@ -5441,6 +5494,10 @@ static void cmd_overlay_add(void *pcmd)
.y = y,
.dw = dw,
.dh = dh,
.primaries = primaries,
.transfer = transfer,
.max_luma = max_luma,
.video_colorspace = video_colorspace,
};
if (!overlay.source)
goto error;
@@ -5500,7 +5557,7 @@ static void overlay_uninit(struct MPContext *mpctx)
return;
for (int id = 0; id < cmd->num_overlays; id++)
replace_overlay(mpctx, id, &(struct overlay){0});
osd_set_external2(mpctx->osd, NULL);
osd_set_bitmaps(mpctx->osd, OSDTYPE_EXTERNAL2, NULL);
for (int n = 0; n < 2; n++)
mp_image_unrefp(&cmd->overlay_osd[n].packed);
}
@@ -7412,6 +7469,50 @@ static void cmd_context_menu(void *p)
vo_control(vo, VOCTRL_SHOW_MENU, NULL);
}
static void cmd_discnav(void *p)
{
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
int action = cmd->args[0].v.i;
struct stream *s = disc_nav_get_stream(mpctx);
if (!s) {
cmd->success = false;
return;
}
struct stream_nav_cmd nc = { .action = action };
struct stream_nav_state pre = {0};
stream_control(s, STREAM_CTRL_GET_NAV_STATE, &pre);
if (action == STREAM_NAV_MOUSE_MOVE || action == STREAM_NAV_MOUSE_CLICK) {
double nx = cmd->args[1].v.d;
double ny = cmd->args[2].v.d;
if (nx >= 0 && ny >= 0) {
if (pre.src_w <= 0 || pre.src_h <= 0 || nx > 1 || ny > 1) {
cmd->success = false;
return;
}
nc.x = (int)(nx * pre.src_w);
nc.y = (int)(ny * pre.src_h);
} else if (!disc_nav_mouse_pos_to_src(mpctx, pre.src_w, pre.src_h,
&nc.x, &nc.y))
{
cmd->success = false;
return;
}
}
if (stream_control(s, STREAM_CTRL_NAV_CMD, &nc) < 1) {
cmd->success = false;
} else if (mpctx->demuxer && stream_nav_action_activates(action)) {
demux_drive_nav(mpctx->demuxer);
}
mp_wakeup_core(mpctx);
}
static void cmd_flush_status_line(void *p)
{
struct mp_cmd_ctx *cmd = p;
@@ -7888,7 +7989,15 @@ const struct mp_cmd_def mp_cmds[] = {
{"h", OPT_INT(v.i)},
{"stride", OPT_INT(v.i)},
{"dw", OPT_INT(v.i), OPTDEF_INT(0)},
{"dh", OPT_INT(v.i), OPTDEF_INT(0)}, }},
{"dh", OPT_INT(v.i), OPTDEF_INT(0)},
{"video_colorspace", OPT_BOOL(v.b),
OPTDEF_INT(0)},
{"max_luma", OPT_FLOAT(v.f),
M_RANGE(0, 10000), OPTDEF_FLOAT(0)},
{"primaries", OPT_CHOICE_C(v.i, pl_csp_prim_names),
OPTDEF_INT(PL_COLOR_PRIM_UNKNOWN)},
{"transfer", OPT_CHOICE_C(v.i, pl_csp_trc_names),
OPTDEF_INT(PL_COLOR_TRC_UNKNOWN)}, }},
{ "overlay-remove", cmd_overlay_remove, { {"id", OPT_INT(v.i)} } },
{ "osd-overlay", cmd_osd_overlay,
@@ -7968,6 +8077,23 @@ const struct mp_cmd_def mp_cmds[] = {
{ "context-menu", cmd_context_menu },
{ "discnav", cmd_discnav,
{ {"action", OPT_CHOICE(v.i,
{"up", STREAM_NAV_UP},
{"down", STREAM_NAV_DOWN},
{"left", STREAM_NAV_LEFT},
{"right", STREAM_NAV_RIGHT},
{"select", STREAM_NAV_SELECT},
{"menu", STREAM_NAV_MENU_ROOT},
{"title-menu", STREAM_NAV_MENU_TITLE},
{"popup", STREAM_NAV_MENU_POPUP},
{"prev", STREAM_NAV_PREV_MENU},
{"mouse-move", STREAM_NAV_MOUSE_MOVE},
{"mouse-click", STREAM_NAV_MOUSE_CLICK})},
{"x", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(-1)},
{"y", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(-1)} },
},
{ "flush-status-line", cmd_flush_status_line, { {"clear", OPT_BOOL(v.b)} } },
{ "notify-property", cmd_notify_property, { {"property", OPT_STRING(v.s)} } },
@@ -8246,9 +8372,6 @@ void mp_option_run_callback(struct MPContext *mpctx, struct mp_option_callback *
int ret = sub_control(sub, SD_CTRL_UPDATE_OPTS, &flags);
if (ret == CONTROL_OK && flags & (UPDATE_SUB_FILT | UPDATE_SUB_HARD)) {
sub_redecode_cached_packets(sub);
sub_reset(sub);
if (track->selected)
reselect_demux_stream(mpctx, track, true);
}
}
}
@@ -8389,9 +8512,17 @@ void mp_option_run_callback(struct MPContext *mpctx, struct mp_option_callback *
mp_switch_track_n(mpctx, i, t, sel, 0);
}
}
if (demuxer->ts_resets_possible) {
reset_playback_state(mpctx);
demux_flush(demuxer);
}
mp_notify_property(mpctx, "current-edition");
print_track_list(mpctx,
mp_tprintf(42, "Selected edition %d:", demuxer->edition));
} else if (disc_nav_get_stream(mpctx)) {
unsigned new_title = opts->edition_id;
stream_control(demuxer->stream,
STREAM_CTRL_SET_CURRENT_TITLE, &new_title);
} else {
if (!mpctx->stop_play)
mpctx->stop_play = PT_CURRENT_ENTRY;
@@ -8407,12 +8538,24 @@ void mp_option_run_callback(struct MPContext *mpctx, struct mp_option_callback *
if (mpctx->playback_initialized && demuxer && demuxer->stream &&
(!strcmp(demuxer->stream->info->name, "bd") ||
!strcmp(demuxer->stream->info->name, "bdmv/bluray"))) {
int angle = opts->stream_bluray_opts->angle - 1;
int angle = opts->stream_bluray_opts->angle;
stream_control(demuxer->stream, STREAM_CTRL_SET_ANGLE, &angle);
}
}
#endif
#if HAVE_DVDA
if (opt_ptr == &opts->dvda_opts->page) {
struct demuxer *demuxer = mpctx->demuxer;
if (mpctx->playback_initialized && demuxer && demuxer->stream &&
(!strcmp(demuxer->stream->info->name, "dvda") ||
!strcmp(demuxer->stream->info->name, "ifo_dvda"))) {
int page = opts->dvda_opts->page;
stream_control(demuxer->stream, STREAM_CTRL_SET_STILL_PAGE, &page);
}
}
#endif
if (opt_ptr == &opts->pause)
set_pause_state(mpctx, opts->pause);
+1 -1
View File
@@ -166,7 +166,7 @@ void mp_load_auto_profiles(struct MPContext *mpctx)
mp_auto_load_profile(mpctx, "protocol",
mp_split_proto(bstr0(mpctx->filename), NULL));
mp_auto_load_profile(mpctx, "extension",
bstr0(mp_splitext(mpctx->filename, NULL)));
mp_get_ext(bstr0(mpctx->filename)));
mp_load_per_file_config(mpctx);
}
+14
View File
@@ -446,6 +446,9 @@ typedef struct MPContext {
struct screenshot_ctx *screenshot_ctx;
struct command_ctx *command_ctx;
struct disc_nav_state *disc_nav;
// Demuxer parked on an indefinite still frame (DVD/BD menu).
bool disc_nav_still_frame;
struct encode_lavc_context *encode_lavc_ctx;
struct mp_option_callback *option_callbacks;
@@ -568,6 +571,7 @@ struct track *select_default_track(struct MPContext *mpctx, int order,
enum stream_type type);
void prefetch_next(struct MPContext *mpctx);
void update_lavfi_complex(struct MPContext *mpctx);
void update_vo_chain_el_pair(struct MPContext *mpctx);
// main.c
int mp_initialize(struct MPContext *mpctx, char **argv);
@@ -670,6 +674,16 @@ void uninit_sub_all(struct MPContext *mpctx);
void update_osd_msg(struct MPContext *mpctx);
bool update_subtitles(struct MPContext *mpctx, double video_pts);
// discnav.c
struct stream;
struct stream_nav_state;
void disc_nav_update(struct MPContext *mpctx);
void disc_nav_reset(struct MPContext *mpctx);
void disc_nav_destroy(struct MPContext *mpctx);
struct stream *disc_nav_get_stream(struct MPContext *mpctx);
bool disc_nav_mouse_pos_to_src(struct MPContext *mpctx, int src_w, int src_h,
int *out_x, int *out_y);
// video.c
void reset_video_state(struct MPContext *mpctx);
int init_video_decoder(struct MPContext *mpctx, struct track *track);
+539
View File
@@ -0,0 +1,539 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdbool.h>
#include "mpv_talloc.h"
#include "common/common.h"
#include "common/msg.h"
#include "input/input.h"
#include "player/command.h"
#include "stream/stream.h"
#include "demux/demux.h"
#include "sub/dec_sub.h"
#include "sub/osd.h"
#include "sub/osd_state.h"
#include "video/mp_image.h"
#include "video/out/vo.h"
#include "core.h"
struct disc_nav_state {
// True while the disc-menu input section is enabled (a menu is on screen).
bool overlay_visible;
// Blu-ray HDMV menu staging. libbluray hands us a pre-composited BGRA IG
// plane via STREAM_CTRL_GET_NAV_OVERLAY; we copy it into bd_image and
// forward through osd_set_bitmaps (OSDTYPE_DISC_MENU).
struct mp_image *bd_image;
uint32_t bd_last_change_id;
struct mp_osd_res bd_last_vo_res;
// Last menu-overlay change_id we forced an OSD redraw for.
uint32_t last_overlay_change_id;
bool overlay_change_seen;
bool drain_was_pending; // last poll saw a boundary awaiting resync
// DVD-only: dvd_subtitle track we force-selected for the menu, and the
// selection it displaced (restored on menu close).
struct track *menu_selected_track;
struct track *menu_saved_track;
// Last disc-driven audio/sub/angle we acted on.
int last_audio_id;
int last_sub_id;
bool last_sub_visible;
int last_angle;
bool track_sync_seen;
// Last disc-discontinuity id we acted on for track sync.
uint32_t last_track_disc_id;
// Last audio id we logged as "not in tracks yet" (avoids log spam).
int last_missing_audio_id;
// Last menu_active pushed through property-change notification.
bool last_menu_active;
bool menu_active_seen;
};
static bool is_dvd_sub_track(struct track *t)
{
return t && t->type == STREAM_SUB && t->stream && t->stream->codec &&
t->stream->codec->codec &&
strcmp(t->stream->codec->codec, "dvd_subtitle") == 0;
}
static struct disc_nav_state *get_state(struct MPContext *mpctx)
{
if (!mpctx->disc_nav)
mpctx->disc_nav = talloc_zero(mpctx, struct disc_nav_state);
return mpctx->disc_nav;
}
// Forget everything tied to the previous playlist entry. Stale state (e.g.
// another disc's discontinuity counter) must not leak into the next file.
void disc_nav_reset(struct MPContext *mpctx)
{
struct disc_nav_state *st = mpctx->disc_nav;
if (!st)
return;
if (st->overlay_visible) {
osd_set_bitmaps(mpctx->osd, OSDTYPE_DISC_MENU, NULL);
mp_input_disable_section(mpctx->input, "discnav");
}
mp_image_unrefp(&st->bd_image);
*st = (struct disc_nav_state){0};
}
void disc_nav_destroy(struct MPContext *mpctx)
{
if (!mpctx->disc_nav)
return;
mp_image_unrefp(&mpctx->disc_nav->bd_image);
TA_FREEP(&mpctx->disc_nav);
}
struct stream *disc_nav_get_stream(struct MPContext *mpctx)
{
if (!mpctx->demuxer || !mpctx->demuxer->stream)
return NULL;
struct stream *s = mpctx->demuxer->stream;
if (!s->info || !s->info->name)
return NULL;
const char *n = s->info->name;
if (strcmp(n, "dvdnav") == 0 || strcmp(n, "ifo_dvdnav") == 0 ||
strcmp(n, "bd") == 0 || strcmp(n, "bdmv/bluray") == 0)
{
return s;
}
return NULL;
}
bool disc_nav_mouse_pos_to_src(struct MPContext *mpctx, int src_w, int src_h,
int *out_x, int *out_y)
{
struct vo *vo = mpctx->video_out;
if (!vo || !vo->config_ok || src_w <= 0 || src_h <= 0)
return false;
int wx, wy, hover;
mp_input_get_mouse_pos(mpctx->input, &wx, &wy, &hover);
struct mp_rect src, dst;
struct mp_osd_res osd; // mandatory out param; ignored
vo_get_src_dst_rects(vo, &src, &dst, &osd);
int dw = dst.x1 - dst.x0;
int dh = dst.y1 - dst.y0;
if (dw <= 0 || dh <= 0)
return false;
double fx = (wx - dst.x0) / (double)dw;
double fy = (wy - dst.y0) / (double)dh;
if (fx < 0 || fx > 1 || fy < 0 || fy > 1)
return false;
*out_x = MPMIN((int)(fx * src_w), src_w - 1);
*out_y = MPMIN((int)(fy * src_h), src_h - 1);
return true;
}
// Push the current menu/highlight state to the dvd_subtitle decoders.
static void push_dvd_overlay(struct MPContext *mpctx, struct stream_nav_state *nav)
{
for (int n = 0; n < mpctx->num_tracks; n++) {
struct track *t = mpctx->tracks[n];
if (!is_dvd_sub_track(t) || !t->d_sub)
continue;
sub_control(t->d_sub, SD_CTRL_APPLY_DVDNAV, nav);
}
}
static void push_bd_overlay(struct MPContext *mpctx, struct stream *s,
struct stream_nav_state *nav, bool visible)
{
struct disc_nav_state *st = get_state(mpctx);
if (!visible) {
if (st->overlay_visible)
osd_set_bitmaps(mpctx->osd, OSDTYPE_DISC_MENU, NULL);
st->bd_last_change_id = 0;
st->bd_last_vo_res = (struct mp_osd_res){0};
return;
}
if (nav->src_w <= 0 || nav->src_h <= 0)
return;
// Skip the work when nothing the renderer cares about changed.
struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd);
if (st->overlay_visible &&
st->bd_last_change_id == nav->change_id &&
osd_res_equals(vo_res, st->bd_last_vo_res))
return;
if (!st->bd_image ||
st->bd_image->w != nav->src_w || st->bd_image->h != nav->src_h)
{
mp_image_unrefp(&st->bd_image);
st->bd_image = mp_image_alloc(IMGFMT_BGRA, nav->src_w, nav->src_h);
if (!st->bd_image)
return;
talloc_steal(st, st->bd_image);
}
// The OSD/VO may still hold references to the previous frame's pixels.
if (!mp_image_make_writeable(st->bd_image))
return;
struct mp_image *img = st->bd_image;
struct stream_nav_overlay_req req = {
.w = img->w,
.h = img->h,
.stride = img->stride[0],
.dst = img->planes[0],
};
if (stream_control(s, STREAM_CTRL_GET_NAV_OVERLAY, &req) < 1)
return;
struct sub_bitmap part = {
.bitmap = img->planes[0],
.stride = img->stride[0],
.w = req.w,
.h = req.h,
.dw = req.w,
.dh = req.h,
};
struct sub_bitmaps imgs = {
.format = SUBBITMAP_BGRA,
.parts = &part,
.num_parts = 1,
.packed = img,
.packed_w = img->w,
.packed_h = img->h,
.change_id = nav->change_id ? (int)nav->change_id : 1,
.video_color_space = true,
};
osd_rescale_bitmaps(&imgs, nav->src_w, nav->src_h, vo_res, 0);
osd_set_bitmaps(mpctx->osd, OSDTYPE_DISC_MENU, &imgs);
st->bd_last_change_id = nav->change_id;
st->bd_last_vo_res = vo_res;
}
// Sync demuxer->edition with what the disc is actually playing.
static void sync_current_edition(struct MPContext *mpctx, struct stream *s,
struct stream_nav_state *nav)
{
struct demuxer *demuxer = mpctx->demuxer;
if (!demuxer || demuxer->num_editions <= 0 || !demuxer->desc ||
strcmp(demuxer->desc->name, "disc") != 0)
return;
int desired = demuxer->edition;
if (nav->menu_active) {
desired = demuxer->num_editions - 1;
} else {
unsigned title;
if (stream_control(s, STREAM_CTRL_GET_CURRENT_TITLE, &title) >= 1 &&
(int)title < demuxer->num_editions - 1)
{
desired = (int)title;
}
}
if (desired != demuxer->edition) {
MP_VERBOSE(s, "current-edition %d->%d "
"(menu_active=%d)\n",
demuxer->edition, desired, nav->menu_active);
demuxer->edition = desired;
mp_notify_property(mpctx, "current-edition");
}
}
// Catch playlist/title jumps driven by the disc itself (menu buttons, HDMV
// bytecode, end of title, dvdnav HOP_CHANNEL, etc.).
static void check_async_discontinuity(struct MPContext *mpctx, struct stream *s,
struct stream_nav_state *nav)
{
struct disc_nav_state *st = get_state(mpctx);
if (!mpctx->demuxer)
return;
bool new_drain = nav->drain_pending && !st->drain_was_pending;
st->drain_was_pending = nav->drain_pending;
if (!new_drain)
return;
// The disc jumped on its own, resync through the regular seek path.
// demux_disc releases the held boundary instead of repositioning the VM.
double t = 0;
if (stream_control(s, STREAM_CTRL_GET_CURRENT_TIME, &t) < 1)
t = 0;
MP_VERBOSE(s, "disc jumped (id %u), resync seek to %f\n",
nav->discontinuity_id, t);
queue_seek(mpctx, MPSEEK_ABSOLUTE, t, MPSEEK_KEYFRAME, 0);
}
static struct track *find_track_by_demuxer_id(struct MPContext *mpctx,
enum stream_type type, int id)
{
if (id < 0)
return NULL;
for (int n = 0; n < mpctx->num_tracks; n++) {
struct track *t = mpctx->tracks[n];
if (t->type == type && t->stream && t->stream->demuxer_id == id)
return t;
}
return NULL;
}
static void sync_disc_track_selection(struct MPContext *mpctx, struct stream *s,
struct stream_nav_state *nav)
{
struct disc_nav_state *st = get_state(mpctx);
bool first = !st->track_sync_seen;
bool hopped = nav->discontinuity_id != st->last_track_disc_id;
st->last_track_disc_id = nav->discontinuity_id;
st->track_sync_seen = true;
if (!first && hopped && nav->no_audio &&
mpctx->opts->stream_id[0][STREAM_AUDIO] != -2)
{
// The disc hopped into a domain without any audio stream. Deselect audio.
if (mpctx->current_track[0][STREAM_AUDIO])
mp_switch_track_n(mpctx, 0, STREAM_AUDIO, NULL, 0);
st->last_audio_id = -1;
} else if (!first && (hopped || nav->active_audio_id != st->last_audio_id) &&
mpctx->opts->stream_id[0][STREAM_AUDIO] == -1)
{
struct track *t = find_track_by_demuxer_id(mpctx, STREAM_AUDIO,
nav->active_audio_id);
if (t) {
if (mpctx->current_track[0][STREAM_AUDIO] != t) {
MP_VERBOSE(s, "disc audio -> demuxer_id 0x%x\n",
nav->active_audio_id);
mp_switch_track_n(mpctx, 0, STREAM_AUDIO, t, 0);
}
st->last_audio_id = nav->active_audio_id;
} else if (nav->active_audio_id >= 0) {
if (st->last_missing_audio_id != nav->active_audio_id) {
st->last_missing_audio_id = nav->active_audio_id;
MP_VERBOSE(s, "disc audio 0x%x not in tracks yet\n",
nav->active_audio_id);
}
}
} else if (first) {
st->last_audio_id = nav->active_audio_id;
if (mpctx->opts->stream_id[0][STREAM_AUDIO] == -1) {
struct track *t = find_track_by_demuxer_id(mpctx, STREAM_AUDIO,
nav->active_audio_id);
if (t && mpctx->current_track[0][STREAM_AUDIO] != t) {
MP_VERBOSE(s, "initial disc audio -> demuxer_id 0x%x\n",
nav->active_audio_id);
mp_switch_track_n(mpctx, 0, STREAM_AUDIO, t, 0);
}
}
}
if (!first && (hopped ||
nav->active_sub_id != st->last_sub_id ||
nav->sub_visible != st->last_sub_visible) &&
!nav->menu_active && !st->menu_selected_track &&
mpctx->opts->stream_id[0][STREAM_SUB] == -1)
{
if (nav->sub_visible) {
struct track *t = find_track_by_demuxer_id(mpctx, STREAM_SUB,
nav->active_sub_id);
if (t) {
if (mpctx->current_track[0][STREAM_SUB] != t) {
MP_VERBOSE(s, "disc sub -> demuxer_id 0x%x\n",
nav->active_sub_id);
mp_switch_track_n(mpctx, 0, STREAM_SUB, t, 0);
}
st->last_sub_id = nav->active_sub_id;
st->last_sub_visible = true;
} else if (nav->active_sub_id >= 0) {
MP_TRACE(s, "disc sub 0x%x not in tracks yet\n",
nav->active_sub_id);
}
} else {
if (mpctx->current_track[0][STREAM_SUB]) {
MP_VERBOSE(s, "disc sub -> off\n");
mp_switch_track_n(mpctx, 0, STREAM_SUB, NULL, 0);
}
st->last_sub_id = nav->active_sub_id;
st->last_sub_visible = false;
}
} else if (first) {
st->last_sub_id = nav->active_sub_id;
st->last_sub_visible = nav->sub_visible;
}
if (first) {
st->last_angle = nav->angle;
} else if (nav->angle > 0 && nav->angle != st->last_angle) {
MP_VERBOSE(s, "disc angle %d -> %d (of %d)\n",
st->last_angle, nav->angle, nav->num_angles);
st->last_angle = nav->angle;
mp_notify_property(mpctx, "angle");
}
}
// Make sure a dvd_subtitle track is selected while a menu is visible,
// so the SPU graphics decoded by sd_lavc reach the OSD.
static void ensure_menu_sub_selection(struct MPContext *mpctx, bool menu_on)
{
struct disc_nav_state *st = get_state(mpctx);
struct track *cur = mpctx->current_track[0][STREAM_SUB];
// If the track list got rebuilt under us (e.g. another file/disc loaded
// mid-session) the cached pointers could be stale. Trust only what we
// can still see in mpctx->tracks.
for (int i = 0; i < 2; i++) {
struct track **slot = i ? &st->menu_saved_track : &st->menu_selected_track;
if (!*slot)
continue;
bool still_present = false;
for (int n = 0; n < mpctx->num_tracks; n++) {
if (mpctx->tracks[n] == *slot) {
still_present = true;
break;
}
}
if (!still_present)
*slot = NULL;
}
if (menu_on) {
// Re-checked every frame; other selectors (stream auto-select, slave
// reopens) can change the sub under us.
if (st->menu_selected_track && cur == st->menu_selected_track)
return;
// A dvd_subtitle track is already active; nothing to force.
if (is_dvd_sub_track(cur))
return;
if (mpctx->opts->stream_id[0][STREAM_SUB] == -2)
return;
struct track *pick = NULL;
for (int n = 0; n < mpctx->num_tracks; n++) {
if (is_dvd_sub_track(mpctx->tracks[n])) {
pick = mpctx->tracks[n];
break;
}
}
if (!pick)
return;
// Remember the displaced selection only on the first override.
if (!st->menu_selected_track)
st->menu_saved_track = cur;
mp_switch_track_n(mpctx, 0, STREAM_SUB, pick, 0);
st->menu_selected_track = pick;
} else {
if (!st->menu_selected_track)
return;
// Only revert if our override is still the active selection.
if (cur == st->menu_selected_track)
mp_switch_track_n(mpctx, 0, STREAM_SUB, st->menu_saved_track, 0);
st->menu_selected_track = NULL;
st->menu_saved_track = NULL;
}
}
void disc_nav_update(struct MPContext *mpctx)
{
struct stream *s = disc_nav_get_stream(mpctx);
if (!s && !mpctx->disc_nav)
return;
struct disc_nav_state *st = get_state(mpctx);
struct stream_nav_state nav = {0};
bool have = s && stream_control(s, STREAM_CTRL_GET_NAV_STATE, &nav) >= 1;
bool still = have && nav.still_active;
if (s && still != mpctx->disc_nav_still_frame)
MP_VERBOSE(s, "still_frame %d->%d\n",
mpctx->disc_nav_still_frame, still);
mpctx->disc_nav_still_frame = still;
// The BD-J VM progresses only on reads, pump them while playback idles
// at EOF or a held still, per libbluray's requirement. Our core wakes up
// at 1s intervals even at pause, but that's not enough for fluid button
// navigation. So, we always drive the VM at least at 20Hz.
if (have && mpctx->demuxer) {
bool bd_idle = (strcmp(s->info->name, "bd") == 0 ||
strcmp(s->info->name, "bdmv/bluray") == 0) &&
(still || (mpctx->video_status == STATUS_EOF &&
mpctx->audio_status == STATUS_EOF));
if (bd_idle || (nav.menu_active && mpctx->paused)) {
if (bd_idle)
demux_drive_nav(mpctx->demuxer);
mp_set_timeout(mpctx, 0.05);
}
}
if (!have) {
if (st->overlay_visible) {
osd_set_bitmaps(mpctx->osd, OSDTYPE_DISC_MENU, NULL);
mp_input_disable_section(mpctx->input, "discnav");
st->overlay_visible = false;
}
st->bd_last_change_id = 0;
st->bd_last_vo_res = (struct mp_osd_res){0};
st->menu_selected_track = NULL;
st->menu_saved_track = NULL;
st->overlay_change_seen = false;
if (st->menu_active_seen && st->last_menu_active) {
st->last_menu_active = false;
mp_notify_property(mpctx, "disc-menu-active");
}
return;
}
if (!st->menu_active_seen || nav.menu_active != st->last_menu_active) {
st->menu_active_seen = true;
st->last_menu_active = nav.menu_active;
mp_notify_property(mpctx, "disc-menu-active");
}
check_async_discontinuity(mpctx, s, &nav);
sync_current_edition(mpctx, s, &nav);
sync_disc_track_selection(mpctx, s, &nav);
bool is_bd = strcmp(s->info->name, "bd") == 0 || strcmp(s->info->name, "bdmv/bluray") == 0;
bool visible = nav.menu_active &&
(is_bd || (mp_rect_w(nav.hl.rect) > 0 && mp_rect_h(nav.hl.rect) > 0));
if (is_bd) {
push_bd_overlay(mpctx, s, &nav, visible);
} else {
push_dvd_overlay(mpctx, &nav);
ensure_menu_sub_selection(mpctx, nav.menu_active);
}
if (!st->overlay_change_seen || nav.change_id != st->last_overlay_change_id) {
st->overlay_change_seen = true;
st->last_overlay_change_id = nav.change_id;
osd_changed(mpctx->osd);
}
if (visible != st->overlay_visible) {
MP_VERBOSE(s, "overlay %s\n", visible ? "on" : "off");
if (visible) {
mp_input_enable_section(mpctx->input, "discnav",
MP_INPUT_ON_TOP | MP_INPUT_ALLOW_VO_DRAGGING | MP_INPUT_ALLOW_HIDE_CURSOR);
} else {
mp_input_disable_section(mpctx->input, "discnav");
}
st->overlay_visible = visible;
}
}
Loaded 100 of 185 files, more files were not shown because too many files have changed in this diff. Show more