Compare commits
292
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
182fa6ca49 | ||
|
|
c318236b88 | ||
|
|
e1cf77c0e9 | ||
|
|
93451ef64f | ||
|
|
795cd15438 | ||
|
|
96b9d84b60 | ||
|
|
79186bd167 | ||
|
|
6f6bedec72 | ||
|
|
a47452aac0 | ||
|
|
0c87e46c83 | ||
|
|
15750e1212 | ||
|
|
16896f33c0 | ||
|
|
dd18d1eabc | ||
|
|
abbbffe0dd | ||
|
|
4a110f39cf | ||
|
|
02ec593037 | ||
|
|
fee9b14d46 | ||
|
|
4109805d0c | ||
|
|
c855d13170 | ||
|
|
daaab42166 | ||
|
|
b3e3d1774e | ||
|
|
34c649b864 | ||
|
|
a2448f3b81 | ||
|
|
263bea5261 | ||
|
|
8c6680a868 | ||
|
|
be04215d3f | ||
|
|
76c0c5de03 | ||
|
|
bb19b0fc36 | ||
|
|
c9422ff88f | ||
|
|
c7859fe5b6 | ||
|
|
b17b4371f8 | ||
|
|
15f8a74aa4 | ||
|
|
7c13c58ffa | ||
|
|
61416bd8b5 | ||
|
|
b48fb6c86c | ||
|
|
2c8d954d21 | ||
|
|
8356c7f674 | ||
|
|
47a53910a5 | ||
|
|
b0c4e8cab6 | ||
|
|
582810ac1d | ||
|
|
dbe496e6be | ||
|
|
af53a9dc4b | ||
|
|
4084e7609f | ||
|
|
3897b3a579 | ||
|
|
fbcced5bf0 | ||
|
|
5f19209953 | ||
|
|
f0bc30aaf1 | ||
|
|
c625405ddc | ||
|
|
70174945a4 | ||
|
|
4725c2e492 | ||
|
|
216e26c871 | ||
|
|
a5682eb2ad | ||
|
|
5713c30160 | ||
|
|
816eca1fef | ||
|
|
652ca81af9 | ||
|
|
8d36904e05 | ||
|
|
e204c1cfd9 | ||
|
|
af9a11a8e5 | ||
|
|
3a8b6995e9 | ||
|
|
b17feb1f79 | ||
|
|
fbd0e047b1 | ||
|
|
e2b7a983fa | ||
|
|
5db1db2852 | ||
|
|
3ba4f76096 | ||
|
|
8b583e9a0d | ||
|
|
fad4b3f321 | ||
|
|
b573edaaa1 | ||
|
|
fbca538bfe | ||
|
|
f73dd13376 | ||
|
|
57b0a0a695 | ||
|
|
654e9382c0 | ||
|
|
1c2ff7d56b | ||
|
|
66ff2efbc7 | ||
|
|
345e783643 | ||
|
|
e89a491052 | ||
|
|
413153fd4e | ||
|
|
65d138fd57 | ||
|
|
9f9f8c4dd4 | ||
|
|
b955aa28f3 | ||
|
|
228f3109fd | ||
|
|
3b4caf0f8b | ||
|
|
49418246f3 | ||
|
|
a87a678573 | ||
|
|
e7191f2a65 | ||
|
|
b6d3434493 | ||
|
|
e034d612cf | ||
|
|
e167836802 | ||
|
|
52bb166f30 | ||
|
|
7b8915bc1d | ||
|
|
f4d13e1c2c | ||
|
|
513d3407d4 | ||
|
|
dd5d17d328 | ||
|
|
21277b0ccf | ||
|
|
1d15686142 | ||
|
|
55146ecd14 | ||
|
|
612bf5b6e8 | ||
|
|
22c09166d7 | ||
|
|
63ada87ec7 | ||
|
|
56b6200986 | ||
|
|
74356c0fc6 | ||
|
|
1388a45395 | ||
|
|
24c1cc52a3 | ||
|
|
7f72f64b7f | ||
|
|
48e6c35c0e | ||
|
|
390ebe2eac | ||
|
|
b27573a239 | ||
|
|
762c5999c2 | ||
|
|
5910e9c2ba | ||
|
|
8849c4963f | ||
|
|
5659a9a9c8 | ||
|
|
f76519ebd8 | ||
|
|
05d45f4955 | ||
|
|
42d50061f6 | ||
|
|
731229bd2b | ||
|
|
19215efe61 | ||
|
|
97db44f89f | ||
|
|
8c67647b50 | ||
|
|
0fb136f685 | ||
|
|
6f6c29b0b4 | ||
|
|
1c4adc9819 | ||
|
|
11a9054b1d | ||
|
|
7ff087a6b0 | ||
|
|
e61d819bf6 | ||
|
|
893ffae982 | ||
|
|
0583bb892e | ||
|
|
181090e70d | ||
|
|
e2eaf31ce2 | ||
|
|
e4abaaa26b | ||
|
|
23d39a6048 | ||
|
|
11e6a50025 | ||
|
|
15de3e6193 | ||
|
|
8ab3f8b66d | ||
|
|
25451bcc11 | ||
|
|
94335ab87a | ||
|
|
e5486b96d7 | ||
|
|
115b87b521 | ||
|
|
5b087dfb18 | ||
|
|
e517fd6043 | ||
|
|
68387ea859 | ||
|
|
cc763d17dc | ||
|
|
cef62fbc39 | ||
|
|
7f93b06797 | ||
|
|
02baec9701 | ||
|
|
ab34ec35d2 | ||
|
|
4c220ffd95 | ||
|
|
6a0ef86e00 | ||
|
|
ba92eb63c4 | ||
|
|
8e88661257 | ||
|
|
28a7932599 | ||
|
|
ed9c05d06e | ||
|
|
9281bccfdf | ||
|
|
c8c7d91a8e | ||
|
|
7bf2d9cee6 | ||
|
|
318e4e21fb | ||
|
|
f6df7dfd8b | ||
|
|
33111f3212 | ||
|
|
7a94ec5719 | ||
|
|
7aa8580cab | ||
|
|
c02751a0bf | ||
|
|
dc8101574d | ||
|
|
0279c0f07d | ||
|
|
c22cac669e | ||
|
|
6d64f2867c | ||
|
|
1c467ad4f9 | ||
|
|
a3d4b59197 | ||
|
|
99b4c12ccc | ||
|
|
abfc3b21c8 | ||
|
|
8930fdb132 | ||
|
|
f53e8f3fed | ||
|
|
103aee7261 | ||
|
|
6b4a205c99 | ||
|
|
95f8f18d13 | ||
|
|
116c8ecf3c | ||
|
|
998c20af67 | ||
|
|
6f724890e0 | ||
|
|
29c693c039 | ||
|
|
5058e6e737 | ||
|
|
5330cae57e | ||
|
|
81037dae3a | ||
|
|
0480011ac4 | ||
|
|
6c4480cbfd | ||
|
|
314e80f2ec | ||
|
|
25606307ff | ||
|
|
bc5684c2cf | ||
|
|
3f1b23abd0 | ||
|
|
5abce15f1c | ||
|
|
4dbc235dba | ||
|
|
523aea99ad | ||
|
|
42149e9eac | ||
|
|
508d25590d | ||
|
|
62f1494661 | ||
|
|
a01e391851 | ||
|
|
be8d1eb3d6 | ||
|
|
a8a1cdbbbb | ||
|
|
069a6f004e | ||
|
|
5d15a41b0f | ||
|
|
245652c34c | ||
|
|
fe4eaf7c9d | ||
|
|
5c97ee2a48 | ||
|
|
aa845b965a | ||
|
|
8395d89069 | ||
|
|
9dbf5d681d | ||
|
|
089c172dff | ||
|
|
4fa3c38983 | ||
|
|
1a09154988 | ||
|
|
9ffc0cd861 | ||
|
|
df3b76b018 | ||
|
|
2057e34f45 | ||
|
|
381e7eb155 | ||
|
|
4eaef66f4c | ||
|
|
29c26b5bc5 | ||
|
|
9bfdb3d67f | ||
|
|
d8c82c3b16 | ||
|
|
1cf081e66c | ||
|
|
80865a7d1f | ||
|
|
a40319a514 | ||
|
|
fb6c700ecb | ||
|
|
ccfe4871db | ||
|
|
449944e25f | ||
|
|
fc26cd23c0 | ||
|
|
7da362dfb5 | ||
|
|
6b623e29cd | ||
|
|
669f431cf0 | ||
|
|
f5a894de87 | ||
|
|
8c6ad1b002 | ||
|
|
ae99e39239 | ||
|
|
0251783b95 | ||
|
|
23b9e96e9b | ||
|
|
351291d583 | ||
|
|
0d68a842a0 | ||
|
|
2a49cc01d6 | ||
|
|
1d8cb602ac | ||
|
|
07c8c16bef | ||
|
|
70894ae039 | ||
|
|
c75b8d2cca | ||
|
|
289723b41a | ||
|
|
c4207644ba | ||
|
|
2339eb7276 | ||
|
|
2bd9c3229f | ||
|
|
1bcc17a7be | ||
|
|
dde8d8c4a0 | ||
|
|
efe8644a93 | ||
|
|
2d5dfb343a | ||
|
|
f8150c78d5 | ||
|
|
02491a3949 | ||
|
|
715d2b7cf2 | ||
|
|
3159500884 | ||
|
|
4acaa77de5 | ||
|
|
9d828d5e55 | ||
|
|
306546f35f | ||
|
|
7bc56a77a9 | ||
|
|
688b2738c6 | ||
|
|
49b06cc337 | ||
|
|
6b48326ccf | ||
|
|
d849fd60d0 | ||
|
|
de1c73a44b | ||
|
|
5fb6a76ddf | ||
|
|
53796ca2ea | ||
|
|
f88bcd25c7 | ||
|
|
9baeefe635 | ||
|
|
cab6fbe222 | ||
|
|
89da9deafe | ||
|
|
65a1852ba3 | ||
|
|
662d41093c | ||
|
|
7d245fd100 | ||
|
|
bda1dcaa78 | ||
|
|
3d4893a893 | ||
|
|
304426c390 | ||
|
|
fb5345809d | ||
|
|
c1c047f8d6 | ||
|
|
894c350f83 | ||
|
|
1ad719ff19 | ||
|
|
f232963c61 | ||
|
|
a7d0ebaf76 | ||
|
|
dddafe8b60 | ||
|
|
5e4b06d977 | ||
|
|
5dfdd39997 | ||
|
|
6444c05059 | ||
|
|
e7f5d8f9c8 | ||
|
|
c42393a280 | ||
|
|
c9cf510d6a | ||
|
|
6a5e514509 | ||
|
|
a24227d7ad | ||
|
|
f29fcafb81 | ||
|
|
579a62c2ba | ||
|
|
47f81c8d7e | ||
|
|
43b14a4c9f | ||
|
|
71ebd08406 | ||
|
|
85fe21d290 | ||
|
|
f88f423829 | ||
|
|
1ad1322335 | ||
|
|
0fc000b10b |
No files matched your search
+147
-103
@@ -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.
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
add `--disc-menu` option
|
||||
add `disc-menu-active` property
|
||||
add `discnav` command
|
||||
@@ -0,0 +1,2 @@
|
||||
add `dvda://` protocol for DVD-Audio playback
|
||||
add `--dvda-device` and `--dvda-page` options
|
||||
@@ -0,0 +1 @@
|
||||
add `vf=format=enhancement-layer` option
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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::
|
||||
|
||||
|
||||
@@ -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``
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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
@@ -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
@@ -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] = '?';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(©);
|
||||
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(©);
|
||||
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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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, ".".
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -211,6 +211,7 @@ static wchar_t *convert_environ(void *ctx, char **env)
|
||||
abort();
|
||||
pos += count;
|
||||
}
|
||||
ret[pos] = L'\0';
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in new issue
Block a user