mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-08-08 21:28:38 +03:00
Compare commits
175
Commits
2021.04.22
...
2021.06.01
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3de7c2ce9a | ||
|
|
bc6b9bcd65 | ||
|
|
6e6390321c | ||
|
|
4040428efc | ||
|
|
cc1dfc9373 | ||
|
|
14eb1ee1cb | ||
|
|
879e7199bb | ||
|
|
d89da64b1d | ||
|
|
5dcd8e1d88 | ||
|
|
10bb7e51e8 | ||
|
|
b0089e8992 | ||
|
|
a3ed14cbaf | ||
|
|
9dee4df559 | ||
|
|
adddc50cbf | ||
|
|
46c43ffc9d | ||
|
|
37a3bb66a7 | ||
|
|
337e0c62f8 | ||
|
|
885cc0b75c | ||
|
|
46953e7e6e | ||
|
|
ae8f99e648 | ||
|
|
077c476276 | ||
|
|
835a1478b4 | ||
|
|
120fe5134a | ||
|
|
56a8fb4f77 | ||
|
|
55575225b4 | ||
|
|
483336e79e | ||
|
|
c77495e3a4 | ||
|
|
65af1839c6 | ||
|
|
177877c544 | ||
|
|
b25522ba52 | ||
|
|
c19bc311cb | ||
|
|
5435dcf96e | ||
|
|
f17c702270 | ||
|
|
3907333c5d | ||
|
|
acdecdfaef | ||
|
|
09d18ad07e | ||
|
|
bc516a3f3c | ||
|
|
9572eaaa11 | ||
|
|
18e674b4f6 | ||
|
|
8d68ab98a7 | ||
|
|
135e6b93f4 | ||
|
|
13a49340ed | ||
|
|
81a23040eb | ||
|
|
857f63136d | ||
|
|
a927acb1ec | ||
|
|
09f1580e2d | ||
|
|
cd59e22191 | ||
|
|
7237fdc6ce | ||
|
|
0fdf490d33 | ||
|
|
b73612a254 | ||
|
|
5014558ab9 | ||
|
|
28b0eb0f65 | ||
|
|
95131b2176 | ||
|
|
2305e2e5c9 | ||
|
|
00ae27690d | ||
|
|
9d5d4d64f8 | ||
|
|
98784ef8d6 | ||
|
|
d3fc8074a4 | ||
|
|
9c2b75b561 | ||
|
|
856bb8f99d | ||
|
|
af32f40bf5 | ||
|
|
4ec82a72bb | ||
|
|
07cce701de | ||
|
|
74e001af1d | ||
|
|
ff2751ac9c | ||
|
|
abcdd12b26 | ||
|
|
18db754858 | ||
|
|
fe03a6cdc8 | ||
|
|
cd684175ad | ||
|
|
da692b7920 | ||
|
|
95c01b6c16 | ||
|
|
6911e11edd | ||
|
|
5112f26a60 | ||
|
|
a06916d98e | ||
|
|
681de68e9d | ||
|
|
7aee40c13c | ||
|
|
9297939ec3 | ||
|
|
774d79cc4c | ||
|
|
2412044c90 | ||
|
|
120916dac2 | ||
|
|
fe346461ff | ||
|
|
d2a1fad968 | ||
|
|
0fb983f62d | ||
|
|
53c18592d3 | ||
|
|
e632bce2e4 | ||
|
|
0760b0a7e2 | ||
|
|
d908aa636a | ||
|
|
3d89341b47 | ||
|
|
d8ec40b39f | ||
|
|
4171221823 | ||
|
|
eaeca38fc4 | ||
|
|
fac988053f | ||
|
|
61241abbb0 | ||
|
|
53ed7066ab | ||
|
|
a61f4b287b | ||
|
|
486fb17975 | ||
|
|
2f567473c6 | ||
|
|
000ee7ef34 | ||
|
|
41d1cca328 | ||
|
|
717297545b | ||
|
|
e8e738406a | ||
|
|
e625be0d10 | ||
|
|
12e73423f1 | ||
|
|
7700b37f39 | ||
|
|
c28cfda81f | ||
|
|
848887eb7a | ||
|
|
3158150cb7 | ||
|
|
6ef6bcbd6b | ||
|
|
06425e9621 | ||
|
|
4d224a3022 | ||
|
|
f59ae58163 | ||
|
|
0d1bb027aa | ||
|
|
4cd0a709aa | ||
|
|
1815d1028b | ||
|
|
0fa9a1e236 | ||
|
|
eb55bad5a0 | ||
|
|
cc0ec3e161 | ||
|
|
80185155a1 | ||
|
|
c755f1901f | ||
|
|
68b91dc905 | ||
|
|
88f06afc0c | ||
|
|
40078a55e2 | ||
|
|
d2558234cf | ||
|
|
f5fa042c82 | ||
|
|
07e4a40a9a | ||
|
|
e28f1c0ae8 | ||
|
|
ef39f8600a | ||
|
|
2291dbce2a | ||
|
|
58f197b76c | ||
|
|
895b0931e5 | ||
|
|
1ad047d0f7 | ||
|
|
be6202f12b | ||
|
|
e8f834cd8d | ||
|
|
e0e624ca7f | ||
|
|
ec4f374c05 | ||
|
|
c811e8d8bd | ||
|
|
b2cd5da460 | ||
|
|
2de3b21e05 | ||
|
|
4bed436371 | ||
|
|
efe9dba595 | ||
|
|
47f4203dd3 | ||
|
|
015c10aeec | ||
|
|
a00d781b73 | ||
|
|
0c541b563f | ||
|
|
64a5cf7929 | ||
|
|
7a450a3b1c | ||
|
|
7de27caf16 | ||
|
|
c26326c1be | ||
|
|
66a1b8643a | ||
|
|
15828bcf25 | ||
|
|
333217f43e | ||
|
|
4a2f19abbd | ||
|
|
5fbcebed8c | ||
|
|
becdc7f82c | ||
|
|
73b9088a1c | ||
|
|
f6a1d69a87 | ||
|
|
fd76a14259 | ||
|
|
171e59edd4 | ||
|
|
a0c3b2d5cf | ||
|
|
19bb39202d | ||
|
|
d4553567d2 | ||
|
|
4d49884c58 | ||
|
|
5873d4ccdd | ||
|
|
db9a564b6a | ||
|
|
c72967d5de | ||
|
|
598d185db1 | ||
|
|
b982cbdd0e | ||
|
|
6a04a74e8b | ||
|
|
88728713c8 | ||
|
|
6b1d8c1e30 | ||
|
|
87c3d06271 | ||
|
|
915f911e36 | ||
|
|
cf9d6cfb0c | ||
|
|
bbed5763f1 | ||
|
|
ca0b91b39e |
@@ -21,7 +21,7 @@ assignees: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
||||||
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.20. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
||||||
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/yt-dlp/yt-dlp.
|
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/yt-dlp/yt-dlp.
|
||||||
- Search the bugtracker for similar issues: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
- Search the bugtracker for similar issues: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
||||||
@@ -29,7 +29,7 @@ Carefully read and work through this check list in order to prevent the most com
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a broken site support
|
- [ ] I'm reporting a broken site support
|
||||||
- [ ] I've verified that I'm running yt-dlp version **2021.04.11**
|
- [ ] I've verified that I'm running yt-dlp version **2021.05.20**
|
||||||
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
||||||
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
||||||
- [ ] I've searched the bugtracker for similar issues including closed ones
|
- [ ] I've searched the bugtracker for similar issues including closed ones
|
||||||
@@ -44,7 +44,7 @@ Add the `-v` flag to your command line you run yt-dlp with (`yt-dlp -v <your com
|
|||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] yt-dlp version 2021.04.11
|
[debug] yt-dlp version 2021.05.20
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ assignees: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
||||||
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.20. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
||||||
- Make sure that site you are requesting is not dedicated to copyright infringement, see https://github.com/yt-dlp/yt-dlp. yt-dlp does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
|
- Make sure that site you are requesting is not dedicated to copyright infringement, see https://github.com/yt-dlp/yt-dlp. yt-dlp does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
|
||||||
- Search the bugtracker for similar site support requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
- Search the bugtracker for similar site support requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
||||||
@@ -29,7 +29,7 @@ Carefully read and work through this check list in order to prevent the most com
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a new site support request
|
- [ ] I'm reporting a new site support request
|
||||||
- [ ] I've verified that I'm running yt-dlp version **2021.04.11**
|
- [ ] I've verified that I'm running yt-dlp version **2021.05.20**
|
||||||
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
||||||
- [ ] I've checked that none of provided URLs violate any copyrights
|
- [ ] I've checked that none of provided URLs violate any copyrights
|
||||||
- [ ] I've searched the bugtracker for similar site support requests including closed ones
|
- [ ] I've searched the bugtracker for similar site support requests including closed ones
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ assignees: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
||||||
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.20. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Search the bugtracker for similar site feature requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
- Search the bugtracker for similar site feature requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
||||||
- Finally, put x into all relevant boxes like this [x] (Dont forget to delete the empty space)
|
- Finally, put x into all relevant boxes like this [x] (Dont forget to delete the empty space)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a site feature request
|
- [ ] I'm reporting a site feature request
|
||||||
- [ ] I've verified that I'm running yt-dlp version **2021.04.11**
|
- [ ] I've verified that I'm running yt-dlp version **2021.05.20**
|
||||||
- [ ] I've searched the bugtracker for similar site feature requests including closed ones
|
- [ ] I've searched the bugtracker for similar site feature requests including closed ones
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ assignees: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
||||||
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.20. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
|
||||||
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/yt-dlp/yt-dlp.
|
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/yt-dlp/yt-dlp.
|
||||||
- Search the bugtracker for similar issues: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
- Search the bugtracker for similar issues: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
||||||
@@ -30,7 +30,7 @@ Carefully read and work through this check list in order to prevent the most com
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a broken site support issue
|
- [ ] I'm reporting a broken site support issue
|
||||||
- [ ] I've verified that I'm running yt-dlp version **2021.04.11**
|
- [ ] I've verified that I'm running yt-dlp version **2021.05.20**
|
||||||
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
- [ ] I've checked that all provided URLs are alive and playable in a browser
|
||||||
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
- [ ] I've checked that all URLs and arguments with special characters are properly quoted or escaped
|
||||||
- [ ] I've searched the bugtracker for similar bug reports including closed ones
|
- [ ] I've searched the bugtracker for similar bug reports including closed ones
|
||||||
@@ -46,7 +46,7 @@ Add the `-v` flag to your command line you run yt-dlp with (`yt-dlp -v <your com
|
|||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] yt-dlp version 2021.04.11
|
[debug] yt-dlp version 2021.05.20
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ assignees: ''
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
|
||||||
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.20. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
|
||||||
- Search the bugtracker for similar feature requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
- Search the bugtracker for similar feature requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
|
||||||
- Finally, put x into all relevant boxes like this [x] (Dont forget to delete the empty space)
|
- Finally, put x into all relevant boxes like this [x] (Dont forget to delete the empty space)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
- [ ] I'm reporting a feature request
|
- [ ] I'm reporting a feature request
|
||||||
- [ ] I've verified that I'm running yt-dlp version **2021.04.11**
|
- [ ] I've verified that I'm running yt-dlp version **2021.05.20**
|
||||||
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
- name: Upgrade pip and enable wheel support
|
- name: Upgrade pip and enable wheel support
|
||||||
run: python -m pip install pip==19.1.1 setuptools==43.0.0 wheel==0.33.6
|
run: python -m pip install pip==19.1.1 setuptools==43.0.0 wheel==0.33.6
|
||||||
- name: Install Requirements for 32 Bit
|
- name: Install Requirements for 32 Bit
|
||||||
run: pip install pyinstaller==3.5 mutagen==1.42.0 pycryptodome==3.9.4
|
run: pip install pyinstaller==3.5 mutagen==1.42.0 pycryptodome==3.9.4 pefile==2019.4.18
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
id: bump_version
|
id: bump_version
|
||||||
run: python devscripts/update-version.py
|
run: python devscripts/update-version.py
|
||||||
|
|||||||
@@ -41,11 +41,18 @@ jobs:
|
|||||||
- name: Install Jython
|
- name: Install Jython
|
||||||
if: ${{ matrix.python-impl == 'jython' }}
|
if: ${{ matrix.python-impl == 'jython' }}
|
||||||
run: |
|
run: |
|
||||||
wget http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar -O jython-installer.jar
|
wget https://repo1.maven.org/maven2/org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar -O jython-installer.jar
|
||||||
java -jar jython-installer.jar -s -d "$HOME/jython"
|
java -jar jython-installer.jar -s -d "$HOME/jython"
|
||||||
echo "$HOME/jython/bin" >> $GITHUB_PATH
|
echo "$HOME/jython/bin" >> $GITHUB_PATH
|
||||||
- name: Install nose
|
- name: Install nose
|
||||||
|
if: ${{ matrix.python-impl != 'jython' }}
|
||||||
run: pip install nose
|
run: pip install nose
|
||||||
|
- name: Install nose (Jython)
|
||||||
|
if: ${{ matrix.python-impl == 'jython' }}
|
||||||
|
# Working around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
|
||||||
|
run: |
|
||||||
|
wget https://files.pythonhosted.org/packages/99/4f/13fb671119e65c4dce97c60e67d3fd9e6f7f809f2b307e2611f4701205cb/nose-1.3.7-py2-none-any.whl
|
||||||
|
pip install nose-1.3.7-py2-none-any.whl
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
continue-on-error: ${{ matrix.ytdl-test-set == 'download' || matrix.python-impl == 'jython' }}
|
continue-on-error: ${{ matrix.ytdl-test-set == 'download' || matrix.python-impl == 'jython' }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -41,11 +41,18 @@ jobs:
|
|||||||
- name: Install Jython
|
- name: Install Jython
|
||||||
if: ${{ matrix.python-impl == 'jython' }}
|
if: ${{ matrix.python-impl == 'jython' }}
|
||||||
run: |
|
run: |
|
||||||
wget http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar -O jython-installer.jar
|
wget https://repo1.maven.org/maven2/org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar -O jython-installer.jar
|
||||||
java -jar jython-installer.jar -s -d "$HOME/jython"
|
java -jar jython-installer.jar -s -d "$HOME/jython"
|
||||||
echo "$HOME/jython/bin" >> $GITHUB_PATH
|
echo "$HOME/jython/bin" >> $GITHUB_PATH
|
||||||
- name: Install nose
|
- name: Install nose
|
||||||
|
if: ${{ matrix.python-impl != 'jython' }}
|
||||||
run: pip install nose
|
run: pip install nose
|
||||||
|
- name: Install nose (Jython)
|
||||||
|
if: ${{ matrix.python-impl == 'jython' }}
|
||||||
|
# Working around deprecation of support for non-SNI clients at PyPI CDN (see https://status.python.org/incidents/hzmjhqsdjqgb)
|
||||||
|
run: |
|
||||||
|
wget https://files.pythonhosted.org/packages/99/4f/13fb671119e65c4dce97c60e67d3fd9e6f7f809f2b307e2611f4701205cb/nose-1.3.7-py2-none-any.whl
|
||||||
|
pip install nose-1.3.7-py2-none-any.whl
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
continue-on-error: ${{ matrix.ytdl-test-set == 'download' || matrix.python-impl == 'jython' }}
|
continue-on-error: ${{ matrix.ytdl-test-set == 'download' || matrix.python-impl == 'jython' }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
+43
-42
@@ -1,3 +1,46 @@
|
|||||||
|
# Config
|
||||||
|
*.conf
|
||||||
|
*.spec
|
||||||
|
cookies
|
||||||
|
cookies.txt
|
||||||
|
|
||||||
|
# Downloaded
|
||||||
|
*.srt
|
||||||
|
*.ttml
|
||||||
|
*.sbv
|
||||||
|
*.vtt
|
||||||
|
*.flv
|
||||||
|
*.mp4
|
||||||
|
*.m4a
|
||||||
|
*.m4v
|
||||||
|
*.mp3
|
||||||
|
*.3gp
|
||||||
|
*.webm
|
||||||
|
*.wav
|
||||||
|
*.ape
|
||||||
|
*.mkv
|
||||||
|
*.swf
|
||||||
|
*.part
|
||||||
|
*.part-*
|
||||||
|
*.ytdl
|
||||||
|
*.dump
|
||||||
|
*.frag
|
||||||
|
*.frag.urls
|
||||||
|
*.aria2
|
||||||
|
*.swp
|
||||||
|
*.ogg
|
||||||
|
*.opus
|
||||||
|
*.info.json
|
||||||
|
*.live_chat.json
|
||||||
|
*.jpg
|
||||||
|
*.png
|
||||||
|
*.webp
|
||||||
|
*.annotations.xml
|
||||||
|
*.description
|
||||||
|
|
||||||
|
# Allow config/media files in testdata
|
||||||
|
!test/testdata/**
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
@@ -43,48 +86,6 @@ README.txt
|
|||||||
yt-dlp.zip
|
yt-dlp.zip
|
||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
# Downloaded
|
|
||||||
*.srt
|
|
||||||
*.ttml
|
|
||||||
*.sbv
|
|
||||||
*.vtt
|
|
||||||
*.flv
|
|
||||||
*.mp4
|
|
||||||
*.m4a
|
|
||||||
*.m4v
|
|
||||||
*.mp3
|
|
||||||
*.3gp
|
|
||||||
*.webm
|
|
||||||
*.wav
|
|
||||||
*.ape
|
|
||||||
*.mkv
|
|
||||||
*.swf
|
|
||||||
*.part
|
|
||||||
*.part-*
|
|
||||||
*.ytdl
|
|
||||||
*.dump
|
|
||||||
*.frag
|
|
||||||
*.frag.urls
|
|
||||||
*.aria2
|
|
||||||
*.swp
|
|
||||||
*.ogg
|
|
||||||
*.opus
|
|
||||||
*.info.json
|
|
||||||
*.live_chat.json
|
|
||||||
*.jpg
|
|
||||||
*.png
|
|
||||||
*.webp
|
|
||||||
*.annotations.xml
|
|
||||||
*.description
|
|
||||||
|
|
||||||
# Config
|
|
||||||
*.conf
|
|
||||||
*.spec
|
|
||||||
cookies
|
|
||||||
cookies.txt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Text Editor / IDE
|
# Text Editor / IDE
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
|||||||
@@ -40,3 +40,11 @@ hheimbuerger
|
|||||||
B0pol
|
B0pol
|
||||||
lkho
|
lkho
|
||||||
fstirlitz
|
fstirlitz
|
||||||
|
Lamieur
|
||||||
|
tsukumijima
|
||||||
|
Hadi0609
|
||||||
|
b5eff52
|
||||||
|
craftingmod
|
||||||
|
tpikonen
|
||||||
|
tripulse
|
||||||
|
king-millez
|
||||||
|
|||||||
+139
-4
@@ -10,7 +10,7 @@
|
|||||||
* Commit to master as `Release <version>`
|
* Commit to master as `Release <version>`
|
||||||
* Push to origin/release using `git push origin master:release`
|
* Push to origin/release using `git push origin master:release`
|
||||||
build task will now run
|
build task will now run
|
||||||
* Update version.py using devscripts\update-version.py
|
* Update version.py using `devscripts\update-version.py`
|
||||||
* Run `make issuetemplates`
|
* Run `make issuetemplates`
|
||||||
* Commit to master as `[version] update :ci skip all`
|
* Commit to master as `[version] update :ci skip all`
|
||||||
* Push to origin/master
|
* Push to origin/master
|
||||||
@@ -19,6 +19,141 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### 2021.06.01
|
||||||
|
|
||||||
|
* Merge youtube-dl: Upto [commit/d495292](https://github.com/ytdl-org/youtube-dl/commit/d495292852b6c2f1bd58bc2141ff2b0265c952cf)
|
||||||
|
* Pre-check archive and filters during playlist extraction
|
||||||
|
* Handle Basic Auth `user:pass` in URLs by [hhirtz](https://github.com/hhirtz) and [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [archiveorg] Add YoutubeWebArchiveIE by [colethedj](https://github.com/colethedj) and [alex-gedeon](https://github.com/alex-gedeon)
|
||||||
|
* [fancode] Add extractor by [rmsmachine](https://github.com/rmsmachine)
|
||||||
|
* [patreon] Support vimeo embeds by [rhsmachine](https://github.com/rhsmachine)
|
||||||
|
* [Saitosan] Add new extractor by [llacb47](https://github.com/llacb47)
|
||||||
|
* [ShemarooMe] Add extractor by [Ashish0804](https://github.com/Ashish0804) and [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [telemundo] Add extractor by [king-millez](https://github.com/king-millez)
|
||||||
|
* [SonyLIV] Add SonyLIVSeriesIE and subtitle support by [Ashish0804](https://github.com/Ashish0804)
|
||||||
|
* [Hotstar] Add HotStarSeriesIE by [Ashish0804](https://github.com/Ashish0804)
|
||||||
|
* [Voot] Add VootSeriesIE by [Ashish0804](https://github.com/Ashish0804)
|
||||||
|
* [vidio] Support login and premium videos by [MinePlayersPE](https://github.com/MinePlayersPE)
|
||||||
|
* [fragment] When using `-N`, do not keep the fragment content in memory
|
||||||
|
* [ffmpeg] Download and merge in a single step if possible
|
||||||
|
* [ThumbnailsConvertor] Support conversion to `png` and make it the default by [louie-github](https://github.com/louie-github)
|
||||||
|
* [VideoConvertor] Generalize with remuxer and allow conditional recoding
|
||||||
|
* [EmbedThumbnail] Embed in `mp4`/`m4a` using mutagen by [tripulse](https://github.com/tripulse) and [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [EmbedThumbnail] Embed if any thumbnail was downloaded, not just the best
|
||||||
|
* [EmbedThumbnail] Correctly escape filename
|
||||||
|
* [update] replace self without launching a subprocess in windows
|
||||||
|
* [update] Block further update for unsupported systems
|
||||||
|
* Refactor `__process_playlist` by creating `LazyList`
|
||||||
|
* Write messages to `stderr` when both `quiet` and `verbose`
|
||||||
|
* Sanitize and sort playlist thumbnails
|
||||||
|
* Remove `None` values from `info.json`
|
||||||
|
* [extractor] Always prefer native hls downloader by default
|
||||||
|
* [extractor] Skip subtitles without URI in m3u8 manifests by [hheimbuerger](https://github.com/hheimbuerger)
|
||||||
|
* [extractor] Functions to parse `socket.io` response as `json` by [pukkandan](https://github.com/pukkandan) and [llacb47](https://github.com/llacb47)
|
||||||
|
* [extractor] Allow `note=False` when extracting manifests
|
||||||
|
* [utils] Escape URLs in `sanitized_Request`, not `sanitize_url`
|
||||||
|
* [hls] Disable external downloader for `webtt`
|
||||||
|
* [youtube] `/live` URLs should raise error if channel is not live
|
||||||
|
* [youtube] Bug fixes
|
||||||
|
* [zee5] Fix m3u8 formats' extension
|
||||||
|
* [ard] Allow URLs without `-` before id by [olifre](https://github.com/olifre)
|
||||||
|
* [cleanup] `YoutubeDL._match_entry`
|
||||||
|
* [cleanup] Refactor updater
|
||||||
|
* [cleanup] Refactor ffmpeg convertors
|
||||||
|
* [cleanup] setup.py
|
||||||
|
|
||||||
|
|
||||||
|
### 2021.05.20
|
||||||
|
|
||||||
|
* **Youtube improvements**:
|
||||||
|
* Support youtube music `MP`, `VL` and `browse` pages
|
||||||
|
* Extract more formats for youtube music by [craftingmod](https://github.com/craftingmod), [colethedj](https://github.com/colethedj) and [pukkandan](https://github.com/pukkandan)
|
||||||
|
* Extract multiple subtitles in same language by [pukkandan](https://github.com/pukkandan) and [tpikonen](https://github.com/tpikonen)
|
||||||
|
* Redirect channels that doesn't have a `videos` tab to their `UU` playlists
|
||||||
|
* Support in-channel search
|
||||||
|
* Sort audio-only formats correctly
|
||||||
|
* Always extract `maxresdefault` thumbnail
|
||||||
|
* Extract audio language
|
||||||
|
* Add subtitle language names by [nixxo](https://github.com/nixxo) and [tpikonen](https://github.com/tpikonen)
|
||||||
|
* Show alerts only from the final webpage
|
||||||
|
* Add `html5=1` param to `get_video_info` page requests by [colethedj](https://github.com/colethedj)
|
||||||
|
* Better message when login required
|
||||||
|
* **Add option `--print`**: to print any field/template
|
||||||
|
* Deprecates: `--get-description`, `--get-duration`, `--get-filename`, `--get-format`, `--get-id`, `--get-thumbnail`, `--get-title`, `--get-url`
|
||||||
|
* Field `additional_urls` to download additional videos from metadata using [`--parse-metadata`](https://github.com/yt-dlp/yt-dlp#modifying-metadata)
|
||||||
|
* Merge youtube-dl: Upto [commit/dfbbe29](https://github.com/ytdl-org/youtube-dl/commit/dfbbe2902fc67f0f93ee47a8077c148055c67a9b)
|
||||||
|
* Write thumbnail of playlist and add `pl_thumbnail` outtmpl key
|
||||||
|
* [embedthumbnail] Add `flac` support and refactor `mutagen` code by [pukkandan](https://github.com/pukkandan) and [tripulse](https://github.com/tripulse)
|
||||||
|
* [audius:artist] Add extractor by [king-millez](https://github.com/king-millez)
|
||||||
|
* [parlview] Add extractor by [king-millez](https://github.com/king-millez)
|
||||||
|
* [tenplay] Fix extractor by [king-millez](https://github.com/king-millez)
|
||||||
|
* [rmcdecouverte] Generalize `_VALID_URL`
|
||||||
|
* Add compat-option `no-attach-infojson`
|
||||||
|
* Add field `name` for subtitles
|
||||||
|
* Ensure `post_extract` and `pre_process` only run once
|
||||||
|
* Fix `--check-formats` when there is network error
|
||||||
|
* Standardize `write_debug` and `get_param`
|
||||||
|
* [options] Alias `--write-comments`, `--no-write-comments`
|
||||||
|
* [options] Refactor callbacks
|
||||||
|
* [test:download] Only extract enough videos for `playlist_mincount`
|
||||||
|
* [extractor] bugfix for when `compat_opts` is not given
|
||||||
|
* [build] Fix x86 build by [shirt](https://github.com/shirt-dev)
|
||||||
|
* [cleanup] code formatting, youtube tests and readme
|
||||||
|
|
||||||
|
### 2021.05.11
|
||||||
|
* **Deprecate support for python versions < 3.6**
|
||||||
|
* **Subtitle extraction from manifests** by [fstirlitz](https://github.com/fstirlitz). See [be6202f](https://github.com/yt-dlp/yt-dlp/commit/be6202f12b97858b9d716e608394b51065d0419f) for details
|
||||||
|
* **Improve output template:**
|
||||||
|
* Allow slicing lists/strings using `field.start:end:step`
|
||||||
|
* A field can also be used as offset like `field1+num+field2`
|
||||||
|
* A default value can be given using `field|default`
|
||||||
|
* Prevent invalid fields from causing errors
|
||||||
|
* **Merge youtube-dl**: Upto [commit/a726009](https://github.com/ytdl-org/youtube-dl/commit/a7260099873acc6dc7d76cafad2f6b139087afd0)
|
||||||
|
* **Remove options** `-l`, `-t`, `-A` completely and disable `--auto-number`, `--title`, `--literal`, `--id`
|
||||||
|
* [Plugins] Prioritize plugins over standard extractors and prevent plugins from overwriting the standard extractor classes
|
||||||
|
* [downloader] Fix `quiet` and `to_stderr`
|
||||||
|
* [fragment] Ensure the file is closed on error
|
||||||
|
* [fragment] Make sure first segment is not skipped
|
||||||
|
* [aria2c] Fix whitespace being stripped off
|
||||||
|
* [embedthumbnail] Fix bug where jpeg thumbnails were converted again
|
||||||
|
* [FormatSort] Fix for when some formats have quality and others don't
|
||||||
|
* [utils] Add `network_exceptions`
|
||||||
|
* [utils] Escape URL while sanitizing
|
||||||
|
* [ukcolumn] Add Extractor
|
||||||
|
* [whowatch] Add extractor by [nao20010128nao](https://github.com/nao20010128nao)
|
||||||
|
* [CBS] Improve `_VALID_URL` to support movies
|
||||||
|
* [crackle] Improve extraction
|
||||||
|
* [curiositystream] Fix collections
|
||||||
|
* [francetvinfo] Improve video id extraction
|
||||||
|
* [generic] Respect the encoding in manifest
|
||||||
|
* [limelight] Obey `allow_unplayable_formats`
|
||||||
|
* [mediasite] Generalize URL pattern by [fstirlitz](https://github.com/fstirlitz)
|
||||||
|
* [mxplayer] Add MxplayerShowIE by [Ashish0804](https://github.com/Ashish0804)
|
||||||
|
* [nebula] Move to nebula.app by [Lamieur](https://github.com/Lamieur)
|
||||||
|
* [niconico] Fix HLS formats by [CXwudi](https://github.com/CXwudi), [tsukumijima](https://github.com/tsukumijima), [nao20010128nao](https://github.com/nao20010128nao) and [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [niconico] Fix title and thumbnail extraction by [CXwudi](https://github.com/CXwudi)
|
||||||
|
* [plutotv] Extract subtitles from manifests
|
||||||
|
* [plutotv] Fix format extraction for some urls
|
||||||
|
* [rmcdecouverte] Improve `_VALID_URL`
|
||||||
|
* [sonyliv] Fix `title` and `series` extraction by [Ashish0804](https://github.com/Ashish0804)
|
||||||
|
* [tubi] Raise "no video formats" error when video url is empty
|
||||||
|
* [youtube:tab] Detect playlists inside community posts
|
||||||
|
* [youtube] Add `oembed` to reserved names
|
||||||
|
* [zee5] Fix extraction for some URLs by [Hadi0609](https://github.com/Hadi0609)
|
||||||
|
* [zee5] Fix py2 compatibility
|
||||||
|
* Fix `playlist_index` and add `playlist_autonumber`. See [#302](https://github.com/yt-dlp/yt-dlp/issues/302) for details
|
||||||
|
* Add experimental option `--check-formats` to test the URLs before format selection
|
||||||
|
* Option `--compat-options` to revert [some of yt-dlp's changes](https://github.com/yt-dlp/yt-dlp#differences-in-default-behavior)
|
||||||
|
* Deprecates `--list-formats-as-table`, `--list-formats-old`
|
||||||
|
* Fix number of digits in `%(playlist_index)s`
|
||||||
|
* Fix case sensitivity of format selector
|
||||||
|
* Revert "[core] be able to hand over id and title using url_result"
|
||||||
|
* Do not strip out whitespaces in `-o` and `-P`
|
||||||
|
* Fix `preload_download_archive` writing verbose message to `stdout`
|
||||||
|
* Move option warnings to `YoutubeDL`so that they obey `--no-warnings` and can output colors
|
||||||
|
* Py2 compatibility for `FileNotFoundError`
|
||||||
|
|
||||||
|
|
||||||
### 2021.04.22
|
### 2021.04.22
|
||||||
* **Improve output template:**
|
* **Improve output template:**
|
||||||
* Objects can be traversed like `%(field.key1.key2)s`
|
* Objects can be traversed like `%(field.key1.key2)s`
|
||||||
@@ -33,11 +168,11 @@
|
|||||||
* Add option `--skip-playlist-after-errors` to skip the rest of a playlist after a given number of errors are encountered
|
* Add option `--skip-playlist-after-errors` to skip the rest of a playlist after a given number of errors are encountered
|
||||||
* Merge youtube-dl: Upto [commit/7e8b3f9](https://github.com/ytdl-org/youtube-dl/commit/7e8b3f9439ebefb3a3a4e5da9c0bd2b595976438)
|
* Merge youtube-dl: Upto [commit/7e8b3f9](https://github.com/ytdl-org/youtube-dl/commit/7e8b3f9439ebefb3a3a4e5da9c0bd2b595976438)
|
||||||
* [downloader] Fix bug in downloader selection
|
* [downloader] Fix bug in downloader selection
|
||||||
* [BilibiliChannel] Fix pagination by [nao20010128nao](https://github.com/nao20010128nao) and[pukkandan](https://github.com/pukkandan)
|
* [BilibiliChannel] Fix pagination by [nao20010128nao](https://github.com/nao20010128nao) and [pukkandan](https://github.com/pukkandan)
|
||||||
* [rai] Add support for http formats by [nixxo](https://github.com/nixxo)
|
* [rai] Add support for http formats by [nixxo](https://github.com/nixxo)
|
||||||
* [TubiTv] Add TubiTvShowIE by [Ashish0804](https://github.com/Ashish0804)
|
* [TubiTv] Add TubiTvShowIE by [Ashish0804](https://github.com/Ashish0804)
|
||||||
* [twitcasting] Fix extractor
|
* [twitcasting] Fix extractor
|
||||||
* [viu:ott] Fix extractor and support series by [lkho](https://github.com/lkho) and[pukkandan](https://github.com/pukkandan)
|
* [viu:ott] Fix extractor and support series by [lkho](https://github.com/lkho) and [pukkandan](https://github.com/pukkandan)
|
||||||
* [youtube:tab] Show unavailable videos in playlists by [colethedj](https://github.com/colethedj)
|
* [youtube:tab] Show unavailable videos in playlists by [colethedj](https://github.com/colethedj)
|
||||||
* [youtube:tab] Reload with unavailable videos for all playlists
|
* [youtube:tab] Reload with unavailable videos for all playlists
|
||||||
* [youtube] Ignore invalid stretch ratio
|
* [youtube] Ignore invalid stretch ratio
|
||||||
@@ -519,4 +654,4 @@
|
|||||||
* [generic] Extract embedded youtube and twitter videos by [diegorodriguezv](https://github.com/diegorodriguezv)
|
* [generic] Extract embedded youtube and twitter videos by [diegorodriguezv](https://github.com/diegorodriguezv)
|
||||||
* [ffmpeg] Ensure all streams are copied by [pukkandan](https://github.com/pukkandan)
|
* [ffmpeg] Ensure all streams are copied by [pukkandan](https://github.com/pukkandan)
|
||||||
* [embedthumbnail] Fix for os.rename error by [pukkandan](https://github.com/pukkandan)
|
* [embedthumbnail] Fix for os.rename error by [pukkandan](https://github.com/pukkandan)
|
||||||
* make_win.bat: don't use UPX to pack vcruntime140.dll by [jbruchon](https://github.com/jbruchon)
|
* make_win.bat: don't use UPX to pack vcruntime140.dll by [jbruchon](https://github.com/jbruchon)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ A command-line program to download videos from YouTube and many other [video pla
|
|||||||
yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on the now inactive [youtube-dlc](https://github.com/blackjack4494/yt-dlc). The main focus of this project is adding new features and patches while also keeping up to date with the original project
|
yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on the now inactive [youtube-dlc](https://github.com/blackjack4494/yt-dlc). The main focus of this project is adding new features and patches while also keeping up to date with the original project
|
||||||
|
|
||||||
* [NEW FEATURES](#new-features)
|
* [NEW FEATURES](#new-features)
|
||||||
|
* [Differences in default behavior](#differences-in-default-behavior)
|
||||||
* [INSTALLATION](#installation)
|
* [INSTALLATION](#installation)
|
||||||
* [Dependencies](#dependencies)
|
* [Dependencies](#dependencies)
|
||||||
* [Update](#update)
|
* [Update](#update)
|
||||||
@@ -65,15 +66,17 @@ The major new features from the latest release of [blackjack4494/yt-dlc](https:/
|
|||||||
|
|
||||||
* **[Format Sorting](#sorting-formats)**: The default format sorting options have been changed so that higher resolution and better codecs will be now preferred instead of simply using larger bitrate. Furthermore, you can now specify the sort order using `-S`. This allows for much easier format selection that what is possible by simply using `--format` ([examples](#format-selection-examples))
|
* **[Format Sorting](#sorting-formats)**: The default format sorting options have been changed so that higher resolution and better codecs will be now preferred instead of simply using larger bitrate. Furthermore, you can now specify the sort order using `-S`. This allows for much easier format selection that what is possible by simply using `--format` ([examples](#format-selection-examples))
|
||||||
|
|
||||||
* **Merged with youtube-dl v2021.04.17**: You get all the latest features and patches of [youtube-dl](https://github.com/ytdl-org/youtube-dl) in addition to all the features of [youtube-dlc](https://github.com/blackjack4494/yt-dlc)
|
* **Merged with youtube-dl [commit/d495292](https://github.com/ytdl-org/youtube-dl/commit/d495292852b6c2f1bd58bc2141ff2b0265c952cf)**: (v2021.05.16) You get all the latest features and patches of [youtube-dl](https://github.com/ytdl-org/youtube-dl) in addition to all the features of [youtube-dlc](https://github.com/blackjack4494/yt-dlc)
|
||||||
|
|
||||||
* **Merged with animelover1984/youtube-dl**: You get most of the features and improvements from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl) including `--get-comments`, `BiliBiliSearch`, `BilibiliChannel`, Embedding thumbnail in mp4/ogg/opus, Playlist infojson etc. Note that the NicoNico improvements are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details.
|
* **Merged with animelover1984/youtube-dl**: You get most of the features and improvements from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl) including `--write-comments`, `BiliBiliSearch`, `BilibiliChannel`, Embedding thumbnail in mp4/ogg/opus, playlist infojson etc. Note that the NicoNico improvements are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details.
|
||||||
|
|
||||||
* **Youtube improvements**:
|
* **Youtube improvements**:
|
||||||
* All Youtube Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) works and supports downloading multiple pages of content
|
* All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) supports downloading multiple pages of content
|
||||||
* Youtube search (`ytsearch:`, `ytsearchdate:`) along with Search URLs work
|
* Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works
|
||||||
* Youtube mixes supports downloading multiple pages of content
|
* Mixes supports downloading multiple pages of content
|
||||||
* Redirect channel's home URL automatically to `/video` to preserve the old behaviour
|
* Redirect channel's home URL automatically to `/video` to preserve the old behaviour
|
||||||
|
* `255kbps` audio is extracted from youtube music if premium cookies are given
|
||||||
|
* Youtube music Albums, channels etc can be downloaded
|
||||||
|
|
||||||
* **Split video by chapters**: Videos can be split into multiple files based on chapters using `--split-chapters`
|
* **Split video by chapters**: Videos can be split into multiple files based on chapters using `--split-chapters`
|
||||||
|
|
||||||
@@ -81,17 +84,21 @@ The major new features from the latest release of [blackjack4494/yt-dlc](https:/
|
|||||||
|
|
||||||
* **Aria2c with HLS/DASH**: You can use `aria2c` as the external downloader for DASH(mpd) and HLS(m3u8) formats
|
* **Aria2c with HLS/DASH**: You can use `aria2c` as the external downloader for DASH(mpd) and HLS(m3u8) formats
|
||||||
|
|
||||||
* **New extractors**: AnimeLab, Philo MSO, Rcs, Gedi, bitwave.tv, mildom, audius, zee5, mtv.it, wimtv, pluto.tv, niconico users, discoveryplus.in, mediathek, NFHSNetwork, nebula
|
* **New extractors**: AnimeLab, Philo MSO, Rcs, Gedi, bitwave.tv, mildom, audius, zee5, mtv.it, wimtv, pluto.tv, niconico users, discoveryplus.in, mediathek, NFHSNetwork, nebula, ukcolumn, whowatch, MxplayerShow, parlview (au), YoutubeWebArchive, fancode, Saitosan, ShemarooMe, telemundo, VootSeries, SonyLIVSeries, HotstarSeries
|
||||||
|
|
||||||
* **Fixed extractors**: archive.org, roosterteeth.com, skyit, instagram, itv, SouthparkDe, spreaker, Vlive, akamai, ina, rumble, tennistv, amcnetworks, la7 podcasts, linuxacadamy, nitter, twitcasting, viu
|
* **Fixed extractors**: archive.org, roosterteeth.com, skyit, instagram, itv, SouthparkDe, spreaker, Vlive, akamai, ina, rumble, tennistv, amcnetworks, la7 podcasts, linuxacadamy, nitter, twitcasting, viu, crackle, curiositystream, mediasite, rmcdecouverte, sonyliv, tubi, tenplay, patreon
|
||||||
|
|
||||||
|
* **Subtitle extraction from manifests**: Subtitles can be extracted from streaming media manifests. See [commit/be6202f](https://github.com/yt-dlp/yt-dlp/commit/be6202f12b97858b9d716e608394b51065d0419f) for details
|
||||||
|
|
||||||
* **Multiple paths and output templates**: You can give different [output templates](#output-template) and download paths for different types of files. You can also set a temporary path where intermediary files are downloaded to using `--paths` (`-P`)
|
* **Multiple paths and output templates**: You can give different [output templates](#output-template) and download paths for different types of files. You can also set a temporary path where intermediary files are downloaded to using `--paths` (`-P`)
|
||||||
|
|
||||||
* **Portable Configuration**: Configuration files are automatically loaded from the home and root directories. See [configuration](#configuration) for details
|
* **Portable Configuration**: Configuration files are automatically loaded from the home and root directories. See [configuration](#configuration) for details
|
||||||
|
|
||||||
* **Other new options**: `--parse-metadata`, `--list-formats-as-table`, `--write-link`, `--force-download-archive`, `--force-overwrites`, `--break-on-reject` etc
|
* **Output template improvements**: Output templates can now have date-time formatting, numeric offsets, object traversal etc. See [output template](#output-template) for details. Even more advanced operations can also be done with the help of `--parse-metadata`
|
||||||
|
|
||||||
* **Improvements**: Multiple `--postprocessor-args` and `--external-downloader-args`, Date/time formatting in `-o`, faster archive checking, more [format selection options](#format-selection) etc
|
* **Other new options**: `--sleep-requests`, `--convert-thumbnails`, `--write-link`, `--force-download-archive`, `--force-overwrites`, `--break-on-reject` etc
|
||||||
|
|
||||||
|
* **Improvements**: Multiple `--postprocessor-args` and `--downloader-args`, faster archive checking, more [format selection options](#format-selection) etc
|
||||||
|
|
||||||
* **Plugin extractors**: Extractors can be loaded from an external file. See [plugins](#plugins) for details
|
* **Plugin extractors**: Extractors can be loaded from an external file. See [plugins](#plugins) for details
|
||||||
|
|
||||||
@@ -105,15 +112,42 @@ See [changelog](Changelog.md) or [commits](https://github.com/yt-dlp/yt-dlp/comm
|
|||||||
|
|
||||||
If you are coming from [youtube-dl](https://github.com/ytdl-org/youtube-dl), the amount of changes are very large. Compare [options](#options) and [supported sites](supportedsites.md) with youtube-dl's to get an idea of the massive number of features/patches [youtube-dlc](https://github.com/blackjack4494/yt-dlc) has accumulated.
|
If you are coming from [youtube-dl](https://github.com/ytdl-org/youtube-dl), the amount of changes are very large. Compare [options](#options) and [supported sites](supportedsites.md) with youtube-dl's to get an idea of the massive number of features/patches [youtube-dlc](https://github.com/blackjack4494/yt-dlc) has accumulated.
|
||||||
|
|
||||||
|
### Differences in default behavior
|
||||||
|
|
||||||
|
Some of yt-dlp's default options are different from that of youtube-dl and youtube-dlc.
|
||||||
|
|
||||||
|
* The options `--id`, `--auto-number` (`-A`), `--title` (`-t`) and `--literal` (`-l`), no longer work. See [removed options](#Removed) for details
|
||||||
|
* `avconv` is not supported as as an alternative to `ffmpeg`
|
||||||
|
* The default [output template](#output-template) is `%(title)s [%(id)s].%(ext)s`. There is no real reason for this change. This was changed before yt-dlp was ever made public and now there are no plans to change it back to `%(title)s.%(id)s.%(ext)s`. Instead, you may use `--compat-options filename`
|
||||||
|
* The default [format sorting](sorting-formats) is different from youtube-dl and prefers higher resolution and better codecs rather than higher bitrates. You can use the `--format-sort` option to change this to any order you prefer, or use `--compat-options format-sort` to use youtube-dl's sorting order
|
||||||
|
* The default format selector is `bv*+ba/b`. This means that if a combined video + audio format that is better than the best video-only format is found, the former will be prefered. Use `-f bv+ba/b` or `--compat-options format-spec` to revert this
|
||||||
|
* Unlike youtube-dlc, yt-dlp does not allow merging multiple audio/video streams into one file by default (since this conflicts with the use of `-f bv*+ba`). If needed, this feature must be enabled using `--audio-multistreams` and `--video-multistreams`. You can also use `--compat-options multistreams` to enable both
|
||||||
|
* `--ignore-errors` is enabled by default. Use `--abort-on-error` or `--compat-options abort-on-error` to abort on errors instead
|
||||||
|
* When writing metadata files such as thumbnails, description or infojson, the same information (if available) is also written for playlists. Use `--no-write-playlist-metafiles` or `--compat-options no-playlist-metafiles` to not write these files
|
||||||
|
* `--add-metadata` attaches the `infojson` to `mkv` files in addition to writing the metadata when used with `--write-infojson`. Use `--compat-options no-attach-info-json` to revert this
|
||||||
|
* `playlist_index` behaves differently when used with options like `--playlist-reverse` and `--playlist-items`. See [#302](https://github.com/yt-dlp/yt-dlp/issues/302) for details. You can use `--compat-options playlist-index` if you want to keep the earlier behavior
|
||||||
|
* The output of `-F` is listed in a new format. Use `--compat-options list-formats` to revert this
|
||||||
|
* Youtube live chat (if available) is considered as a subtitle. Use `--sub-langs all,-live_chat` to download all subtitles except live chat. You can also use `--compat-options no-live-chat` to prevent live chat from downloading
|
||||||
|
* Youtube channel URLs are automatically redirected to `/video`. Append a `/featured` to the URL to download only the videos in the home page. If the channel does not have a videos tab, we try to download the equivalent `UU` playlist instead. Also, `/live` URLs raise an error if there are no live videos instead of silently downloading the entire channel. You may use `--compat-options no-youtube-channel-redirect` to revert all these redirections
|
||||||
|
* Unavailable videos are also listed for youtube playlists. Use `--compat-options no-youtube-unavailable-videos` to remove this
|
||||||
|
* If `ffmpeg` is used as the downloader, the downloading and merging of formats happen in a single step when possible. Use `--compat-options no-direct-merge` to revert this
|
||||||
|
|
||||||
|
For ease of use, a few more compat options are available:
|
||||||
|
* `--compat-options all`: Use all compat options
|
||||||
|
* `--compat-options youtube-dl`: Same as `--compat-options all,-multistreams`
|
||||||
|
* `--compat-options youtube-dlc`: Same as `--compat-options all,-no-live-chat,-no-youtube-channel-redirect`
|
||||||
|
|
||||||
|
|
||||||
# INSTALLATION
|
# INSTALLATION
|
||||||
yt-dlp is not platform specific. So it should work on your Unix box, on Windows or on macOS
|
yt-dlp is not platform specific. So it should work on your Unix box, on Windows or on macOS
|
||||||
|
|
||||||
You can install yt-dlp using one of the following methods:
|
You can install yt-dlp using one of the following methods:
|
||||||
* Download the binary from the [latest release](https://github.com/yt-dlp/yt-dlp/releases/latest) (recommended method)
|
* Download the binary from the [latest release](https://github.com/yt-dlp/yt-dlp/releases/latest) (recommended method)
|
||||||
* Use [PyPI package](https://pypi.org/project/yt-dlp): `python -m pip install --upgrade yt-dlp`
|
* Use [PyPI package](https://pypi.org/project/yt-dlp): `python3 -m pip install --upgrade yt-dlp`
|
||||||
* Use pip+git: `python -m pip install --upgrade git+https://github.com/yt-dlp/yt-dlp.git@release`
|
* Use pip+git: `python3 -m pip install --upgrade git+https://github.com/yt-dlp/yt-dlp.git@release`
|
||||||
* Install master branch: `python -m pip install --upgrade git+https://github.com/yt-dlp/yt-dlp`
|
* Install master branch: `python3 -m pip install --upgrade git+https://github.com/yt-dlp/yt-dlp`
|
||||||
|
|
||||||
|
Note that on some systems, you may need to use `py` or `python` instead of `python3`
|
||||||
|
|
||||||
UNIX users (Linux, macOS, BSD) can also install the [latest release](https://github.com/yt-dlp/yt-dlp/releases/latest) one of the following ways:
|
UNIX users (Linux, macOS, BSD) can also install the [latest release](https://github.com/yt-dlp/yt-dlp/releases/latest) one of the following ways:
|
||||||
|
|
||||||
@@ -133,9 +167,11 @@ sudo chmod a+rx /usr/local/bin/yt-dlp
|
|||||||
```
|
```
|
||||||
|
|
||||||
### DEPENDENCIES
|
### DEPENDENCIES
|
||||||
Python versions 2.6, 2.7, or 3.2+ are currently supported. However, 3.2+ is strongly recommended and python2 support will be deprecated in the future.
|
Python versions 3.6+ (CPython and PyPy) are officially supported. Other versions and implementations may or maynot work correctly.
|
||||||
|
|
||||||
Although there are no required dependencies, `ffmpeg` and `ffprobe` are highly recommended. Other optional dependencies are `sponskrub`, `AtomicParsley`, `mutagen`, `pycryptodome` and any of the supported external downloaders. Note that the windows releases are already built with the python interpreter, mutagen and pycryptodome included.
|
On windows, [Microsoft Visual C++ 2010 Redistributable Package (x86)](https://www.microsoft.com/en-us/download/details.aspx?id=26999) is also necessary to run yt-dlp. You probably already have this, but if the executable throws an error due to missing `MSVCR100.dll` you need to install it.
|
||||||
|
|
||||||
|
Although there are no other required dependencies, `ffmpeg` and `ffprobe` are highly recommended. Other optional dependencies are `sponskrub`, `AtomicParsley`, `mutagen`, `pycryptodome`, `phantomjs` and any of the supported external downloaders. Note that the windows releases are already built with the python interpreter, mutagen and pycryptodome included.
|
||||||
|
|
||||||
### UPDATE
|
### UPDATE
|
||||||
You can use `yt-dlp -U` to update if you are using the provided release.
|
You can use `yt-dlp -U` to update if you are using the provided release.
|
||||||
@@ -146,13 +182,15 @@ If you are using `pip`, simply re-run the same command that was used to install
|
|||||||
**For Windows**:
|
**For Windows**:
|
||||||
To build the Windows executable, you must have pyinstaller (and optionally mutagen and pycryptodome)
|
To build the Windows executable, you must have pyinstaller (and optionally mutagen and pycryptodome)
|
||||||
|
|
||||||
python -m pip install --upgrade pyinstaller mutagen pycryptodome
|
python3 -m pip install --upgrade pyinstaller mutagen pycryptodome
|
||||||
|
|
||||||
Once you have all the necessary dependencies installed, just run `py pyinst.py`. The executable will be built for the same architecture (32/64 bit) as the python used to build it. It is strongly recommended to use python3 although python2.6+ is supported.
|
Once you have all the necessary dependencies installed, just run `py pyinst.py`. The executable will be built for the same architecture (32/64 bit) as the python used to build it.
|
||||||
|
|
||||||
You can also build the executable without any version info or metadata by using:
|
You can also build the executable without any version info or metadata by using:
|
||||||
|
|
||||||
pyinstaller.exe yt_dlp\__main__.py --onefile --name yt-dlp
|
pyinstaller.exe yt_dlp\__main__.py --onefile --name yt-dlp
|
||||||
|
|
||||||
|
Note that pyinstaller [does not support](https://github.com/pyinstaller/pyinstaller#requirements-and-tested-platforms) Python installed from the Windows store without using a virtual environment
|
||||||
|
|
||||||
**For Unix**:
|
**For Unix**:
|
||||||
You will need the required build tools: `python`, `make` (GNU), `pandoc`, `zip`, `nosetests`
|
You will need the required build tools: `python`, `make` (GNU), `pandoc`, `zip`, `nosetests`
|
||||||
@@ -210,6 +248,11 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
--mark-watched Mark videos watched (YouTube only)
|
--mark-watched Mark videos watched (YouTube only)
|
||||||
--no-mark-watched Do not mark videos watched (default)
|
--no-mark-watched Do not mark videos watched (default)
|
||||||
--no-colors Do not emit color codes in output
|
--no-colors Do not emit color codes in output
|
||||||
|
--compat-options OPTS Options that can help keep compatibility
|
||||||
|
with youtube-dl and youtube-dlc
|
||||||
|
configurations by reverting some of the
|
||||||
|
changes made in yt-dlp. See "Differences in
|
||||||
|
default behavior" for details
|
||||||
|
|
||||||
## Network Options:
|
## Network Options:
|
||||||
--proxy URL Use the specified HTTP/HTTPS/SOCKS proxy.
|
--proxy URL Use the specified HTTP/HTTPS/SOCKS proxy.
|
||||||
@@ -443,10 +486,13 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
could still contain some personal
|
could still contain some personal
|
||||||
information (default)
|
information (default)
|
||||||
--no-clean-infojson Write all fields to the infojson
|
--no-clean-infojson Write all fields to the infojson
|
||||||
--get-comments Retrieve video comments to be placed in the
|
--write-comments Retrieve video comments to be placed in the
|
||||||
.info.json file. The comments are fetched
|
infojson. The comments are fetched even
|
||||||
even without this option if the extraction
|
without this option if the extraction is
|
||||||
is known to be quick
|
known to be quick (Alias: --get-comments)
|
||||||
|
--no-write-comments Do not retrieve video comments unless the
|
||||||
|
extraction is known to be quick
|
||||||
|
(Alias: --no-get-comments)
|
||||||
--load-info-json FILE JSON file containing the video information
|
--load-info-json FILE JSON file containing the video information
|
||||||
(created with the "--write-info-json"
|
(created with the "--write-info-json"
|
||||||
option)
|
option)
|
||||||
@@ -494,14 +540,9 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
formats are found (default)
|
formats are found (default)
|
||||||
--skip-download Do not download the video but write all
|
--skip-download Do not download the video but write all
|
||||||
related files (Alias: --no-download)
|
related files (Alias: --no-download)
|
||||||
-g, --get-url Simulate, quiet but print URL
|
-O, --print TEMPLATE Simulate, quiet but print the given fields.
|
||||||
-e, --get-title Simulate, quiet but print title
|
Either a field name or similar formatting
|
||||||
--get-id Simulate, quiet but print id
|
as the output template can be used
|
||||||
--get-thumbnail Simulate, quiet but print thumbnail URL
|
|
||||||
--get-description Simulate, quiet but print video description
|
|
||||||
--get-duration Simulate, quiet but print video length
|
|
||||||
--get-filename Simulate, quiet but print output filename
|
|
||||||
--get-format Simulate, quiet but print output format
|
|
||||||
-j, --dump-json Simulate, quiet but print JSON information.
|
-j, --dump-json Simulate, quiet but print JSON information.
|
||||||
See "OUTPUT TEMPLATE" for a description of
|
See "OUTPUT TEMPLATE" for a description of
|
||||||
available keys
|
available keys
|
||||||
@@ -577,12 +618,10 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
containers irrespective of quality
|
containers irrespective of quality
|
||||||
--no-prefer-free-formats Don't give any special preference to free
|
--no-prefer-free-formats Don't give any special preference to free
|
||||||
containers (default)
|
containers (default)
|
||||||
|
--check-formats Check that the formats selected are
|
||||||
|
actually downloadable (Experimental)
|
||||||
-F, --list-formats List all available formats of requested
|
-F, --list-formats List all available formats of requested
|
||||||
videos
|
videos
|
||||||
--list-formats-as-table Present the output of -F in tabular form
|
|
||||||
(default)
|
|
||||||
--list-formats-old Present the output of -F in the old form
|
|
||||||
(Alias: --no-list-formats-as-table)
|
|
||||||
--merge-output-format FORMAT If a merge is required (e.g.
|
--merge-output-format FORMAT If a merge is required (e.g.
|
||||||
bestvideo+bestaudio), output to given
|
bestvideo+bestaudio), output to given
|
||||||
container format. One of mkv, mp4, ogg,
|
container format. One of mkv, mp4, ogg,
|
||||||
@@ -632,10 +671,10 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
## Post-Processing Options:
|
## Post-Processing Options:
|
||||||
-x, --extract-audio Convert video files to audio-only files
|
-x, --extract-audio Convert video files to audio-only files
|
||||||
(requires ffmpeg and ffprobe)
|
(requires ffmpeg and ffprobe)
|
||||||
--audio-format FORMAT Specify audio format: "best", "aac",
|
--audio-format FORMAT Specify audio format to convert the audio
|
||||||
"flac", "mp3", "m4a", "opus", "vorbis", or
|
to when -x is used. Currently supported
|
||||||
"wav"; "best" by default; No effect without
|
formats are: best (default) or one of
|
||||||
-x
|
aac|flac|mp3|m4a|opus|vorbis|wav
|
||||||
--audio-quality QUALITY Specify ffmpeg audio quality, insert a
|
--audio-quality QUALITY Specify ffmpeg audio quality, insert a
|
||||||
value between 0 (better) and 9 (worse) for
|
value between 0 (better) and 9 (worse) for
|
||||||
VBR or a specific bitrate like 128K
|
VBR or a specific bitrate like 128K
|
||||||
@@ -645,12 +684,12 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
|webm|mov|avi|mp3|mka|m4a|ogg|opus). If
|
|webm|mov|avi|mp3|mka|m4a|ogg|opus). If
|
||||||
target container does not support the
|
target container does not support the
|
||||||
video/audio codec, remuxing will fail. You
|
video/audio codec, remuxing will fail. You
|
||||||
can specify multiple rules; eg.
|
can specify multiple rules; Eg.
|
||||||
"aac>m4a/mov>mp4/mkv" will remux aac to
|
"aac>m4a/mov>mp4/mkv" will remux aac to
|
||||||
m4a, mov to mp4 and anything else to mkv.
|
m4a, mov to mp4 and anything else to mkv.
|
||||||
--recode-video FORMAT Re-encode the video into another format if
|
--recode-video FORMAT Re-encode the video into another format if
|
||||||
re-encoding is necessary. The supported
|
re-encoding is necessary. The syntax and
|
||||||
formats are the same as --remux-video
|
supported formats are the same as --remux-video
|
||||||
--postprocessor-args NAME:ARGS Give these arguments to the postprocessors.
|
--postprocessor-args NAME:ARGS Give these arguments to the postprocessors.
|
||||||
Specify the postprocessor/executable name
|
Specify the postprocessor/executable name
|
||||||
and the arguments separated by a colon ":"
|
and the arguments separated by a colon ":"
|
||||||
@@ -711,10 +750,10 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
|
|||||||
fields are passed, "%(filepath)s" is
|
fields are passed, "%(filepath)s" is
|
||||||
appended to the end of the command
|
appended to the end of the command
|
||||||
--convert-subs FORMAT Convert the subtitles to another format
|
--convert-subs FORMAT Convert the subtitles to another format
|
||||||
(currently supported: srt|ass|vtt|lrc)
|
(currently supported: srt|vtt|ass|lrc)
|
||||||
(Alias: --convert-subtitles)
|
(Alias: --convert-subtitles)
|
||||||
--convert-thumbnails FORMAT Convert the thumbnails to another format
|
--convert-thumbnails FORMAT Convert the thumbnails to another format
|
||||||
(currently supported: jpg)
|
(currently supported: jpg|png)
|
||||||
--split-chapters Split video into multiple files based on
|
--split-chapters Split video into multiple files based on
|
||||||
internal chapters. The "chapter:" prefix
|
internal chapters. The "chapter:" prefix
|
||||||
can be used with "--paths" and "--output"
|
can be used with "--paths" and "--output"
|
||||||
@@ -785,7 +824,7 @@ You can configure yt-dlp by placing any supported command line option to a confi
|
|||||||
* `~/yt-dlp.conf.txt`
|
* `~/yt-dlp.conf.txt`
|
||||||
|
|
||||||
Note that `~` points to `C:\Users\<user name>` on windows. Also, `%XDG_CONFIG_HOME%` defaults to `~/.config` if undefined
|
Note that `~` points to `C:\Users\<user name>` on windows. Also, `%XDG_CONFIG_HOME%` defaults to `~/.config` if undefined
|
||||||
1. **System Configuration**: `/etc/yt-dlp.conf` or `/etc/yt-dlp.conf`
|
1. **System Configuration**: `/etc/yt-dlp.conf`
|
||||||
|
|
||||||
For example, with the following configuration file yt-dlp will always extract the audio, not copy the mtime, use a proxy and save all videos under `YouTube` directory in your home directory:
|
For example, with the following configuration file yt-dlp will always extract the audio, not copy the mtime, use a proxy and save all videos under `YouTube` directory in your home directory:
|
||||||
```
|
```
|
||||||
@@ -842,16 +881,17 @@ The simplest usage of `-o` is not to set any template arguments when downloading
|
|||||||
It may however also contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations.
|
It may however also contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations.
|
||||||
|
|
||||||
The field names themselves (the part inside the parenthesis) can also have some special formatting:
|
The field names themselves (the part inside the parenthesis) can also have some special formatting:
|
||||||
1. **Date/time Formatting**: Date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a `>`. Eg: `%(duration>%H-%M-%S)s` or `%(upload_date>%Y-%m-%d)s`
|
1. **Object traversal**: The dictionaries and lists available in metadata can be traversed by using a `.` (dot) separator. You can also do python slicing using `:`. Eg: `%(tags.0)s`, `%(subtitles.en.-1.ext)`, `%(id.3:7:-1)s`. Note that the fields that become available using this method are not listed below. Use `-j` to see such fields
|
||||||
2. **Offset numbers**: Numeric fields can have an initial offset specified by using a `+` separator. Eg: `%(playlist_index+10)03d`. This can also be used in conjunction with the date-time formatting. Eg: `%(epoch+-3600>%H-%M-%S)s`
|
1. **Addition**: Addition and subtraction of numeric fields can be done using `+` and `-` respectively. Eg: `%(playlist_index+10)03d`, `%(n_entries+1-playlist_index)d`
|
||||||
3. **Object traversal**: The dictionaries and lists available in metadata can be traversed by using a `.` (dot) separator. Eg: `%(tags.0)s` or `%(subtitles.en.-1.ext)`. Note that the fields that become available using this method are not listed below. Use `-j` to see such fields
|
1. **Date/time Formatting**: Date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a `>`. Eg: `%(duration>%H-%M-%S)s`, `%(upload_date>%Y-%m-%d)s`, `%(epoch-3600>%H-%M-%S)s`
|
||||||
|
1. **Default**: A default value can be specified for when the field is empty using a `|` seperator. This overrides `--output-na-template`. Eg: `%(uploader|Unknown)s`
|
||||||
|
|
||||||
To summarize, the general syntax for a field is:
|
To summarize, the general syntax for a field is:
|
||||||
```
|
```
|
||||||
%(name[.keys][+offset][>strf])[flags][width][.precision][length]type
|
%(name[.keys][addition][>strf][|default])[flags][width][.precision][length]type
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation`, `infojson`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video.
|
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation`, `infojson`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video.
|
||||||
|
|
||||||
The available fields are:
|
The available fields are:
|
||||||
|
|
||||||
@@ -873,7 +913,7 @@ The available fields are:
|
|||||||
- `channel_id` (string): Id of the channel
|
- `channel_id` (string): Id of the channel
|
||||||
- `location` (string): Physical location where the video was filmed
|
- `location` (string): Physical location where the video was filmed
|
||||||
- `duration` (numeric): Length of the video in seconds
|
- `duration` (numeric): Length of the video in seconds
|
||||||
- `duration_string` (string): Length of the video (HH-mm-ss)
|
- `duration_string` (string): Length of the video (HH:mm:ss)
|
||||||
- `view_count` (numeric): How many users have watched the video on the platform
|
- `view_count` (numeric): How many users have watched the video on the platform
|
||||||
- `like_count` (numeric): Number of positive ratings of the video
|
- `like_count` (numeric): Number of positive ratings of the video
|
||||||
- `dislike_count` (numeric): Number of negative ratings of the video
|
- `dislike_count` (numeric): Number of negative ratings of the video
|
||||||
@@ -951,6 +991,11 @@ Available for `chapter:` prefix when using `--split-chapters` for videos with in
|
|||||||
- `section_start` (numeric): Start time of the chapter in seconds
|
- `section_start` (numeric): Start time of the chapter in seconds
|
||||||
- `section_end` (numeric): End time of the chapter in seconds
|
- `section_end` (numeric): End time of the chapter in seconds
|
||||||
|
|
||||||
|
Available only when used in `--print`:
|
||||||
|
|
||||||
|
- `urls` (string): The URLs of all requested formats, one in each line
|
||||||
|
- `filename` (string): Name of the video file. Note that the actual filename may be different due to post-processing. Use `--exec echo` to get the name after all postprocessing is complete
|
||||||
|
|
||||||
Each aforementioned sequence when referenced in an output template will be replaced by the actual value corresponding to the sequence name. Note that some of the sequences are not guaranteed to be present since they depend on the metadata obtained by a particular extractor. Such sequences will be replaced with placeholder value provided with `--output-na-placeholder` (`NA` by default).
|
Each aforementioned sequence when referenced in an output template will be replaced by the actual value corresponding to the sequence name. Note that some of the sequences are not guaranteed to be present since they depend on the metadata obtained by a particular extractor. Such sequences will be replaced with placeholder value provided with `--output-na-placeholder` (`NA` by default).
|
||||||
|
|
||||||
For example for `-o %(title)s-%(id)s.%(ext)s` and an mp4 video with title `yt-dlp test video` and id `BaW_jenozKcj`, this will result in a `yt-dlp test video-BaW_jenozKcj.mp4` file created in the current directory.
|
For example for `-o %(title)s-%(id)s.%(ext)s` and an mp4 video with title `yt-dlp test video` and id `BaW_jenozKcj`, this will result in a `yt-dlp test video-BaW_jenozKcj.mp4` file created in the current directory.
|
||||||
@@ -1240,7 +1285,9 @@ The metadata obtained the the extractors can be modified by using `--parse-metad
|
|||||||
|
|
||||||
Note that any field created by this can be used in the [output template](#output-template) and will also affect the media file's metadata added when using `--add-metadata`.
|
Note that any field created by this can be used in the [output template](#output-template) and will also affect the media file's metadata added when using `--add-metadata`.
|
||||||
|
|
||||||
You can also use this to change only the metadata that is embedded in the media file. To do this, set the value of the corresponding field with a `meta_` prefix. For example, any value you set to `meta_description` field will be added to the `description` field in the file. You can use this to set a different "description" and "synopsis", for example.
|
This option also has a few special uses:
|
||||||
|
* You can use this to change the metadata that is embedded in the media file. To do this, set the value of the corresponding field with a `meta_` prefix. For example, any value you set to `meta_description` field will be added to the `description` field in the file. You can use this to set a different "description" and "synopsis", for example
|
||||||
|
* You can download an additional URL based on the metadata of the currently downloaded video. To do this, set the field `additional_urls` to the URL that you want to download. Eg: `--parse-metadata "description:(?P<additional_urls>https?://www\.vimeo\.com/\d+)` will download the first vimeo video found in the description
|
||||||
|
|
||||||
## Modifying metadata examples
|
## Modifying metadata examples
|
||||||
|
|
||||||
@@ -1274,6 +1321,14 @@ These are all the deprecated options and the current alternative to achieve the
|
|||||||
#### Not recommended
|
#### Not recommended
|
||||||
While these options still work, their use is not recommended since there are other alternatives to achieve the same
|
While these options still work, their use is not recommended since there are other alternatives to achieve the same
|
||||||
|
|
||||||
|
--get-description --print description
|
||||||
|
--get-duration --print duration_string
|
||||||
|
--get-filename --print filename
|
||||||
|
--get-format --print format
|
||||||
|
--get-id --print id
|
||||||
|
--get-thumbnail --print thumbnail
|
||||||
|
-e, --get-title --print title
|
||||||
|
-g, --get-url --print urls
|
||||||
--all-formats -f all
|
--all-formats -f all
|
||||||
--all-subs --sub-langs all --write-subs
|
--all-subs --sub-langs all --write-subs
|
||||||
--autonumber-size NUMBER Use string formatting. Eg: %(autonumber)03d
|
--autonumber-size NUMBER Use string formatting. Eg: %(autonumber)03d
|
||||||
@@ -1281,8 +1336,11 @@ While these options still work, their use is not recommended since there are oth
|
|||||||
--metadata-from-title FORMAT --parse-metadata "%(title)s:FORMAT"
|
--metadata-from-title FORMAT --parse-metadata "%(title)s:FORMAT"
|
||||||
--hls-prefer-native --downloader "m3u8:native"
|
--hls-prefer-native --downloader "m3u8:native"
|
||||||
--hls-prefer-ffmpeg --downloader "m3u8:ffmpeg"
|
--hls-prefer-ffmpeg --downloader "m3u8:ffmpeg"
|
||||||
|
--list-formats-old --compat-options list-formats (Alias: --no-list-formats-as-table)
|
||||||
|
--list-formats-as-table --compat-options -list-formats [Default] (Alias: --no-list-formats-old)
|
||||||
--sponskrub-args ARGS --ppa "sponskrub:ARGS"
|
--sponskrub-args ARGS --ppa "sponskrub:ARGS"
|
||||||
--test Used by developers for testing extractors. Not intended for the end user
|
--test Used by developers for testing extractors. Not intended for the end user
|
||||||
|
--youtube-print-sig-code Used for testing youtube signatures
|
||||||
|
|
||||||
|
|
||||||
#### Old aliases
|
#### Old aliases
|
||||||
@@ -1313,16 +1371,15 @@ These options may no longer work as intended
|
|||||||
--no-call-home Default
|
--no-call-home Default
|
||||||
--include-ads No longer supported
|
--include-ads No longer supported
|
||||||
--no-include-ads Default
|
--no-include-ads Default
|
||||||
--youtube-print-sig-code No longer supported
|
|
||||||
|
#### Removed
|
||||||
|
These options were deprecated since 2014 and have now been entirely removed
|
||||||
|
|
||||||
--id -o "%(id)s.%(ext)s"
|
--id -o "%(id)s.%(ext)s"
|
||||||
-A, --auto-number -o "%(autonumber)s-%(id)s.%(ext)s"
|
-A, --auto-number -o "%(autonumber)s-%(id)s.%(ext)s"
|
||||||
-t, --title -o "%(title)s-%(id)s.%(ext)s"
|
-t, --title -o "%(title)s-%(id)s.%(ext)s"
|
||||||
-l, --literal -o accepts literal names
|
-l, --literal -o accepts literal names
|
||||||
|
|
||||||
#### Removed
|
|
||||||
Currently, there are no options that have been completely removed. But there are plans to remove the old output options `-A`,`-t`, `-l`, `--id` (which have been deprecated since 2014) in the near future. If you are still using these, please move to using `--output` instead
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# MORE
|
# MORE
|
||||||
For FAQ, Developer Instructions etc., see the [original README](https://github.com/ytdl-org/youtube-dl#faq)
|
For FAQ, Developer Instructions etc., see the [original README](https://github.com/ytdl-org/youtube-dl#faq)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ assert arch in ('32', '64')
|
|||||||
print('Building %sbit version' % arch)
|
print('Building %sbit version' % arch)
|
||||||
_x86 = '_x86' if arch == '32' else ''
|
_x86 = '_x86' if arch == '32' else ''
|
||||||
|
|
||||||
FILE_DESCRIPTION = 'Media Downloader%s' % (' (32 Bit)' if _x86 else '')
|
FILE_DESCRIPTION = 'yt-dlp%s' % (' (32 Bit)' if _x86 else '')
|
||||||
|
|
||||||
# root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
# root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
# print('Changing working directory to %s' % root_dir)
|
# print('Changing working directory to %s' % root_dir)
|
||||||
|
|||||||
@@ -9,45 +9,44 @@ from distutils.spawn import spawn
|
|||||||
|
|
||||||
|
|
||||||
# Get the version from yt_dlp/version.py without importing the package
|
# Get the version from yt_dlp/version.py without importing the package
|
||||||
exec(compile(open('yt_dlp/version.py').read(),
|
exec(compile(open('yt_dlp/version.py').read(), 'yt_dlp/version.py', 'exec'))
|
||||||
'yt_dlp/version.py', 'exec'))
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION = 'Command-line program to download videos from YouTube.com and many other other video platforms.'
|
DESCRIPTION = 'Command-line program to download videos from YouTube.com and many other other video platforms.'
|
||||||
|
|
||||||
LONG_DESCRIPTION = '\n\n'.join((
|
LONG_DESCRIPTION = '\n\n'.join((
|
||||||
'Official repository: <https://github.com/yt-dlp/yt-dlp>',
|
'Official repository: <https://github.com/yt-dlp/yt-dlp>',
|
||||||
'**PS**: Many links in this document will not work since this is a copy of the README.md from Github',
|
'**PS**: Some links in this document will not work since this is a copy of the README.md from Github',
|
||||||
open("README.md", "r", encoding="utf-8").read()))
|
open('README.md', 'r', encoding='utf-8').read()))
|
||||||
|
|
||||||
REQUIREMENTS = ['mutagen', 'pycryptodome']
|
REQUIREMENTS = ['mutagen', 'pycryptodome']
|
||||||
|
|
||||||
|
if sys.argv[1:2] == ['py2exe']:
|
||||||
|
raise NotImplementedError('py2exe is not currently supported; instead, use "pyinst.py" to build with pyinstaller')
|
||||||
|
|
||||||
if len(sys.argv) >= 2 and sys.argv[1] == 'py2exe':
|
|
||||||
print("inv")
|
|
||||||
else:
|
|
||||||
files_spec = [
|
|
||||||
('share/bash-completion/completions', ['completions/bash/yt-dlp']),
|
|
||||||
('share/zsh/site-functions', ['completions/zsh/_yt-dlp']),
|
|
||||||
('share/fish/vendor_completions.d', ['completions/fish/yt-dlp.fish']),
|
|
||||||
('share/doc/yt_dlp', ['README.txt']),
|
|
||||||
('share/man/man1', ['yt-dlp.1'])
|
|
||||||
]
|
|
||||||
root = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
data_files = []
|
|
||||||
for dirname, files in files_spec:
|
|
||||||
resfiles = []
|
|
||||||
for fn in files:
|
|
||||||
if not os.path.exists(fn):
|
|
||||||
warnings.warn('Skipping file %s since it is not present. Try running `make pypi-files` first.' % fn)
|
|
||||||
else:
|
|
||||||
resfiles.append(fn)
|
|
||||||
data_files.append((dirname, resfiles))
|
|
||||||
|
|
||||||
params = {
|
files_spec = [
|
||||||
'data_files': data_files,
|
('share/bash-completion/completions', ['completions/bash/yt-dlp']),
|
||||||
}
|
('share/zsh/site-functions', ['completions/zsh/_yt-dlp']),
|
||||||
params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']}
|
('share/fish/vendor_completions.d', ['completions/fish/yt-dlp.fish']),
|
||||||
|
('share/doc/yt_dlp', ['README.txt']),
|
||||||
|
('share/man/man1', ['yt-dlp.1'])
|
||||||
|
]
|
||||||
|
root = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
data_files = []
|
||||||
|
for dirname, files in files_spec:
|
||||||
|
resfiles = []
|
||||||
|
for fn in files:
|
||||||
|
if not os.path.exists(fn):
|
||||||
|
warnings.warn('Skipping file %s since it is not present. Try running `make pypi-files` first' % fn)
|
||||||
|
else:
|
||||||
|
resfiles.append(fn)
|
||||||
|
data_files.append((dirname, resfiles))
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'data_files': data_files,
|
||||||
|
}
|
||||||
|
params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']}
|
||||||
|
|
||||||
|
|
||||||
class build_lazy_extractors(Command):
|
class build_lazy_extractors(Command):
|
||||||
@@ -61,23 +60,21 @@ class build_lazy_extractors(Command):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
spawn(
|
spawn([sys.executable, 'devscripts/make_lazy_extractors.py', 'yt_dlp/extractor/lazy_extractors.py'],
|
||||||
[sys.executable, 'devscripts/make_lazy_extractors.py', 'yt_dlp/extractor/lazy_extractors.py'],
|
dry_run=self.dry_run)
|
||||||
dry_run=self.dry_run,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
packages = find_packages(exclude=("youtube_dl", "test", "ytdlp_plugins"))
|
packages = find_packages(exclude=('youtube_dl', 'test', 'ytdlp_plugins'))
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="yt-dlp",
|
name='yt-dlp',
|
||||||
version=__version__,
|
version=__version__,
|
||||||
maintainer="pukkandan",
|
maintainer='pukkandan',
|
||||||
maintainer_email="pukkandan.ytdlp@gmail.com",
|
maintainer_email='pukkandan.ytdlp@gmail.com',
|
||||||
description=DESCRIPTION,
|
description=DESCRIPTION,
|
||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type='text/markdown',
|
||||||
url="https://github.com/yt-dlp/yt-dlp",
|
url='https://github.com/yt-dlp/yt-dlp',
|
||||||
packages=packages,
|
packages=packages,
|
||||||
install_requires=REQUIREMENTS,
|
install_requires=REQUIREMENTS,
|
||||||
project_urls={
|
project_urls={
|
||||||
@@ -87,28 +84,28 @@ setup(
|
|||||||
#'Funding': 'https://donate.pypi.org',
|
#'Funding': 'https://donate.pypi.org',
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Topic :: Multimedia :: Video",
|
'Topic :: Multimedia :: Video',
|
||||||
"Development Status :: 5 - Production/Stable",
|
'Development Status :: 5 - Production/Stable',
|
||||||
"Environment :: Console",
|
'Environment :: Console',
|
||||||
"Programming Language :: Python",
|
'Programming Language :: Python',
|
||||||
"Programming Language :: Python :: 2",
|
'Programming Language :: Python :: 2',
|
||||||
"Programming Language :: Python :: 2.6",
|
'Programming Language :: Python :: 2.6',
|
||||||
"Programming Language :: Python :: 2.7",
|
'Programming Language :: Python :: 2.7',
|
||||||
"Programming Language :: Python :: 3",
|
'Programming Language :: Python :: 3',
|
||||||
"Programming Language :: Python :: 3.2",
|
'Programming Language :: Python :: 3.2',
|
||||||
"Programming Language :: Python :: 3.3",
|
'Programming Language :: Python :: 3.3',
|
||||||
"Programming Language :: Python :: 3.4",
|
'Programming Language :: Python :: 3.4',
|
||||||
"Programming Language :: Python :: 3.5",
|
'Programming Language :: Python :: 3.5',
|
||||||
"Programming Language :: Python :: 3.6",
|
'Programming Language :: Python :: 3.6',
|
||||||
"Programming Language :: Python :: 3.7",
|
'Programming Language :: Python :: 3.7',
|
||||||
"Programming Language :: Python :: 3.8",
|
'Programming Language :: Python :: 3.8',
|
||||||
"Programming Language :: Python :: Implementation",
|
'Programming Language :: Python :: Implementation',
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
'Programming Language :: Python :: Implementation :: CPython',
|
||||||
"Programming Language :: Python :: Implementation :: IronPython",
|
'Programming Language :: Python :: Implementation :: IronPython',
|
||||||
"Programming Language :: Python :: Implementation :: Jython",
|
'Programming Language :: Python :: Implementation :: Jython',
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
'Programming Language :: Python :: Implementation :: PyPy',
|
||||||
"License :: Public Domain",
|
'License :: Public Domain',
|
||||||
"Operating System :: OS Independent",
|
'Operating System :: OS Independent',
|
||||||
],
|
],
|
||||||
python_requires='>=2.6',
|
python_requires='>=2.6',
|
||||||
|
|
||||||
|
|||||||
+17
-3
@@ -82,6 +82,7 @@
|
|||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **Audius**: Audius.co
|
- **Audius**: Audius.co
|
||||||
|
- **audius:artist**: Audius.co profile/artist pages
|
||||||
- **audius:playlist**: Audius.co playlists
|
- **audius:playlist**: Audius.co playlists
|
||||||
- **audius:track**: Audius track ID or API link. Prepend with "audius:"
|
- **audius:track**: Audius track ID or API link. Prepend with "audius:"
|
||||||
- **AWAAN**
|
- **AWAAN**
|
||||||
@@ -130,7 +131,6 @@
|
|||||||
- **bitwave:stream**
|
- **bitwave:stream**
|
||||||
- **BleacherReport**
|
- **BleacherReport**
|
||||||
- **BleacherReportCMS**
|
- **BleacherReportCMS**
|
||||||
- **blinkx**
|
|
||||||
- **Bloomberg**
|
- **Bloomberg**
|
||||||
- **BokeCC**
|
- **BokeCC**
|
||||||
- **BongaCams**
|
- **BongaCams**
|
||||||
@@ -225,7 +225,8 @@
|
|||||||
- **Culturebox**
|
- **Culturebox**
|
||||||
- **CultureUnplugged**
|
- **CultureUnplugged**
|
||||||
- **curiositystream**
|
- **curiositystream**
|
||||||
- **curiositystream:collection**
|
- **curiositystream:collections**
|
||||||
|
- **curiositystream:series**
|
||||||
- **CWTV**
|
- **CWTV**
|
||||||
- **DagelijkseKost**: dagelijksekost.een.be
|
- **DagelijkseKost**: dagelijksekost.een.be
|
||||||
- **DailyMail**
|
- **DailyMail**
|
||||||
@@ -306,6 +307,7 @@
|
|||||||
- **EyedoTV**
|
- **EyedoTV**
|
||||||
- **facebook**
|
- **facebook**
|
||||||
- **FacebookPluginsVideo**
|
- **FacebookPluginsVideo**
|
||||||
|
- **fancode:vod**
|
||||||
- **faz.net**
|
- **faz.net**
|
||||||
- **fc2**
|
- **fc2**
|
||||||
- **fc2:embed**
|
- **fc2:embed**
|
||||||
@@ -391,6 +393,7 @@
|
|||||||
- **HotNewHipHop**
|
- **HotNewHipHop**
|
||||||
- **hotstar**
|
- **hotstar**
|
||||||
- **hotstar:playlist**
|
- **hotstar:playlist**
|
||||||
|
- **hotstar:series**
|
||||||
- **Howcast**
|
- **Howcast**
|
||||||
- **HowStuffWorks**
|
- **HowStuffWorks**
|
||||||
- **hrfernsehen**
|
- **hrfernsehen**
|
||||||
@@ -584,6 +587,7 @@
|
|||||||
- **Mwave**
|
- **Mwave**
|
||||||
- **MwaveMeetGreet**
|
- **MwaveMeetGreet**
|
||||||
- **Mxplayer**
|
- **Mxplayer**
|
||||||
|
- **MxplayerShow**
|
||||||
- **MyChannels**
|
- **MyChannels**
|
||||||
- **MySpace**
|
- **MySpace**
|
||||||
- **MySpace:album**
|
- **MySpace:album**
|
||||||
@@ -724,6 +728,7 @@
|
|||||||
- **pandora.tv**: 판도라TV
|
- **pandora.tv**: 판도라TV
|
||||||
- **ParamountNetwork**
|
- **ParamountNetwork**
|
||||||
- **parliamentlive.tv**: UK parliament videos
|
- **parliamentlive.tv**: UK parliament videos
|
||||||
|
- **Parlview**
|
||||||
- **Patreon**
|
- **Patreon**
|
||||||
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
||||||
- **PearVideo**
|
- **PearVideo**
|
||||||
@@ -747,6 +752,7 @@
|
|||||||
- **play.fm**
|
- **play.fm**
|
||||||
- **player.sky.it**
|
- **player.sky.it**
|
||||||
- **PlayPlusTV**
|
- **PlayPlusTV**
|
||||||
|
- **PlayStuff**
|
||||||
- **PlaysTV**
|
- **PlaysTV**
|
||||||
- **Playtvak**: Playtvak.cz, iDNES.cz and Lidovky.cz
|
- **Playtvak**: Playtvak.cz, iDNES.cz and Lidovky.cz
|
||||||
- **Playvid**
|
- **Playvid**
|
||||||
@@ -855,6 +861,7 @@
|
|||||||
- **safari**: safaribooksonline.com online video
|
- **safari**: safaribooksonline.com online video
|
||||||
- **safari:api**
|
- **safari:api**
|
||||||
- **safari:course**: safaribooksonline.com online courses
|
- **safari:course**: safaribooksonline.com online courses
|
||||||
|
- **Saitosan**
|
||||||
- **SAKTV**
|
- **SAKTV**
|
||||||
- **SaltTV**
|
- **SaltTV**
|
||||||
- **SampleFocus**
|
- **SampleFocus**
|
||||||
@@ -879,6 +886,7 @@
|
|||||||
- **Shahid**
|
- **Shahid**
|
||||||
- **ShahidShow**
|
- **ShahidShow**
|
||||||
- **Shared**: shared.sx
|
- **Shared**: shared.sx
|
||||||
|
- **ShemarooMe**
|
||||||
- **ShowRoomLive**
|
- **ShowRoomLive**
|
||||||
- **simplecast**
|
- **simplecast**
|
||||||
- **simplecast:episode**
|
- **simplecast:episode**
|
||||||
@@ -898,6 +906,7 @@
|
|||||||
- **Snotr**
|
- **Snotr**
|
||||||
- **Sohu**
|
- **Sohu**
|
||||||
- **SonyLIV**
|
- **SonyLIV**
|
||||||
|
- **SonyLIVSeries**
|
||||||
- **soundcloud**
|
- **soundcloud**
|
||||||
- **soundcloud:playlist**
|
- **soundcloud:playlist**
|
||||||
- **soundcloud:search**: Soundcloud search
|
- **soundcloud:search**: Soundcloud search
|
||||||
@@ -976,6 +985,7 @@
|
|||||||
- **Telecinco**: telecinco.es, cuatro.com and mediaset.es
|
- **Telecinco**: telecinco.es, cuatro.com and mediaset.es
|
||||||
- **Telegraaf**
|
- **Telegraaf**
|
||||||
- **TeleMB**
|
- **TeleMB**
|
||||||
|
- **Telemundo**
|
||||||
- **TeleQuebec**
|
- **TeleQuebec**
|
||||||
- **TeleQuebecEmission**
|
- **TeleQuebecEmission**
|
||||||
- **TeleQuebecLive**
|
- **TeleQuebecLive**
|
||||||
@@ -1076,6 +1086,7 @@
|
|||||||
- **UDNEmbed**: 聯合影音
|
- **UDNEmbed**: 聯合影音
|
||||||
- **UFCArabia**
|
- **UFCArabia**
|
||||||
- **UFCTV**
|
- **UFCTV**
|
||||||
|
- **ukcolumn**
|
||||||
- **UKTVPlay**
|
- **UKTVPlay**
|
||||||
- **umg:de**: Universal Music Deutschland
|
- **umg:de**: Universal Music Deutschland
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
@@ -1158,6 +1169,7 @@
|
|||||||
- **VODPlatform**
|
- **VODPlatform**
|
||||||
- **VoiceRepublic**
|
- **VoiceRepublic**
|
||||||
- **Voot**
|
- **Voot**
|
||||||
|
- **VootSeries**
|
||||||
- **VoxMedia**
|
- **VoxMedia**
|
||||||
- **VoxMediaVolume**
|
- **VoxMediaVolume**
|
||||||
- **vpro**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
- **vpro**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
@@ -1187,6 +1199,7 @@
|
|||||||
- **wdr:mobile**
|
- **wdr:mobile**
|
||||||
- **WDRElefant**
|
- **WDRElefant**
|
||||||
- **WDRPage**
|
- **WDRPage**
|
||||||
|
- **web.archive:youtube**: web.archive.org saved youtube videos
|
||||||
- **Webcaster**
|
- **Webcaster**
|
||||||
- **WebcasterFeed**
|
- **WebcasterFeed**
|
||||||
- **WebOfStories**
|
- **WebOfStories**
|
||||||
@@ -1194,6 +1207,7 @@
|
|||||||
- **Weibo**
|
- **Weibo**
|
||||||
- **WeiboMobile**
|
- **WeiboMobile**
|
||||||
- **WeiqiTV**: WQTV
|
- **WeiqiTV**: WQTV
|
||||||
|
- **whowatch**
|
||||||
- **WimTV**
|
- **WimTV**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
- **WistiaPlaylist**
|
- **WistiaPlaylist**
|
||||||
@@ -1204,7 +1218,7 @@
|
|||||||
- **WWE**
|
- **WWE**
|
||||||
- **XBef**
|
- **XBef**
|
||||||
- **XboxClips**
|
- **XboxClips**
|
||||||
- **XFileShare**: XFileShare based sites: Aparat, ClipWatching, GoUnlimited, GoVid, HolaVid, Streamty, TheVideoBee, Uqload, VidBom, vidlo, VidLocker, VidShare, VUp, XVideoSharing
|
- **XFileShare**: XFileShare based sites: Aparat, ClipWatching, GoUnlimited, GoVid, HolaVid, Streamty, TheVideoBee, Uqload, VidBom, vidlo, VidLocker, VidShare, VUp, WolfStream, XVideoSharing
|
||||||
- **XHamster**
|
- **XHamster**
|
||||||
- **XHamsterEmbed**
|
- **XHamsterEmbed**
|
||||||
- **XHamsterUser**
|
- **XHamsterUser**
|
||||||
|
|||||||
+33
-34
@@ -1,41 +1,40 @@
|
|||||||
{
|
{
|
||||||
"consoletitle": false,
|
"consoletitle": false,
|
||||||
"continuedl": true,
|
"continuedl": true,
|
||||||
"forcedescription": false,
|
"forcedescription": false,
|
||||||
"forcefilename": false,
|
"forcefilename": false,
|
||||||
"forceformat": false,
|
"forceformat": false,
|
||||||
"forcethumbnail": false,
|
"forcethumbnail": false,
|
||||||
"forcetitle": false,
|
"forcetitle": false,
|
||||||
"forceurl": false,
|
"forceurl": false,
|
||||||
"force_write_download_archive": false,
|
"force_write_download_archive": false,
|
||||||
"format": "best",
|
"format": "best",
|
||||||
"ignoreerrors": false,
|
"ignoreerrors": false,
|
||||||
"listformats": null,
|
"listformats": null,
|
||||||
"logtostderr": false,
|
"logtostderr": false,
|
||||||
"matchtitle": null,
|
"matchtitle": null,
|
||||||
"max_downloads": null,
|
"max_downloads": null,
|
||||||
"overwrites": null,
|
"overwrites": null,
|
||||||
"nopart": false,
|
"nopart": false,
|
||||||
"noprogress": false,
|
"noprogress": false,
|
||||||
"outtmpl": "%(id)s.%(ext)s",
|
"outtmpl": "%(id)s.%(ext)s",
|
||||||
"password": null,
|
"password": null,
|
||||||
"playlistend": -1,
|
"playliststart": 1,
|
||||||
"playliststart": 1,
|
"prefer_free_formats": false,
|
||||||
"prefer_free_formats": false,
|
"quiet": false,
|
||||||
"quiet": false,
|
"ratelimit": null,
|
||||||
"ratelimit": null,
|
"rejecttitle": null,
|
||||||
"rejecttitle": null,
|
"retries": 10,
|
||||||
"retries": 10,
|
"simulate": false,
|
||||||
"simulate": false,
|
"subtitleslang": null,
|
||||||
"subtitleslang": null,
|
|
||||||
"subtitlesformat": "best",
|
"subtitlesformat": "best",
|
||||||
"test": true,
|
"test": true,
|
||||||
"updatetime": true,
|
"updatetime": true,
|
||||||
"usenetrc": false,
|
"usenetrc": false,
|
||||||
"username": null,
|
"username": null,
|
||||||
"verbose": true,
|
"verbose": true,
|
||||||
"writedescription": false,
|
"writedescription": false,
|
||||||
"writeinfojson": true,
|
"writeinfojson": true,
|
||||||
"writeannotations": false,
|
"writeannotations": false,
|
||||||
"writelink": false,
|
"writelink": false,
|
||||||
"writeurllink": false,
|
"writeurllink": false,
|
||||||
|
|||||||
+516
-35
@@ -450,7 +450,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'language': 'en',
|
'language': 'en',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'audio_ext': 'mp4',
|
'audio_ext': 'mp4',
|
||||||
}, {
|
}, {
|
||||||
'format_id': 'aud2-English',
|
'format_id': 'aud2-English',
|
||||||
@@ -458,7 +458,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'language': 'en',
|
'language': 'en',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'audio_ext': 'mp4',
|
'audio_ext': 'mp4',
|
||||||
}, {
|
}, {
|
||||||
'format_id': 'aud3-English',
|
'format_id': 'aud3-English',
|
||||||
@@ -466,14 +466,14 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'language': 'en',
|
'language': 'en',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'audio_ext': 'mp4',
|
'audio_ext': 'mp4',
|
||||||
}, {
|
}, {
|
||||||
'format_id': '530',
|
'format_id': '530',
|
||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v2/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v2/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 480,
|
'width': 480,
|
||||||
'height': 270,
|
'height': 270,
|
||||||
'vcodec': 'avc1.640015',
|
'vcodec': 'avc1.640015',
|
||||||
@@ -482,7 +482,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v2/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v2/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 480,
|
'width': 480,
|
||||||
'height': 270,
|
'height': 270,
|
||||||
'vcodec': 'avc1.640015',
|
'vcodec': 'avc1.640015',
|
||||||
@@ -491,7 +491,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v2/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v2/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 480,
|
'width': 480,
|
||||||
'height': 270,
|
'height': 270,
|
||||||
'vcodec': 'avc1.640015',
|
'vcodec': 'avc1.640015',
|
||||||
@@ -500,7 +500,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v3/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v3/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 640,
|
'width': 640,
|
||||||
'height': 360,
|
'height': 360,
|
||||||
'vcodec': 'avc1.64001e',
|
'vcodec': 'avc1.64001e',
|
||||||
@@ -509,7 +509,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v3/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v3/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 640,
|
'width': 640,
|
||||||
'height': 360,
|
'height': 360,
|
||||||
'vcodec': 'avc1.64001e',
|
'vcodec': 'avc1.64001e',
|
||||||
@@ -518,7 +518,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v3/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v3/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 640,
|
'width': 640,
|
||||||
'height': 360,
|
'height': 360,
|
||||||
'vcodec': 'avc1.64001e',
|
'vcodec': 'avc1.64001e',
|
||||||
@@ -527,7 +527,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v4/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v4/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 768,
|
'width': 768,
|
||||||
'height': 432,
|
'height': 432,
|
||||||
'vcodec': 'avc1.64001e',
|
'vcodec': 'avc1.64001e',
|
||||||
@@ -536,7 +536,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v4/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v4/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 768,
|
'width': 768,
|
||||||
'height': 432,
|
'height': 432,
|
||||||
'vcodec': 'avc1.64001e',
|
'vcodec': 'avc1.64001e',
|
||||||
@@ -545,7 +545,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v4/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v4/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 768,
|
'width': 768,
|
||||||
'height': 432,
|
'height': 432,
|
||||||
'vcodec': 'avc1.64001e',
|
'vcodec': 'avc1.64001e',
|
||||||
@@ -554,7 +554,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 960,
|
'width': 960,
|
||||||
'height': 540,
|
'height': 540,
|
||||||
'vcodec': 'avc1.640020',
|
'vcodec': 'avc1.640020',
|
||||||
@@ -563,7 +563,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 960,
|
'width': 960,
|
||||||
'height': 540,
|
'height': 540,
|
||||||
'vcodec': 'avc1.640020',
|
'vcodec': 'avc1.640020',
|
||||||
@@ -572,7 +572,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 960,
|
'width': 960,
|
||||||
'height': 540,
|
'height': 540,
|
||||||
'vcodec': 'avc1.640020',
|
'vcodec': 'avc1.640020',
|
||||||
@@ -581,7 +581,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v6/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v6/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1280,
|
'width': 1280,
|
||||||
'height': 720,
|
'height': 720,
|
||||||
'vcodec': 'avc1.640020',
|
'vcodec': 'avc1.640020',
|
||||||
@@ -590,7 +590,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v6/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v6/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1280,
|
'width': 1280,
|
||||||
'height': 720,
|
'height': 720,
|
||||||
'vcodec': 'avc1.640020',
|
'vcodec': 'avc1.640020',
|
||||||
@@ -599,7 +599,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v6/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v6/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1280,
|
'width': 1280,
|
||||||
'height': 720,
|
'height': 720,
|
||||||
'vcodec': 'avc1.640020',
|
'vcodec': 'avc1.640020',
|
||||||
@@ -608,7 +608,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v7/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v7/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -617,7 +617,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v7/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v7/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -626,7 +626,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v7/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v7/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -635,7 +635,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v8/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v8/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -644,7 +644,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v8/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v8/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -653,7 +653,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v8/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v8/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -662,7 +662,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -671,7 +671,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
@@ -680,21 +680,190 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8',
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8',
|
||||||
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'protocol': 'm3u8',
|
'protocol': 'm3u8_native',
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
'vcodec': 'avc1.64002a',
|
'vcodec': 'avc1.64002a',
|
||||||
}]
|
}],
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'bipbop_16x9',
|
||||||
|
'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
[{
|
||||||
|
'format_id': 'bipbop_audio-BipBop Audio 2',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/alternate_audio_aac/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'language': 'eng',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'vcodec': 'none',
|
||||||
|
'audio_ext': 'mp4',
|
||||||
|
'video_ext': 'none',
|
||||||
|
}, {
|
||||||
|
'format_id': '41',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear0/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'tbr': 41.457,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'fps': None,
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'audio_ext': 'mp4',
|
||||||
|
'video_ext': 'none',
|
||||||
|
'abr': 41.457,
|
||||||
|
}, {
|
||||||
|
'format_id': '263',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear1/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'tbr': 263.851,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'fps': None,
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'width': 416,
|
||||||
|
'height': 234,
|
||||||
|
'vcodec': 'avc1.4d400d',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 263.851,
|
||||||
|
'abr': 0,
|
||||||
|
}, {
|
||||||
|
'format_id': '577',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear2/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'tbr': 577.61,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'fps': None,
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'width': 640,
|
||||||
|
'height': 360,
|
||||||
|
'vcodec': 'avc1.4d401e',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 577.61,
|
||||||
|
'abr': 0,
|
||||||
|
}, {
|
||||||
|
'format_id': '915',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear3/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'tbr': 915.905,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'fps': None,
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'width': 960,
|
||||||
|
'height': 540,
|
||||||
|
'vcodec': 'avc1.4d401f',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 915.905,
|
||||||
|
'abr': 0,
|
||||||
|
}, {
|
||||||
|
'format_id': '1030',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear4/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'tbr': 1030.138,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'fps': None,
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
'vcodec': 'avc1.4d401f',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 1030.138,
|
||||||
|
'abr': 0,
|
||||||
|
}, {
|
||||||
|
'format_id': '1924',
|
||||||
|
'format_index': None,
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/gear5/prog_index.m3u8',
|
||||||
|
'manifest_url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8',
|
||||||
|
'tbr': 1924.009,
|
||||||
|
'ext': 'mp4',
|
||||||
|
'fps': None,
|
||||||
|
'protocol': 'm3u8_native',
|
||||||
|
'preference': None,
|
||||||
|
'quality': None,
|
||||||
|
'width': 1920,
|
||||||
|
'height': 1080,
|
||||||
|
'vcodec': 'avc1.4d401f',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 1924.009,
|
||||||
|
'abr': 0,
|
||||||
|
}],
|
||||||
|
{
|
||||||
|
'en': [{
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/eng/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}, {
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/eng_forced/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}],
|
||||||
|
'fr': [{
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/fra/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}, {
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/fra_forced/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}],
|
||||||
|
'es': [{
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/spa/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}, {
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/spa_forced/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}],
|
||||||
|
'ja': [{
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/jpn/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}, {
|
||||||
|
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/jpn_forced/prog_index.m3u8',
|
||||||
|
'ext': 'vtt',
|
||||||
|
'protocol': 'm3u8_native'
|
||||||
|
}],
|
||||||
|
}
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
for m3u8_file, m3u8_url, expected_formats in _TEST_CASES:
|
for m3u8_file, m3u8_url, expected_formats, expected_subs in _TEST_CASES:
|
||||||
with io.open('./test/testdata/m3u8/%s.m3u8' % m3u8_file,
|
with io.open('./test/testdata/m3u8/%s.m3u8' % m3u8_file,
|
||||||
mode='r', encoding='utf-8') as f:
|
mode='r', encoding='utf-8') as f:
|
||||||
formats = self.ie._parse_m3u8_formats(
|
formats, subs = self.ie._parse_m3u8_formats_and_subtitles(
|
||||||
f.read(), m3u8_url, ext='mp4')
|
f.read(), m3u8_url, ext='mp4')
|
||||||
self.ie._sort_formats(formats)
|
self.ie._sort_formats(formats)
|
||||||
expect_value(self, formats, expected_formats, None)
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
expect_value(self, subs, expected_subs, None)
|
||||||
|
|
||||||
def test_parse_mpd_formats(self):
|
def test_parse_mpd_formats(self):
|
||||||
_TEST_CASES = [
|
_TEST_CASES = [
|
||||||
@@ -780,7 +949,8 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'tbr': 5997.485,
|
'tbr': 5997.485,
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
}]
|
}],
|
||||||
|
{},
|
||||||
), (
|
), (
|
||||||
# https://github.com/ytdl-org/youtube-dl/pull/14844
|
# https://github.com/ytdl-org/youtube-dl/pull/14844
|
||||||
'urls_only',
|
'urls_only',
|
||||||
@@ -863,7 +1033,8 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'tbr': 4400,
|
'tbr': 4400,
|
||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
}]
|
}],
|
||||||
|
{},
|
||||||
), (
|
), (
|
||||||
# https://github.com/ytdl-org/youtube-dl/issues/20346
|
# https://github.com/ytdl-org/youtube-dl/issues/20346
|
||||||
# Media considered unfragmented even though it contains
|
# Media considered unfragmented even though it contains
|
||||||
@@ -909,18 +1080,328 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'width': 360,
|
'width': 360,
|
||||||
'height': 360,
|
'height': 360,
|
||||||
'fps': 30,
|
'fps': 30,
|
||||||
}]
|
}],
|
||||||
|
{},
|
||||||
|
), (
|
||||||
|
'subtitles',
|
||||||
|
'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/',
|
||||||
|
[{
|
||||||
|
'format_id': 'audio=128001',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'ext': 'm4a',
|
||||||
|
'tbr': 128.001,
|
||||||
|
'asr': 48000,
|
||||||
|
'format_note': 'DASH audio',
|
||||||
|
'container': 'm4a_dash',
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'audio_ext': 'm4a',
|
||||||
|
'video_ext': 'none',
|
||||||
|
'abr': 128.001,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video=100000',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'width': 336,
|
||||||
|
'height': 144,
|
||||||
|
'tbr': 100,
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'container': 'mp4_dash',
|
||||||
|
'vcodec': 'avc1.4D401F',
|
||||||
|
'acodec': 'none',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 100,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video=326000',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'width': 562,
|
||||||
|
'height': 240,
|
||||||
|
'tbr': 326,
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'container': 'mp4_dash',
|
||||||
|
'vcodec': 'avc1.4D401F',
|
||||||
|
'acodec': 'none',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 326,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video=698000',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'width': 844,
|
||||||
|
'height': 360,
|
||||||
|
'tbr': 698,
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'container': 'mp4_dash',
|
||||||
|
'vcodec': 'avc1.4D401F',
|
||||||
|
'acodec': 'none',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 698,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video=1493000',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'width': 1126,
|
||||||
|
'height': 480,
|
||||||
|
'tbr': 1493,
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'container': 'mp4_dash',
|
||||||
|
'vcodec': 'avc1.4D401F',
|
||||||
|
'acodec': 'none',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 1493,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video=4482000',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'width': 1688,
|
||||||
|
'height': 720,
|
||||||
|
'tbr': 4482,
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'container': 'mp4_dash',
|
||||||
|
'vcodec': 'avc1.4D401F',
|
||||||
|
'acodec': 'none',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'video_ext': 'mp4',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 4482,
|
||||||
|
}],
|
||||||
|
{
|
||||||
|
'en': [
|
||||||
|
{
|
||||||
|
'ext': 'mp4',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
|
||||||
|
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
for mpd_file, mpd_url, mpd_base_url, expected_formats in _TEST_CASES:
|
for mpd_file, mpd_url, mpd_base_url, expected_formats, expected_subtitles in _TEST_CASES:
|
||||||
with io.open('./test/testdata/mpd/%s.mpd' % mpd_file,
|
with io.open('./test/testdata/mpd/%s.mpd' % mpd_file,
|
||||||
mode='r', encoding='utf-8') as f:
|
mode='r', encoding='utf-8') as f:
|
||||||
formats = self.ie._parse_mpd_formats(
|
formats, subtitles = self.ie._parse_mpd_formats_and_subtitles(
|
||||||
compat_etree_fromstring(f.read().encode('utf-8')),
|
compat_etree_fromstring(f.read().encode('utf-8')),
|
||||||
mpd_base_url=mpd_base_url, mpd_url=mpd_url)
|
mpd_base_url=mpd_base_url, mpd_url=mpd_url)
|
||||||
self.ie._sort_formats(formats)
|
self.ie._sort_formats(formats)
|
||||||
expect_value(self, formats, expected_formats, None)
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
expect_value(self, subtitles, expected_subtitles, None)
|
||||||
|
|
||||||
|
def test_parse_ism_formats(self):
|
||||||
|
_TEST_CASES = [
|
||||||
|
(
|
||||||
|
'sintel',
|
||||||
|
'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
[{
|
||||||
|
'format_id': 'audio-128',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'ext': 'isma',
|
||||||
|
'tbr': 128,
|
||||||
|
'asr': 48000,
|
||||||
|
'vcodec': 'none',
|
||||||
|
'acodec': 'AACL',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'audio',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'width': 0,
|
||||||
|
'height': 0,
|
||||||
|
'fourcc': 'AACL',
|
||||||
|
'codec_private_data': '1190',
|
||||||
|
'sampling_rate': 48000,
|
||||||
|
'channels': 2,
|
||||||
|
'bits_per_sample': 16,
|
||||||
|
'nal_unit_length_field': 4
|
||||||
|
},
|
||||||
|
'audio_ext': 'isma',
|
||||||
|
'video_ext': 'none',
|
||||||
|
'abr': 128,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video-100',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'ext': 'ismv',
|
||||||
|
'width': 336,
|
||||||
|
'height': 144,
|
||||||
|
'tbr': 100,
|
||||||
|
'vcodec': 'AVC1',
|
||||||
|
'acodec': 'none',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'video',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'width': 336,
|
||||||
|
'height': 144,
|
||||||
|
'fourcc': 'AVC1',
|
||||||
|
'codec_private_data': '00000001674D401FDA0544EFFC2D002CBC40000003004000000C03C60CA80000000168EF32C8',
|
||||||
|
'channels': 2,
|
||||||
|
'bits_per_sample': 16,
|
||||||
|
'nal_unit_length_field': 4
|
||||||
|
},
|
||||||
|
'video_ext': 'ismv',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 100,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video-326',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'ext': 'ismv',
|
||||||
|
'width': 562,
|
||||||
|
'height': 240,
|
||||||
|
'tbr': 326,
|
||||||
|
'vcodec': 'AVC1',
|
||||||
|
'acodec': 'none',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'video',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'width': 562,
|
||||||
|
'height': 240,
|
||||||
|
'fourcc': 'AVC1',
|
||||||
|
'codec_private_data': '00000001674D401FDA0241FE23FFC3BC83BA44000003000400000300C03C60CA800000000168EF32C8',
|
||||||
|
'channels': 2,
|
||||||
|
'bits_per_sample': 16,
|
||||||
|
'nal_unit_length_field': 4
|
||||||
|
},
|
||||||
|
'video_ext': 'ismv',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 326,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video-698',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'ext': 'ismv',
|
||||||
|
'width': 844,
|
||||||
|
'height': 360,
|
||||||
|
'tbr': 698,
|
||||||
|
'vcodec': 'AVC1',
|
||||||
|
'acodec': 'none',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'video',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'width': 844,
|
||||||
|
'height': 360,
|
||||||
|
'fourcc': 'AVC1',
|
||||||
|
'codec_private_data': '00000001674D401FDA0350BFB97FF06AF06AD1000003000100000300300F1832A00000000168EF32C8',
|
||||||
|
'channels': 2,
|
||||||
|
'bits_per_sample': 16,
|
||||||
|
'nal_unit_length_field': 4
|
||||||
|
},
|
||||||
|
'video_ext': 'ismv',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 698,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video-1493',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'ext': 'ismv',
|
||||||
|
'width': 1126,
|
||||||
|
'height': 480,
|
||||||
|
'tbr': 1493,
|
||||||
|
'vcodec': 'AVC1',
|
||||||
|
'acodec': 'none',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'video',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'width': 1126,
|
||||||
|
'height': 480,
|
||||||
|
'fourcc': 'AVC1',
|
||||||
|
'codec_private_data': '00000001674D401FDA011C3DE6FFF0D890D871000003000100000300300F1832A00000000168EF32C8',
|
||||||
|
'channels': 2,
|
||||||
|
'bits_per_sample': 16,
|
||||||
|
'nal_unit_length_field': 4
|
||||||
|
},
|
||||||
|
'video_ext': 'ismv',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 1493,
|
||||||
|
}, {
|
||||||
|
'format_id': 'video-4482',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'ext': 'ismv',
|
||||||
|
'width': 1688,
|
||||||
|
'height': 720,
|
||||||
|
'tbr': 4482,
|
||||||
|
'vcodec': 'AVC1',
|
||||||
|
'acodec': 'none',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'video',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'width': 1688,
|
||||||
|
'height': 720,
|
||||||
|
'fourcc': 'AVC1',
|
||||||
|
'codec_private_data': '00000001674D401FDA01A816F97FFC1ABC1AB440000003004000000C03C60CA80000000168EF32C8',
|
||||||
|
'channels': 2,
|
||||||
|
'bits_per_sample': 16,
|
||||||
|
'nal_unit_length_field': 4
|
||||||
|
},
|
||||||
|
'video_ext': 'ismv',
|
||||||
|
'audio_ext': 'none',
|
||||||
|
'vbr': 4482,
|
||||||
|
}],
|
||||||
|
{
|
||||||
|
'eng': [
|
||||||
|
{
|
||||||
|
'ext': 'ismt',
|
||||||
|
'protocol': 'ism',
|
||||||
|
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'manifest_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/Manifest',
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': 'text',
|
||||||
|
'duration': 8880746666,
|
||||||
|
'timescale': 10000000,
|
||||||
|
'fourcc': 'TTML',
|
||||||
|
'codec_private_data': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for ism_file, ism_url, expected_formats, expected_subtitles in _TEST_CASES:
|
||||||
|
with io.open('./test/testdata/ism/%s.Manifest' % ism_file,
|
||||||
|
mode='r', encoding='utf-8') as f:
|
||||||
|
formats, subtitles = self.ie._parse_ism_formats_and_subtitles(
|
||||||
|
compat_etree_fromstring(f.read().encode('utf-8')), ism_url=ism_url)
|
||||||
|
self.ie._sort_formats(formats)
|
||||||
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
expect_value(self, subtitles, expected_subtitles, None)
|
||||||
|
|
||||||
def test_parse_f4m_formats(self):
|
def test_parse_f4m_formats(self):
|
||||||
_TEST_CASES = [
|
_TEST_CASES = [
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class YDL(FakeYDL):
|
|||||||
self.msgs = []
|
self.msgs = []
|
||||||
|
|
||||||
def process_info(self, info_dict):
|
def process_info(self, info_dict):
|
||||||
|
info_dict.pop('__original_infodict', None)
|
||||||
self.downloaded_info_dicts.append(info_dict)
|
self.downloaded_info_dicts.append(info_dict)
|
||||||
|
|
||||||
def to_screen(self, msg):
|
def to_screen(self, msg):
|
||||||
@@ -755,7 +756,7 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
def process_info(self, info_dict):
|
def process_info(self, info_dict):
|
||||||
super(YDL, self).process_info(info_dict)
|
super(YDL, self).process_info(info_dict)
|
||||||
|
|
||||||
def _match_entry(self, info_dict, incomplete):
|
def _match_entry(self, info_dict, incomplete=False):
|
||||||
res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
|
res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
|
||||||
if res is None:
|
if res is None:
|
||||||
self.downloaded_info_dicts.append(info_dict)
|
self.downloaded_info_dicts.append(info_dict)
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ def generator(test_case, tname):
|
|||||||
params['outtmpl'] = tname + '_' + params['outtmpl']
|
params['outtmpl'] = tname + '_' + params['outtmpl']
|
||||||
if is_playlist and 'playlist' not in test_case:
|
if is_playlist and 'playlist' not in test_case:
|
||||||
params.setdefault('extract_flat', 'in_playlist')
|
params.setdefault('extract_flat', 'in_playlist')
|
||||||
|
params.setdefault('playlistend', test_case.get('playlist_mincount'))
|
||||||
params.setdefault('skip_download', True)
|
params.setdefault('skip_download', True)
|
||||||
|
|
||||||
ydl = YoutubeDL(params, auto_init=False)
|
ydl = YoutubeDL(params, auto_init=False)
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import sys
|
|||||||
import unittest
|
import unittest
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from yt_dlp.postprocessor import MetadataFromFieldPP, MetadataFromTitlePP
|
from yt_dlp.postprocessor import (
|
||||||
|
FFmpegThumbnailsConvertorPP,
|
||||||
|
MetadataFromFieldPP,
|
||||||
|
MetadataFromTitlePP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestMetadataFromField(unittest.TestCase):
|
class TestMetadataFromField(unittest.TestCase):
|
||||||
@@ -30,3 +34,24 @@ class TestMetadataFromTitle(unittest.TestCase):
|
|||||||
def test_format_to_regex(self):
|
def test_format_to_regex(self):
|
||||||
pp = MetadataFromTitlePP(None, '%(title)s - %(artist)s')
|
pp = MetadataFromTitlePP(None, '%(title)s - %(artist)s')
|
||||||
self.assertEqual(pp._titleregex, r'(?P<title>.+)\ \-\ (?P<artist>.+)')
|
self.assertEqual(pp._titleregex, r'(?P<title>.+)\ \-\ (?P<artist>.+)')
|
||||||
|
|
||||||
|
|
||||||
|
class TestConvertThumbnail(unittest.TestCase):
|
||||||
|
def test_escaping(self):
|
||||||
|
pp = FFmpegThumbnailsConvertorPP()
|
||||||
|
if not pp.available:
|
||||||
|
print('Skipping: ffmpeg not found')
|
||||||
|
return
|
||||||
|
|
||||||
|
file = 'test/testdata/thumbnails/foo %d bar/foo_%d.{}'
|
||||||
|
tests = (('webp', 'png'), ('png', 'jpg'))
|
||||||
|
|
||||||
|
for inp, out in tests:
|
||||||
|
out_file = file.format(out)
|
||||||
|
if os.path.exists(out_file):
|
||||||
|
os.remove(out_file)
|
||||||
|
pp.convert_thumbnail(file.format(inp), out)
|
||||||
|
assert os.path.exists(out_file)
|
||||||
|
|
||||||
|
for _, out in tests:
|
||||||
|
os.remove(file.format(out))
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ from yt_dlp.utils import (
|
|||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
sanitize_path,
|
sanitize_path,
|
||||||
sanitize_url,
|
sanitize_url,
|
||||||
|
sanitized_Request,
|
||||||
expand_path,
|
expand_path,
|
||||||
prepend_extension,
|
prepend_extension,
|
||||||
replace_extension,
|
replace_extension,
|
||||||
@@ -238,6 +239,16 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(sanitize_url('httpss://foo.bar'), 'https://foo.bar')
|
self.assertEqual(sanitize_url('httpss://foo.bar'), 'https://foo.bar')
|
||||||
self.assertEqual(sanitize_url('rmtps://foo.bar'), 'rtmps://foo.bar')
|
self.assertEqual(sanitize_url('rmtps://foo.bar'), 'rtmps://foo.bar')
|
||||||
self.assertEqual(sanitize_url('https://foo.bar'), 'https://foo.bar')
|
self.assertEqual(sanitize_url('https://foo.bar'), 'https://foo.bar')
|
||||||
|
self.assertEqual(sanitize_url('foo bar'), 'foo bar')
|
||||||
|
|
||||||
|
def test_extract_basic_auth(self):
|
||||||
|
auth_header = lambda url: sanitized_Request(url).get_header('Authorization')
|
||||||
|
self.assertFalse(auth_header('http://foo.bar'))
|
||||||
|
self.assertFalse(auth_header('http://:foo.bar'))
|
||||||
|
self.assertEqual(auth_header('http://@foo.bar'), 'Basic Og==')
|
||||||
|
self.assertEqual(auth_header('http://:pass@foo.bar'), 'Basic OnBhc3M=')
|
||||||
|
self.assertEqual(auth_header('http://user:@foo.bar'), 'Basic dXNlcjo=')
|
||||||
|
self.assertEqual(auth_header('http://user:pass@foo.bar'), 'Basic dXNlcjpwYXNz')
|
||||||
|
|
||||||
def test_expand_path(self):
|
def test_expand_path(self):
|
||||||
def env(var):
|
def env(var):
|
||||||
|
|||||||
Vendored
+988
@@ -0,0 +1,988 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Created with Unified Streaming Platform (version=1.10.18-20255) -->
|
||||||
|
<SmoothStreamingMedia
|
||||||
|
MajorVersion="2"
|
||||||
|
MinorVersion="0"
|
||||||
|
TimeScale="10000000"
|
||||||
|
Duration="8880746666">
|
||||||
|
<StreamIndex
|
||||||
|
Type="audio"
|
||||||
|
QualityLevels="1"
|
||||||
|
TimeScale="10000000"
|
||||||
|
Name="audio"
|
||||||
|
Chunks="445"
|
||||||
|
Url="QualityLevels({bitrate})/Fragments(audio={start time})">
|
||||||
|
<QualityLevel
|
||||||
|
Index="0"
|
||||||
|
Bitrate="128001"
|
||||||
|
CodecPrivateData="1190"
|
||||||
|
SamplingRate="48000"
|
||||||
|
Channels="2"
|
||||||
|
BitsPerSample="16"
|
||||||
|
PacketSize="4"
|
||||||
|
AudioTag="255"
|
||||||
|
FourCC="AACL" />
|
||||||
|
<c t="0" d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053333" />
|
||||||
|
<c d="20053334" />
|
||||||
|
<c d="19840000" />
|
||||||
|
<c d="746666" />
|
||||||
|
</StreamIndex>
|
||||||
|
<StreamIndex
|
||||||
|
Type="text"
|
||||||
|
QualityLevels="1"
|
||||||
|
TimeScale="10000000"
|
||||||
|
Language="eng"
|
||||||
|
Subtype="CAPT"
|
||||||
|
Name="textstream_eng"
|
||||||
|
Chunks="11"
|
||||||
|
Url="QualityLevels({bitrate})/Fragments(textstream_eng={start time})">
|
||||||
|
<QualityLevel
|
||||||
|
Index="0"
|
||||||
|
Bitrate="1000"
|
||||||
|
CodecPrivateData=""
|
||||||
|
FourCC="TTML" />
|
||||||
|
<c t="0" d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="600000000" />
|
||||||
|
<c d="240000000" />
|
||||||
|
</StreamIndex>
|
||||||
|
<StreamIndex
|
||||||
|
Type="video"
|
||||||
|
QualityLevels="5"
|
||||||
|
TimeScale="10000000"
|
||||||
|
Name="video"
|
||||||
|
Chunks="444"
|
||||||
|
Url="QualityLevels({bitrate})/Fragments(video={start time})"
|
||||||
|
MaxWidth="1688"
|
||||||
|
MaxHeight="720"
|
||||||
|
DisplayWidth="1689"
|
||||||
|
DisplayHeight="720">
|
||||||
|
<QualityLevel
|
||||||
|
Index="0"
|
||||||
|
Bitrate="100000"
|
||||||
|
CodecPrivateData="00000001674D401FDA0544EFFC2D002CBC40000003004000000C03C60CA80000000168EF32C8"
|
||||||
|
MaxWidth="336"
|
||||||
|
MaxHeight="144"
|
||||||
|
FourCC="AVC1" />
|
||||||
|
<QualityLevel
|
||||||
|
Index="1"
|
||||||
|
Bitrate="326000"
|
||||||
|
CodecPrivateData="00000001674D401FDA0241FE23FFC3BC83BA44000003000400000300C03C60CA800000000168EF32C8"
|
||||||
|
MaxWidth="562"
|
||||||
|
MaxHeight="240"
|
||||||
|
FourCC="AVC1" />
|
||||||
|
<QualityLevel
|
||||||
|
Index="2"
|
||||||
|
Bitrate="698000"
|
||||||
|
CodecPrivateData="00000001674D401FDA0350BFB97FF06AF06AD1000003000100000300300F1832A00000000168EF32C8"
|
||||||
|
MaxWidth="844"
|
||||||
|
MaxHeight="360"
|
||||||
|
FourCC="AVC1" />
|
||||||
|
<QualityLevel
|
||||||
|
Index="3"
|
||||||
|
Bitrate="1493000"
|
||||||
|
CodecPrivateData="00000001674D401FDA011C3DE6FFF0D890D871000003000100000300300F1832A00000000168EF32C8"
|
||||||
|
MaxWidth="1126"
|
||||||
|
MaxHeight="480"
|
||||||
|
FourCC="AVC1" />
|
||||||
|
<QualityLevel
|
||||||
|
Index="4"
|
||||||
|
Bitrate="4482000"
|
||||||
|
CodecPrivateData="00000001674D401FDA01A816F97FFC1ABC1AB440000003004000000C03C60CA80000000168EF32C8"
|
||||||
|
MaxWidth="1688"
|
||||||
|
MaxHeight="720"
|
||||||
|
FourCC="AVC1" />
|
||||||
|
<c t="0" d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
<c d="20000000" />
|
||||||
|
</StreamIndex>
|
||||||
|
</SmoothStreamingMedia>
|
||||||
Vendored
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#EXTM3U
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 1",AUTOSELECT=YES,DEFAULT=YES
|
||||||
|
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 2",AUTOSELECT=NO,DEFAULT=NO,URI="alternate_audio_aac/prog_index.m3u8"
|
||||||
|
|
||||||
|
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/eng/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="en",URI="subtitles/eng_forced/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/fra/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="fr",URI="subtitles/fra_forced/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="es",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/spa/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="es",URI="subtitles/spa_forced/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="ja",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/jpn/prog_index.m3u8"
|
||||||
|
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語 (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="ja",URI="subtitles/jpn_forced/prog_index.m3u8"
|
||||||
|
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d",RESOLUTION=416x234,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear1/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=28451,CODECS="avc1.4d400d",URI="gear1/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=577610,CODECS="mp4a.40.2, avc1.4d401e",RESOLUTION=640x360,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear2/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=181534,CODECS="avc1.4d401e",URI="gear2/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=915905,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=960x540,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear3/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=297056,CODECS="avc1.4d401f",URI="gear3/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1030138,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1280x720,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear4/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=339492,CODECS="avc1.4d401f",URI="gear4/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=1924009,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1920x1080,AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear5/prog_index.m3u8
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=669554,CODECS="avc1.4d401f",URI="gear5/iframe_index.m3u8"
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS="mp4a.40.2",AUDIO="bipbop_audio",SUBTITLES="subs"
|
||||||
|
gear0/prog_index.m3u8
|
||||||
Vendored
+351
@@ -0,0 +1,351 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Created with Unified Streaming Platform (version=1.10.18-20255) -->
|
||||||
|
<MPD
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||||
|
xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
||||||
|
type="static"
|
||||||
|
mediaPresentationDuration="PT14M48S"
|
||||||
|
maxSegmentDuration="PT1M"
|
||||||
|
minBufferTime="PT10S"
|
||||||
|
profiles="urn:mpeg:dash:profile:isoff-live:2011">
|
||||||
|
<Period
|
||||||
|
id="1"
|
||||||
|
duration="PT14M48S">
|
||||||
|
<BaseURL>dash/</BaseURL>
|
||||||
|
<AdaptationSet
|
||||||
|
id="1"
|
||||||
|
group="1"
|
||||||
|
contentType="audio"
|
||||||
|
segmentAlignment="true"
|
||||||
|
audioSamplingRate="48000"
|
||||||
|
mimeType="audio/mp4"
|
||||||
|
codecs="mp4a.40.2"
|
||||||
|
startWithSAP="1">
|
||||||
|
<AudioChannelConfiguration
|
||||||
|
schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
|
||||||
|
value="2" />
|
||||||
|
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main" />
|
||||||
|
<SegmentTemplate
|
||||||
|
timescale="48000"
|
||||||
|
initialization="3144-kZT4LWMQw6Rh7Kpd-$RepresentationID$.dash"
|
||||||
|
media="3144-kZT4LWMQw6Rh7Kpd-$RepresentationID$-$Time$.dash">
|
||||||
|
<SegmentTimeline>
|
||||||
|
<S t="0" d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="96256" r="2" />
|
||||||
|
<S d="95232" />
|
||||||
|
<S d="3584" />
|
||||||
|
</SegmentTimeline>
|
||||||
|
</SegmentTemplate>
|
||||||
|
<Representation
|
||||||
|
id="audio=128001"
|
||||||
|
bandwidth="128001">
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet
|
||||||
|
id="2"
|
||||||
|
group="3"
|
||||||
|
contentType="text"
|
||||||
|
lang="en"
|
||||||
|
mimeType="application/mp4"
|
||||||
|
codecs="stpp"
|
||||||
|
startWithSAP="1">
|
||||||
|
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle" />
|
||||||
|
<SegmentTemplate
|
||||||
|
timescale="1000"
|
||||||
|
initialization="3144-kZT4LWMQw6Rh7Kpd-$RepresentationID$.dash"
|
||||||
|
media="3144-kZT4LWMQw6Rh7Kpd-$RepresentationID$-$Time$.dash">
|
||||||
|
<SegmentTimeline>
|
||||||
|
<S t="0" d="60000" r="9" />
|
||||||
|
<S d="24000" />
|
||||||
|
</SegmentTimeline>
|
||||||
|
</SegmentTemplate>
|
||||||
|
<Representation
|
||||||
|
id="textstream_eng=1000"
|
||||||
|
bandwidth="1000">
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet
|
||||||
|
id="3"
|
||||||
|
group="2"
|
||||||
|
contentType="video"
|
||||||
|
par="960:409"
|
||||||
|
minBandwidth="100000"
|
||||||
|
maxBandwidth="4482000"
|
||||||
|
maxWidth="1689"
|
||||||
|
maxHeight="720"
|
||||||
|
segmentAlignment="true"
|
||||||
|
mimeType="video/mp4"
|
||||||
|
codecs="avc1.4D401F"
|
||||||
|
startWithSAP="1">
|
||||||
|
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main" />
|
||||||
|
<SegmentTemplate
|
||||||
|
timescale="12288"
|
||||||
|
initialization="3144-kZT4LWMQw6Rh7Kpd-$RepresentationID$.dash"
|
||||||
|
media="3144-kZT4LWMQw6Rh7Kpd-$RepresentationID$-$Time$.dash">
|
||||||
|
<SegmentTimeline>
|
||||||
|
<S t="0" d="24576" r="443" />
|
||||||
|
</SegmentTimeline>
|
||||||
|
</SegmentTemplate>
|
||||||
|
<Representation
|
||||||
|
id="video=100000"
|
||||||
|
bandwidth="100000"
|
||||||
|
width="336"
|
||||||
|
height="144"
|
||||||
|
sar="2880:2863"
|
||||||
|
scanType="progressive">
|
||||||
|
</Representation>
|
||||||
|
<Representation
|
||||||
|
id="video=326000"
|
||||||
|
bandwidth="326000"
|
||||||
|
width="562"
|
||||||
|
height="240"
|
||||||
|
sar="115200:114929"
|
||||||
|
scanType="progressive">
|
||||||
|
</Representation>
|
||||||
|
<Representation
|
||||||
|
id="video=698000"
|
||||||
|
bandwidth="698000"
|
||||||
|
width="844"
|
||||||
|
height="360"
|
||||||
|
sar="86400:86299"
|
||||||
|
scanType="progressive">
|
||||||
|
</Representation>
|
||||||
|
<Representation
|
||||||
|
id="video=1493000"
|
||||||
|
bandwidth="1493000"
|
||||||
|
width="1126"
|
||||||
|
height="480"
|
||||||
|
sar="230400:230267"
|
||||||
|
scanType="progressive">
|
||||||
|
</Representation>
|
||||||
|
<Representation
|
||||||
|
id="video=4482000"
|
||||||
|
bandwidth="4482000"
|
||||||
|
width="1688"
|
||||||
|
height="720"
|
||||||
|
sar="86400:86299"
|
||||||
|
scanType="progressive">
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
+459
-283
File diff suppressed because it is too large
Load Diff
+101
-41
@@ -31,20 +31,26 @@ from .utils import (
|
|||||||
preferredencoding,
|
preferredencoding,
|
||||||
read_batch_urls,
|
read_batch_urls,
|
||||||
RejectedVideoReached,
|
RejectedVideoReached,
|
||||||
REMUX_EXTENSIONS,
|
|
||||||
render_table,
|
render_table,
|
||||||
SameFileError,
|
SameFileError,
|
||||||
setproctitle,
|
setproctitle,
|
||||||
std_headers,
|
std_headers,
|
||||||
write_string,
|
write_string,
|
||||||
)
|
)
|
||||||
from .update import update_self
|
from .update import run_update
|
||||||
from .downloader import (
|
from .downloader import (
|
||||||
FileDownloader,
|
FileDownloader,
|
||||||
)
|
)
|
||||||
from .extractor import gen_extractors, list_extractors
|
from .extractor import gen_extractors, list_extractors
|
||||||
from .extractor.common import InfoExtractor
|
from .extractor.common import InfoExtractor
|
||||||
from .extractor.adobepass import MSO_INFO
|
from .extractor.adobepass import MSO_INFO
|
||||||
|
from .postprocessor.ffmpeg import (
|
||||||
|
FFmpegExtractAudioPP,
|
||||||
|
FFmpegSubtitlesConvertorPP,
|
||||||
|
FFmpegThumbnailsConvertorPP,
|
||||||
|
FFmpegVideoConvertorPP,
|
||||||
|
FFmpegVideoRemuxerPP,
|
||||||
|
)
|
||||||
from .postprocessor.metadatafromfield import MetadataFromFieldPP
|
from .postprocessor.metadatafromfield import MetadataFromFieldPP
|
||||||
from .YoutubeDL import YoutubeDL
|
from .YoutubeDL import YoutubeDL
|
||||||
|
|
||||||
@@ -60,6 +66,7 @@ def _real_main(argv=None):
|
|||||||
setproctitle('yt-dlp')
|
setproctitle('yt-dlp')
|
||||||
|
|
||||||
parser, opts, args = parseOpts(argv)
|
parser, opts, args = parseOpts(argv)
|
||||||
|
warnings = []
|
||||||
|
|
||||||
# Set user agent
|
# Set user agent
|
||||||
if opts.user_agent is not None:
|
if opts.user_agent is not None:
|
||||||
@@ -128,16 +135,12 @@ def _real_main(argv=None):
|
|||||||
parser.error('account username missing\n')
|
parser.error('account username missing\n')
|
||||||
if opts.ap_password is not None and opts.ap_username is None:
|
if opts.ap_password is not None and opts.ap_username is None:
|
||||||
parser.error('TV Provider account username missing\n')
|
parser.error('TV Provider account username missing\n')
|
||||||
if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
|
|
||||||
parser.error('using output template conflicts with using title, video ID or auto number')
|
|
||||||
if opts.autonumber_size is not None:
|
if opts.autonumber_size is not None:
|
||||||
if opts.autonumber_size <= 0:
|
if opts.autonumber_size <= 0:
|
||||||
parser.error('auto number size must be positive')
|
parser.error('auto number size must be positive')
|
||||||
if opts.autonumber_start is not None:
|
if opts.autonumber_start is not None:
|
||||||
if opts.autonumber_start < 0:
|
if opts.autonumber_start < 0:
|
||||||
parser.error('auto number start must be positive or 0')
|
parser.error('auto number start must be positive or 0')
|
||||||
if opts.usetitle and opts.useid:
|
|
||||||
parser.error('using title conflicts with using video ID')
|
|
||||||
if opts.username is not None and opts.password is None:
|
if opts.username is not None and opts.password is None:
|
||||||
opts.password = compat_getpass('Type account password and press [Return]: ')
|
opts.password = compat_getpass('Type account password and press [Return]: ')
|
||||||
if opts.ap_username is not None and opts.ap_password is None:
|
if opts.ap_username is not None and opts.ap_password is None:
|
||||||
@@ -177,8 +180,7 @@ def _real_main(argv=None):
|
|||||||
parser.error('requests sleep interval must be positive or 0')
|
parser.error('requests sleep interval must be positive or 0')
|
||||||
if opts.ap_mso and opts.ap_mso not in MSO_INFO:
|
if opts.ap_mso and opts.ap_mso not in MSO_INFO:
|
||||||
parser.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
|
parser.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
|
||||||
if opts.overwrites:
|
if opts.overwrites: # --yes-overwrites implies --no-continue
|
||||||
# --yes-overwrites implies --no-continue
|
|
||||||
opts.continue_dl = False
|
opts.continue_dl = False
|
||||||
if opts.concurrent_fragment_downloads <= 0:
|
if opts.concurrent_fragment_downloads <= 0:
|
||||||
raise ValueError('Concurrent fragments must be positive')
|
raise ValueError('Concurrent fragments must be positive')
|
||||||
@@ -213,25 +215,25 @@ def _real_main(argv=None):
|
|||||||
if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart:
|
if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart:
|
||||||
raise ValueError('Playlist end must be greater than playlist start')
|
raise ValueError('Playlist end must be greater than playlist start')
|
||||||
if opts.extractaudio:
|
if opts.extractaudio:
|
||||||
if opts.audioformat not in ['best', 'aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
|
if opts.audioformat not in ['best'] + list(FFmpegExtractAudioPP.SUPPORTED_EXTS):
|
||||||
parser.error('invalid audio format specified')
|
parser.error('invalid audio format specified')
|
||||||
if opts.audioquality:
|
if opts.audioquality:
|
||||||
opts.audioquality = opts.audioquality.strip('k').strip('K')
|
opts.audioquality = opts.audioquality.strip('k').strip('K')
|
||||||
if not opts.audioquality.isdigit():
|
if not opts.audioquality.isdigit():
|
||||||
parser.error('invalid audio quality specified')
|
parser.error('invalid audio quality specified')
|
||||||
if opts.recodevideo is not None:
|
if opts.recodevideo is not None:
|
||||||
if opts.recodevideo not in REMUX_EXTENSIONS:
|
opts.recodevideo = opts.recodevideo.replace(' ', '')
|
||||||
parser.error('invalid video recode format specified')
|
if not re.match(FFmpegVideoConvertorPP.FORMAT_RE, opts.recodevideo):
|
||||||
|
parser.error('invalid video remux format specified')
|
||||||
if opts.remuxvideo is not None:
|
if opts.remuxvideo is not None:
|
||||||
opts.remuxvideo = opts.remuxvideo.replace(' ', '')
|
opts.remuxvideo = opts.remuxvideo.replace(' ', '')
|
||||||
remux_regex = r'{0}(?:/{0})*$'.format(r'(?:\w+>)?(?:%s)' % '|'.join(REMUX_EXTENSIONS))
|
if not re.match(FFmpegVideoRemuxerPP.FORMAT_RE, opts.remuxvideo):
|
||||||
if not re.match(remux_regex, opts.remuxvideo):
|
|
||||||
parser.error('invalid video remux format specified')
|
parser.error('invalid video remux format specified')
|
||||||
if opts.convertsubtitles is not None:
|
if opts.convertsubtitles is not None:
|
||||||
if opts.convertsubtitles not in ('srt', 'vtt', 'ass', 'lrc'):
|
if opts.convertsubtitles not in FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS:
|
||||||
parser.error('invalid subtitle format specified')
|
parser.error('invalid subtitle format specified')
|
||||||
if opts.convertthumbnails is not None:
|
if opts.convertthumbnails is not None:
|
||||||
if opts.convertthumbnails not in ('jpg', ):
|
if opts.convertthumbnails not in FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS:
|
||||||
parser.error('invalid thumbnail format specified')
|
parser.error('invalid thumbnail format specified')
|
||||||
|
|
||||||
if opts.date is not None:
|
if opts.date is not None:
|
||||||
@@ -239,21 +241,75 @@ def _real_main(argv=None):
|
|||||||
else:
|
else:
|
||||||
date = DateRange(opts.dateafter, opts.datebefore)
|
date = DateRange(opts.dateafter, opts.datebefore)
|
||||||
|
|
||||||
# Do not download videos when there are audio-only formats
|
def parse_compat_opts():
|
||||||
|
parsed_compat_opts, compat_opts = set(), opts.compat_opts[::-1]
|
||||||
|
while compat_opts:
|
||||||
|
actual_opt = opt = compat_opts.pop().lower()
|
||||||
|
if opt == 'youtube-dl':
|
||||||
|
compat_opts.extend(['-multistreams', 'all'])
|
||||||
|
elif opt == 'youtube-dlc':
|
||||||
|
compat_opts.extend(['-no-youtube-channel-redirect', '-no-live-chat', 'all'])
|
||||||
|
elif opt == 'all':
|
||||||
|
parsed_compat_opts.update(all_compat_opts)
|
||||||
|
elif opt == '-all':
|
||||||
|
parsed_compat_opts = set()
|
||||||
|
else:
|
||||||
|
if opt[0] == '-':
|
||||||
|
opt = opt[1:]
|
||||||
|
parsed_compat_opts.discard(opt)
|
||||||
|
else:
|
||||||
|
parsed_compat_opts.update([opt])
|
||||||
|
if opt not in all_compat_opts:
|
||||||
|
parser.error('Invalid compatibility option %s' % actual_opt)
|
||||||
|
return parsed_compat_opts
|
||||||
|
|
||||||
|
all_compat_opts = [
|
||||||
|
'filename', 'format-sort', 'abort-on-error', 'format-spec', 'no-playlist-metafiles',
|
||||||
|
'multistreams', 'no-live-chat', 'playlist-index', 'list-formats', 'no-direct-merge',
|
||||||
|
'no-youtube-channel-redirect', 'no-youtube-unavailable-videos', 'no-attach-info-json',
|
||||||
|
]
|
||||||
|
compat_opts = parse_compat_opts()
|
||||||
|
|
||||||
|
def _unused_compat_opt(name):
|
||||||
|
if name not in compat_opts:
|
||||||
|
return False
|
||||||
|
compat_opts.discard(name)
|
||||||
|
compat_opts.update(['*%s' % name])
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_default_compat(compat_name, opt_name, default=True, remove_compat=False):
|
||||||
|
attr = getattr(opts, opt_name)
|
||||||
|
if compat_name in compat_opts:
|
||||||
|
if attr is None:
|
||||||
|
setattr(opts, opt_name, not default)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if remove_compat:
|
||||||
|
_unused_compat_opt(compat_name)
|
||||||
|
return False
|
||||||
|
elif attr is None:
|
||||||
|
setattr(opts, opt_name, default)
|
||||||
|
return None
|
||||||
|
|
||||||
|
set_default_compat('abort-on-error', 'ignoreerrors')
|
||||||
|
set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
|
||||||
|
if 'format-sort' in compat_opts:
|
||||||
|
opts.format_sort.extend(InfoExtractor.FormatSort.ytdl_default)
|
||||||
|
_video_multistreams_set = set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat=False)
|
||||||
|
_audio_multistreams_set = set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat=False)
|
||||||
|
if _video_multistreams_set is False and _audio_multistreams_set is False:
|
||||||
|
_unused_compat_opt('multistreams')
|
||||||
|
outtmpl_default = opts.outtmpl.get('default')
|
||||||
|
if 'filename' in compat_opts:
|
||||||
|
if outtmpl_default is None:
|
||||||
|
outtmpl_default = '%(title)s.%(id)s.%(ext)s'
|
||||||
|
opts.outtmpl.update({'default': outtmpl_default})
|
||||||
|
else:
|
||||||
|
_unused_compat_opt('filename')
|
||||||
|
|
||||||
if opts.extractaudio and not opts.keepvideo and opts.format is None:
|
if opts.extractaudio and not opts.keepvideo and opts.format is None:
|
||||||
opts.format = 'bestaudio/best'
|
opts.format = 'bestaudio/best'
|
||||||
|
|
||||||
outtmpl = opts.outtmpl
|
|
||||||
if not outtmpl:
|
|
||||||
outtmpl = {'default': (
|
|
||||||
'%(title)s-%(id)s-%(format)s.%(ext)s' if opts.format == '-1' and opts.usetitle
|
|
||||||
else '%(id)s-%(format)s.%(ext)s' if opts.format == '-1'
|
|
||||||
else '%(autonumber)s-%(title)s-%(id)s.%(ext)s' if opts.usetitle and opts.autonumber
|
|
||||||
else '%(title)s-%(id)s.%(ext)s' if opts.usetitle
|
|
||||||
else '%(id)s.%(ext)s' if opts.useid
|
|
||||||
else '%(autonumber)s-%(id)s.%(ext)s' if opts.autonumber
|
|
||||||
else None)}
|
|
||||||
outtmpl_default = outtmpl.get('default')
|
|
||||||
if outtmpl_default is not None and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio:
|
if outtmpl_default is not None and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio:
|
||||||
parser.error('Cannot download a video and extract audio into the same'
|
parser.error('Cannot download a video and extract audio into the same'
|
||||||
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
||||||
@@ -271,7 +327,7 @@ def _real_main(argv=None):
|
|||||||
if re.match(MetadataFromFieldPP.regex, f) is None:
|
if re.match(MetadataFromFieldPP.regex, f) is None:
|
||||||
parser.error('invalid format string "%s" specified for --parse-metadata' % f)
|
parser.error('invalid format string "%s" specified for --parse-metadata' % f)
|
||||||
|
|
||||||
any_getting = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
|
any_getting = opts.forceprint or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
|
||||||
any_printing = opts.print_json
|
any_printing = opts.print_json
|
||||||
download_archive_fn = expand_path(opts.download_archive) if opts.download_archive is not None else opts.download_archive
|
download_archive_fn = expand_path(opts.download_archive) if opts.download_archive is not None else opts.download_archive
|
||||||
|
|
||||||
@@ -281,7 +337,7 @@ def _real_main(argv=None):
|
|||||||
opts.writeinfojson = True
|
opts.writeinfojson = True
|
||||||
|
|
||||||
def report_conflict(arg1, arg2):
|
def report_conflict(arg1, arg2):
|
||||||
write_string('WARNING: %s is ignored since %s was given\n' % (arg2, arg1), out=sys.stderr)
|
warnings.append('%s is ignored since %s was given' % (arg2, arg1))
|
||||||
|
|
||||||
if opts.remuxvideo and opts.recodevideo:
|
if opts.remuxvideo and opts.recodevideo:
|
||||||
report_conflict('--recode-video', '--remux-video')
|
report_conflict('--recode-video', '--remux-video')
|
||||||
@@ -419,11 +475,10 @@ def _real_main(argv=None):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def report_args_compat(arg, name):
|
def report_args_compat(arg, name):
|
||||||
write_string(
|
warnings.append('%s given without specifying name. The arguments will be given to all %s' % (arg, name))
|
||||||
'WARNING: %s given without specifying name. The arguments will be given to all %s\n' % (arg, name),
|
|
||||||
out=sys.stderr)
|
|
||||||
if 'default' in opts.external_downloader_args:
|
if 'default' in opts.external_downloader_args:
|
||||||
report_args_compat('--external-downloader-args', 'external downloaders')
|
report_args_compat('--downloader-args', 'external downloaders')
|
||||||
|
|
||||||
if 'default-compat' in opts.postprocessor_args and 'default' not in opts.postprocessor_args:
|
if 'default-compat' in opts.postprocessor_args and 'default' not in opts.postprocessor_args:
|
||||||
report_args_compat('--post-processor-args', 'post-processors')
|
report_args_compat('--post-processor-args', 'post-processors')
|
||||||
@@ -431,10 +486,10 @@ def _real_main(argv=None):
|
|||||||
opts.postprocessor_args['default'] = opts.postprocessor_args['default-compat']
|
opts.postprocessor_args['default'] = opts.postprocessor_args['default-compat']
|
||||||
|
|
||||||
final_ext = (
|
final_ext = (
|
||||||
opts.recodevideo
|
opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
|
||||||
or (opts.remuxvideo in REMUX_EXTENSIONS) and opts.remuxvideo
|
else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS
|
||||||
or (opts.extractaudio and opts.audioformat != 'best') and opts.audioformat
|
else opts.audioformat if (opts.extractaudio and opts.audioformat != 'best')
|
||||||
or None)
|
else None)
|
||||||
|
|
||||||
match_filter = (
|
match_filter = (
|
||||||
None if opts.match_filter is None
|
None if opts.match_filter is None
|
||||||
@@ -459,6 +514,7 @@ def _real_main(argv=None):
|
|||||||
'forceduration': opts.getduration,
|
'forceduration': opts.getduration,
|
||||||
'forcefilename': opts.getfilename,
|
'forcefilename': opts.getfilename,
|
||||||
'forceformat': opts.getformat,
|
'forceformat': opts.getformat,
|
||||||
|
'forceprint': opts.forceprint,
|
||||||
'forcejson': opts.dumpjson or opts.print_json,
|
'forcejson': opts.dumpjson or opts.print_json,
|
||||||
'dump_single_json': opts.dump_single_json,
|
'dump_single_json': opts.dump_single_json,
|
||||||
'force_write_download_archive': opts.force_write_download_archive,
|
'force_write_download_archive': opts.force_write_download_archive,
|
||||||
@@ -471,9 +527,10 @@ def _real_main(argv=None):
|
|||||||
'format_sort_force': opts.format_sort_force,
|
'format_sort_force': opts.format_sort_force,
|
||||||
'allow_multiple_video_streams': opts.allow_multiple_video_streams,
|
'allow_multiple_video_streams': opts.allow_multiple_video_streams,
|
||||||
'allow_multiple_audio_streams': opts.allow_multiple_audio_streams,
|
'allow_multiple_audio_streams': opts.allow_multiple_audio_streams,
|
||||||
|
'check_formats': opts.check_formats,
|
||||||
'listformats': opts.listformats,
|
'listformats': opts.listformats,
|
||||||
'listformats_table': opts.listformats_table,
|
'listformats_table': opts.listformats_table,
|
||||||
'outtmpl': outtmpl,
|
'outtmpl': opts.outtmpl,
|
||||||
'outtmpl_na_placeholder': opts.outtmpl_na_placeholder,
|
'outtmpl_na_placeholder': opts.outtmpl_na_placeholder,
|
||||||
'paths': opts.paths,
|
'paths': opts.paths,
|
||||||
'autonumber_size': opts.autonumber_size,
|
'autonumber_size': opts.autonumber_size,
|
||||||
@@ -588,9 +645,12 @@ def _real_main(argv=None):
|
|||||||
'geo_bypass': opts.geo_bypass,
|
'geo_bypass': opts.geo_bypass,
|
||||||
'geo_bypass_country': opts.geo_bypass_country,
|
'geo_bypass_country': opts.geo_bypass_country,
|
||||||
'geo_bypass_ip_block': opts.geo_bypass_ip_block,
|
'geo_bypass_ip_block': opts.geo_bypass_ip_block,
|
||||||
|
'warnings': warnings,
|
||||||
|
'compat_opts': compat_opts,
|
||||||
# just for deprecation check
|
# just for deprecation check
|
||||||
'autonumber': opts.autonumber if opts.autonumber is True else None,
|
'autonumber': opts.autonumber or None,
|
||||||
'usetitle': opts.usetitle if opts.usetitle is True else None,
|
'usetitle': opts.usetitle or None,
|
||||||
|
'useid': opts.useid or None,
|
||||||
}
|
}
|
||||||
|
|
||||||
with YoutubeDL(ydl_opts) as ydl:
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
@@ -603,7 +663,7 @@ def _real_main(argv=None):
|
|||||||
# Update version
|
# Update version
|
||||||
if opts.update_self:
|
if opts.update_self:
|
||||||
# If updater returns True, exit. Required for windows
|
# If updater returns True, exit. Required for windows
|
||||||
if update_self(ydl.to_screen, opts.verbose, ydl._opener):
|
if run_update(ydl):
|
||||||
if actual_use:
|
if actual_use:
|
||||||
sys.exit('ERROR: The program must exit for the update to complete')
|
sys.exit('ERROR: The program must exit for the update to complete')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|||||||
@@ -3018,10 +3018,24 @@ else:
|
|||||||
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
compat_Pattern = re.Pattern
|
||||||
|
except AttributeError:
|
||||||
|
compat_Pattern = type(re.compile(''))
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
compat_Match = re.Match
|
||||||
|
except AttributeError:
|
||||||
|
compat_Match = type(re.compile('').match(''))
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'compat_HTMLParseError',
|
'compat_HTMLParseError',
|
||||||
'compat_HTMLParser',
|
'compat_HTMLParser',
|
||||||
'compat_HTTPError',
|
'compat_HTTPError',
|
||||||
|
'compat_Match',
|
||||||
|
'compat_Pattern',
|
||||||
'compat_Struct',
|
'compat_Struct',
|
||||||
'compat_b64decode',
|
'compat_b64decode',
|
||||||
'compat_basestring',
|
'compat_basestring',
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from .external import (
|
|||||||
|
|
||||||
PROTOCOL_MAP = {
|
PROTOCOL_MAP = {
|
||||||
'rtmp': RtmpFD,
|
'rtmp': RtmpFD,
|
||||||
|
'rtmp_ffmpeg': FFmpegFD,
|
||||||
'm3u8_native': HlsFD,
|
'm3u8_native': HlsFD,
|
||||||
'm3u8': FFmpegFD,
|
'm3u8': FFmpegFD,
|
||||||
'mms': RtspFD,
|
'mms': RtspFD,
|
||||||
@@ -46,6 +47,7 @@ PROTOCOL_MAP = {
|
|||||||
def shorten_protocol_name(proto, simplify=False):
|
def shorten_protocol_name(proto, simplify=False):
|
||||||
short_protocol_names = {
|
short_protocol_names = {
|
||||||
'm3u8_native': 'm3u8_n',
|
'm3u8_native': 'm3u8_n',
|
||||||
|
'rtmp_ffmpeg': 'rtmp_f',
|
||||||
'http_dash_segments': 'dash',
|
'http_dash_segments': 'dash',
|
||||||
'niconico_dmc': 'dmc',
|
'niconico_dmc': 'dmc',
|
||||||
}
|
}
|
||||||
@@ -54,6 +56,7 @@ def shorten_protocol_name(proto, simplify=False):
|
|||||||
'https': 'http',
|
'https': 'http',
|
||||||
'ftps': 'ftp',
|
'ftps': 'ftp',
|
||||||
'm3u8_native': 'm3u8',
|
'm3u8_native': 'm3u8',
|
||||||
|
'rtmp_ffmpeg': 'rtmp',
|
||||||
'm3u8_frag_urls': 'm3u8',
|
'm3u8_frag_urls': 'm3u8',
|
||||||
'dash_frag_urls': 'dash',
|
'dash_frag_urls': 'dash',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -147,10 +147,10 @@ class FileDownloader(object):
|
|||||||
return int(round(number * multiplier))
|
return int(round(number * multiplier))
|
||||||
|
|
||||||
def to_screen(self, *args, **kargs):
|
def to_screen(self, *args, **kargs):
|
||||||
self.ydl.to_screen(*args, **kargs)
|
self.ydl.to_stdout(*args, quiet=self.params.get('quiet'), **kargs)
|
||||||
|
|
||||||
def to_stderr(self, message):
|
def to_stderr(self, message):
|
||||||
self.ydl.to_screen(message)
|
self.ydl.to_stderr(message)
|
||||||
|
|
||||||
def to_console_title(self, message):
|
def to_console_title(self, message):
|
||||||
self.ydl.to_console_title(message)
|
self.ydl.to_console_title(message)
|
||||||
@@ -164,6 +164,9 @@ class FileDownloader(object):
|
|||||||
def report_error(self, *args, **kargs):
|
def report_error(self, *args, **kargs):
|
||||||
self.ydl.report_error(*args, **kargs)
|
self.ydl.report_error(*args, **kargs)
|
||||||
|
|
||||||
|
def write_debug(self, *args, **kargs):
|
||||||
|
self.ydl.write_debug(*args, **kargs)
|
||||||
|
|
||||||
def slow_down(self, start_time, now, byte_counter):
|
def slow_down(self, start_time, now, byte_counter):
|
||||||
"""Sleep if the download speed is over the rate limit."""
|
"""Sleep if the download speed is over the rate limit."""
|
||||||
rate_limit = self.params.get('ratelimit')
|
rate_limit = self.params.get('ratelimit')
|
||||||
@@ -402,5 +405,4 @@ class FileDownloader(object):
|
|||||||
if exe is None:
|
if exe is None:
|
||||||
exe = os.path.basename(str_args[0])
|
exe = os.path.basename(str_args[0])
|
||||||
|
|
||||||
self.to_screen('[debug] %s command line: %s' % (
|
self.write_debug('%s command line: %s' % (exe, shell_quote(str_args)))
|
||||||
exe, shell_quote(str_args)))
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import errno
|
||||||
try:
|
try:
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
can_threaded_download = True
|
can_threaded_download = True
|
||||||
@@ -112,12 +113,14 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
if count > fragment_retries:
|
if count > fragment_retries:
|
||||||
if not fatal:
|
if not fatal:
|
||||||
return False, frag_index
|
return False, frag_index
|
||||||
|
ctx['dest_stream'].close()
|
||||||
self.report_error('Giving up after %s fragment retries' % fragment_retries)
|
self.report_error('Giving up after %s fragment retries' % fragment_retries)
|
||||||
return False, frag_index
|
return False, frag_index
|
||||||
|
|
||||||
return frag_content, frag_index
|
return frag_content, frag_index
|
||||||
|
|
||||||
def append_fragment(frag_content, frag_index):
|
def append_fragment(frag_content, frag_index):
|
||||||
|
fatal = frag_index == 1 or not skip_unavailable_fragments
|
||||||
if frag_content:
|
if frag_content:
|
||||||
fragment_filename = '%s-Frag%d' % (ctx['tmpfilename'], frag_index)
|
fragment_filename = '%s-Frag%d' % (ctx['tmpfilename'], frag_index)
|
||||||
try:
|
try:
|
||||||
@@ -126,19 +129,24 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
file.close()
|
file.close()
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
return True
|
return True
|
||||||
except FileNotFoundError:
|
except EnvironmentError as ose:
|
||||||
if skip_unavailable_fragments:
|
if ose.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
# FileNotFoundError
|
||||||
|
if not fatal:
|
||||||
self.report_skip_fragment(frag_index)
|
self.report_skip_fragment(frag_index)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
ctx['dest_stream'].close()
|
||||||
self.report_error(
|
self.report_error(
|
||||||
'fragment %s not found, unable to continue' % frag_index)
|
'fragment %s not found, unable to continue' % frag_index)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if skip_unavailable_fragments:
|
if not fatal:
|
||||||
self.report_skip_fragment(frag_index)
|
self.report_skip_fragment(frag_index)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
ctx['dest_stream'].close()
|
||||||
self.report_error(
|
self.report_error(
|
||||||
'fragment %s not found, unable to continue' % frag_index)
|
'fragment %s not found, unable to continue' % frag_index)
|
||||||
return False
|
return False
|
||||||
@@ -146,8 +154,9 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
max_workers = self.params.get('concurrent_fragment_downloads', 1)
|
max_workers = self.params.get('concurrent_fragment_downloads', 1)
|
||||||
if can_threaded_download and max_workers > 1:
|
if can_threaded_download and max_workers > 1:
|
||||||
self.report_warning('The download speed shown is only of one thread. This is a known issue')
|
self.report_warning('The download speed shown is only of one thread. This is a known issue')
|
||||||
|
_download_fragment = lambda f: (f, download_fragment(f)[1])
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers) as pool:
|
with concurrent.futures.ThreadPoolExecutor(max_workers) as pool:
|
||||||
futures = [pool.submit(download_fragment, fragment) for fragment in fragments_to_download]
|
futures = [pool.submit(_download_fragment, fragment) for fragment in fragments_to_download]
|
||||||
# timeout must be 0 to return instantly
|
# timeout must be 0 to return instantly
|
||||||
done, not_done = concurrent.futures.wait(futures, timeout=0)
|
done, not_done = concurrent.futures.wait(futures, timeout=0)
|
||||||
try:
|
try:
|
||||||
@@ -161,9 +170,13 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
# timeout must be none to cancel
|
# timeout must be none to cancel
|
||||||
concurrent.futures.wait(not_done, timeout=None)
|
concurrent.futures.wait(not_done, timeout=None)
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
results = [future.result() for future in futures]
|
|
||||||
|
|
||||||
for frag_content, frag_index in results:
|
for fragment, frag_index in map(lambda x: x.result(), futures):
|
||||||
|
fragment_filename = '%s-Frag%d' % (ctx['tmpfilename'], frag_index)
|
||||||
|
down, frag_sanitized = sanitize_open(fragment_filename, 'rb')
|
||||||
|
fragment['fragment_filename_sanitized'] = frag_sanitized
|
||||||
|
frag_content = down.read()
|
||||||
|
down.close()
|
||||||
result = append_fragment(frag_content, frag_index)
|
result = append_fragment(frag_content, frag_index)
|
||||||
if not result:
|
if not result:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -290,11 +290,19 @@ class Aria2cFD(ExternalFD):
|
|||||||
cmd += self._bool_option('--remote-time', 'updatetime', 'true', 'false', '=')
|
cmd += self._bool_option('--remote-time', 'updatetime', 'true', 'false', '=')
|
||||||
cmd += self._configuration_args()
|
cmd += self._configuration_args()
|
||||||
|
|
||||||
|
# aria2c strips out spaces from the beginning/end of filenames and paths.
|
||||||
|
# We work around this issue by adding a "./" to the beginning of the
|
||||||
|
# filename and relative path, and adding a "/" at the end of the path.
|
||||||
|
# See: https://github.com/yt-dlp/yt-dlp/issues/276
|
||||||
|
# https://github.com/ytdl-org/youtube-dl/issues/20312
|
||||||
|
# https://github.com/aria2/aria2/issues/1373
|
||||||
dn = os.path.dirname(tmpfilename)
|
dn = os.path.dirname(tmpfilename)
|
||||||
if dn:
|
if dn:
|
||||||
cmd += ['--dir', dn]
|
if not os.path.isabs(dn):
|
||||||
|
dn = '.%s%s' % (os.path.sep, dn)
|
||||||
|
cmd += ['--dir', dn + os.path.sep]
|
||||||
if 'fragments' not in info_dict:
|
if 'fragments' not in info_dict:
|
||||||
cmd += ['--out', os.path.basename(tmpfilename)]
|
cmd += ['--out', '.%s%s' % (os.path.sep, os.path.basename(tmpfilename))]
|
||||||
cmd += ['--auto-file-renaming=false']
|
cmd += ['--auto-file-renaming=false']
|
||||||
|
|
||||||
if 'fragments' in info_dict:
|
if 'fragments' in info_dict:
|
||||||
@@ -330,7 +338,7 @@ class HttpieFD(ExternalFD):
|
|||||||
|
|
||||||
|
|
||||||
class FFmpegFD(ExternalFD):
|
class FFmpegFD(ExternalFD):
|
||||||
SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'mms')
|
SUPPORTED_PROTOCOLS = ('http', 'https', 'ftp', 'ftps', 'm3u8', 'm3u8_native', 'rtsp', 'rtmp', 'rtmp_ffmpeg', 'mms')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def available(cls, path=None):
|
def available(cls, path=None):
|
||||||
@@ -338,7 +346,7 @@ class FFmpegFD(ExternalFD):
|
|||||||
return FFmpegPostProcessor().available
|
return FFmpegPostProcessor().available
|
||||||
|
|
||||||
def _call_downloader(self, tmpfilename, info_dict):
|
def _call_downloader(self, tmpfilename, info_dict):
|
||||||
url = info_dict['url']
|
urls = [f['url'] for f in info_dict.get('requested_formats', [])] or [info_dict['url']]
|
||||||
ffpp = FFmpegPostProcessor(downloader=self)
|
ffpp = FFmpegPostProcessor(downloader=self)
|
||||||
if not ffpp.available:
|
if not ffpp.available:
|
||||||
self.report_error('m3u8 download detected but ffmpeg could not be found. Please install')
|
self.report_error('m3u8 download detected but ffmpeg could not be found. Please install')
|
||||||
@@ -370,7 +378,7 @@ class FFmpegFD(ExternalFD):
|
|||||||
# if end_time:
|
# if end_time:
|
||||||
# args += ['-t', compat_str(end_time - start_time)]
|
# args += ['-t', compat_str(end_time - start_time)]
|
||||||
|
|
||||||
if info_dict.get('http_headers') is not None and re.match(r'^https?://', url):
|
if info_dict.get('http_headers') is not None and re.match(r'^https?://', urls[0]):
|
||||||
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
||||||
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
||||||
headers = handle_youtubedl_headers(info_dict['http_headers'])
|
headers = handle_youtubedl_headers(info_dict['http_headers'])
|
||||||
@@ -428,7 +436,15 @@ class FFmpegFD(ExternalFD):
|
|||||||
elif isinstance(conn, compat_str):
|
elif isinstance(conn, compat_str):
|
||||||
args += ['-rtmp_conn', conn]
|
args += ['-rtmp_conn', conn]
|
||||||
|
|
||||||
args += ['-i', url, '-c', 'copy']
|
for url in urls:
|
||||||
|
args += ['-i', url]
|
||||||
|
args += ['-c', 'copy']
|
||||||
|
if info_dict.get('requested_formats'):
|
||||||
|
for (i, fmt) in enumerate(info_dict['requested_formats']):
|
||||||
|
if fmt.get('acodec') != 'none':
|
||||||
|
args.extend(['-map', '%d:a:0' % i])
|
||||||
|
if fmt.get('vcodec') != 'none':
|
||||||
|
args.extend(['-map', '%d:v:0' % i])
|
||||||
|
|
||||||
if self.params.get('test', False):
|
if self.params.get('test', False):
|
||||||
args += ['-fs', compat_str(self._TEST_FILE_SIZE)]
|
args += ['-fs', compat_str(self._TEST_FILE_SIZE)]
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class FragmentFD(FileDownloader):
|
|||||||
Skip unavailable fragments (DASH and hlsnative only)
|
Skip unavailable fragments (DASH and hlsnative only)
|
||||||
keep_fragments: Keep downloaded fragments on disk after downloading is
|
keep_fragments: Keep downloaded fragments on disk after downloading is
|
||||||
finished
|
finished
|
||||||
|
_no_ytdl_file: Don't use .ytdl file
|
||||||
|
|
||||||
For each incomplete fragment download yt-dlp keeps on disk a special
|
For each incomplete fragment download yt-dlp keeps on disk a special
|
||||||
bookkeeping file with download state and metadata (in future such files will
|
bookkeeping file with download state and metadata (in future such files will
|
||||||
@@ -69,15 +70,17 @@ class FragmentFD(FileDownloader):
|
|||||||
self._prepare_frag_download(ctx)
|
self._prepare_frag_download(ctx)
|
||||||
self._start_frag_download(ctx)
|
self._start_frag_download(ctx)
|
||||||
|
|
||||||
@staticmethod
|
def __do_ytdl_file(self, ctx):
|
||||||
def __do_ytdl_file(ctx):
|
return not ctx['live'] and not ctx['tmpfilename'] == '-' and not self.params.get('_no_ytdl_file')
|
||||||
return not ctx['live'] and not ctx['tmpfilename'] == '-'
|
|
||||||
|
|
||||||
def _read_ytdl_file(self, ctx):
|
def _read_ytdl_file(self, ctx):
|
||||||
assert 'ytdl_corrupt' not in ctx
|
assert 'ytdl_corrupt' not in ctx
|
||||||
stream, _ = sanitize_open(self.ytdl_filename(ctx['filename']), 'r')
|
stream, _ = sanitize_open(self.ytdl_filename(ctx['filename']), 'r')
|
||||||
try:
|
try:
|
||||||
ctx['fragment_index'] = json.loads(stream.read())['downloader']['current_fragment']['index']
|
ytdl_data = json.loads(stream.read())
|
||||||
|
ctx['fragment_index'] = ytdl_data['downloader']['current_fragment']['index']
|
||||||
|
if 'extra_state' in ytdl_data['downloader']:
|
||||||
|
ctx['extra_state'] = ytdl_data['downloader']['extra_state']
|
||||||
except Exception:
|
except Exception:
|
||||||
ctx['ytdl_corrupt'] = True
|
ctx['ytdl_corrupt'] = True
|
||||||
finally:
|
finally:
|
||||||
@@ -90,6 +93,8 @@ class FragmentFD(FileDownloader):
|
|||||||
'index': ctx['fragment_index'],
|
'index': ctx['fragment_index'],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if 'extra_state' in ctx:
|
||||||
|
downloader['extra_state'] = ctx['extra_state']
|
||||||
if ctx.get('fragment_count') is not None:
|
if ctx.get('fragment_count') is not None:
|
||||||
downloader['fragment_count'] = ctx['fragment_count']
|
downloader['fragment_count'] = ctx['fragment_count']
|
||||||
frag_index_stream.write(json.dumps({'downloader': downloader}))
|
frag_index_stream.write(json.dumps({'downloader': downloader}))
|
||||||
|
|||||||
+100
-7
@@ -1,6 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import errno
|
||||||
import re
|
import re
|
||||||
|
import io
|
||||||
import binascii
|
import binascii
|
||||||
try:
|
try:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
@@ -26,7 +28,9 @@ from ..utils import (
|
|||||||
parse_m3u8_attributes,
|
parse_m3u8_attributes,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
update_url_query,
|
update_url_query,
|
||||||
|
bug_reports_message,
|
||||||
)
|
)
|
||||||
|
from .. import webvtt
|
||||||
|
|
||||||
|
|
||||||
class HlsFD(FragmentFD):
|
class HlsFD(FragmentFD):
|
||||||
@@ -95,7 +99,11 @@ class HlsFD(FragmentFD):
|
|||||||
# fd.add_progress_hook(ph)
|
# fd.add_progress_hook(ph)
|
||||||
return fd.real_download(filename, info_dict)
|
return fd.real_download(filename, info_dict)
|
||||||
|
|
||||||
real_downloader = _get_real_downloader(info_dict, 'm3u8_frag_urls', self.params, None)
|
is_webvtt = info_dict['ext'] == 'vtt'
|
||||||
|
if is_webvtt:
|
||||||
|
real_downloader = None # Packing the fragments is not currently supported for external downloader
|
||||||
|
else:
|
||||||
|
real_downloader = _get_real_downloader(info_dict, 'm3u8_frag_urls', self.params, None)
|
||||||
if real_downloader and not real_downloader.supports_manifest(s):
|
if real_downloader and not real_downloader.supports_manifest(s):
|
||||||
real_downloader = None
|
real_downloader = None
|
||||||
if real_downloader:
|
if real_downloader:
|
||||||
@@ -141,6 +149,8 @@ class HlsFD(FragmentFD):
|
|||||||
else:
|
else:
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx)
|
||||||
|
|
||||||
|
extra_state = ctx.setdefault('extra_state', {})
|
||||||
|
|
||||||
fragment_retries = self.params.get('fragment_retries', 0)
|
fragment_retries = self.params.get('fragment_retries', 0)
|
||||||
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
||||||
test = self.params.get('test', False)
|
test = self.params.get('test', False)
|
||||||
@@ -291,6 +301,7 @@ class HlsFD(FragmentFD):
|
|||||||
if count <= fragment_retries:
|
if count <= fragment_retries:
|
||||||
self.report_retry_fragment(err, frag_index, count, fragment_retries)
|
self.report_retry_fragment(err, frag_index, count, fragment_retries)
|
||||||
if count > fragment_retries:
|
if count > fragment_retries:
|
||||||
|
ctx['dest_stream'].close()
|
||||||
self.report_error('Giving up after %s fragment retries' % fragment_retries)
|
self.report_error('Giving up after %s fragment retries' % fragment_retries)
|
||||||
return False, frag_index
|
return False, frag_index
|
||||||
|
|
||||||
@@ -307,28 +318,105 @@ class HlsFD(FragmentFD):
|
|||||||
|
|
||||||
return frag_content, frag_index
|
return frag_content, frag_index
|
||||||
|
|
||||||
|
pack_fragment = lambda frag_content, _: frag_content
|
||||||
|
|
||||||
|
if is_webvtt:
|
||||||
|
def pack_fragment(frag_content, frag_index):
|
||||||
|
output = io.StringIO()
|
||||||
|
adjust = 0
|
||||||
|
for block in webvtt.parse_fragment(frag_content):
|
||||||
|
if isinstance(block, webvtt.CueBlock):
|
||||||
|
block.start += adjust
|
||||||
|
block.end += adjust
|
||||||
|
|
||||||
|
dedup_window = extra_state.setdefault('webvtt_dedup_window', [])
|
||||||
|
cue = block.as_json
|
||||||
|
|
||||||
|
# skip the cue if an identical one appears
|
||||||
|
# in the window of potential duplicates
|
||||||
|
# and prune the window of unviable candidates
|
||||||
|
i = 0
|
||||||
|
skip = True
|
||||||
|
while i < len(dedup_window):
|
||||||
|
window_cue = dedup_window[i]
|
||||||
|
if window_cue == cue:
|
||||||
|
break
|
||||||
|
if window_cue['end'] >= cue['start']:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
del dedup_window[i]
|
||||||
|
else:
|
||||||
|
skip = False
|
||||||
|
|
||||||
|
if skip:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# add the cue to the window
|
||||||
|
dedup_window.append(cue)
|
||||||
|
elif isinstance(block, webvtt.Magic):
|
||||||
|
# take care of MPEG PES timestamp overflow
|
||||||
|
if block.mpegts is None:
|
||||||
|
block.mpegts = 0
|
||||||
|
extra_state.setdefault('webvtt_mpegts_adjust', 0)
|
||||||
|
block.mpegts += extra_state['webvtt_mpegts_adjust'] << 33
|
||||||
|
if block.mpegts < extra_state.get('webvtt_mpegts_last', 0):
|
||||||
|
extra_state['webvtt_mpegts_adjust'] += 1
|
||||||
|
block.mpegts += 1 << 33
|
||||||
|
extra_state['webvtt_mpegts_last'] = block.mpegts
|
||||||
|
|
||||||
|
if frag_index == 1:
|
||||||
|
extra_state['webvtt_mpegts'] = block.mpegts or 0
|
||||||
|
extra_state['webvtt_local'] = block.local or 0
|
||||||
|
# XXX: block.local = block.mpegts = None ?
|
||||||
|
else:
|
||||||
|
if block.mpegts is not None and block.local is not None:
|
||||||
|
adjust = (
|
||||||
|
(block.mpegts - extra_state.get('webvtt_mpegts', 0))
|
||||||
|
- (block.local - extra_state.get('webvtt_local', 0))
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
elif isinstance(block, webvtt.HeaderBlock):
|
||||||
|
if frag_index != 1:
|
||||||
|
# XXX: this should probably be silent as well
|
||||||
|
# or verify that all segments contain the same data
|
||||||
|
self.report_warning(bug_reports_message(
|
||||||
|
'Discarding a %s block found in the middle of the stream; '
|
||||||
|
'if the subtitles display incorrectly,'
|
||||||
|
% (type(block).__name__)))
|
||||||
|
continue
|
||||||
|
block.write_into(output)
|
||||||
|
|
||||||
|
return output.getvalue().encode('utf-8')
|
||||||
|
|
||||||
def append_fragment(frag_content, frag_index):
|
def append_fragment(frag_content, frag_index):
|
||||||
|
fatal = frag_index == 1 or not skip_unavailable_fragments
|
||||||
if frag_content:
|
if frag_content:
|
||||||
fragment_filename = '%s-Frag%d' % (ctx['tmpfilename'], frag_index)
|
fragment_filename = '%s-Frag%d' % (ctx['tmpfilename'], frag_index)
|
||||||
try:
|
try:
|
||||||
file, frag_sanitized = sanitize_open(fragment_filename, 'rb')
|
file, frag_sanitized = sanitize_open(fragment_filename, 'rb')
|
||||||
ctx['fragment_filename_sanitized'] = frag_sanitized
|
ctx['fragment_filename_sanitized'] = frag_sanitized
|
||||||
file.close()
|
file.close()
|
||||||
|
frag_content = pack_fragment(frag_content, frag_index)
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
return True
|
return True
|
||||||
except FileNotFoundError:
|
except EnvironmentError as ose:
|
||||||
if skip_unavailable_fragments:
|
if ose.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
# FileNotFoundError
|
||||||
|
if not fatal:
|
||||||
self.report_skip_fragment(frag_index)
|
self.report_skip_fragment(frag_index)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
ctx['dest_stream'].close()
|
||||||
self.report_error(
|
self.report_error(
|
||||||
'fragment %s not found, unable to continue' % frag_index)
|
'fragment %s not found, unable to continue' % frag_index)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if skip_unavailable_fragments:
|
if not fatal:
|
||||||
self.report_skip_fragment(frag_index)
|
self.report_skip_fragment(frag_index)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
ctx['dest_stream'].close()
|
||||||
self.report_error(
|
self.report_error(
|
||||||
'fragment %s not found, unable to continue' % frag_index)
|
'fragment %s not found, unable to continue' % frag_index)
|
||||||
return False
|
return False
|
||||||
@@ -336,8 +424,9 @@ class HlsFD(FragmentFD):
|
|||||||
max_workers = self.params.get('concurrent_fragment_downloads', 1)
|
max_workers = self.params.get('concurrent_fragment_downloads', 1)
|
||||||
if can_threaded_download and max_workers > 1:
|
if can_threaded_download and max_workers > 1:
|
||||||
self.report_warning('The download speed shown is only of one thread. This is a known issue')
|
self.report_warning('The download speed shown is only of one thread. This is a known issue')
|
||||||
|
_download_fragment = lambda f: (f, download_fragment(f)[1])
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers) as pool:
|
with concurrent.futures.ThreadPoolExecutor(max_workers) as pool:
|
||||||
futures = [pool.submit(download_fragment, fragment) for fragment in fragments]
|
futures = [pool.submit(_download_fragment, fragment) for fragment in fragments]
|
||||||
# timeout must be 0 to return instantly
|
# timeout must be 0 to return instantly
|
||||||
done, not_done = concurrent.futures.wait(futures, timeout=0)
|
done, not_done = concurrent.futures.wait(futures, timeout=0)
|
||||||
try:
|
try:
|
||||||
@@ -351,9 +440,13 @@ class HlsFD(FragmentFD):
|
|||||||
# timeout must be none to cancel
|
# timeout must be none to cancel
|
||||||
concurrent.futures.wait(not_done, timeout=None)
|
concurrent.futures.wait(not_done, timeout=None)
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
results = [future.result() for future in futures]
|
|
||||||
|
|
||||||
for frag_content, frag_index in results:
|
for fragment, frag_index in map(lambda x: x.result(), futures):
|
||||||
|
fragment_filename = '%s-Frag%d' % (ctx['tmpfilename'], frag_index)
|
||||||
|
down, frag_sanitized = sanitize_open(fragment_filename, 'rb')
|
||||||
|
fragment['fragment_filename_sanitized'] = frag_sanitized
|
||||||
|
frag_content = down.read()
|
||||||
|
down.close()
|
||||||
result = append_fragment(frag_content, frag_index)
|
result = append_fragment(frag_content, frag_index)
|
||||||
if not result:
|
if not result:
|
||||||
return False
|
return False
|
||||||
|
|||||||
+42
-12
@@ -48,7 +48,7 @@ def write_piff_header(stream, params):
|
|||||||
language = params.get('language', 'und')
|
language = params.get('language', 'und')
|
||||||
height = params.get('height', 0)
|
height = params.get('height', 0)
|
||||||
width = params.get('width', 0)
|
width = params.get('width', 0)
|
||||||
is_audio = width == 0 and height == 0
|
stream_type = params['stream_type']
|
||||||
creation_time = modification_time = int(time.time())
|
creation_time = modification_time = int(time.time())
|
||||||
|
|
||||||
ftyp_payload = b'isml' # major brand
|
ftyp_payload = b'isml' # major brand
|
||||||
@@ -77,7 +77,7 @@ def write_piff_header(stream, params):
|
|||||||
tkhd_payload += u32.pack(0) * 2 # reserved
|
tkhd_payload += u32.pack(0) * 2 # reserved
|
||||||
tkhd_payload += s16.pack(0) # layer
|
tkhd_payload += s16.pack(0) # layer
|
||||||
tkhd_payload += s16.pack(0) # alternate group
|
tkhd_payload += s16.pack(0) # alternate group
|
||||||
tkhd_payload += s88.pack(1 if is_audio else 0) # volume
|
tkhd_payload += s88.pack(1 if stream_type == 'audio' else 0) # volume
|
||||||
tkhd_payload += u16.pack(0) # reserved
|
tkhd_payload += u16.pack(0) # reserved
|
||||||
tkhd_payload += unity_matrix
|
tkhd_payload += unity_matrix
|
||||||
tkhd_payload += u1616.pack(width)
|
tkhd_payload += u1616.pack(width)
|
||||||
@@ -93,19 +93,34 @@ def write_piff_header(stream, params):
|
|||||||
mdia_payload = full_box(b'mdhd', 1, 0, mdhd_payload) # Media Header Box
|
mdia_payload = full_box(b'mdhd', 1, 0, mdhd_payload) # Media Header Box
|
||||||
|
|
||||||
hdlr_payload = u32.pack(0) # pre defined
|
hdlr_payload = u32.pack(0) # pre defined
|
||||||
hdlr_payload += b'soun' if is_audio else b'vide' # handler type
|
if stream_type == 'audio': # handler type
|
||||||
hdlr_payload += u32.pack(0) * 3 # reserved
|
hdlr_payload += b'soun'
|
||||||
hdlr_payload += (b'Sound' if is_audio else b'Video') + b'Handler\0' # name
|
hdlr_payload += u32.pack(0) * 3 # reserved
|
||||||
|
hdlr_payload += b'SoundHandler\0' # name
|
||||||
|
elif stream_type == 'video':
|
||||||
|
hdlr_payload += b'vide'
|
||||||
|
hdlr_payload += u32.pack(0) * 3 # reserved
|
||||||
|
hdlr_payload += b'VideoHandler\0' # name
|
||||||
|
elif stream_type == 'text':
|
||||||
|
hdlr_payload += b'subt'
|
||||||
|
hdlr_payload += u32.pack(0) * 3 # reserved
|
||||||
|
hdlr_payload += b'SubtitleHandler\0' # name
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
mdia_payload += full_box(b'hdlr', 0, 0, hdlr_payload) # Handler Reference Box
|
mdia_payload += full_box(b'hdlr', 0, 0, hdlr_payload) # Handler Reference Box
|
||||||
|
|
||||||
if is_audio:
|
if stream_type == 'audio':
|
||||||
smhd_payload = s88.pack(0) # balance
|
smhd_payload = s88.pack(0) # balance
|
||||||
smhd_payload += u16.pack(0) # reserved
|
smhd_payload += u16.pack(0) # reserved
|
||||||
media_header_box = full_box(b'smhd', 0, 0, smhd_payload) # Sound Media Header
|
media_header_box = full_box(b'smhd', 0, 0, smhd_payload) # Sound Media Header
|
||||||
else:
|
elif stream_type == 'video':
|
||||||
vmhd_payload = u16.pack(0) # graphics mode
|
vmhd_payload = u16.pack(0) # graphics mode
|
||||||
vmhd_payload += u16.pack(0) * 3 # opcolor
|
vmhd_payload += u16.pack(0) * 3 # opcolor
|
||||||
media_header_box = full_box(b'vmhd', 0, 1, vmhd_payload) # Video Media Header
|
media_header_box = full_box(b'vmhd', 0, 1, vmhd_payload) # Video Media Header
|
||||||
|
elif stream_type == 'text':
|
||||||
|
media_header_box = full_box(b'sthd', 0, 0, b'') # Subtitle Media Header
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
minf_payload = media_header_box
|
minf_payload = media_header_box
|
||||||
|
|
||||||
dref_payload = u32.pack(1) # entry count
|
dref_payload = u32.pack(1) # entry count
|
||||||
@@ -117,7 +132,7 @@ def write_piff_header(stream, params):
|
|||||||
|
|
||||||
sample_entry_payload = u8.pack(0) * 6 # reserved
|
sample_entry_payload = u8.pack(0) * 6 # reserved
|
||||||
sample_entry_payload += u16.pack(1) # data reference index
|
sample_entry_payload += u16.pack(1) # data reference index
|
||||||
if is_audio:
|
if stream_type == 'audio':
|
||||||
sample_entry_payload += u32.pack(0) * 2 # reserved
|
sample_entry_payload += u32.pack(0) * 2 # reserved
|
||||||
sample_entry_payload += u16.pack(params.get('channels', 2))
|
sample_entry_payload += u16.pack(params.get('channels', 2))
|
||||||
sample_entry_payload += u16.pack(params.get('bits_per_sample', 16))
|
sample_entry_payload += u16.pack(params.get('bits_per_sample', 16))
|
||||||
@@ -127,7 +142,7 @@ def write_piff_header(stream, params):
|
|||||||
|
|
||||||
if fourcc == 'AACL':
|
if fourcc == 'AACL':
|
||||||
sample_entry_box = box(b'mp4a', sample_entry_payload)
|
sample_entry_box = box(b'mp4a', sample_entry_payload)
|
||||||
else:
|
elif stream_type == 'video':
|
||||||
sample_entry_payload += u16.pack(0) # pre defined
|
sample_entry_payload += u16.pack(0) # pre defined
|
||||||
sample_entry_payload += u16.pack(0) # reserved
|
sample_entry_payload += u16.pack(0) # reserved
|
||||||
sample_entry_payload += u32.pack(0) * 3 # pre defined
|
sample_entry_payload += u32.pack(0) * 3 # pre defined
|
||||||
@@ -155,6 +170,18 @@ def write_piff_header(stream, params):
|
|||||||
avcc_payload += pps
|
avcc_payload += pps
|
||||||
sample_entry_payload += box(b'avcC', avcc_payload) # AVC Decoder Configuration Record
|
sample_entry_payload += box(b'avcC', avcc_payload) # AVC Decoder Configuration Record
|
||||||
sample_entry_box = box(b'avc1', sample_entry_payload) # AVC Simple Entry
|
sample_entry_box = box(b'avc1', sample_entry_payload) # AVC Simple Entry
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
elif stream_type == 'text':
|
||||||
|
if fourcc == 'TTML':
|
||||||
|
sample_entry_payload += b'http://www.w3.org/ns/ttml\0' # namespace
|
||||||
|
sample_entry_payload += b'\0' # schema location
|
||||||
|
sample_entry_payload += b'\0' # auxilary mime types(??)
|
||||||
|
sample_entry_box = box(b'stpp', sample_entry_payload)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
stsd_payload += sample_entry_box
|
stsd_payload += sample_entry_box
|
||||||
|
|
||||||
stbl_payload = full_box(b'stsd', 0, 0, stsd_payload) # Sample Description Box
|
stbl_payload = full_box(b'stsd', 0, 0, stsd_payload) # Sample Description Box
|
||||||
@@ -221,10 +248,13 @@ class IsmFD(FragmentFD):
|
|||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx)
|
||||||
|
|
||||||
|
extra_state = ctx.setdefault('extra_state', {
|
||||||
|
'ism_track_written': False,
|
||||||
|
})
|
||||||
|
|
||||||
fragment_retries = self.params.get('fragment_retries', 0)
|
fragment_retries = self.params.get('fragment_retries', 0)
|
||||||
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
|
||||||
|
|
||||||
track_written = False
|
|
||||||
frag_index = 0
|
frag_index = 0
|
||||||
for i, segment in enumerate(segments):
|
for i, segment in enumerate(segments):
|
||||||
frag_index += 1
|
frag_index += 1
|
||||||
@@ -236,11 +266,11 @@ class IsmFD(FragmentFD):
|
|||||||
success, frag_content = self._download_fragment(ctx, segment['url'], info_dict)
|
success, frag_content = self._download_fragment(ctx, segment['url'], info_dict)
|
||||||
if not success:
|
if not success:
|
||||||
return False
|
return False
|
||||||
if not track_written:
|
if not extra_state['ism_track_written']:
|
||||||
tfhd_data = extract_box_data(frag_content, [b'moof', b'traf', b'tfhd'])
|
tfhd_data = extract_box_data(frag_content, [b'moof', b'traf', b'tfhd'])
|
||||||
info_dict['_download_params']['track_id'] = u32.unpack(tfhd_data[4:8])[0]
|
info_dict['_download_params']['track_id'] = u32.unpack(tfhd_data[4:8])[0]
|
||||||
write_piff_header(ctx['dest_stream'], info_dict['_download_params'])
|
write_piff_header(ctx['dest_stream'], info_dict['_download_params'])
|
||||||
track_written = True
|
extra_state['ism_track_written'] = True
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
break
|
break
|
||||||
except compat_urllib_error.HTTPError as err:
|
except compat_urllib_error.HTTPError as err:
|
||||||
|
|||||||
@@ -24,16 +24,14 @@ class NiconicoDmcFD(FileDownloader):
|
|||||||
|
|
||||||
success = download_complete = False
|
success = download_complete = False
|
||||||
timer = [None]
|
timer = [None]
|
||||||
|
|
||||||
heartbeat_lock = threading.Lock()
|
heartbeat_lock = threading.Lock()
|
||||||
heartbeat_url = heartbeat_info_dict['url']
|
heartbeat_url = heartbeat_info_dict['url']
|
||||||
heartbeat_data = heartbeat_info_dict['data']
|
heartbeat_data = heartbeat_info_dict['data'].encode()
|
||||||
heartbeat_interval = heartbeat_info_dict.get('interval', 30)
|
heartbeat_interval = heartbeat_info_dict.get('interval', 30)
|
||||||
self.to_screen('[%s] Heartbeat with %s second interval ...' % (self.FD_NAME, heartbeat_interval))
|
|
||||||
|
|
||||||
def heartbeat():
|
def heartbeat():
|
||||||
try:
|
try:
|
||||||
compat_urllib_request.urlopen(url=heartbeat_url, data=heartbeat_data.encode())
|
compat_urllib_request.urlopen(url=heartbeat_url, data=heartbeat_data)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.to_screen('[%s] Heartbeat failed' % self.FD_NAME)
|
self.to_screen('[%s] Heartbeat failed' % self.FD_NAME)
|
||||||
|
|
||||||
@@ -42,13 +40,16 @@ class NiconicoDmcFD(FileDownloader):
|
|||||||
timer[0] = threading.Timer(heartbeat_interval, heartbeat)
|
timer[0] = threading.Timer(heartbeat_interval, heartbeat)
|
||||||
timer[0].start()
|
timer[0].start()
|
||||||
|
|
||||||
|
heartbeat_info_dict['ping']()
|
||||||
|
self.to_screen('[%s] Heartbeat with %d second interval ...' % (self.FD_NAME, heartbeat_interval))
|
||||||
try:
|
try:
|
||||||
heartbeat()
|
heartbeat()
|
||||||
|
if type(fd).__name__ == 'HlsFD':
|
||||||
|
info_dict.update(ie._extract_m3u8_formats(info_dict['url'], info_dict['id'])[0])
|
||||||
success = fd.real_download(filename, info_dict)
|
success = fd.real_download(filename, info_dict)
|
||||||
finally:
|
finally:
|
||||||
if heartbeat_lock:
|
if heartbeat_lock:
|
||||||
with heartbeat_lock:
|
with heartbeat_lock:
|
||||||
timer[0].cancel()
|
timer[0].cancel()
|
||||||
download_complete = True
|
download_complete = True
|
||||||
|
|
||||||
return success
|
return success
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ except ImportError:
|
|||||||
|
|
||||||
if not _LAZY_LOADER:
|
if not _LAZY_LOADER:
|
||||||
from .extractors import *
|
from .extractors import *
|
||||||
|
|
||||||
_PLUGIN_CLASSES = load_plugins('extractor', 'IE', globals())
|
|
||||||
|
|
||||||
_ALL_CLASSES = [
|
_ALL_CLASSES = [
|
||||||
klass
|
klass
|
||||||
for name, klass in globals().items()
|
for name, klass in globals().items()
|
||||||
@@ -22,6 +19,9 @@ if not _LAZY_LOADER:
|
|||||||
]
|
]
|
||||||
_ALL_CLASSES.append(GenericIE)
|
_ALL_CLASSES.append(GenericIE)
|
||||||
|
|
||||||
|
_PLUGIN_CLASSES = load_plugins('extractor', 'IE', globals())
|
||||||
|
_ALL_CLASSES = _PLUGIN_CLASSES + _ALL_CLASSES
|
||||||
|
|
||||||
|
|
||||||
def gen_extractor_classes():
|
def gen_extractor_classes():
|
||||||
""" Return a list of supported extractors.
|
""" Return a list of supported extractors.
|
||||||
|
|||||||
@@ -1414,7 +1414,7 @@ class AdobePassIE(InfoExtractor):
|
|||||||
authn_token = None
|
authn_token = None
|
||||||
if not authn_token:
|
if not authn_token:
|
||||||
# TODO add support for other TV Providers
|
# TODO add support for other TV Providers
|
||||||
mso_id = self._downloader.params.get('ap_mso')
|
mso_id = self.get_param('ap_mso')
|
||||||
if not mso_id:
|
if not mso_id:
|
||||||
raise_mvpd_required()
|
raise_mvpd_required()
|
||||||
username, password = self._get_login_info('ap_username', 'ap_password', mso_id)
|
username, password = self._get_login_info('ap_username', 'ap_password', mso_id)
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ class AfreecaTVIE(InfoExtractor):
|
|||||||
'url': file_url,
|
'url': file_url,
|
||||||
'format_id': 'http',
|
'format_id': 'http',
|
||||||
}]
|
}]
|
||||||
if not formats and not self._downloader.params.get('ignore_no_formats'):
|
if not formats and not self.get_param('ignore_no_formats'):
|
||||||
continue
|
continue
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
file_info = common_entry.copy()
|
file_info = common_entry.copy()
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_parse_unquote_plus
|
from .youtube import YoutubeIE
|
||||||
|
from ..compat import (
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
compat_urllib_parse_unquote_plus,
|
||||||
|
compat_urlparse,
|
||||||
|
compat_parse_qs,
|
||||||
|
compat_HTTPError
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
KNOWN_EXTENSIONS,
|
clean_html,
|
||||||
|
determine_ext,
|
||||||
|
dict_get,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
|
ExtractorError,
|
||||||
|
HEADRequest,
|
||||||
|
int_or_none,
|
||||||
|
KNOWN_EXTENSIONS,
|
||||||
|
merge_dicts,
|
||||||
|
mimetype2ext,
|
||||||
|
parse_duration,
|
||||||
|
RegexNotFoundError,
|
||||||
|
str_to_int,
|
||||||
|
str_or_none,
|
||||||
|
try_get,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
clean_html,
|
|
||||||
dict_get,
|
|
||||||
parse_duration,
|
|
||||||
int_or_none,
|
|
||||||
str_or_none,
|
|
||||||
merge_dicts,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -241,3 +255,165 @@ class ArchiveOrgIE(InfoExtractor):
|
|||||||
'parent': 'root'})
|
'parent': 'root'})
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
class YoutubeWebArchiveIE(InfoExtractor):
|
||||||
|
IE_NAME = 'web.archive:youtube'
|
||||||
|
IE_DESC = 'web.archive.org saved youtube videos'
|
||||||
|
_VALID_URL = r"""(?x)^
|
||||||
|
(?:https?://)?web\.archive\.org/
|
||||||
|
(?:web/)?
|
||||||
|
(?:[0-9A-Za-z_*]+/)? # /web and the version index is optional
|
||||||
|
|
||||||
|
(?:https?(?::|%3[Aa])//)?
|
||||||
|
(?:
|
||||||
|
(?:\w+\.)?youtube\.com/watch(?:\?|%3[fF])(?:[^\#]+(?:&|%26))?v(?:=|%3[dD]) # Youtube URL
|
||||||
|
|(wayback-fakeurl\.archive\.org/yt/) # Or the internal fake url
|
||||||
|
)
|
||||||
|
(?P<id>[0-9A-Za-z_-]{11})(?:%26|\#|&|$)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'https://web.archive.org/web/20150415002341/https://www.youtube.com/watch?v=aYAGB11YrSs',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'aYAGB11YrSs',
|
||||||
|
'ext': 'webm',
|
||||||
|
'title': 'Team Fortress 2 - Sandviches!'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# Internal link
|
||||||
|
'url': 'https://web.archive.org/web/2oe/http://wayback-fakeurl.archive.org/yt/97t7Xj_iBv0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '97t7Xj_iBv0',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'How Flexible Machines Could Save The World'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# Video from 2012, webm format itag 45.
|
||||||
|
'url': 'https://web.archive.org/web/20120712231619/http://www.youtube.com/watch?v=AkhihxRKcrs&gl=US&hl=en',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'AkhihxRKcrs',
|
||||||
|
'ext': 'webm',
|
||||||
|
'title': 'Limited Run: Mondo\'s Modern Classic 1 of 3 (SDCC 2012)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# Old flash-only video. Webpage title starts with "YouTube - ".
|
||||||
|
'url': 'https://web.archive.org/web/20081211103536/http://www.youtube.com/watch?v=jNQXAC9IVRw',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'jNQXAC9IVRw',
|
||||||
|
'ext': 'unknown_video',
|
||||||
|
'title': 'Me at the zoo'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# Flash video with .flv extension (itag 34). Title has prefix "YouTube -"
|
||||||
|
# Title has some weird unicode characters too.
|
||||||
|
'url': 'https://web.archive.org/web/20110712231407/http://www.youtube.com/watch?v=lTx3G6h2xyA',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'lTx3G6h2xyA',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Madeon - Pop Culture (live mashup)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ # Some versions of Youtube have have "YouTube" as page title in html (and later rewritten by js).
|
||||||
|
'url': 'https://web.archive.org/web/http://www.youtube.com/watch?v=kH-G_aIBlFw',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'kH-G_aIBlFw',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'kH-G_aIBlFw'
|
||||||
|
},
|
||||||
|
'expected_warnings': [
|
||||||
|
'unable to extract title',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# First capture is a 302 redirect intermediary page.
|
||||||
|
'url': 'https://web.archive.org/web/20050214000000/http://www.youtube.com/watch?v=0altSZ96U4M',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0altSZ96U4M',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '0altSZ96U4M'
|
||||||
|
},
|
||||||
|
'expected_warnings': [
|
||||||
|
'unable to extract title',
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# Video not archived, only capture is unavailable video page
|
||||||
|
'url': 'https://web.archive.org/web/20210530071008/https://www.youtube.com/watch?v=lHJTf93HL1s&spfreload=10',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{ # Encoded url
|
||||||
|
'url': 'https://web.archive.org/web/20120712231619/http%3A//www.youtube.com/watch%3Fgl%3DUS%26v%3DAkhihxRKcrs%26hl%3Den',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://web.archive.org/web/20120712231619/http%3A//www.youtube.com/watch%3Fv%3DAkhihxRKcrs%26gl%3DUS%26hl%3Den',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
title = video_id # if we are not able get a title
|
||||||
|
|
||||||
|
def _extract_title(webpage):
|
||||||
|
page_title = self._html_search_regex(
|
||||||
|
r'<title>([^<]*)</title>', webpage, 'title', fatal=False) or ''
|
||||||
|
# YouTube video pages appear to always have either 'YouTube -' as suffix or '- YouTube' as prefix.
|
||||||
|
try:
|
||||||
|
page_title = self._html_search_regex(
|
||||||
|
r'(?:YouTube\s*-\s*(.*)$)|(?:(.*)\s*-\s*YouTube$)',
|
||||||
|
page_title, 'title', default='')
|
||||||
|
except RegexNotFoundError:
|
||||||
|
page_title = None
|
||||||
|
|
||||||
|
if not page_title:
|
||||||
|
self.report_warning('unable to extract title', video_id=video_id)
|
||||||
|
return
|
||||||
|
return page_title
|
||||||
|
|
||||||
|
# If the video is no longer available, the oldest capture may be one before it was removed.
|
||||||
|
# Setting the capture date in url to early date seems to redirect to earliest capture.
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
'https://web.archive.org/web/20050214000000/http://www.youtube.com/watch?v=%s' % video_id,
|
||||||
|
video_id=video_id, fatal=False, errnote='unable to download video webpage (probably not archived).')
|
||||||
|
if webpage:
|
||||||
|
title = _extract_title(webpage) or title
|
||||||
|
|
||||||
|
# Use link translator mentioned in https://github.com/ytdl-org/youtube-dl/issues/13655
|
||||||
|
internal_fake_url = 'https://web.archive.org/web/2oe_/http://wayback-fakeurl.archive.org/yt/%s' % video_id
|
||||||
|
try:
|
||||||
|
video_file_webpage = self._request_webpage(
|
||||||
|
HEADRequest(internal_fake_url), video_id,
|
||||||
|
note='Fetching video file url', expected_status=True)
|
||||||
|
except ExtractorError as e:
|
||||||
|
# HTTP Error 404 is expected if the video is not saved.
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404:
|
||||||
|
raise ExtractorError(
|
||||||
|
'HTTP Error %s. Most likely the video is not archived or issue with web.archive.org.' % e.cause.code,
|
||||||
|
expected=True)
|
||||||
|
raise
|
||||||
|
video_file_url = compat_urllib_parse_unquote(video_file_webpage.url)
|
||||||
|
video_file_url_qs = compat_parse_qs(compat_urlparse.urlparse(video_file_url).query)
|
||||||
|
|
||||||
|
# Attempt to recover any ext & format info from playback url
|
||||||
|
format = {'url': video_file_url}
|
||||||
|
itag = try_get(video_file_url_qs, lambda x: x['itag'][0])
|
||||||
|
if itag and itag in YoutubeIE._formats: # Naughty access but it works
|
||||||
|
format.update(YoutubeIE._formats[itag])
|
||||||
|
format.update({'format_id': itag})
|
||||||
|
else:
|
||||||
|
mime = try_get(video_file_url_qs, lambda x: x['mime'][0])
|
||||||
|
ext = mimetype2ext(mime) or determine_ext(video_file_url)
|
||||||
|
format.update({'ext': ext})
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': [format],
|
||||||
|
'duration': str_to_int(try_get(video_file_url_qs, lambda x: x['dur'][0]))
|
||||||
|
}
|
||||||
|
|||||||
+18
-6
@@ -290,14 +290,14 @@ class ARDMediathekIE(ARDMediathekBaseIE):
|
|||||||
|
|
||||||
|
|
||||||
class ARDIE(InfoExtractor):
|
class ARDIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?P<mainurl>https?://(?:www\.)?daserste\.de/[^?#]+/videos(?:extern)?/(?P<display_id>[^/?#]+)-(?:video-?)?(?P<id>[0-9]+))\.html'
|
_VALID_URL = r'(?P<mainurl>https?://(?:www\.)?daserste\.de/(?:[^/?#&]+/)+(?P<id>[^/?#&]+))\.html'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# available till 7.01.2022
|
# available till 7.01.2022
|
||||||
'url': 'https://www.daserste.de/information/talk/maischberger/videos/maischberger-die-woche-video100.html',
|
'url': 'https://www.daserste.de/information/talk/maischberger/videos/maischberger-die-woche-video100.html',
|
||||||
'md5': '867d8aa39eeaf6d76407c5ad1bb0d4c1',
|
'md5': '867d8aa39eeaf6d76407c5ad1bb0d4c1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'display_id': 'maischberger-die-woche',
|
'id': 'maischberger-die-woche-video100',
|
||||||
'id': '100',
|
'display_id': 'maischberger-die-woche-video100',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'duration': 3687.0,
|
'duration': 3687.0,
|
||||||
'title': 'maischberger. die woche vom 7. Januar 2021',
|
'title': 'maischberger. die woche vom 7. Januar 2021',
|
||||||
@@ -305,16 +305,28 @@ class ARDIE(InfoExtractor):
|
|||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.daserste.de/information/reportage-dokumentation/erlebnis-erde/videosextern/woelfe-und-herdenschutzhunde-ungleiche-brueder-102.html',
|
'url': 'https://www.daserste.de/information/politik-weltgeschehen/morgenmagazin/videosextern/dominik-kahun-aus-der-nhl-direkt-zur-weltmeisterschaft-100.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.daserste.de/information/nachrichten-wetter/tagesthemen/videosextern/tagesthemen-17736.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.daserste.de/unterhaltung/serie/in-aller-freundschaft-die-jungen-aerzte/videos/diversity-tag-sanam-afrashteh100.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.daserste.de/information/reportage-dokumentation/dokus/videos/die-story-im-ersten-mission-unter-falscher-flagge-100.html',
|
'url': 'http://www.daserste.de/information/reportage-dokumentation/dokus/videos/die-story-im-ersten-mission-unter-falscher-flagge-100.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.daserste.de/unterhaltung/serie/in-aller-freundschaft-die-jungen-aerzte/Drehpause-100.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.daserste.de/unterhaltung/film/filmmittwoch-im-ersten/videos/making-ofwendezeit-video-100.html',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
display_id = mobj.group('display_id')
|
display_id = mobj.group('id')
|
||||||
|
|
||||||
player_url = mobj.group('mainurl') + '~playerXml.xml'
|
player_url = mobj.group('mainurl') + '~playerXml.xml'
|
||||||
doc = self._download_xml(player_url, display_id)
|
doc = self._download_xml(player_url, display_id)
|
||||||
@@ -365,7 +377,7 @@ class ARDIE(InfoExtractor):
|
|||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': mobj.group('id'),
|
'id': xpath_text(video_node, './videoId', default=display_id),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': video_node.find('./title').text,
|
'title': video_node.find('./title').text,
|
||||||
|
|||||||
@@ -86,18 +86,19 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
title = episode['titulo']
|
title = episode['titulo']
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for source in episode.get('sources', []):
|
for source in episode.get('sources', []):
|
||||||
src = source.get('src')
|
src = source.get('src')
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
src_type = source.get('type')
|
src_type = source.get('type')
|
||||||
if src_type == 'application/vnd.apple.mpegurl':
|
if src_type == 'application/vnd.apple.mpegurl':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats, subtitles = self._extract_m3u8_formats(
|
||||||
src, video_id, 'mp4', 'm3u8_native',
|
src, video_id, 'mp4', 'm3u8_native',
|
||||||
m3u8_id='hls', fatal=False))
|
m3u8_id='hls', fatal=False)
|
||||||
elif src_type == 'application/dash+xml':
|
elif src_type == 'application/dash+xml':
|
||||||
formats.extend(self._extract_mpd_formats(
|
formats, subtitles = self._extract_mpd_formats(
|
||||||
src, video_id, mpd_id='dash', fatal=False))
|
src, video_id, mpd_id='dash', fatal=False)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
heartbeat = episode.get('heartbeat') or {}
|
heartbeat = episode.get('heartbeat') or {}
|
||||||
@@ -115,4 +116,5 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
'channel': get_meta('channel'),
|
'channel': get_meta('channel'),
|
||||||
'season': get_meta('season'),
|
'season': get_meta('season'),
|
||||||
'episode_number': int_or_none(get_meta('episodeNumber')),
|
'episode_number': int_or_none(get_meta('episodeNumber')),
|
||||||
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,3 +245,31 @@ class AudiusPlaylistIE(AudiusBaseIE):
|
|||||||
return self.playlist_result(entries, playlist_id,
|
return self.playlist_result(entries, playlist_id,
|
||||||
playlist_data.get('playlist_name', title),
|
playlist_data.get('playlist_name', title),
|
||||||
playlist_data.get('description'))
|
playlist_data.get('description'))
|
||||||
|
|
||||||
|
|
||||||
|
class AudiusProfileIE(AudiusPlaylistIE):
|
||||||
|
IE_NAME = 'audius:artist'
|
||||||
|
IE_DESC = 'Audius.co profile/artist pages'
|
||||||
|
_VALID_URL = r'https?://(?:www)?audius\.co/(?P<id>[^\/]+)/?(?:[?#]|$)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://audius.co/pzl/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ezRo7',
|
||||||
|
'description': 'TAMALE\n\nContact: officialpzl@gmail.com',
|
||||||
|
'title': 'pzl',
|
||||||
|
},
|
||||||
|
'playlist_count': 24,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
self._select_api_base()
|
||||||
|
profile_id = self._match_id(url)
|
||||||
|
try:
|
||||||
|
_profile_data = self._api_request('/full/users/handle/' + profile_id, profile_id)
|
||||||
|
except ExtractorError as e:
|
||||||
|
raise ExtractorError('Could not download profile info; ' + str(e))
|
||||||
|
profile_audius_id = _profile_data[0]['id']
|
||||||
|
profile_bio = _profile_data[0].get('bio')
|
||||||
|
|
||||||
|
api_call = self._api_request('/full/users/handle/%s/tracks' % profile_id, profile_id)
|
||||||
|
return self.playlist_result(self._build_playlist(api_call), profile_audius_id, profile_id, profile_bio)
|
||||||
|
|||||||
@@ -1271,7 +1271,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
entries = []
|
entries = []
|
||||||
for num, media_meta in enumerate(medias, start=1):
|
for num, media_meta in enumerate(medias, start=1):
|
||||||
formats, subtitles = self._extract_from_media_meta(media_meta, playlist_id)
|
formats, subtitles = self._extract_from_media_meta(media_meta, playlist_id)
|
||||||
if not formats and not self._downloader.params.get('ignore_no_formats'):
|
if not formats and not self.get_param('ignore_no_formats'):
|
||||||
continue
|
continue
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
# Bilibili anthologies are similar to playlists but all videos share the same video ID as the anthology itself.
|
# Bilibili anthologies are similar to playlists but all videos share the same video ID as the anthology itself.
|
||||||
# If the video has no page argument, check to see if it's an anthology
|
# If the video has no page argument, check to see if it's an anthology
|
||||||
if page_id is None:
|
if page_id is None:
|
||||||
if not self._downloader.params.get('noplaylist'):
|
if not self.get_param('noplaylist'):
|
||||||
r = self._extract_anthology_entries(bv_id, video_id, webpage)
|
r = self._extract_anthology_entries(bv_id, video_id, webpage)
|
||||||
if r is not None:
|
if r is not None:
|
||||||
self.to_screen('Downloading anthology %s - add --no-playlist to just download video' % video_id)
|
self.to_screen('Downloading anthology %s - add --no-playlist to just download video' % video_id)
|
||||||
@@ -299,7 +299,7 @@ class BiliBiliIE(InfoExtractor):
|
|||||||
'tags': tags,
|
'tags': tags,
|
||||||
'raw_tags': raw_tags,
|
'raw_tags': raw_tags,
|
||||||
}
|
}
|
||||||
if self._downloader.params.get('getcomments', False):
|
if self.get_param('getcomments', False):
|
||||||
def get_comments():
|
def get_comments():
|
||||||
comments = self._get_all_comment_pages(video_id)
|
comments = self._get_all_comment_pages(video_id)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ class BlinkxIE(InfoExtractor):
|
|||||||
'fullid': video_id,
|
'fullid': video_id,
|
||||||
'title': data['title'],
|
'title': data['title'],
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': data['channel_name'],
|
'uploader': data.get('channel_name'),
|
||||||
'timestamp': data['pubdate_epoch'],
|
'timestamp': data.get('pubdate_epoch'),
|
||||||
'description': data.get('description'),
|
'description': data.get('description'),
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
container = source.get('container')
|
container = source.get('container')
|
||||||
ext = mimetype2ext(source.get('type'))
|
ext = mimetype2ext(source.get('type'))
|
||||||
src = source.get('src')
|
src = source.get('src')
|
||||||
skip_unplayable = not self._downloader.params.get('allow_unplayable_formats')
|
skip_unplayable = not self.get_param('allow_unplayable_formats')
|
||||||
# https://support.brightcove.com/playback-api-video-fields-reference#key_systems_object
|
# https://support.brightcove.com/playback-api-video-fields-reference#key_systems_object
|
||||||
if skip_unplayable and (container == 'WVM' or source.get('key_systems')):
|
if skip_unplayable and (container == 'WVM' or source.get('key_systems')):
|
||||||
num_drm_sources += 1
|
num_drm_sources += 1
|
||||||
@@ -547,7 +547,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
error = errors[0]
|
error = errors[0]
|
||||||
self.raise_no_formats(
|
self.raise_no_formats(
|
||||||
error.get('message') or error.get('error_subcode') or error['error_code'], expected=True)
|
error.get('message') or error.get('error_subcode') or error['error_code'], expected=True)
|
||||||
elif (not self._downloader.params.get('allow_unplayable_formats')
|
elif (not self.get_param('allow_unplayable_formats')
|
||||||
and sources and num_drm_sources == len(sources)):
|
and sources and num_drm_sources == len(sources)):
|
||||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class BYUtvIE(InfoExtractor):
|
|||||||
|
|
||||||
info = {}
|
info = {}
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for format_id, ep in video.items():
|
for format_id, ep in video.items():
|
||||||
if not isinstance(ep, dict):
|
if not isinstance(ep, dict):
|
||||||
continue
|
continue
|
||||||
@@ -90,12 +91,16 @@ class BYUtvIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
ext = determine_ext(video_url)
|
ext = determine_ext(video_url)
|
||||||
if ext == 'm3u8':
|
if ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
video_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
video_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id='hls', fatal=False))
|
m3u8_id='hls', fatal=False)
|
||||||
|
formats.extend(m3u8_fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, m3u8_subs)
|
||||||
elif ext == 'mpd':
|
elif ext == 'mpd':
|
||||||
formats.extend(self._extract_mpd_formats(
|
mpd_fmts, mpd_subs = self._extract_mpd_formats_and_subtitles(
|
||||||
video_url, video_id, mpd_id='dash', fatal=False))
|
video_url, video_id, mpd_id='dash', fatal=False)
|
||||||
|
formats.extend(mpd_fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, mpd_subs)
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
@@ -114,4 +119,5 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': display_id,
|
'title': display_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -83,24 +83,31 @@ class CanvasIE(InfoExtractor):
|
|||||||
description = data.get('description')
|
description = data.get('description')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for target in data['targetUrls']:
|
for target in data['targetUrls']:
|
||||||
format_url, format_type = url_or_none(target.get('url')), str_or_none(target.get('type'))
|
format_url, format_type = url_or_none(target.get('url')), str_or_none(target.get('type'))
|
||||||
if not format_url or not format_type:
|
if not format_url or not format_type:
|
||||||
continue
|
continue
|
||||||
format_type = format_type.upper()
|
format_type = format_type.upper()
|
||||||
if format_type in self._HLS_ENTRY_PROTOCOLS_MAP:
|
if format_type in self._HLS_ENTRY_PROTOCOLS_MAP:
|
||||||
formats.extend(self._extract_m3u8_formats(
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
format_url, video_id, 'mp4', self._HLS_ENTRY_PROTOCOLS_MAP[format_type],
|
format_url, video_id, 'mp4', self._HLS_ENTRY_PROTOCOLS_MAP[format_type],
|
||||||
m3u8_id=format_type, fatal=False))
|
m3u8_id=format_type, fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
elif format_type == 'HDS':
|
elif format_type == 'HDS':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
format_url, video_id, f4m_id=format_type, fatal=False))
|
format_url, video_id, f4m_id=format_type, fatal=False))
|
||||||
elif format_type == 'MPEG_DASH':
|
elif format_type == 'MPEG_DASH':
|
||||||
formats.extend(self._extract_mpd_formats(
|
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||||
format_url, video_id, mpd_id=format_type, fatal=False))
|
format_url, video_id, mpd_id=format_type, fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
elif format_type == 'HSS':
|
elif format_type == 'HSS':
|
||||||
formats.extend(self._extract_ism_formats(
|
fmts, subs = self._extract_ism_formats_and_subtitles(
|
||||||
format_url, video_id, ism_id='mss', fatal=False))
|
format_url, video_id, ism_id='mss', fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': format_type,
|
'format_id': format_type,
|
||||||
@@ -108,7 +115,6 @@ class CanvasIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
|
||||||
subtitle_urls = data.get('subtitleUrls')
|
subtitle_urls = data.get('subtitleUrls')
|
||||||
if isinstance(subtitle_urls, list):
|
if isinstance(subtitle_urls, list):
|
||||||
for subtitle in subtitle_urls:
|
for subtitle in subtitle_urls:
|
||||||
|
|||||||
+10
-1
@@ -27,7 +27,13 @@ class CBSBaseIE(ThePlatformFeedIE):
|
|||||||
|
|
||||||
|
|
||||||
class CBSIE(CBSBaseIE):
|
class CBSIE(CBSBaseIE):
|
||||||
_VALID_URL = r'(?:cbs:|https?://(?:www\.)?(?:(?:cbs|paramountplus)\.com/shows/[^/]+/video|colbertlateshow\.com/(?:video|podcasts))/)(?P<id>[\w-]+)'
|
_VALID_URL = r'''(?x)
|
||||||
|
(?:
|
||||||
|
cbs:|
|
||||||
|
https?://(?:www\.)?(?:
|
||||||
|
(?:cbs|paramountplus)\.com/(?:shows/[^/]+/video|movies/[^/]+)/|
|
||||||
|
colbertlateshow\.com/(?:video|podcasts)/)
|
||||||
|
)(?P<id>[\w-]+)'''
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
'url': 'https://www.cbs.com/shows/garth-brooks/video/_u7W953k6la293J7EPTd9oHkSPs6Xn6_/connect-chat-feat-garth-brooks/',
|
||||||
@@ -55,6 +61,9 @@ class CBSIE(CBSBaseIE):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.paramountplus.com/shows/all-rise/video/QmR1WhNkh1a_IrdHZrbcRklm176X_rVc/all-rise-space/',
|
'url': 'https://www.paramountplus.com/shows/all-rise/video/QmR1WhNkh1a_IrdHZrbcRklm176X_rVc/all-rise-space/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.paramountplus.com/movies/million-dollar-american-princesses-meghan-and-harry/C0LpgNwXYeB8txxycdWdR9TjxpJOsdCq',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_video_info(self, content_id, site='cbs', mpx_acc=2198311517):
|
def _extract_video_info(self, content_id, site='cbs', mpx_acc=2198311517):
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ class CDAIE(InfoExtractor):
|
|||||||
'age_limit': 18 if need_confirm_age else 0,
|
'age_limit': 18 if need_confirm_age else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info = self._search_json_ld(webpage, video_id, default={})
|
||||||
|
|
||||||
# Source: https://www.cda.pl/js/player.js?t=1606154898
|
# Source: https://www.cda.pl/js/player.js?t=1606154898
|
||||||
def decrypt_file(a):
|
def decrypt_file(a):
|
||||||
for p in ('_XDDD', '_CDA', '_ADC', '_CXD', '_QWE', '_Q5', '_IKSDE'):
|
for p in ('_XDDD', '_CDA', '_ADC', '_CXD', '_QWE', '_Q5', '_IKSDE'):
|
||||||
@@ -197,7 +199,7 @@ class CDAIE(InfoExtractor):
|
|||||||
handler = self._download_webpage
|
handler = self._download_webpage
|
||||||
|
|
||||||
webpage = handler(
|
webpage = handler(
|
||||||
self._BASE_URL + href, video_id,
|
urljoin(self._BASE_URL, href), video_id,
|
||||||
'Downloading %s version information' % resolution, fatal=False)
|
'Downloading %s version information' % resolution, fatal=False)
|
||||||
if not webpage:
|
if not webpage:
|
||||||
# Manually report warning because empty page is returned when
|
# Manually report warning because empty page is returned when
|
||||||
@@ -209,6 +211,4 @@ class CDAIE(InfoExtractor):
|
|||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
info = self._search_json_ld(webpage, video_id, default={})
|
|
||||||
|
|
||||||
return merge_dicts(info_dict, info)
|
return merge_dicts(info_dict, info)
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class CeskaTelevizeIE(InfoExtractor):
|
|||||||
is_live = item.get('type') == 'LIVE'
|
is_live = item.get('type') == 'LIVE'
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, stream_url in item.get('streamUrls', {}).items():
|
for format_id, stream_url in item.get('streamUrls', {}).items():
|
||||||
if (not self._downloader.params.get('allow_unplayable_formats')
|
if (not self.get_param('allow_unplayable_formats')
|
||||||
and 'drmOnly=true' in stream_url):
|
and 'drmOnly=true' in stream_url):
|
||||||
continue
|
continue
|
||||||
if 'playerType=flash' in stream_url:
|
if 'playerType=flash' in stream_url:
|
||||||
|
|||||||
+339
-167
@@ -9,8 +9,6 @@ import netrc
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
import ssl
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
@@ -58,6 +56,7 @@ from ..utils import (
|
|||||||
js_to_json,
|
js_to_json,
|
||||||
JSON_LD_RE,
|
JSON_LD_RE,
|
||||||
mimetype2ext,
|
mimetype2ext,
|
||||||
|
network_exceptions,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
parse_bitrate,
|
parse_bitrate,
|
||||||
parse_codecs,
|
parse_codecs,
|
||||||
@@ -157,7 +156,7 @@ class InfoExtractor(object):
|
|||||||
* player_url SWF Player URL (used for rtmpdump).
|
* player_url SWF Player URL (used for rtmpdump).
|
||||||
* protocol The protocol that will be used for the actual
|
* protocol The protocol that will be used for the actual
|
||||||
download, lower-case.
|
download, lower-case.
|
||||||
"http", "https", "rtsp", "rtmp", "rtmpe",
|
"http", "https", "rtsp", "rtmp", "rtmp_ffmpeg", "rtmpe",
|
||||||
"m3u8", "m3u8_native" or "http_dash_segments".
|
"m3u8", "m3u8_native" or "http_dash_segments".
|
||||||
* fragment_base_url
|
* fragment_base_url
|
||||||
Base URL for fragments. Each fragment's path
|
Base URL for fragments. Each fragment's path
|
||||||
@@ -251,6 +250,8 @@ class InfoExtractor(object):
|
|||||||
entry and one of:
|
entry and one of:
|
||||||
* "data": The subtitles file contents
|
* "data": The subtitles file contents
|
||||||
* "url": A URL pointing to the subtitles file
|
* "url": A URL pointing to the subtitles file
|
||||||
|
It can optionally also have:
|
||||||
|
* "name": Name or description of the subtitles
|
||||||
"ext" will be calculated from URL if missing
|
"ext" will be calculated from URL if missing
|
||||||
automatic_captions: Like 'subtitles'; contains automatically generated
|
automatic_captions: Like 'subtitles'; contains automatically generated
|
||||||
captions instead of normal subtitles
|
captions instead of normal subtitles
|
||||||
@@ -421,6 +422,14 @@ class InfoExtractor(object):
|
|||||||
_GEO_IP_BLOCKS = None
|
_GEO_IP_BLOCKS = None
|
||||||
_WORKING = True
|
_WORKING = True
|
||||||
|
|
||||||
|
_LOGIN_HINTS = {
|
||||||
|
'any': 'Use --cookies, --username and --password or --netrc to provide account credentials',
|
||||||
|
'cookies': (
|
||||||
|
'Use --cookies for the authentication. '
|
||||||
|
'See https://github.com/ytdl-org/youtube-dl#how-do-i-pass-cookies-to-youtube-dl for how to pass cookies'),
|
||||||
|
'password': 'Use --username and --password or --netrc to provide account credentials',
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, downloader=None):
|
def __init__(self, downloader=None):
|
||||||
"""Constructor. Receives an optional downloader."""
|
"""Constructor. Receives an optional downloader."""
|
||||||
self._ready = False
|
self._ready = False
|
||||||
@@ -490,7 +499,7 @@ class InfoExtractor(object):
|
|||||||
if not self._x_forwarded_for_ip:
|
if not self._x_forwarded_for_ip:
|
||||||
|
|
||||||
# Geo bypass mechanism is explicitly disabled by user
|
# Geo bypass mechanism is explicitly disabled by user
|
||||||
if not self._downloader.params.get('geo_bypass', True):
|
if not self.get_param('geo_bypass', True):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not geo_bypass_context:
|
if not geo_bypass_context:
|
||||||
@@ -512,7 +521,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
# Explicit IP block specified by user, use it right away
|
# Explicit IP block specified by user, use it right away
|
||||||
# regardless of whether extractor is geo bypassable or not
|
# regardless of whether extractor is geo bypassable or not
|
||||||
ip_block = self._downloader.params.get('geo_bypass_ip_block', None)
|
ip_block = self.get_param('geo_bypass_ip_block', None)
|
||||||
|
|
||||||
# Otherwise use random IP block from geo bypass context but only
|
# Otherwise use random IP block from geo bypass context but only
|
||||||
# if extractor is known as geo bypassable
|
# if extractor is known as geo bypassable
|
||||||
@@ -523,17 +532,15 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
if ip_block:
|
if ip_block:
|
||||||
self._x_forwarded_for_ip = GeoUtils.random_ipv4(ip_block)
|
self._x_forwarded_for_ip = GeoUtils.random_ipv4(ip_block)
|
||||||
if self._downloader.params.get('verbose', False):
|
self._downloader.write_debug(
|
||||||
self._downloader.to_screen(
|
'[debug] Using fake IP %s as X-Forwarded-For' % self._x_forwarded_for_ip)
|
||||||
'[debug] Using fake IP %s as X-Forwarded-For.'
|
|
||||||
% self._x_forwarded_for_ip)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Path 2: bypassing based on country code
|
# Path 2: bypassing based on country code
|
||||||
|
|
||||||
# Explicit country code specified by user, use it right away
|
# Explicit country code specified by user, use it right away
|
||||||
# regardless of whether extractor is geo bypassable or not
|
# regardless of whether extractor is geo bypassable or not
|
||||||
country = self._downloader.params.get('geo_bypass_country', None)
|
country = self.get_param('geo_bypass_country', None)
|
||||||
|
|
||||||
# Otherwise use random country code from geo bypass context but
|
# Otherwise use random country code from geo bypass context but
|
||||||
# only if extractor is known as geo bypassable
|
# only if extractor is known as geo bypassable
|
||||||
@@ -544,10 +551,8 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
if country:
|
if country:
|
||||||
self._x_forwarded_for_ip = GeoUtils.random_ipv4(country)
|
self._x_forwarded_for_ip = GeoUtils.random_ipv4(country)
|
||||||
if self._downloader.params.get('verbose', False):
|
self._downloader.write_debug(
|
||||||
self._downloader.to_screen(
|
'Using fake IP %s (%s) as X-Forwarded-For' % (self._x_forwarded_for_ip, country.upper()))
|
||||||
'[debug] Using fake IP %s (%s) as X-Forwarded-For.'
|
|
||||||
% (self._x_forwarded_for_ip, country.upper()))
|
|
||||||
|
|
||||||
def extract(self, url):
|
def extract(self, url):
|
||||||
"""Extracts URL information and returns it in list of dicts."""
|
"""Extracts URL information and returns it in list of dicts."""
|
||||||
@@ -555,9 +560,16 @@ class InfoExtractor(object):
|
|||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
try:
|
try:
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
self.write_debug('Extracting URL: %s' % url)
|
||||||
ie_result = self._real_extract(url)
|
ie_result = self._real_extract(url)
|
||||||
|
if ie_result is None:
|
||||||
|
return None
|
||||||
if self._x_forwarded_for_ip:
|
if self._x_forwarded_for_ip:
|
||||||
ie_result['__x_forwarded_for_ip'] = self._x_forwarded_for_ip
|
ie_result['__x_forwarded_for_ip'] = self._x_forwarded_for_ip
|
||||||
|
subtitles = ie_result.get('subtitles')
|
||||||
|
if (subtitles and 'live_chat' in subtitles
|
||||||
|
and 'no-live-chat' in self.get_param('compat_opts', [])):
|
||||||
|
del subtitles['live_chat']
|
||||||
return ie_result
|
return ie_result
|
||||||
except GeoRestrictedError as e:
|
except GeoRestrictedError as e:
|
||||||
if self.__maybe_fake_ip_and_retry(e.countries):
|
if self.__maybe_fake_ip_and_retry(e.countries):
|
||||||
@@ -571,9 +583,9 @@ class InfoExtractor(object):
|
|||||||
raise ExtractorError('An extractor error has occurred.', cause=e)
|
raise ExtractorError('An extractor error has occurred.', cause=e)
|
||||||
|
|
||||||
def __maybe_fake_ip_and_retry(self, countries):
|
def __maybe_fake_ip_and_retry(self, countries):
|
||||||
if (not self._downloader.params.get('geo_bypass_country', None)
|
if (not self.get_param('geo_bypass_country', None)
|
||||||
and self._GEO_BYPASS
|
and self._GEO_BYPASS
|
||||||
and self._downloader.params.get('geo_bypass', True)
|
and self.get_param('geo_bypass', True)
|
||||||
and not self._x_forwarded_for_ip
|
and not self._x_forwarded_for_ip
|
||||||
and countries):
|
and countries):
|
||||||
country_code = random.choice(countries)
|
country_code = random.choice(countries)
|
||||||
@@ -627,7 +639,7 @@ class InfoExtractor(object):
|
|||||||
See _download_webpage docstring for arguments specification.
|
See _download_webpage docstring for arguments specification.
|
||||||
"""
|
"""
|
||||||
if not self._downloader._first_webpage_request:
|
if not self._downloader._first_webpage_request:
|
||||||
sleep_interval = float_or_none(self._downloader.params.get('sleep_interval_requests')) or 0
|
sleep_interval = float_or_none(self.get_param('sleep_interval_requests')) or 0
|
||||||
if sleep_interval > 0:
|
if sleep_interval > 0:
|
||||||
self.to_screen('Sleeping %s seconds ...' % sleep_interval)
|
self.to_screen('Sleeping %s seconds ...' % sleep_interval)
|
||||||
time.sleep(sleep_interval)
|
time.sleep(sleep_interval)
|
||||||
@@ -659,12 +671,9 @@ class InfoExtractor(object):
|
|||||||
url_or_request = update_url_query(url_or_request, query)
|
url_or_request = update_url_query(url_or_request, query)
|
||||||
if data is not None or headers:
|
if data is not None or headers:
|
||||||
url_or_request = sanitized_Request(url_or_request, data, headers)
|
url_or_request = sanitized_Request(url_or_request, data, headers)
|
||||||
exceptions = [compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error]
|
|
||||||
if hasattr(ssl, 'CertificateError'):
|
|
||||||
exceptions.append(ssl.CertificateError)
|
|
||||||
try:
|
try:
|
||||||
return self._downloader.urlopen(url_or_request)
|
return self._downloader.urlopen(url_or_request)
|
||||||
except tuple(exceptions) as err:
|
except network_exceptions as err:
|
||||||
if isinstance(err, compat_urllib_error.HTTPError):
|
if isinstance(err, compat_urllib_error.HTTPError):
|
||||||
if self.__can_accept_status_code(err, expected_status):
|
if self.__can_accept_status_code(err, expected_status):
|
||||||
# Retain reference to error to prevent file object from
|
# Retain reference to error to prevent file object from
|
||||||
@@ -755,11 +764,11 @@ class InfoExtractor(object):
|
|||||||
webpage_bytes = prefix + webpage_bytes
|
webpage_bytes = prefix + webpage_bytes
|
||||||
if not encoding:
|
if not encoding:
|
||||||
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
||||||
if self._downloader.params.get('dump_intermediate_pages', False):
|
if self.get_param('dump_intermediate_pages', False):
|
||||||
self.to_screen('Dumping request to ' + urlh.geturl())
|
self.to_screen('Dumping request to ' + urlh.geturl())
|
||||||
dump = base64.b64encode(webpage_bytes).decode('ascii')
|
dump = base64.b64encode(webpage_bytes).decode('ascii')
|
||||||
self._downloader.to_screen(dump)
|
self._downloader.to_screen(dump)
|
||||||
if self._downloader.params.get('write_pages', False):
|
if self.get_param('write_pages', False):
|
||||||
basen = '%s_%s' % (video_id, urlh.geturl())
|
basen = '%s_%s' % (video_id, urlh.geturl())
|
||||||
if len(basen) > 240:
|
if len(basen) > 240:
|
||||||
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
||||||
@@ -943,14 +952,65 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
self.report_warning(errmsg + str(ve))
|
self.report_warning(errmsg + str(ve))
|
||||||
|
|
||||||
def report_warning(self, msg, video_id=None):
|
def _parse_socket_response_as_json(self, data, video_id, transform_source=None, fatal=True):
|
||||||
|
return self._parse_json(
|
||||||
|
data[data.find('{'):data.rfind('}') + 1],
|
||||||
|
video_id, transform_source, fatal)
|
||||||
|
|
||||||
|
def _download_socket_json_handle(
|
||||||
|
self, url_or_request, video_id, note='Polling socket',
|
||||||
|
errnote='Unable to poll socket', transform_source=None,
|
||||||
|
fatal=True, encoding=None, data=None, headers={}, query={},
|
||||||
|
expected_status=None):
|
||||||
|
"""
|
||||||
|
Return a tuple (JSON object, URL handle).
|
||||||
|
|
||||||
|
See _download_webpage docstring for arguments specification.
|
||||||
|
"""
|
||||||
|
res = self._download_webpage_handle(
|
||||||
|
url_or_request, video_id, note, errnote, fatal=fatal,
|
||||||
|
encoding=encoding, data=data, headers=headers, query=query,
|
||||||
|
expected_status=expected_status)
|
||||||
|
if res is False:
|
||||||
|
return res
|
||||||
|
webpage, urlh = res
|
||||||
|
return self._parse_socket_response_as_json(
|
||||||
|
webpage, video_id, transform_source=transform_source,
|
||||||
|
fatal=fatal), urlh
|
||||||
|
|
||||||
|
def _download_socket_json(
|
||||||
|
self, url_or_request, video_id, note='Polling socket',
|
||||||
|
errnote='Unable to poll socket', transform_source=None,
|
||||||
|
fatal=True, encoding=None, data=None, headers={}, query={},
|
||||||
|
expected_status=None):
|
||||||
|
"""
|
||||||
|
Return the JSON object as a dict.
|
||||||
|
|
||||||
|
See _download_webpage docstring for arguments specification.
|
||||||
|
"""
|
||||||
|
res = self._download_socket_json_handle(
|
||||||
|
url_or_request, video_id, note=note, errnote=errnote,
|
||||||
|
transform_source=transform_source, fatal=fatal, encoding=encoding,
|
||||||
|
data=data, headers=headers, query=query,
|
||||||
|
expected_status=expected_status)
|
||||||
|
return res if res is False else res[0]
|
||||||
|
|
||||||
|
def report_warning(self, msg, video_id=None, *args, **kwargs):
|
||||||
idstr = '' if video_id is None else '%s: ' % video_id
|
idstr = '' if video_id is None else '%s: ' % video_id
|
||||||
self._downloader.report_warning(
|
self._downloader.report_warning(
|
||||||
'[%s] %s%s' % (self.IE_NAME, idstr, msg))
|
'[%s] %s%s' % (self.IE_NAME, idstr, msg), *args, **kwargs)
|
||||||
|
|
||||||
def to_screen(self, msg):
|
def to_screen(self, msg, *args, **kwargs):
|
||||||
"""Print msg to screen, prefixing it with '[ie_name]'"""
|
"""Print msg to screen, prefixing it with '[ie_name]'"""
|
||||||
self._downloader.to_screen('[%s] %s' % (self.IE_NAME, msg))
|
self._downloader.to_screen('[%s] %s' % (self.IE_NAME, msg), *args, **kwargs)
|
||||||
|
|
||||||
|
def write_debug(self, msg, *args, **kwargs):
|
||||||
|
self._downloader.write_debug('[%s] %s' % (self.IE_NAME, msg), *args, **kwargs)
|
||||||
|
|
||||||
|
def get_param(self, name, default=None, *args, **kwargs):
|
||||||
|
if self._downloader:
|
||||||
|
return self._downloader.params.get(name, default, *args, **kwargs)
|
||||||
|
return default
|
||||||
|
|
||||||
def report_extraction(self, id_or_name):
|
def report_extraction(self, id_or_name):
|
||||||
"""Report information extraction."""
|
"""Report information extraction."""
|
||||||
@@ -969,23 +1029,22 @@ class InfoExtractor(object):
|
|||||||
self.to_screen('Logging in')
|
self.to_screen('Logging in')
|
||||||
|
|
||||||
def raise_login_required(
|
def raise_login_required(
|
||||||
self, msg='This video is only available for registered users', metadata_available=False):
|
self, msg='This video is only available for registered users',
|
||||||
if metadata_available and self._downloader.params.get('ignore_no_formats_error'):
|
metadata_available=False, method='any'):
|
||||||
|
if metadata_available and self.get_param('ignore_no_formats_error'):
|
||||||
self.report_warning(msg)
|
self.report_warning(msg)
|
||||||
raise ExtractorError(
|
raise ExtractorError('%s. %s' % (msg, self._LOGIN_HINTS[method]), expected=True)
|
||||||
'%s. Use --cookies, --username and --password or --netrc to provide account credentials' % msg,
|
|
||||||
expected=True)
|
|
||||||
|
|
||||||
def raise_geo_restricted(
|
def raise_geo_restricted(
|
||||||
self, msg='This video is not available from your location due to geo restriction',
|
self, msg='This video is not available from your location due to geo restriction',
|
||||||
countries=None, metadata_available=False):
|
countries=None, metadata_available=False):
|
||||||
if metadata_available and self._downloader.params.get('ignore_no_formats_error'):
|
if metadata_available and self.get_param('ignore_no_formats_error'):
|
||||||
self.report_warning(msg)
|
self.report_warning(msg)
|
||||||
else:
|
else:
|
||||||
raise GeoRestrictedError(msg, countries=countries)
|
raise GeoRestrictedError(msg, countries=countries)
|
||||||
|
|
||||||
def raise_no_formats(self, msg, expected=False, video_id=None):
|
def raise_no_formats(self, msg, expected=False, video_id=None):
|
||||||
if expected and self._downloader.params.get('ignore_no_formats_error'):
|
if expected and self.get_param('ignore_no_formats_error'):
|
||||||
self.report_warning(msg, video_id)
|
self.report_warning(msg, video_id)
|
||||||
else:
|
else:
|
||||||
raise ExtractorError(msg, expected=expected, video_id=video_id)
|
raise ExtractorError(msg, expected=expected, video_id=video_id)
|
||||||
@@ -1040,7 +1099,7 @@ class InfoExtractor(object):
|
|||||||
if mobj:
|
if mobj:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not self._downloader.params.get('no_color') and compat_os_name != 'nt' and sys.stderr.isatty():
|
if not self.get_param('no_color') and compat_os_name != 'nt' and sys.stderr.isatty():
|
||||||
_name = '\033[0;34m%s\033[0m' % name
|
_name = '\033[0;34m%s\033[0m' % name
|
||||||
else:
|
else:
|
||||||
_name = name
|
_name = name
|
||||||
@@ -1074,7 +1133,7 @@ class InfoExtractor(object):
|
|||||||
password = None
|
password = None
|
||||||
netrc_machine = netrc_machine or self._NETRC_MACHINE
|
netrc_machine = netrc_machine or self._NETRC_MACHINE
|
||||||
|
|
||||||
if self._downloader.params.get('usenetrc', False):
|
if self.get_param('usenetrc', False):
|
||||||
try:
|
try:
|
||||||
info = netrc.netrc().authenticators(netrc_machine)
|
info = netrc.netrc().authenticators(netrc_machine)
|
||||||
if info is not None:
|
if info is not None:
|
||||||
@@ -1098,15 +1157,11 @@ class InfoExtractor(object):
|
|||||||
value.
|
value.
|
||||||
If there's no info available, return (None, None)
|
If there's no info available, return (None, None)
|
||||||
"""
|
"""
|
||||||
if self._downloader is None:
|
|
||||||
return (None, None)
|
|
||||||
|
|
||||||
downloader_params = self._downloader.params
|
|
||||||
|
|
||||||
# Attempt to use provided username and password or .netrc data
|
# Attempt to use provided username and password or .netrc data
|
||||||
if downloader_params.get(username_option) is not None:
|
username = self.get_param(username_option)
|
||||||
username = downloader_params[username_option]
|
if username is not None:
|
||||||
password = downloader_params[password_option]
|
password = self.get_param(password_option)
|
||||||
else:
|
else:
|
||||||
username, password = self._get_netrc_login_info(netrc_machine)
|
username, password = self._get_netrc_login_info(netrc_machine)
|
||||||
|
|
||||||
@@ -1119,12 +1174,10 @@ class InfoExtractor(object):
|
|||||||
currently just uses the command line option
|
currently just uses the command line option
|
||||||
If there's no info available, return None
|
If there's no info available, return None
|
||||||
"""
|
"""
|
||||||
if self._downloader is None:
|
|
||||||
return None
|
|
||||||
downloader_params = self._downloader.params
|
|
||||||
|
|
||||||
if downloader_params.get('twofactor') is not None:
|
tfa = self.get_param('twofactor')
|
||||||
return downloader_params['twofactor']
|
if tfa is not None:
|
||||||
|
return tfa
|
||||||
|
|
||||||
return compat_getpass('Type %s and press [Return]: ' % note)
|
return compat_getpass('Type %s and press [Return]: ' % note)
|
||||||
|
|
||||||
@@ -1419,7 +1472,10 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
default = ('hidden', 'hasvid', 'ie_pref', 'lang', 'quality',
|
default = ('hidden', 'hasvid', 'ie_pref', 'lang', 'quality',
|
||||||
'res', 'fps', 'codec:vp9.2', 'size', 'br', 'asr',
|
'res', 'fps', 'codec:vp9.2', 'size', 'br', 'asr',
|
||||||
'proto', 'ext', 'has_audio', 'source', 'format_id') # These must not be aliases
|
'proto', 'ext', 'hasaud', 'source', 'format_id') # These must not be aliases
|
||||||
|
ytdl_default = ('hasaud', 'quality', 'tbr', 'filesize', 'vbr',
|
||||||
|
'height', 'width', 'proto', 'vext', 'abr', 'aext',
|
||||||
|
'fps', 'fs_approx', 'source', 'format_id')
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
'vcodec': {'type': 'ordered', 'regex': True,
|
'vcodec': {'type': 'ordered', 'regex': True,
|
||||||
@@ -1439,7 +1495,7 @@ class InfoExtractor(object):
|
|||||||
'hasvid': {'priority': True, 'field': 'vcodec', 'type': 'boolean', 'not_in_list': ('none',)},
|
'hasvid': {'priority': True, 'field': 'vcodec', 'type': 'boolean', 'not_in_list': ('none',)},
|
||||||
'hasaud': {'field': 'acodec', 'type': 'boolean', 'not_in_list': ('none',)},
|
'hasaud': {'field': 'acodec', 'type': 'boolean', 'not_in_list': ('none',)},
|
||||||
'lang': {'priority': True, 'convert': 'ignore', 'field': 'language_preference'},
|
'lang': {'priority': True, 'convert': 'ignore', 'field': 'language_preference'},
|
||||||
'quality': {'convert': 'float_none'},
|
'quality': {'convert': 'float_none', 'default': -1},
|
||||||
'filesize': {'convert': 'bytes'},
|
'filesize': {'convert': 'bytes'},
|
||||||
'fs_approx': {'convert': 'bytes', 'field': 'filesize_approx'},
|
'fs_approx': {'convert': 'bytes', 'field': 'filesize_approx'},
|
||||||
'id': {'convert': 'string', 'field': 'format_id'},
|
'id': {'convert': 'string', 'field': 'format_id'},
|
||||||
@@ -1591,12 +1647,12 @@ class InfoExtractor(object):
|
|||||||
else limits[0] if has_limit and not has_multiple_limits
|
else limits[0] if has_limit and not has_multiple_limits
|
||||||
else None)
|
else None)
|
||||||
|
|
||||||
def print_verbose_info(self, to_screen):
|
def print_verbose_info(self, write_debug):
|
||||||
if self._sort_user:
|
if self._sort_user:
|
||||||
to_screen('[debug] Sort order given by user: %s' % ','.join(self._sort_user))
|
write_debug('Sort order given by user: %s' % ', '.join(self._sort_user))
|
||||||
if self._sort_extractor:
|
if self._sort_extractor:
|
||||||
to_screen('[debug] Sort order given by extractor: %s' % ', '.join(self._sort_extractor))
|
write_debug('Sort order given by extractor: %s' % ', '.join(self._sort_extractor))
|
||||||
to_screen('[debug] Formats sorted by: %s' % ', '.join(['%s%s%s' % (
|
write_debug('Formats sorted by: %s' % ', '.join(['%s%s%s' % (
|
||||||
'+' if self._get_field_setting(field, 'reverse') else '', field,
|
'+' if self._get_field_setting(field, 'reverse') else '', field,
|
||||||
'%s%s(%s)' % ('~' if self._get_field_setting(field, 'closest') else ':',
|
'%s%s(%s)' % ('~' if self._get_field_setting(field, 'closest') else ':',
|
||||||
self._get_field_setting(field, 'limit_text'),
|
self._get_field_setting(field, 'limit_text'),
|
||||||
@@ -1621,7 +1677,7 @@ class InfoExtractor(object):
|
|||||||
value = self._resolve_field_value(field, value, True)
|
value = self._resolve_field_value(field, value, True)
|
||||||
|
|
||||||
# try to convert to number
|
# try to convert to number
|
||||||
val_num = float_or_none(value)
|
val_num = float_or_none(value, default=self._get_field_setting(field, 'default'))
|
||||||
is_num = self._get_field_setting(field, 'convert') != 'string' and val_num is not None
|
is_num = self._get_field_setting(field, 'convert') != 'string' and val_num is not None
|
||||||
if is_num:
|
if is_num:
|
||||||
value = val_num
|
value = val_num
|
||||||
@@ -1682,13 +1738,13 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _sort_formats(self, formats, field_preference=[]):
|
def _sort_formats(self, formats, field_preference=[]):
|
||||||
if not formats:
|
if not formats:
|
||||||
if self._downloader.params.get('ignore_no_formats_error'):
|
if self.get_param('ignore_no_formats_error'):
|
||||||
return
|
return
|
||||||
raise ExtractorError('No video formats found')
|
raise ExtractorError('No video formats found')
|
||||||
format_sort = self.FormatSort() # params and to_screen are taken from the downloader
|
format_sort = self.FormatSort() # params and to_screen are taken from the downloader
|
||||||
format_sort.evaluate_params(self._downloader.params, field_preference)
|
format_sort.evaluate_params(self._downloader.params, field_preference)
|
||||||
if self._downloader.params.get('verbose', False):
|
if self.get_param('verbose', False):
|
||||||
format_sort.print_verbose_info(self._downloader.to_screen)
|
format_sort.print_verbose_info(self._downloader.write_debug)
|
||||||
formats.sort(key=lambda f: format_sort.calculate_preference(f))
|
formats.sort(key=lambda f: format_sort.calculate_preference(f))
|
||||||
|
|
||||||
def _check_formats(self, formats, video_id):
|
def _check_formats(self, formats, video_id):
|
||||||
@@ -1727,7 +1783,7 @@ class InfoExtractor(object):
|
|||||||
""" Either "http:" or "https:", depending on the user's preferences """
|
""" Either "http:" or "https:", depending on the user's preferences """
|
||||||
return (
|
return (
|
||||||
'http:'
|
'http:'
|
||||||
if self._downloader.params.get('prefer_insecure', False)
|
if self.get_param('prefer_insecure', False)
|
||||||
else 'https:')
|
else 'https:')
|
||||||
|
|
||||||
def _proto_relative_url(self, url, scheme=None):
|
def _proto_relative_url(self, url, scheme=None):
|
||||||
@@ -1879,48 +1935,62 @@ class InfoExtractor(object):
|
|||||||
'format_note': 'Quality selection URL',
|
'format_note': 'Quality selection URL',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_m3u8_formats(self, m3u8_url, video_id, ext=None,
|
def _extract_m3u8_formats(self, *args, **kwargs):
|
||||||
entry_protocol='m3u8', preference=None, quality=None,
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(*args, **kwargs)
|
||||||
m3u8_id=None, note=None, errnote=None,
|
if subs:
|
||||||
fatal=True, live=False, data=None, headers={},
|
self.report_warning(bug_reports_message(
|
||||||
query={}):
|
"Ignoring subtitle tracks found in the HLS manifest; "
|
||||||
|
"if any subtitle tracks are missing,"
|
||||||
|
))
|
||||||
|
return fmts
|
||||||
|
|
||||||
|
def _extract_m3u8_formats_and_subtitles(
|
||||||
|
self, m3u8_url, video_id, ext=None, entry_protocol='m3u8_native',
|
||||||
|
preference=None, quality=None, m3u8_id=None, note=None,
|
||||||
|
errnote=None, fatal=True, live=False, data=None, headers={},
|
||||||
|
query={}):
|
||||||
|
|
||||||
res = self._download_webpage_handle(
|
res = self._download_webpage_handle(
|
||||||
m3u8_url, video_id,
|
m3u8_url, video_id,
|
||||||
note=note or 'Downloading m3u8 information',
|
note='Downloading m3u8 information' if note is None else note,
|
||||||
errnote=errnote or 'Failed to download m3u8 information',
|
errnote='Failed to download m3u8 information' if errnote is None else errnote,
|
||||||
fatal=fatal, data=data, headers=headers, query=query)
|
fatal=fatal, data=data, headers=headers, query=query)
|
||||||
|
|
||||||
if res is False:
|
if res is False:
|
||||||
return []
|
return [], {}
|
||||||
|
|
||||||
m3u8_doc, urlh = res
|
m3u8_doc, urlh = res
|
||||||
m3u8_url = urlh.geturl()
|
m3u8_url = urlh.geturl()
|
||||||
|
|
||||||
return self._parse_m3u8_formats(
|
return self._parse_m3u8_formats_and_subtitles(
|
||||||
m3u8_doc, m3u8_url, ext=ext, entry_protocol=entry_protocol,
|
m3u8_doc, m3u8_url, ext=ext, entry_protocol=entry_protocol,
|
||||||
preference=preference, quality=quality, m3u8_id=m3u8_id,
|
preference=preference, quality=quality, m3u8_id=m3u8_id,
|
||||||
note=note, errnote=errnote, fatal=fatal, live=live, data=data,
|
note=note, errnote=errnote, fatal=fatal, live=live, data=data,
|
||||||
headers=headers, query=query, video_id=video_id)
|
headers=headers, query=query, video_id=video_id)
|
||||||
|
|
||||||
def _parse_m3u8_formats(self, m3u8_doc, m3u8_url, ext=None,
|
def _parse_m3u8_formats_and_subtitles(
|
||||||
entry_protocol='m3u8', preference=None, quality=None,
|
self, m3u8_doc, m3u8_url, ext=None, entry_protocol='m3u8_native',
|
||||||
m3u8_id=None, live=False, note=None, errnote=None,
|
preference=None, quality=None, m3u8_id=None, live=False, note=None,
|
||||||
fatal=True, data=None, headers={}, query={}, video_id=None):
|
errnote=None, fatal=True, data=None, headers={}, query={},
|
||||||
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
|
video_id=None):
|
||||||
return []
|
|
||||||
|
|
||||||
if (not self._downloader.params.get('allow_unplayable_formats')
|
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
|
||||||
|
return [], {}
|
||||||
|
|
||||||
|
if (not self.get_param('allow_unplayable_formats')
|
||||||
and re.search(r'#EXT-X-SESSION-KEY:.*?URI="skd://', m3u8_doc)): # Apple FairPlay
|
and re.search(r'#EXT-X-SESSION-KEY:.*?URI="skd://', m3u8_doc)): # Apple FairPlay
|
||||||
return []
|
return [], {}
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
|
||||||
format_url = lambda u: (
|
format_url = lambda u: (
|
||||||
u
|
u
|
||||||
if re.match(r'^https?://', u)
|
if re.match(r'^https?://', u)
|
||||||
else compat_urlparse.urljoin(m3u8_url, u))
|
else compat_urlparse.urljoin(m3u8_url, u))
|
||||||
|
|
||||||
split_discontinuity = self._downloader.params.get('hls_split_discontinuity', False)
|
split_discontinuity = self.get_param('hls_split_discontinuity', False)
|
||||||
|
|
||||||
# References:
|
# References:
|
||||||
# 1. https://tools.ietf.org/html/draft-pantos-http-live-streaming-21
|
# 1. https://tools.ietf.org/html/draft-pantos-http-live-streaming-21
|
||||||
@@ -2001,7 +2071,7 @@ class InfoExtractor(object):
|
|||||||
}
|
}
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
|
|
||||||
return formats
|
return formats, subtitles
|
||||||
|
|
||||||
groups = {}
|
groups = {}
|
||||||
last_stream_inf = {}
|
last_stream_inf = {}
|
||||||
@@ -2013,6 +2083,27 @@ class InfoExtractor(object):
|
|||||||
if not (media_type and group_id and name):
|
if not (media_type and group_id and name):
|
||||||
return
|
return
|
||||||
groups.setdefault(group_id, []).append(media)
|
groups.setdefault(group_id, []).append(media)
|
||||||
|
# <https://tools.ietf.org/html/rfc8216#section-4.3.4.1>
|
||||||
|
if media_type == 'SUBTITLES':
|
||||||
|
# According to RFC 8216 §4.3.4.2.1, URI is REQUIRED in the
|
||||||
|
# EXT-X-MEDIA tag if the media type is SUBTITLES.
|
||||||
|
# However, lack of URI has been spotted in the wild.
|
||||||
|
# e.g. NebulaIE; see https://github.com/yt-dlp/yt-dlp/issues/339
|
||||||
|
if not media.get('URI'):
|
||||||
|
return
|
||||||
|
url = format_url(media['URI'])
|
||||||
|
sub_info = {
|
||||||
|
'url': url,
|
||||||
|
'ext': determine_ext(url),
|
||||||
|
}
|
||||||
|
if sub_info['ext'] == 'm3u8':
|
||||||
|
# Per RFC 8216 §3.1, the only possible subtitle format m3u8
|
||||||
|
# files may contain is WebVTT:
|
||||||
|
# <https://tools.ietf.org/html/rfc8216#section-3.1>
|
||||||
|
sub_info['ext'] = 'vtt'
|
||||||
|
sub_info['protocol'] = 'm3u8_native'
|
||||||
|
lang = media.get('LANGUAGE') or 'und'
|
||||||
|
subtitles.setdefault(lang, []).append(sub_info)
|
||||||
if media_type not in ('VIDEO', 'AUDIO'):
|
if media_type not in ('VIDEO', 'AUDIO'):
|
||||||
return
|
return
|
||||||
media_url = media.get('URI')
|
media_url = media.get('URI')
|
||||||
@@ -2160,7 +2251,7 @@ class InfoExtractor(object):
|
|||||||
formats.append(http_f)
|
formats.append(http_f)
|
||||||
|
|
||||||
last_stream_inf = {}
|
last_stream_inf = {}
|
||||||
return formats
|
return formats, subtitles
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _xpath_ns(path, namespace=None):
|
def _xpath_ns(path, namespace=None):
|
||||||
@@ -2403,23 +2494,44 @@ class InfoExtractor(object):
|
|||||||
})
|
})
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def _extract_mpd_formats(self, mpd_url, video_id, mpd_id=None, note=None, errnote=None, fatal=True, data=None, headers={}, query={}):
|
def _extract_mpd_formats(self, *args, **kwargs):
|
||||||
|
fmts, subs = self._extract_mpd_formats_and_subtitles(*args, **kwargs)
|
||||||
|
if subs:
|
||||||
|
self.report_warning(bug_reports_message(
|
||||||
|
"Ignoring subtitle tracks found in the DASH manifest; "
|
||||||
|
"if any subtitle tracks are missing,"
|
||||||
|
))
|
||||||
|
return fmts
|
||||||
|
|
||||||
|
def _extract_mpd_formats_and_subtitles(
|
||||||
|
self, mpd_url, video_id, mpd_id=None, note=None, errnote=None,
|
||||||
|
fatal=True, data=None, headers={}, query={}):
|
||||||
res = self._download_xml_handle(
|
res = self._download_xml_handle(
|
||||||
mpd_url, video_id,
|
mpd_url, video_id,
|
||||||
note=note or 'Downloading MPD manifest',
|
note='Downloading MPD manifest' if note is None else note,
|
||||||
errnote=errnote or 'Failed to download MPD manifest',
|
errnote='Failed to download MPD manifest' if errnote is None else errnote,
|
||||||
fatal=fatal, data=data, headers=headers, query=query)
|
fatal=fatal, data=data, headers=headers, query=query)
|
||||||
if res is False:
|
if res is False:
|
||||||
return []
|
return [], {}
|
||||||
mpd_doc, urlh = res
|
mpd_doc, urlh = res
|
||||||
if mpd_doc is None:
|
if mpd_doc is None:
|
||||||
return []
|
return [], {}
|
||||||
mpd_base_url = base_url(urlh.geturl())
|
mpd_base_url = base_url(urlh.geturl())
|
||||||
|
|
||||||
return self._parse_mpd_formats(
|
return self._parse_mpd_formats_and_subtitles(
|
||||||
mpd_doc, mpd_id, mpd_base_url, mpd_url)
|
mpd_doc, mpd_id, mpd_base_url, mpd_url)
|
||||||
|
|
||||||
def _parse_mpd_formats(self, mpd_doc, mpd_id=None, mpd_base_url='', mpd_url=None):
|
def _parse_mpd_formats(self, *args, **kwargs):
|
||||||
|
fmts, subs = self._parse_mpd_formats_and_subtitles(*args, **kwargs)
|
||||||
|
if subs:
|
||||||
|
self.report_warning(bug_reports_message(
|
||||||
|
"Ignoring subtitle tracks found in the DASH manifest; "
|
||||||
|
"if any subtitle tracks are missing,"
|
||||||
|
))
|
||||||
|
return fmts
|
||||||
|
|
||||||
|
def _parse_mpd_formats_and_subtitles(
|
||||||
|
self, mpd_doc, mpd_id=None, mpd_base_url='', mpd_url=None):
|
||||||
"""
|
"""
|
||||||
Parse formats from MPD manifest.
|
Parse formats from MPD manifest.
|
||||||
References:
|
References:
|
||||||
@@ -2427,9 +2539,9 @@ class InfoExtractor(object):
|
|||||||
http://standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip
|
http://standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip
|
||||||
2. https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP
|
2. https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP
|
||||||
"""
|
"""
|
||||||
if not self._downloader.params.get('dynamic_mpd', True):
|
if not self.get_param('dynamic_mpd', True):
|
||||||
if mpd_doc.get('type') == 'dynamic':
|
if mpd_doc.get('type') == 'dynamic':
|
||||||
return []
|
return [], {}
|
||||||
|
|
||||||
namespace = self._search_regex(r'(?i)^{([^}]+)?}MPD$', mpd_doc.tag, 'namespace', default=None)
|
namespace = self._search_regex(r'(?i)^{([^}]+)?}MPD$', mpd_doc.tag, 'namespace', default=None)
|
||||||
|
|
||||||
@@ -2497,10 +2609,11 @@ class InfoExtractor(object):
|
|||||||
extract_Initialization(segment_template)
|
extract_Initialization(segment_template)
|
||||||
return ms_info
|
return ms_info
|
||||||
|
|
||||||
skip_unplayable = not self._downloader.params.get('allow_unplayable_formats')
|
skip_unplayable = not self.get_param('allow_unplayable_formats')
|
||||||
|
|
||||||
mpd_duration = parse_duration(mpd_doc.get('mediaPresentationDuration'))
|
mpd_duration = parse_duration(mpd_doc.get('mediaPresentationDuration'))
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for period in mpd_doc.findall(_add_ns('Period')):
|
for period in mpd_doc.findall(_add_ns('Period')):
|
||||||
period_duration = parse_duration(period.get('duration')) or mpd_duration
|
period_duration = parse_duration(period.get('duration')) or mpd_duration
|
||||||
period_ms_info = extract_multisegment_info(period, {
|
period_ms_info = extract_multisegment_info(period, {
|
||||||
@@ -2518,11 +2631,9 @@ class InfoExtractor(object):
|
|||||||
representation_attrib.update(representation.attrib)
|
representation_attrib.update(representation.attrib)
|
||||||
# According to [1, 5.3.7.2, Table 9, page 41], @mimeType is mandatory
|
# According to [1, 5.3.7.2, Table 9, page 41], @mimeType is mandatory
|
||||||
mime_type = representation_attrib['mimeType']
|
mime_type = representation_attrib['mimeType']
|
||||||
content_type = mime_type.split('/')[0]
|
content_type = representation_attrib.get('contentType', mime_type.split('/')[0])
|
||||||
if content_type == 'text':
|
|
||||||
# TODO implement WebVTT downloading
|
if content_type in ('video', 'audio', 'text'):
|
||||||
pass
|
|
||||||
elif content_type in ('video', 'audio'):
|
|
||||||
base_url = ''
|
base_url = ''
|
||||||
for element in (representation, adaptation_set, period, mpd_doc):
|
for element in (representation, adaptation_set, period, mpd_doc):
|
||||||
base_url_e = element.find(_add_ns('BaseURL'))
|
base_url_e = element.find(_add_ns('BaseURL'))
|
||||||
@@ -2539,21 +2650,28 @@ class InfoExtractor(object):
|
|||||||
url_el = representation.find(_add_ns('BaseURL'))
|
url_el = representation.find(_add_ns('BaseURL'))
|
||||||
filesize = int_or_none(url_el.attrib.get('{http://youtube.com/yt/2012/10/10}contentLength') if url_el is not None else None)
|
filesize = int_or_none(url_el.attrib.get('{http://youtube.com/yt/2012/10/10}contentLength') if url_el is not None else None)
|
||||||
bandwidth = int_or_none(representation_attrib.get('bandwidth'))
|
bandwidth = int_or_none(representation_attrib.get('bandwidth'))
|
||||||
f = {
|
if content_type in ('video', 'audio'):
|
||||||
'format_id': '%s-%s' % (mpd_id, representation_id) if mpd_id else representation_id,
|
f = {
|
||||||
'manifest_url': mpd_url,
|
'format_id': '%s-%s' % (mpd_id, representation_id) if mpd_id else representation_id,
|
||||||
'ext': mimetype2ext(mime_type),
|
'manifest_url': mpd_url,
|
||||||
'width': int_or_none(representation_attrib.get('width')),
|
'ext': mimetype2ext(mime_type),
|
||||||
'height': int_or_none(representation_attrib.get('height')),
|
'width': int_or_none(representation_attrib.get('width')),
|
||||||
'tbr': float_or_none(bandwidth, 1000),
|
'height': int_or_none(representation_attrib.get('height')),
|
||||||
'asr': int_or_none(representation_attrib.get('audioSamplingRate')),
|
'tbr': float_or_none(bandwidth, 1000),
|
||||||
'fps': int_or_none(representation_attrib.get('frameRate')),
|
'asr': int_or_none(representation_attrib.get('audioSamplingRate')),
|
||||||
'language': lang if lang not in ('mul', 'und', 'zxx', 'mis') else None,
|
'fps': int_or_none(representation_attrib.get('frameRate')),
|
||||||
'format_note': 'DASH %s' % content_type,
|
'language': lang if lang not in ('mul', 'und', 'zxx', 'mis') else None,
|
||||||
'filesize': filesize,
|
'format_note': 'DASH %s' % content_type,
|
||||||
'container': mimetype2ext(mime_type) + '_dash',
|
'filesize': filesize,
|
||||||
}
|
'container': mimetype2ext(mime_type) + '_dash',
|
||||||
f.update(parse_codecs(representation_attrib.get('codecs')))
|
}
|
||||||
|
f.update(parse_codecs(representation_attrib.get('codecs')))
|
||||||
|
elif content_type == 'text':
|
||||||
|
f = {
|
||||||
|
'ext': mimetype2ext(mime_type),
|
||||||
|
'manifest_url': mpd_url,
|
||||||
|
'filesize': filesize,
|
||||||
|
}
|
||||||
representation_ms_info = extract_multisegment_info(representation, adaption_set_ms_info)
|
representation_ms_info = extract_multisegment_info(representation, adaption_set_ms_info)
|
||||||
|
|
||||||
def prepare_template(template_name, identifiers):
|
def prepare_template(template_name, identifiers):
|
||||||
@@ -2700,26 +2818,38 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
# Assuming direct URL to unfragmented media.
|
# Assuming direct URL to unfragmented media.
|
||||||
f['url'] = base_url
|
f['url'] = base_url
|
||||||
formats.append(f)
|
if content_type in ('video', 'audio'):
|
||||||
|
formats.append(f)
|
||||||
|
elif content_type == 'text':
|
||||||
|
subtitles.setdefault(lang or 'und', []).append(f)
|
||||||
else:
|
else:
|
||||||
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
||||||
return formats
|
return formats, subtitles
|
||||||
|
|
||||||
def _extract_ism_formats(self, ism_url, video_id, ism_id=None, note=None, errnote=None, fatal=True, data=None, headers={}, query={}):
|
def _extract_ism_formats(self, *args, **kwargs):
|
||||||
|
fmts, subs = self._extract_ism_formats_and_subtitles(*args, **kwargs)
|
||||||
|
if subs:
|
||||||
|
self.report_warning(bug_reports_message(
|
||||||
|
"Ignoring subtitle tracks found in the ISM manifest; "
|
||||||
|
"if any subtitle tracks are missing,"
|
||||||
|
))
|
||||||
|
return fmts
|
||||||
|
|
||||||
|
def _extract_ism_formats_and_subtitles(self, ism_url, video_id, ism_id=None, note=None, errnote=None, fatal=True, data=None, headers={}, query={}):
|
||||||
res = self._download_xml_handle(
|
res = self._download_xml_handle(
|
||||||
ism_url, video_id,
|
ism_url, video_id,
|
||||||
note=note or 'Downloading ISM manifest',
|
note='Downloading ISM manifest' if note is None else note,
|
||||||
errnote=errnote or 'Failed to download ISM manifest',
|
errnote='Failed to download ISM manifest' if errnote is None else errnote,
|
||||||
fatal=fatal, data=data, headers=headers, query=query)
|
fatal=fatal, data=data, headers=headers, query=query)
|
||||||
if res is False:
|
if res is False:
|
||||||
return []
|
return [], {}
|
||||||
ism_doc, urlh = res
|
ism_doc, urlh = res
|
||||||
if ism_doc is None:
|
if ism_doc is None:
|
||||||
return []
|
return [], {}
|
||||||
|
|
||||||
return self._parse_ism_formats(ism_doc, urlh.geturl(), ism_id)
|
return self._parse_ism_formats_and_subtitles(ism_doc, urlh.geturl(), ism_id)
|
||||||
|
|
||||||
def _parse_ism_formats(self, ism_doc, ism_url, ism_id=None):
|
def _parse_ism_formats_and_subtitles(self, ism_doc, ism_url, ism_id=None):
|
||||||
"""
|
"""
|
||||||
Parse formats from ISM manifest.
|
Parse formats from ISM manifest.
|
||||||
References:
|
References:
|
||||||
@@ -2727,26 +2857,28 @@ class InfoExtractor(object):
|
|||||||
https://msdn.microsoft.com/en-us/library/ff469518.aspx
|
https://msdn.microsoft.com/en-us/library/ff469518.aspx
|
||||||
"""
|
"""
|
||||||
if ism_doc.get('IsLive') == 'TRUE':
|
if ism_doc.get('IsLive') == 'TRUE':
|
||||||
return []
|
return [], {}
|
||||||
if (not self._downloader.params.get('allow_unplayable_formats')
|
if (not self.get_param('allow_unplayable_formats')
|
||||||
and ism_doc.find('Protection') is not None):
|
and ism_doc.find('Protection') is not None):
|
||||||
return []
|
return [], {}
|
||||||
|
|
||||||
duration = int(ism_doc.attrib['Duration'])
|
duration = int(ism_doc.attrib['Duration'])
|
||||||
timescale = int_or_none(ism_doc.get('TimeScale')) or 10000000
|
timescale = int_or_none(ism_doc.get('TimeScale')) or 10000000
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for stream in ism_doc.findall('StreamIndex'):
|
for stream in ism_doc.findall('StreamIndex'):
|
||||||
stream_type = stream.get('Type')
|
stream_type = stream.get('Type')
|
||||||
if stream_type not in ('video', 'audio'):
|
if stream_type not in ('video', 'audio', 'text'):
|
||||||
continue
|
continue
|
||||||
url_pattern = stream.attrib['Url']
|
url_pattern = stream.attrib['Url']
|
||||||
stream_timescale = int_or_none(stream.get('TimeScale')) or timescale
|
stream_timescale = int_or_none(stream.get('TimeScale')) or timescale
|
||||||
stream_name = stream.get('Name')
|
stream_name = stream.get('Name')
|
||||||
|
stream_language = stream.get('Language', 'und')
|
||||||
for track in stream.findall('QualityLevel'):
|
for track in stream.findall('QualityLevel'):
|
||||||
fourcc = track.get('FourCC', 'AACL' if track.get('AudioTag') == '255' else None)
|
fourcc = track.get('FourCC', 'AACL' if track.get('AudioTag') == '255' else None)
|
||||||
# TODO: add support for WVC1 and WMAP
|
# TODO: add support for WVC1 and WMAP
|
||||||
if fourcc not in ('H264', 'AVC1', 'AACL'):
|
if fourcc not in ('H264', 'AVC1', 'AACL', 'TTML'):
|
||||||
self.report_warning('%s is not a supported codec' % fourcc)
|
self.report_warning('%s is not a supported codec' % fourcc)
|
||||||
continue
|
continue
|
||||||
tbr = int(track.attrib['Bitrate']) // 1000
|
tbr = int(track.attrib['Bitrate']) // 1000
|
||||||
@@ -2789,33 +2921,52 @@ class InfoExtractor(object):
|
|||||||
format_id.append(stream_name)
|
format_id.append(stream_name)
|
||||||
format_id.append(compat_str(tbr))
|
format_id.append(compat_str(tbr))
|
||||||
|
|
||||||
formats.append({
|
if stream_type == 'text':
|
||||||
'format_id': '-'.join(format_id),
|
subtitles.setdefault(stream_language, []).append({
|
||||||
'url': ism_url,
|
'ext': 'ismt',
|
||||||
'manifest_url': ism_url,
|
'protocol': 'ism',
|
||||||
'ext': 'ismv' if stream_type == 'video' else 'isma',
|
'url': ism_url,
|
||||||
'width': width,
|
'manifest_url': ism_url,
|
||||||
'height': height,
|
'fragments': fragments,
|
||||||
'tbr': tbr,
|
'_download_params': {
|
||||||
'asr': sampling_rate,
|
'stream_type': stream_type,
|
||||||
'vcodec': 'none' if stream_type == 'audio' else fourcc,
|
'duration': duration,
|
||||||
'acodec': 'none' if stream_type == 'video' else fourcc,
|
'timescale': stream_timescale,
|
||||||
'protocol': 'ism',
|
'fourcc': fourcc,
|
||||||
'fragments': fragments,
|
'language': stream_language,
|
||||||
'_download_params': {
|
'codec_private_data': track.get('CodecPrivateData'),
|
||||||
'duration': duration,
|
}
|
||||||
'timescale': stream_timescale,
|
})
|
||||||
'width': width or 0,
|
elif stream_type in ('video', 'audio'):
|
||||||
'height': height or 0,
|
formats.append({
|
||||||
'fourcc': fourcc,
|
'format_id': '-'.join(format_id),
|
||||||
'codec_private_data': track.get('CodecPrivateData'),
|
'url': ism_url,
|
||||||
'sampling_rate': sampling_rate,
|
'manifest_url': ism_url,
|
||||||
'channels': int_or_none(track.get('Channels', 2)),
|
'ext': 'ismv' if stream_type == 'video' else 'isma',
|
||||||
'bits_per_sample': int_or_none(track.get('BitsPerSample', 16)),
|
'width': width,
|
||||||
'nal_unit_length_field': int_or_none(track.get('NALUnitLengthField', 4)),
|
'height': height,
|
||||||
},
|
'tbr': tbr,
|
||||||
})
|
'asr': sampling_rate,
|
||||||
return formats
|
'vcodec': 'none' if stream_type == 'audio' else fourcc,
|
||||||
|
'acodec': 'none' if stream_type == 'video' else fourcc,
|
||||||
|
'protocol': 'ism',
|
||||||
|
'fragments': fragments,
|
||||||
|
'_download_params': {
|
||||||
|
'stream_type': stream_type,
|
||||||
|
'duration': duration,
|
||||||
|
'timescale': stream_timescale,
|
||||||
|
'width': width or 0,
|
||||||
|
'height': height or 0,
|
||||||
|
'fourcc': fourcc,
|
||||||
|
'language': stream_language,
|
||||||
|
'codec_private_data': track.get('CodecPrivateData'),
|
||||||
|
'sampling_rate': sampling_rate,
|
||||||
|
'channels': int_or_none(track.get('Channels', 2)),
|
||||||
|
'bits_per_sample': int_or_none(track.get('BitsPerSample', 16)),
|
||||||
|
'nal_unit_length_field': int_or_none(track.get('NALUnitLengthField', 4)),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return formats, subtitles
|
||||||
|
|
||||||
def _parse_html5_media_entries(self, base_url, webpage, video_id, m3u8_id=None, m3u8_entry_protocol='m3u8', mpd_id=None, preference=None, quality=None):
|
def _parse_html5_media_entries(self, base_url, webpage, video_id, m3u8_id=None, m3u8_entry_protocol='m3u8', mpd_id=None, preference=None, quality=None):
|
||||||
def absolute_url(item_url):
|
def absolute_url(item_url):
|
||||||
@@ -2940,7 +3091,16 @@ class InfoExtractor(object):
|
|||||||
entries.append(media_info)
|
entries.append(media_info)
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def _extract_akamai_formats(self, manifest_url, video_id, hosts={}):
|
def _extract_akamai_formats(self, *args, **kwargs):
|
||||||
|
fmts, subs = self._extract_akamai_formats_and_subtitles(*args, **kwargs)
|
||||||
|
if subs:
|
||||||
|
self.report_warning(bug_reports_message(
|
||||||
|
"Ignoring subtitle tracks found in the manifests; "
|
||||||
|
"if any subtitle tracks are missing,"
|
||||||
|
))
|
||||||
|
return fmts
|
||||||
|
|
||||||
|
def _extract_akamai_formats_and_subtitles(self, manifest_url, video_id, hosts={}):
|
||||||
signed = 'hdnea=' in manifest_url
|
signed = 'hdnea=' in manifest_url
|
||||||
if not signed:
|
if not signed:
|
||||||
# https://learn.akamai.com/en-us/webhelp/media-services-on-demand/stream-packaging-user-guide/GUID-BE6C0F73-1E06-483B-B0EA-57984B91B7F9.html
|
# https://learn.akamai.com/en-us/webhelp/media-services-on-demand/stream-packaging-user-guide/GUID-BE6C0F73-1E06-483B-B0EA-57984B91B7F9.html
|
||||||
@@ -2949,6 +3109,7 @@ class InfoExtractor(object):
|
|||||||
'', manifest_url).strip('?')
|
'', manifest_url).strip('?')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
|
|
||||||
hdcore_sign = 'hdcore=3.7.0'
|
hdcore_sign = 'hdcore=3.7.0'
|
||||||
f4m_url = re.sub(r'(https?://[^/]+)/i/', r'\1/z/', manifest_url).replace('/master.m3u8', '/manifest.f4m')
|
f4m_url = re.sub(r'(https?://[^/]+)/i/', r'\1/z/', manifest_url).replace('/master.m3u8', '/manifest.f4m')
|
||||||
@@ -2967,10 +3128,11 @@ class InfoExtractor(object):
|
|||||||
hls_host = hosts.get('hls')
|
hls_host = hosts.get('hls')
|
||||||
if hls_host:
|
if hls_host:
|
||||||
m3u8_url = re.sub(r'(https?://)[^/]+', r'\1' + hls_host, m3u8_url)
|
m3u8_url = re.sub(r'(https?://)[^/]+', r'\1' + hls_host, m3u8_url)
|
||||||
m3u8_formats = self._extract_m3u8_formats(
|
m3u8_formats, m3u8_subtitles = self._extract_m3u8_formats_and_subtitles(
|
||||||
m3u8_url, video_id, 'mp4', 'm3u8_native',
|
m3u8_url, video_id, 'mp4', 'm3u8_native',
|
||||||
m3u8_id='hls', fatal=False)
|
m3u8_id='hls', fatal=False)
|
||||||
formats.extend(m3u8_formats)
|
formats.extend(m3u8_formats)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, m3u8_subtitles)
|
||||||
|
|
||||||
http_host = hosts.get('http')
|
http_host = hosts.get('http')
|
||||||
if http_host and m3u8_formats and not signed:
|
if http_host and m3u8_formats and not signed:
|
||||||
@@ -2994,7 +3156,7 @@ class InfoExtractor(object):
|
|||||||
formats.append(http_f)
|
formats.append(http_f)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
return formats
|
return formats, subtitles
|
||||||
|
|
||||||
def _extract_wowza_formats(self, url, video_id, m3u8_entry_protocol='m3u8_native', skip_protocols=[]):
|
def _extract_wowza_formats(self, url, video_id, m3u8_entry_protocol='m3u8_native', skip_protocols=[]):
|
||||||
query = compat_urlparse.urlparse(url).query
|
query = compat_urlparse.urlparse(url).query
|
||||||
@@ -3301,8 +3463,8 @@ class InfoExtractor(object):
|
|||||||
return not any_restricted
|
return not any_restricted
|
||||||
|
|
||||||
def extract_subtitles(self, *args, **kwargs):
|
def extract_subtitles(self, *args, **kwargs):
|
||||||
if (self._downloader.params.get('writesubtitles', False)
|
if (self.get_param('writesubtitles', False)
|
||||||
or self._downloader.params.get('listsubtitles')):
|
or self.get_param('listsubtitles')):
|
||||||
return self._get_subtitles(*args, **kwargs)
|
return self._get_subtitles(*args, **kwargs)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -3319,16 +3481,26 @@ class InfoExtractor(object):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _merge_subtitles(cls, subtitle_dict1, subtitle_dict2):
|
def _merge_subtitles(cls, *dicts, **kwargs):
|
||||||
""" Merge two subtitle dictionaries, language by language. """
|
""" Merge subtitle dictionaries, language by language. """
|
||||||
ret = dict(subtitle_dict1)
|
|
||||||
for lang in subtitle_dict2:
|
target = (lambda target=None: target)(**kwargs)
|
||||||
ret[lang] = cls._merge_subtitle_items(subtitle_dict1.get(lang, []), subtitle_dict2[lang])
|
# The above lambda extracts the keyword argument 'target' from kwargs
|
||||||
return ret
|
# while ensuring there are no stray ones. When Python 2 support
|
||||||
|
# is dropped, remove it and change the function signature to:
|
||||||
|
#
|
||||||
|
# def _merge_subtitles(cls, *dicts, target=None):
|
||||||
|
|
||||||
|
if target is None:
|
||||||
|
target = {}
|
||||||
|
for d in dicts:
|
||||||
|
for lang, subs in d.items():
|
||||||
|
target[lang] = cls._merge_subtitle_items(target.get(lang, []), subs)
|
||||||
|
return target
|
||||||
|
|
||||||
def extract_automatic_captions(self, *args, **kwargs):
|
def extract_automatic_captions(self, *args, **kwargs):
|
||||||
if (self._downloader.params.get('writeautomaticsub', False)
|
if (self.get_param('writeautomaticsub', False)
|
||||||
or self._downloader.params.get('listsubtitles')):
|
or self.get_param('listsubtitles')):
|
||||||
return self._get_automatic_captions(*args, **kwargs)
|
return self._get_automatic_captions(*args, **kwargs)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -3336,9 +3508,9 @@ class InfoExtractor(object):
|
|||||||
raise NotImplementedError('This method must be implemented by subclasses')
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
def mark_watched(self, *args, **kwargs):
|
def mark_watched(self, *args, **kwargs):
|
||||||
if (self._downloader.params.get('mark_watched', False)
|
if (self.get_param('mark_watched', False)
|
||||||
and (self._get_login_info()[0] is not None
|
and (self._get_login_info()[0] is not None
|
||||||
or self._downloader.params.get('cookiefile') is not None)):
|
or self.get_param('cookiefile') is not None)):
|
||||||
self._mark_watched(*args, **kwargs)
|
self._mark_watched(*args, **kwargs)
|
||||||
|
|
||||||
def _mark_watched(self, *args, **kwargs):
|
def _mark_watched(self, *args, **kwargs):
|
||||||
@@ -3346,7 +3518,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def geo_verification_headers(self):
|
def geo_verification_headers(self):
|
||||||
headers = {}
|
headers = {}
|
||||||
geo_verification_proxy = self._downloader.params.get('geo_verification_proxy')
|
geo_verification_proxy = self.get_param('geo_verification_proxy')
|
||||||
if geo_verification_proxy:
|
if geo_verification_proxy:
|
||||||
headers['Ytdl-request-proxy'] = geo_verification_proxy
|
headers['Ytdl-request-proxy'] = geo_verification_proxy
|
||||||
return headers
|
return headers
|
||||||
@@ -3358,7 +3530,7 @@ class InfoExtractor(object):
|
|||||||
return compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0])
|
return compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _availability(is_private, needs_premium, needs_subscription, needs_auth, is_unlisted):
|
def _availability(is_private=None, needs_premium=None, needs_subscription=None, needs_auth=None, is_unlisted=None):
|
||||||
all_known = all(map(
|
all_known = all(map(
|
||||||
lambda x: x is not None,
|
lambda x: x is not None,
|
||||||
(is_private, needs_premium, needs_subscription, needs_auth, is_unlisted)))
|
(is_private, needs_premium, needs_subscription, needs_auth, is_unlisted)))
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ class CommonMistakesIE(InfoExtractor):
|
|||||||
'That doesn\'t make any sense. '
|
'That doesn\'t make any sense. '
|
||||||
'Simply remove the parameter in your command or configuration.'
|
'Simply remove the parameter in your command or configuration.'
|
||||||
) % url
|
) % url
|
||||||
if not self._downloader.params.get('verbose'):
|
if not self.get_param('verbose'):
|
||||||
msg += ' Add -v to the command line to see what arguments and configuration yt-dlp got.'
|
msg += ' Add -v to the command line to see what arguments and configuration yt-dlp has'
|
||||||
raise ExtractorError(msg, expected=True)
|
raise ExtractorError(msg, expected=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+158
-113
@@ -12,6 +12,7 @@ from ..utils import (
|
|||||||
determine_ext,
|
determine_ext,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
orderedSet,
|
||||||
parse_age_limit,
|
parse_age_limit,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
url_or_none,
|
url_or_none,
|
||||||
@@ -66,135 +67,179 @@ class CrackleIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _download_json(self, url, *args, **kwargs):
|
||||||
|
# Authorization generation algorithm is reverse engineered from:
|
||||||
|
# https://www.sonycrackle.com/static/js/main.ea93451f.chunk.js
|
||||||
|
timestamp = time.strftime('%Y%m%d%H%M', time.gmtime())
|
||||||
|
h = hmac.new(b'IGSLUQCBDFHEOIFM', '|'.join([url, timestamp]).encode(), hashlib.sha1).hexdigest().upper()
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': '|'.join([h, timestamp, '117', '1']),
|
||||||
|
}
|
||||||
|
return InfoExtractor._download_json(self, url, *args, headers=headers, **kwargs)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
country_code = self._downloader.params.get('geo_bypass_country', None)
|
geo_bypass_country = self.get_param('geo_bypass_country', None)
|
||||||
countries = [country_code] if country_code else (
|
countries = orderedSet((geo_bypass_country, 'US', 'AU', 'CA', 'AS', 'FM', 'GU', 'MP', 'PR', 'PW', 'MH', 'VI', ''))
|
||||||
'US', 'AU', 'CA', 'AS', 'FM', 'GU', 'MP', 'PR', 'PW', 'MH', 'VI')
|
num_countries, num = len(countries) - 1, 0
|
||||||
|
|
||||||
last_e = None
|
media = {}
|
||||||
|
for num, country in enumerate(countries):
|
||||||
|
if num == 1: # start hard-coded list
|
||||||
|
self.report_warning('%s. Trying with a list of known countries' % (
|
||||||
|
'Unable to obtain video formats from %s API' % geo_bypass_country if geo_bypass_country
|
||||||
|
else 'No country code was given using --geo-bypass-country'))
|
||||||
|
elif num == num_countries: # end of list
|
||||||
|
geo_info = self._download_json(
|
||||||
|
'https://web-api-us.crackle.com/Service.svc/geo/country',
|
||||||
|
video_id, fatal=False, note='Downloading geo-location information from crackle API',
|
||||||
|
errnote='Unable to fetch geo-location information from crackle') or {}
|
||||||
|
country = geo_info.get('CountryCode')
|
||||||
|
if country is None:
|
||||||
|
continue
|
||||||
|
self.to_screen('%s identified country as %s' % (self.IE_NAME, country))
|
||||||
|
if country in countries:
|
||||||
|
self.to_screen('Downloading from %s API was already attempted. Skipping...' % country)
|
||||||
|
continue
|
||||||
|
|
||||||
for country in countries:
|
if country is None:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
# Authorization generation algorithm is reverse engineered from:
|
|
||||||
# https://www.sonycrackle.com/static/js/main.ea93451f.chunk.js
|
|
||||||
media_detail_url = 'https://web-api-us.crackle.com/Service.svc/details/media/%s/%s?disableProtocols=true' % (video_id, country)
|
|
||||||
timestamp = time.strftime('%Y%m%d%H%M', time.gmtime())
|
|
||||||
h = hmac.new(b'IGSLUQCBDFHEOIFM', '|'.join([media_detail_url, timestamp]).encode(), hashlib.sha1).hexdigest().upper()
|
|
||||||
media = self._download_json(
|
media = self._download_json(
|
||||||
media_detail_url, video_id, 'Downloading media JSON as %s' % country,
|
'https://web-api-us.crackle.com/Service.svc/details/media/%s/%s?disableProtocols=true' % (video_id, country),
|
||||||
'Unable to download media JSON', headers={
|
video_id, note='Downloading media JSON from %s API' % country,
|
||||||
'Accept': 'application/json',
|
errnote='Unable to download media JSON')
|
||||||
'Authorization': '|'.join([h, timestamp, '117', '1']),
|
|
||||||
})
|
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
# 401 means geo restriction, trying next country
|
# 401 means geo restriction, trying next country
|
||||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
|
||||||
last_e = e
|
|
||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
|
|
||||||
media_urls = media.get('MediaURLs')
|
status = media.get('status')
|
||||||
if not media_urls or not isinstance(media_urls, list):
|
if status.get('messageCode') != '0':
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s said: %s %s - %s' % (
|
||||||
|
self.IE_NAME, status.get('messageCodeDescription'), status.get('messageCode'), status.get('message')),
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
# Found video formats
|
||||||
|
if isinstance(media.get('MediaURLs'), list):
|
||||||
|
break
|
||||||
|
|
||||||
|
ignore_no_formats = self.get_param('ignore_no_formats_error')
|
||||||
|
allow_unplayable_formats = self.get_param('allow_unplayable_formats')
|
||||||
|
|
||||||
|
if not media or (not media.get('MediaURLs') and not ignore_no_formats):
|
||||||
|
raise ExtractorError(
|
||||||
|
'Unable to access the crackle API. Try passing your country code '
|
||||||
|
'to --geo-bypass-country. If it still does not work and the '
|
||||||
|
'video is available in your country')
|
||||||
|
title = media['Title']
|
||||||
|
|
||||||
|
formats, subtitles = [], {}
|
||||||
|
has_drm = False
|
||||||
|
for e in media.get('MediaURLs') or []:
|
||||||
|
if e.get('UseDRM'):
|
||||||
|
has_drm = True
|
||||||
|
if not allow_unplayable_formats:
|
||||||
|
continue
|
||||||
|
format_url = url_or_none(e.get('Path'))
|
||||||
|
if not format_url:
|
||||||
continue
|
continue
|
||||||
|
ext = determine_ext(format_url)
|
||||||
title = media['Title']
|
if ext == 'm3u8':
|
||||||
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
formats = []
|
format_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
for e in media['MediaURLs']:
|
m3u8_id='hls', fatal=False)
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and e.get('UseDRM') is True:
|
formats.extend(fmts)
|
||||||
continue
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
format_url = url_or_none(e.get('Path'))
|
elif ext == 'mpd':
|
||||||
if not format_url:
|
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||||
continue
|
format_url, video_id, mpd_id='dash', fatal=False)
|
||||||
ext = determine_ext(format_url)
|
formats.extend(fmts)
|
||||||
if ext == 'm3u8':
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
formats.extend(self._extract_m3u8_formats(
|
elif format_url.endswith('.ism/Manifest'):
|
||||||
format_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
fmts, subs = self._extract_ism_formats_and_subtitles(
|
||||||
m3u8_id='hls', fatal=False))
|
format_url, video_id, ism_id='mss', fatal=False)
|
||||||
elif ext == 'mpd':
|
formats.extend(fmts)
|
||||||
formats.extend(self._extract_mpd_formats(
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
format_url, video_id, mpd_id='dash', fatal=False))
|
|
||||||
elif format_url.endswith('.ism/Manifest'):
|
|
||||||
formats.extend(self._extract_ism_formats(
|
|
||||||
format_url, video_id, ism_id='mss', fatal=False))
|
|
||||||
else:
|
|
||||||
mfs_path = e.get('Type')
|
|
||||||
mfs_info = self._MEDIA_FILE_SLOTS.get(mfs_path)
|
|
||||||
if not mfs_info:
|
|
||||||
continue
|
|
||||||
formats.append({
|
|
||||||
'url': format_url,
|
|
||||||
'format_id': 'http-' + mfs_path.split('.')[0],
|
|
||||||
'width': mfs_info['width'],
|
|
||||||
'height': mfs_info['height'],
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
description = media.get('Description')
|
|
||||||
duration = int_or_none(media.get(
|
|
||||||
'DurationInSeconds')) or parse_duration(media.get('Duration'))
|
|
||||||
view_count = int_or_none(media.get('CountViews'))
|
|
||||||
average_rating = float_or_none(media.get('UserRating'))
|
|
||||||
age_limit = parse_age_limit(media.get('Rating'))
|
|
||||||
genre = media.get('Genre')
|
|
||||||
release_year = int_or_none(media.get('ReleaseYear'))
|
|
||||||
creator = media.get('Directors')
|
|
||||||
artist = media.get('Cast')
|
|
||||||
|
|
||||||
if media.get('MediaTypeDisplayValue') == 'Full Episode':
|
|
||||||
series = media.get('ShowName')
|
|
||||||
episode = title
|
|
||||||
season_number = int_or_none(media.get('Season'))
|
|
||||||
episode_number = int_or_none(media.get('Episode'))
|
|
||||||
else:
|
else:
|
||||||
series = episode = season_number = episode_number = None
|
mfs_path = e.get('Type')
|
||||||
|
mfs_info = self._MEDIA_FILE_SLOTS.get(mfs_path)
|
||||||
|
if not mfs_info:
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'url': format_url,
|
||||||
|
'format_id': 'http-' + mfs_path.split('.')[0],
|
||||||
|
'width': mfs_info['width'],
|
||||||
|
'height': mfs_info['height'],
|
||||||
|
})
|
||||||
|
if not formats and has_drm and not ignore_no_formats:
|
||||||
|
raise ExtractorError('The video is DRM protected', expected=True)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
description = media.get('Description')
|
||||||
cc_files = media.get('ClosedCaptionFiles')
|
duration = int_or_none(media.get(
|
||||||
if isinstance(cc_files, list):
|
'DurationInSeconds')) or parse_duration(media.get('Duration'))
|
||||||
for cc_file in cc_files:
|
view_count = int_or_none(media.get('CountViews'))
|
||||||
if not isinstance(cc_file, dict):
|
average_rating = float_or_none(media.get('UserRating'))
|
||||||
continue
|
age_limit = parse_age_limit(media.get('Rating'))
|
||||||
cc_url = url_or_none(cc_file.get('Path'))
|
genre = media.get('Genre')
|
||||||
if not cc_url:
|
release_year = int_or_none(media.get('ReleaseYear'))
|
||||||
continue
|
creator = media.get('Directors')
|
||||||
lang = cc_file.get('Locale') or 'en'
|
artist = media.get('Cast')
|
||||||
subtitles.setdefault(lang, []).append({'url': cc_url})
|
|
||||||
|
|
||||||
thumbnails = []
|
if media.get('MediaTypeDisplayValue') == 'Full Episode':
|
||||||
images = media.get('Images')
|
series = media.get('ShowName')
|
||||||
if isinstance(images, list):
|
episode = title
|
||||||
for image_key, image_url in images.items():
|
season_number = int_or_none(media.get('Season'))
|
||||||
mobj = re.search(r'Img_(\d+)[xX](\d+)', image_key)
|
episode_number = int_or_none(media.get('Episode'))
|
||||||
if not mobj:
|
else:
|
||||||
continue
|
series = episode = season_number = episode_number = None
|
||||||
thumbnails.append({
|
|
||||||
'url': image_url,
|
|
||||||
'width': int(mobj.group(1)),
|
|
||||||
'height': int(mobj.group(2)),
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
cc_files = media.get('ClosedCaptionFiles')
|
||||||
'id': video_id,
|
if isinstance(cc_files, list):
|
||||||
'title': title,
|
for cc_file in cc_files:
|
||||||
'description': description,
|
if not isinstance(cc_file, dict):
|
||||||
'duration': duration,
|
continue
|
||||||
'view_count': view_count,
|
cc_url = url_or_none(cc_file.get('Path'))
|
||||||
'average_rating': average_rating,
|
if not cc_url:
|
||||||
'age_limit': age_limit,
|
continue
|
||||||
'genre': genre,
|
lang = cc_file.get('Locale') or 'en'
|
||||||
'creator': creator,
|
subtitles.setdefault(lang, []).append({'url': cc_url})
|
||||||
'artist': artist,
|
|
||||||
'release_year': release_year,
|
|
||||||
'series': series,
|
|
||||||
'episode': episode,
|
|
||||||
'season_number': season_number,
|
|
||||||
'episode_number': episode_number,
|
|
||||||
'thumbnails': thumbnails,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
|
||||||
raise last_e
|
thumbnails = []
|
||||||
|
images = media.get('Images')
|
||||||
|
if isinstance(images, list):
|
||||||
|
for image_key, image_url in images.items():
|
||||||
|
mobj = re.search(r'Img_(\d+)[xX](\d+)', image_key)
|
||||||
|
if not mobj:
|
||||||
|
continue
|
||||||
|
thumbnails.append({
|
||||||
|
'url': image_url,
|
||||||
|
'width': int(mobj.group(1)),
|
||||||
|
'height': int(mobj.group(2)),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'view_count': view_count,
|
||||||
|
'average_rating': average_rating,
|
||||||
|
'age_limit': age_limit,
|
||||||
|
'genre': genre,
|
||||||
|
'creator': creator,
|
||||||
|
'artist': artist,
|
||||||
|
'release_year': release_year,
|
||||||
|
'series': series,
|
||||||
|
'episode': episode,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
|||||||
@@ -143,32 +143,45 @@ class CuriosityStreamIE(CuriosityStreamBaseIE):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CuriosityStreamCollectionIE(CuriosityStreamBaseIE):
|
class CuriosityStreamCollectionsIE(CuriosityStreamBaseIE):
|
||||||
IE_NAME = 'curiositystream:collection'
|
IE_NAME = 'curiositystream:collections'
|
||||||
_VALID_URL = r'https?://(?:app\.)?curiositystream\.com/(?:collection|series)/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:app\.)?curiositystream\.com/collections/(?P<id>\d+)'
|
||||||
|
_API_BASE_URL = 'https://api.curiositystream.com/v2/collections/'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://app.curiositystream.com/collection/2',
|
'url': 'https://curiositystream.com/collections/86',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '86',
|
||||||
|
'title': 'Staff Picks',
|
||||||
|
'description': 'Wondering where to start? Here are a few of our favorite series and films... from our couch to yours.',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 7,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
collection_id = self._match_id(url)
|
||||||
|
collection = self._call_api(collection_id, collection_id)
|
||||||
|
entries = []
|
||||||
|
for media in collection.get('media', []):
|
||||||
|
media_id = compat_str(media.get('id'))
|
||||||
|
media_type, ie = ('series', CuriosityStreamSeriesIE) if media.get('is_collection') else ('video', CuriosityStreamIE)
|
||||||
|
entries.append(self.url_result(
|
||||||
|
'https://curiositystream.com/%s/%s' % (media_type, media_id),
|
||||||
|
ie=ie.ie_key(), video_id=media_id))
|
||||||
|
return self.playlist_result(
|
||||||
|
entries, collection_id,
|
||||||
|
collection.get('title'), collection.get('description'))
|
||||||
|
|
||||||
|
|
||||||
|
class CuriosityStreamSeriesIE(CuriosityStreamCollectionsIE):
|
||||||
|
IE_NAME = 'curiositystream:series'
|
||||||
|
_VALID_URL = r'https?://(?:app\.)?curiositystream\.com/series/(?P<id>\d+)'
|
||||||
|
_API_BASE_URL = 'https://api.curiositystream.com/v2/series/'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://app.curiositystream.com/series/2',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2',
|
'id': '2',
|
||||||
'title': 'Curious Minds: The Internet',
|
'title': 'Curious Minds: The Internet',
|
||||||
'description': 'How is the internet shaping our lives in the 21st Century?',
|
'description': 'How is the internet shaping our lives in the 21st Century?',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 16,
|
'playlist_mincount': 16,
|
||||||
}, {
|
|
||||||
'url': 'https://curiositystream.com/series/2',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
collection_id = self._match_id(url)
|
|
||||||
collection = self._call_api(
|
|
||||||
'collections/' + collection_id, collection_id)
|
|
||||||
entries = []
|
|
||||||
for media in collection.get('media', []):
|
|
||||||
media_id = compat_str(media.get('id'))
|
|
||||||
entries.append(self.url_result(
|
|
||||||
'https://curiositystream.com/video/' + media_id,
|
|
||||||
CuriosityStreamIE.ie_key(), media_id))
|
|
||||||
return self.playlist_result(
|
|
||||||
entries, collection_id,
|
|
||||||
collection.get('title'), collection.get('description'))
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
|
|||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
cookies = self._get_dailymotion_cookies()
|
cookies = self._get_dailymotion_cookies()
|
||||||
ff = self._get_cookie_value(cookies, 'ff')
|
ff = self._get_cookie_value(cookies, 'ff')
|
||||||
self._FAMILY_FILTER = ff == 'on' if ff else age_restricted(18, self._downloader.params.get('age_limit'))
|
self._FAMILY_FILTER = ff == 'on' if ff else age_restricted(18, self.get_param('age_limit'))
|
||||||
self._set_dailymotion_cookie('ff', 'on' if self._FAMILY_FILTER else 'off')
|
self._set_dailymotion_cookie('ff', 'on' if self._FAMILY_FILTER else 'off')
|
||||||
|
|
||||||
def _call_api(self, object_type, xid, object_fields, note, filter_extra=None):
|
def _call_api(self, object_type, xid, object_fields, note, filter_extra=None):
|
||||||
@@ -207,14 +207,14 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
video_id, playlist_id = re.match(self._VALID_URL, url).groups()
|
video_id, playlist_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
if playlist_id:
|
if playlist_id:
|
||||||
if not self._downloader.params.get('noplaylist'):
|
if not self.get_param('noplaylist'):
|
||||||
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
|
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'http://www.dailymotion.com/playlist/' + playlist_id,
|
'http://www.dailymotion.com/playlist/' + playlist_id,
|
||||||
'DailymotionPlaylist', playlist_id)
|
'DailymotionPlaylist', playlist_id)
|
||||||
self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
|
self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
|
||||||
|
|
||||||
password = self._downloader.params.get('videopassword')
|
password = self.get_param('videopassword')
|
||||||
media = self._call_api(
|
media = self._call_api(
|
||||||
'media', video_id, '''... on Video {
|
'media', video_id, '''... on Video {
|
||||||
%s
|
%s
|
||||||
@@ -232,7 +232,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
audienceCount
|
audienceCount
|
||||||
isOnAir
|
isOnAir
|
||||||
}''' % (self._COMMON_MEDIA_FIELDS, self._COMMON_MEDIA_FIELDS), 'Downloading media JSON metadata',
|
}''' % (self._COMMON_MEDIA_FIELDS, self._COMMON_MEDIA_FIELDS), 'Downloading media JSON metadata',
|
||||||
'password: "%s"' % self._downloader.params.get('videopassword') if password else None)
|
'password: "%s"' % self.get_param('videopassword') if password else None)
|
||||||
xid = media['xid']
|
xid = media['xid']
|
||||||
|
|
||||||
metadata = self._download_json(
|
metadata = self._download_json(
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class DaumListIE(InfoExtractor):
|
|||||||
query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
|
query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
|
||||||
if 'clipid' in query_dict:
|
if 'clipid' in query_dict:
|
||||||
clip_id = query_dict['clipid'][0]
|
clip_id = query_dict['clipid'][0]
|
||||||
if self._downloader.params.get('noplaylist'):
|
if self.get_param('noplaylist'):
|
||||||
self.to_screen('Downloading just video %s because of --no-playlist' % clip_id)
|
self.to_screen('Downloading just video %s because of --no-playlist' % clip_id)
|
||||||
return self.url_result(DaumClipIE._URL_TEMPLATE % clip_id, 'DaumClip')
|
return self.url_result(DaumClipIE._URL_TEMPLATE % clip_id, 'DaumClip')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from ..utils import (
|
|||||||
|
|
||||||
class DeezerBaseInfoExtractor(InfoExtractor):
|
class DeezerBaseInfoExtractor(InfoExtractor):
|
||||||
def get_data(self, url):
|
def get_data(self, url):
|
||||||
if not self._downloader.params.get('test'):
|
if not self.get_param('test'):
|
||||||
self.report_warning('For now, this extractor only supports the 30 second previews. Patches welcome!')
|
self.report_warning('For now, this extractor only supports the 30 second previews. Patches welcome!')
|
||||||
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
|||||||
+25
-19
@@ -32,6 +32,18 @@ class DigitallySpeakingIE(InfoExtractor):
|
|||||||
# From http://www.gdcvault.com/play/1013700/Advanced-Material
|
# From http://www.gdcvault.com/play/1013700/Advanced-Material
|
||||||
'url': 'http://sevt.dispeak.com/ubm/gdc/eur10/xml/11256_1282118587281VNIT.xml',
|
'url': 'http://sevt.dispeak.com/ubm/gdc/eur10/xml/11256_1282118587281VNIT.xml',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# From https://gdcvault.com/play/1016624, empty speakerVideo
|
||||||
|
'url': 'https://sevt.dispeak.com/ubm/gdc/online12/xml/201210-822101_1349794556671DDDD.xml',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '201210-822101_1349794556671DDDD',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Pre-launch - Preparing to Take the Plunge',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# From http://www.gdcvault.com/play/1014846/Conference-Keynote-Shigeru, empty slideVideo
|
||||||
|
'url': 'http://events.digitallyspeaking.com/gdc/project25/xml/p25-miyamoto1999_1282467389849HSVB.xml',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _parse_mp4(self, metadata):
|
def _parse_mp4(self, metadata):
|
||||||
@@ -85,25 +97,19 @@ class DigitallySpeakingIE(InfoExtractor):
|
|||||||
'quality': 1,
|
'quality': 1,
|
||||||
'format_id': audio.get('code'),
|
'format_id': audio.get('code'),
|
||||||
})
|
})
|
||||||
slide_video_path = xpath_text(metadata, './slideVideo', fatal=True)
|
for video_key, format_id, preference in (
|
||||||
formats.append({
|
('slide', 'slides', -2), ('speaker', 'speaker', -1)):
|
||||||
'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
|
video_path = xpath_text(metadata, './%sVideo' % video_key)
|
||||||
'play_path': remove_end(slide_video_path, '.flv'),
|
if not video_path:
|
||||||
'ext': 'flv',
|
continue
|
||||||
'format_note': 'slide deck video',
|
formats.append({
|
||||||
'quality': -2,
|
'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
|
||||||
'format_id': 'slides',
|
'play_path': remove_end(video_path, '.flv'),
|
||||||
'acodec': 'none',
|
'ext': 'flv',
|
||||||
})
|
'format_note': '%s video' % video_key,
|
||||||
speaker_video_path = xpath_text(metadata, './speakerVideo', fatal=True)
|
'quality': preference,
|
||||||
formats.append({
|
'format_id': format_id,
|
||||||
'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
|
})
|
||||||
'play_path': remove_end(speaker_video_path, '.flv'),
|
|
||||||
'ext': 'flv',
|
|
||||||
'format_note': 'speaker video',
|
|
||||||
'quality': -1,
|
|
||||||
'format_id': 'speaker',
|
|
||||||
})
|
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -107,8 +107,7 @@ class EggheadLessonIE(EggheadBaseIE):
|
|||||||
ext = determine_ext(format_url)
|
ext = determine_ext(format_url)
|
||||||
if ext == 'm3u8':
|
if ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
format_url, lesson_id, 'mp4', entry_protocol='m3u8',
|
format_url, lesson_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
m3u8_id='hls', fatal=False))
|
|
||||||
elif ext == 'mpd':
|
elif ext == 'mpd':
|
||||||
formats.extend(self._extract_mpd_formats(
|
formats.extend(self._extract_mpd_formats(
|
||||||
format_url, lesson_id, mpd_id='dash', fatal=False))
|
format_url, lesson_id, mpd_id='dash', fatal=False))
|
||||||
|
|||||||
+17
-65
@@ -1,9 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@@ -12,12 +10,12 @@ from ..utils import (
|
|||||||
try_get,
|
try_get,
|
||||||
)
|
)
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..downloader.hls import HlsFD
|
|
||||||
|
|
||||||
|
|
||||||
class ElonetIE(InfoExtractor):
|
class ElonetIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://elonet\.finna\.fi/Record/kavi\.elonet_elokuva_(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://elonet\.finna\.fi/Record/kavi\.elonet_elokuva_(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
|
# m3u8 with subtitles
|
||||||
'url': 'https://elonet.finna.fi/Record/kavi.elonet_elokuva_107867',
|
'url': 'https://elonet.finna.fi/Record/kavi.elonet_elokuva_107867',
|
||||||
'md5': '8efc954b96c543711707f87de757caea',
|
'md5': '8efc954b96c543711707f87de757caea',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -27,62 +25,17 @@ class ElonetIE(InfoExtractor):
|
|||||||
'description': 'Valkoinen peura (1952) on Erik Blombergin ohjaama ja yhdessä Mirjami Kuosmasen kanssa käsikirjoittama tarunomainen kertomus valkoisen peuran hahmossa lii...',
|
'description': 'Valkoinen peura (1952) on Erik Blombergin ohjaama ja yhdessä Mirjami Kuosmasen kanssa käsikirjoittama tarunomainen kertomus valkoisen peuran hahmossa lii...',
|
||||||
'thumbnail': 'https://elonet.finna.fi/Cover/Show?id=kavi.elonet_elokuva_107867&index=0&size=large',
|
'thumbnail': 'https://elonet.finna.fi/Cover/Show?id=kavi.elonet_elokuva_107867&index=0&size=large',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
# DASH with subtitles
|
||||||
def _download_m3u8_chunked_subtitle(self, chunklist_url):
|
'url': 'https://elonet.finna.fi/Record/kavi.elonet_elokuva_116539',
|
||||||
"""
|
'info_dict': {
|
||||||
Download VTT subtitles from pieces in manifest URL.
|
'id': '116539',
|
||||||
Return a string containing joined chunks with extra headers removed.
|
'ext': 'mp4',
|
||||||
"""
|
'title': 'Minulla on tiikeri',
|
||||||
with tempfile.NamedTemporaryFile(delete=True) as outfile:
|
'description': 'Pienellä pojalla, joka asuu kerrostalossa, on kotieläimenä tiikeri. Se on kuitenkin salaisuus. Kerrostalon räpätäti on Kotilaisen täti, joka on aina vali...',
|
||||||
fname = outfile.name
|
'thumbnail': 'https://elonet.finna.fi/Cover/Show?id=kavi.elonet_elokuva_116539&index=0&size=large&source=Solr',
|
||||||
hlsdl = HlsFD(self._downloader, {})
|
}
|
||||||
hlsdl.download(compat_str(fname), {"url": chunklist_url})
|
}]
|
||||||
with open(fname, 'r') as fin:
|
|
||||||
# Remove (some) headers
|
|
||||||
fdata = re.sub(r'X-TIMESTAMP-MAP.*\n+|WEBVTT\n+', '', fin.read())
|
|
||||||
os.remove(fname)
|
|
||||||
return "WEBVTT\n\n" + fdata
|
|
||||||
|
|
||||||
def _parse_m3u8_subtitles(self, m3u8_doc, m3u8_url):
|
|
||||||
"""
|
|
||||||
Parse subtitles from HLS / m3u8 manifest.
|
|
||||||
"""
|
|
||||||
subtitles = {}
|
|
||||||
baseurl = m3u8_url[:m3u8_url.rindex('/') + 1]
|
|
||||||
for line in m3u8_doc.split('\n'):
|
|
||||||
if 'EXT-X-MEDIA:TYPE=SUBTITLES' in line:
|
|
||||||
lang = self._search_regex(
|
|
||||||
r'LANGUAGE="(.+?)"', line, 'lang', default=False)
|
|
||||||
uri = self._search_regex(
|
|
||||||
r'URI="(.+?)"', line, 'uri', default=False)
|
|
||||||
if lang and uri:
|
|
||||||
data = self._download_m3u8_chunked_subtitle(baseurl + uri)
|
|
||||||
subtitles[lang] = [{'ext': 'vtt', 'data': data}]
|
|
||||||
return subtitles
|
|
||||||
|
|
||||||
def _parse_mpd_subtitles(self, mpd_doc):
|
|
||||||
"""
|
|
||||||
Parse subtitles from MPD manifest.
|
|
||||||
"""
|
|
||||||
ns = '{urn:mpeg:dash:schema:mpd:2011}'
|
|
||||||
subtitles = {}
|
|
||||||
for aset in mpd_doc.findall(".//%sAdaptationSet[@mimeType='text/vtt']" % (ns)):
|
|
||||||
lang = aset.attrib.get('lang', 'unk')
|
|
||||||
url = aset.find("./%sRepresentation/%sBaseURL" % (ns, ns)).text
|
|
||||||
subtitles[lang] = [{'ext': 'vtt', 'url': url}]
|
|
||||||
return subtitles
|
|
||||||
|
|
||||||
def _get_subtitles(self, fmt, doc, url):
|
|
||||||
if fmt == 'm3u8':
|
|
||||||
subs = self._parse_m3u8_subtitles(doc, url)
|
|
||||||
elif fmt == 'mpd':
|
|
||||||
subs = self._parse_mpd_subtitles(doc)
|
|
||||||
else:
|
|
||||||
self.report_warning(
|
|
||||||
"Cannot download subtitles from '%s' streams." % (fmt))
|
|
||||||
subs = {}
|
|
||||||
return subs
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
@@ -101,8 +54,8 @@ class ElonetIE(InfoExtractor):
|
|||||||
self._parse_json(json_s, video_id),
|
self._parse_json(json_s, video_id),
|
||||||
lambda x: x[0]["src"], compat_str)
|
lambda x: x[0]["src"], compat_str)
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
if re.search(r'\.m3u8\??', src):
|
if re.search(r'\.m3u8\??', src):
|
||||||
fmt = 'm3u8'
|
|
||||||
res = self._download_webpage_handle(
|
res = self._download_webpage_handle(
|
||||||
# elonet servers have certificate problems
|
# elonet servers have certificate problems
|
||||||
src.replace('https:', 'http:'), video_id,
|
src.replace('https:', 'http:'), video_id,
|
||||||
@@ -111,11 +64,10 @@ class ElonetIE(InfoExtractor):
|
|||||||
if res:
|
if res:
|
||||||
doc, urlh = res
|
doc, urlh = res
|
||||||
url = urlh.geturl()
|
url = urlh.geturl()
|
||||||
formats = self._parse_m3u8_formats(doc, url)
|
formats, subtitles = self._parse_m3u8_formats_and_subtitles(doc, url)
|
||||||
for f in formats:
|
for f in formats:
|
||||||
f['ext'] = 'mp4'
|
f['ext'] = 'mp4'
|
||||||
elif re.search(r'\.mpd\??', src):
|
elif re.search(r'\.mpd\??', src):
|
||||||
fmt = 'mpd'
|
|
||||||
res = self._download_xml_handle(
|
res = self._download_xml_handle(
|
||||||
src, video_id,
|
src, video_id,
|
||||||
note='Downloading MPD manifest',
|
note='Downloading MPD manifest',
|
||||||
@@ -123,7 +75,7 @@ class ElonetIE(InfoExtractor):
|
|||||||
if res:
|
if res:
|
||||||
doc, urlh = res
|
doc, urlh = res
|
||||||
url = base_url(urlh.geturl())
|
url = base_url(urlh.geturl())
|
||||||
formats = self._parse_mpd_formats(doc, mpd_base_url=url)
|
formats, subtitles = self._parse_mpd_formats_and_subtitles(doc, mpd_base_url=url)
|
||||||
else:
|
else:
|
||||||
raise ExtractorError("Unknown streaming format")
|
raise ExtractorError("Unknown streaming format")
|
||||||
|
|
||||||
@@ -133,5 +85,5 @@ class ElonetIE(InfoExtractor):
|
|||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': self.extract_subtitles(fmt, doc, url),
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from .common import InfoExtractor
|
|||||||
from ..compat import compat_urllib_parse_urlencode
|
from ..compat import compat_urllib_parse_urlencode
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
unescapeHTML
|
merge_dicts,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@ class EroProfileIE(InfoExtractor):
|
|||||||
'title': 'sexy babe softcore',
|
'title': 'sexy babe softcore',
|
||||||
'thumbnail': r're:https?://.*\.jpg',
|
'thumbnail': r're:https?://.*\.jpg',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
},
|
||||||
|
'skip': 'Video not found',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.eroprofile.com/m/videos/view/Try-It-On-Pee_cut_2-wmv-4shared-com-file-sharing-download-movie-file',
|
'url': 'http://www.eroprofile.com/m/videos/view/Try-It-On-Pee_cut_2-wmv-4shared-com-file-sharing-download-movie-file',
|
||||||
'md5': '1baa9602ede46ce904c431f5418d8916',
|
'md5': '1baa9602ede46ce904c431f5418d8916',
|
||||||
@@ -77,19 +78,15 @@ class EroProfileIE(InfoExtractor):
|
|||||||
[r"glbUpdViews\s*\('\d*','(\d+)'", r'p/report/video/(\d+)'],
|
[r"glbUpdViews\s*\('\d*','(\d+)'", r'p/report/video/(\d+)'],
|
||||||
webpage, 'video id', default=None)
|
webpage, 'video id', default=None)
|
||||||
|
|
||||||
video_url = unescapeHTML(self._search_regex(
|
|
||||||
r'<source src="([^"]+)', webpage, 'video url'))
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'Title:</th><td>([^<]+)</td>', webpage, 'title')
|
(r'Title:</th><td>([^<]+)</td>', r'<h1[^>]*>(.+?)</h1>'),
|
||||||
thumbnail = self._search_regex(
|
webpage, 'title')
|
||||||
r'onclick="showVideoPlayer\(\)"><img src="([^"]+)',
|
|
||||||
webpage, 'thumbnail', fatal=False)
|
|
||||||
|
|
||||||
return {
|
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
|
||||||
|
|
||||||
|
return merge_dicts(info, {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'url': video_url,
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
})
|
||||||
|
|||||||
@@ -67,7 +67,10 @@ from .appletrailers import (
|
|||||||
AppleTrailersSectionIE,
|
AppleTrailersSectionIE,
|
||||||
)
|
)
|
||||||
from .applepodcasts import ApplePodcastsIE
|
from .applepodcasts import ApplePodcastsIE
|
||||||
from .archiveorg import ArchiveOrgIE
|
from .archiveorg import (
|
||||||
|
ArchiveOrgIE,
|
||||||
|
YoutubeWebArchiveIE,
|
||||||
|
)
|
||||||
from .arcpublishing import ArcPublishingIE
|
from .arcpublishing import ArcPublishingIE
|
||||||
from .arkena import ArkenaIE
|
from .arkena import ArkenaIE
|
||||||
from .ard import (
|
from .ard import (
|
||||||
@@ -94,7 +97,8 @@ from .audiomack import AudiomackIE, AudiomackAlbumIE
|
|||||||
from .audius import (
|
from .audius import (
|
||||||
AudiusIE,
|
AudiusIE,
|
||||||
AudiusTrackIE,
|
AudiusTrackIE,
|
||||||
AudiusPlaylistIE
|
AudiusPlaylistIE,
|
||||||
|
AudiusProfileIE,
|
||||||
)
|
)
|
||||||
from .awaan import (
|
from .awaan import (
|
||||||
AWAANIE,
|
AWAANIE,
|
||||||
@@ -151,7 +155,6 @@ from .bleacherreport import (
|
|||||||
BleacherReportIE,
|
BleacherReportIE,
|
||||||
BleacherReportCMSIE,
|
BleacherReportCMSIE,
|
||||||
)
|
)
|
||||||
from .blinkx import BlinkxIE
|
|
||||||
from .bloomberg import BloombergIE
|
from .bloomberg import BloombergIE
|
||||||
from .bokecc import BokeCCIE
|
from .bokecc import BokeCCIE
|
||||||
from .bongacams import BongaCamsIE
|
from .bongacams import BongaCamsIE
|
||||||
@@ -288,7 +291,8 @@ from .ctvnews import CTVNewsIE
|
|||||||
from .cultureunplugged import CultureUnpluggedIE
|
from .cultureunplugged import CultureUnpluggedIE
|
||||||
from .curiositystream import (
|
from .curiositystream import (
|
||||||
CuriosityStreamIE,
|
CuriosityStreamIE,
|
||||||
CuriosityStreamCollectionIE,
|
CuriosityStreamCollectionsIE,
|
||||||
|
CuriosityStreamSeriesIE,
|
||||||
)
|
)
|
||||||
from .cwtv import CWTVIE
|
from .cwtv import CWTVIE
|
||||||
from .dailymail import DailyMailIE
|
from .dailymail import DailyMailIE
|
||||||
@@ -395,6 +399,7 @@ from .facebook import (
|
|||||||
FacebookIE,
|
FacebookIE,
|
||||||
FacebookPluginsVideoIE,
|
FacebookPluginsVideoIE,
|
||||||
)
|
)
|
||||||
|
from .fancode import FancodeVodIE
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
from .fc2 import (
|
from .fc2 import (
|
||||||
FC2IE,
|
FC2IE,
|
||||||
@@ -500,6 +505,7 @@ from .hotnewhiphop import HotNewHipHopIE
|
|||||||
from .hotstar import (
|
from .hotstar import (
|
||||||
HotStarIE,
|
HotStarIE,
|
||||||
HotStarPlaylistIE,
|
HotStarPlaylistIE,
|
||||||
|
HotStarSeriesIE,
|
||||||
)
|
)
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .howstuffworks import HowStuffWorksIE
|
from .howstuffworks import HowStuffWorksIE
|
||||||
@@ -760,7 +766,10 @@ from .mtv import (
|
|||||||
)
|
)
|
||||||
from .muenchentv import MuenchenTVIE
|
from .muenchentv import MuenchenTVIE
|
||||||
from .mwave import MwaveIE, MwaveMeetGreetIE
|
from .mwave import MwaveIE, MwaveMeetGreetIE
|
||||||
from .mxplayer import MxplayerIE
|
from .mxplayer import (
|
||||||
|
MxplayerIE,
|
||||||
|
MxplayerShowIE,
|
||||||
|
)
|
||||||
from .mychannels import MyChannelsIE
|
from .mychannels import MyChannelsIE
|
||||||
from .myspace import MySpaceIE, MySpaceAlbumIE
|
from .myspace import MySpaceIE, MySpaceAlbumIE
|
||||||
from .myspass import MySpassIE
|
from .myspass import MySpassIE
|
||||||
@@ -949,6 +958,7 @@ from .palcomp3 import (
|
|||||||
)
|
)
|
||||||
from .pandoratv import PandoraTVIE
|
from .pandoratv import PandoraTVIE
|
||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
|
from .parlview import ParlviewIE
|
||||||
from .patreon import PatreonIE
|
from .patreon import PatreonIE
|
||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
from .pearvideo import PearVideoIE
|
from .pearvideo import PearVideoIE
|
||||||
@@ -980,6 +990,7 @@ from .platzi import (
|
|||||||
from .playfm import PlayFMIE
|
from .playfm import PlayFMIE
|
||||||
from .playplustv import PlayPlusTVIE
|
from .playplustv import PlayPlusTVIE
|
||||||
from .plays import PlaysTVIE
|
from .plays import PlaysTVIE
|
||||||
|
from .playstuff import PlayStuffIE
|
||||||
from .playtvak import PlaytvakIE
|
from .playtvak import PlaytvakIE
|
||||||
from .playvid import PlayvidIE
|
from .playvid import PlayvidIE
|
||||||
from .playwire import PlaywireIE
|
from .playwire import PlaywireIE
|
||||||
@@ -1113,6 +1124,7 @@ from .safari import (
|
|||||||
SafariApiIE,
|
SafariApiIE,
|
||||||
SafariCourseIE,
|
SafariCourseIE,
|
||||||
)
|
)
|
||||||
|
from .saitosan import SaitosanIE
|
||||||
from .samplefocus import SampleFocusIE
|
from .samplefocus import SampleFocusIE
|
||||||
from .sapo import SapoIE
|
from .sapo import SapoIE
|
||||||
from .savefrom import SaveFromIE
|
from .savefrom import SaveFromIE
|
||||||
@@ -1145,6 +1157,7 @@ from .shared import (
|
|||||||
SharedIE,
|
SharedIE,
|
||||||
VivoIE,
|
VivoIE,
|
||||||
)
|
)
|
||||||
|
from .shemaroome import ShemarooMeIE
|
||||||
from .showroomlive import ShowRoomLiveIE
|
from .showroomlive import ShowRoomLiveIE
|
||||||
from .simplecast import (
|
from .simplecast import (
|
||||||
SimplecastIE,
|
SimplecastIE,
|
||||||
@@ -1178,7 +1191,10 @@ from .slideslive import SlidesLiveIE
|
|||||||
from .slutload import SlutloadIE
|
from .slutload import SlutloadIE
|
||||||
from .snotr import SnotrIE
|
from .snotr import SnotrIE
|
||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
from .sonyliv import SonyLIVIE
|
from .sonyliv import (
|
||||||
|
SonyLIVIE,
|
||||||
|
SonyLIVSeriesIE,
|
||||||
|
)
|
||||||
from .soundcloud import (
|
from .soundcloud import (
|
||||||
SoundcloudEmbedIE,
|
SoundcloudEmbedIE,
|
||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
@@ -1286,6 +1302,7 @@ from .telebruxelles import TeleBruxellesIE
|
|||||||
from .telecinco import TelecincoIE
|
from .telecinco import TelecincoIE
|
||||||
from .telegraaf import TelegraafIE
|
from .telegraaf import TelegraafIE
|
||||||
from .telemb import TeleMBIE
|
from .telemb import TeleMBIE
|
||||||
|
from .telemundo import TelemundoIE
|
||||||
from .telequebec import (
|
from .telequebec import (
|
||||||
TeleQuebecIE,
|
TeleQuebecIE,
|
||||||
TeleQuebecSquatIE,
|
TeleQuebecSquatIE,
|
||||||
@@ -1438,6 +1455,7 @@ from .ufctv import (
|
|||||||
UFCTVIE,
|
UFCTVIE,
|
||||||
UFCArabiaIE,
|
UFCArabiaIE,
|
||||||
)
|
)
|
||||||
|
from .ukcolumn import UkColumnIE
|
||||||
from .uktvplay import UKTVPlayIE
|
from .uktvplay import UKTVPlayIE
|
||||||
from .digiteka import DigitekaIE
|
from .digiteka import DigitekaIE
|
||||||
from .dlive import (
|
from .dlive import (
|
||||||
@@ -1546,7 +1564,10 @@ from .vodlocker import VodlockerIE
|
|||||||
from .vodpl import VODPlIE
|
from .vodpl import VODPlIE
|
||||||
from .vodplatform import VODPlatformIE
|
from .vodplatform import VODPlatformIE
|
||||||
from .voicerepublic import VoiceRepublicIE
|
from .voicerepublic import VoiceRepublicIE
|
||||||
from .voot import VootIE
|
from .voot import (
|
||||||
|
VootIE,
|
||||||
|
VootSeriesIE,
|
||||||
|
)
|
||||||
from .voxmedia import (
|
from .voxmedia import (
|
||||||
VoxMediaVolumeIE,
|
VoxMediaVolumeIE,
|
||||||
VoxMediaIE,
|
VoxMediaIE,
|
||||||
@@ -1597,6 +1618,7 @@ from .weibo import (
|
|||||||
)
|
)
|
||||||
from .weiqitv import WeiqiTVIE
|
from .weiqitv import WeiqiTVIE
|
||||||
from .wimtv import WimTVIE
|
from .wimtv import WimTVIE
|
||||||
|
from .whowatch import WhoWatchIE
|
||||||
from .wistia import (
|
from .wistia import (
|
||||||
WistiaIE,
|
WistiaIE,
|
||||||
WistiaPlaylistIE,
|
WistiaPlaylistIE,
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_etree_fromstring,
|
compat_etree_fromstring,
|
||||||
compat_http_client,
|
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_error,
|
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_unquote_plus,
|
compat_urllib_parse_unquote_plus,
|
||||||
)
|
)
|
||||||
@@ -23,6 +20,7 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
limit_length,
|
limit_length,
|
||||||
|
network_exceptions,
|
||||||
parse_count,
|
parse_count,
|
||||||
qualities,
|
qualities,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
@@ -370,7 +368,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
note='Confirming login')
|
note='Confirming login')
|
||||||
if re.search(r'id="checkpointSubmitButton"', check_response) is not None:
|
if re.search(r'id="checkpointSubmitButton"', check_response) is not None:
|
||||||
self.report_warning('Unable to confirm login, you have to login in your browser and authorize the login.')
|
self.report_warning('Unable to confirm login, you have to login in your browser and authorize the login.')
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
except network_exceptions as err:
|
||||||
self.report_warning('unable to log in: %s' % error_to_compat_str(err))
|
self.report_warning('unable to log in: %s' % error_to_compat_str(err))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
parse_iso8601,
|
||||||
|
ExtractorError,
|
||||||
|
try_get
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FancodeVodIE(InfoExtractor):
|
||||||
|
IE_NAME = 'fancode:vod'
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?fancode\.com/video/(?P<id>[0-9]+)\b'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://fancode.com/video/15043/match-preview-pbks-vs-mi',
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
'format': 'bestvideo'
|
||||||
|
},
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6249806281001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Match Preview: PBKS vs MI',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
"timestamp": 1619081590,
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
'upload_date': '20210422',
|
||||||
|
'uploader_id': '6008340455001'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://fancode.com/video/15043',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
|
||||||
|
BRIGHTCOVE_URL_TEMPLATE = 'https://players.brightcove.net/%s/default_default/index.html?videoId=%s'
|
||||||
|
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
brightcove_user_id = self._html_search_regex(
|
||||||
|
r'(?:https?://)?players\.brightcove\.net/(\d+)/default_default/index(?:\.min)?\.js',
|
||||||
|
webpage, 'user id')
|
||||||
|
|
||||||
|
data = '''{
|
||||||
|
"query":"query Video($id: Int\\u0021, $filter: SegmentFilter) { media(id: $id, filter: $filter) { id contentId title contentId publishedTime totalViews totalUpvotes provider thumbnail { src } mediaSource {brightcove } duration isPremium isUserEntitled tags duration }}",
|
||||||
|
"variables":{
|
||||||
|
"id":%s,
|
||||||
|
"filter":{
|
||||||
|
"contentDataType":"DEFAULT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationName":"Video"
|
||||||
|
}''' % video_id
|
||||||
|
|
||||||
|
metadata_json = self._download_json(
|
||||||
|
'https://www.fancode.com/graphql', video_id, data=data.encode(), note='Downloading metadata',
|
||||||
|
headers={
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'origin': 'https://fancode.com',
|
||||||
|
'referer': url,
|
||||||
|
})
|
||||||
|
|
||||||
|
media = try_get(metadata_json, lambda x: x['data']['media'], dict) or {}
|
||||||
|
brightcove_video_id = try_get(media, lambda x: x['mediaSource']['brightcove'], compat_str)
|
||||||
|
|
||||||
|
if brightcove_video_id is None:
|
||||||
|
raise ExtractorError('Unable to extract brightcove Video ID')
|
||||||
|
|
||||||
|
is_premium = media.get('isPremium')
|
||||||
|
if is_premium:
|
||||||
|
self.report_warning('this video requires a premium account', video_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': BRIGHTCOVE_URL_TEMPLATE % (brightcove_user_id, brightcove_video_id),
|
||||||
|
'ie_key': 'BrightcoveNew',
|
||||||
|
'id': video_id,
|
||||||
|
'title': media['title'],
|
||||||
|
'like_count': media.get('totalUpvotes'),
|
||||||
|
'view_count': media.get('totalViews'),
|
||||||
|
'tags': media.get('tags'),
|
||||||
|
'release_timestamp': parse_iso8601(media.get('publishedTime')),
|
||||||
|
'availability': self._availability(needs_premium=is_premium),
|
||||||
|
}
|
||||||
@@ -151,6 +151,7 @@ class FranceTVIE(InfoExtractor):
|
|||||||
videos.append(fallback_info['video'])
|
videos.append(fallback_info['video'])
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for video in videos:
|
for video in videos:
|
||||||
video_url = video.get('url')
|
video_url = video.get('url')
|
||||||
if not video_url:
|
if not video_url:
|
||||||
@@ -171,10 +172,12 @@ class FranceTVIE(InfoExtractor):
|
|||||||
sign(video_url, format_id) + '&hdcore=3.7.0&plugin=aasp-3.7.0.39.44',
|
sign(video_url, format_id) + '&hdcore=3.7.0&plugin=aasp-3.7.0.39.44',
|
||||||
video_id, f4m_id=format_id, fatal=False))
|
video_id, f4m_id=format_id, fatal=False))
|
||||||
elif ext == 'm3u8':
|
elif ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
sign(video_url, format_id), video_id, 'mp4',
|
sign(video_url, format_id), video_id, 'mp4',
|
||||||
entry_protocol='m3u8_native', m3u8_id=format_id,
|
entry_protocol='m3u8_native', m3u8_id=format_id,
|
||||||
fatal=False))
|
fatal=False)
|
||||||
|
formats.extend(m3u8_fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, m3u8_subs)
|
||||||
elif ext == 'mpd':
|
elif ext == 'mpd':
|
||||||
formats.extend(self._extract_mpd_formats(
|
formats.extend(self._extract_mpd_formats(
|
||||||
sign(video_url, format_id), video_id, mpd_id=format_id, fatal=False))
|
sign(video_url, format_id), video_id, mpd_id=format_id, fatal=False))
|
||||||
@@ -199,13 +202,12 @@ class FranceTVIE(InfoExtractor):
|
|||||||
title += ' - %s' % subtitle
|
title += ' - %s' % subtitle
|
||||||
title = title.strip()
|
title = title.strip()
|
||||||
|
|
||||||
subtitles = {}
|
subtitles.setdefault('fr', []).extend(
|
||||||
subtitles_list = [{
|
[{
|
||||||
'url': subformat['url'],
|
'url': subformat['url'],
|
||||||
'ext': subformat.get('format'),
|
'ext': subformat.get('format'),
|
||||||
} for subformat in info.get('subtitles', []) if subformat.get('url')]
|
} for subformat in info.get('subtitles', []) if subformat.get('url')]
|
||||||
if subtitles_list:
|
)
|
||||||
subtitles['fr'] = subtitles_list
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -357,6 +359,22 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'add_ie': [FranceTVIE.ie_key()],
|
'add_ie': [FranceTVIE.ie_key()],
|
||||||
|
}, {
|
||||||
|
'note': 'Only an image exists in initial webpage instead of the video',
|
||||||
|
'url': 'https://www.francetvinfo.fr/sante/maladie/coronavirus/covid-19-en-inde-une-situation-catastrophique-a-new-dehli_4381095.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '7d204c9e-a2d3-11eb-9e4c-000d3a23d482',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Covid-19 : une situation catastrophique à New Dehli',
|
||||||
|
'thumbnail': str,
|
||||||
|
'duration': 76,
|
||||||
|
'timestamp': 1619028518,
|
||||||
|
'upload_date': '20210421',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'add_ie': [FranceTVIE.ie_key()],
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
'url': 'http://www.francetvinfo.fr/elections/europeennes/direct-europeennes-regardez-le-debat-entre-les-candidats-a-la-presidence-de-la-commission_600639.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -384,6 +402,10 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://france3-regions.francetvinfo.fr/limousin/emissions/jt-1213-limousin',
|
'url': 'http://france3-regions.francetvinfo.fr/limousin/emissions/jt-1213-limousin',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# "<figure id=" pattern (#28792)
|
||||||
|
'url': 'https://www.francetvinfo.fr/culture/patrimoine/incendie-de-notre-dame-de-paris/notre-dame-de-paris-de-l-incendie-de-la-cathedrale-a-sa-reconstruction_4372291.html',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@@ -401,7 +423,7 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
(r'player\.load[^;]+src:\s*["\']([^"\']+)',
|
(r'player\.load[^;]+src:\s*["\']([^"\']+)',
|
||||||
r'id-video=([^@]+@[^"]+)',
|
r'id-video=([^@]+@[^"]+)',
|
||||||
r'<a[^>]+href="(?:https?:)?//videos\.francetv\.fr/video/([^@]+@[^"]+)"',
|
r'<a[^>]+href="(?:https?:)?//videos\.francetv\.fr/video/([^@]+@[^"]+)"',
|
||||||
r'data-id=["\']([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'),
|
r'(?:data-id|<figure[^<]+\bid)=["\']([\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'),
|
||||||
webpage, 'video id')
|
webpage, 'video id')
|
||||||
|
|
||||||
return self._make_url_result(video_id)
|
return self._make_url_result(video_id)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class FunimationIE(InfoExtractor):
|
class FunimationIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?funimation(?:\.com|now\.uk)/shows/[^/]+/(?P<id>[^/?#&]+)'
|
_VALID_URL = r'https?://(?:www\.)?funimation(?:\.com|now\.uk)/(?:[^/]+/)?shows/[^/]+/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
_NETRC_MACHINE = 'funimation'
|
_NETRC_MACHINE = 'funimation'
|
||||||
_TOKEN = None
|
_TOKEN = None
|
||||||
@@ -51,6 +51,10 @@ class FunimationIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.funimationnow.uk/shows/puzzle-dragons-x/drop-impact/simulcast/',
|
'url': 'https://www.funimationnow.uk/shows/puzzle-dragons-x/drop-impact/simulcast/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# with lang code
|
||||||
|
'url': 'https://www.funimation.com/en/shows/hacksign/role-play/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .kaltura import KalturaIE
|
from .kaltura import KalturaIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
HEADRequest,
|
||||||
|
remove_start,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
|
smuggle_url,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,6 +103,26 @@ class GDCVaultIE(InfoExtractor):
|
|||||||
'format': 'mp4-408',
|
'format': 'mp4-408',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# Kaltura embed, whitespace between quote and embedded URL in iframe's src
|
||||||
|
'url': 'https://www.gdcvault.com/play/1025699',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0_zagynv0a',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Tech Toolbox',
|
||||||
|
'upload_date': '20190408',
|
||||||
|
'uploader_id': 'joe@blazestreaming.com',
|
||||||
|
'timestamp': 1554764629,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# HTML5 video
|
||||||
|
'url': 'http://www.gdcvault.com/play/1014846/Conference-Keynote-Shigeru',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _login(self, webpage_url, display_id):
|
def _login(self, webpage_url, display_id):
|
||||||
@@ -120,38 +143,78 @@ class GDCVaultIE(InfoExtractor):
|
|||||||
request = sanitized_Request(login_url, urlencode_postdata(login_form))
|
request = sanitized_Request(login_url, urlencode_postdata(login_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
self._download_webpage(request, display_id, 'Logging in')
|
self._download_webpage(request, display_id, 'Logging in')
|
||||||
webpage = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
|
start_page = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
|
||||||
self._download_webpage(logout_url, display_id, 'Logging out')
|
self._download_webpage(logout_url, display_id, 'Logging out')
|
||||||
|
|
||||||
return webpage
|
return start_page
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id, name = re.match(self._VALID_URL, url).groups()
|
video_id, name = re.match(self._VALID_URL, url).groups()
|
||||||
display_id = name or video_id
|
display_id = name or video_id
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage_url = 'http://www.gdcvault.com/play/' + video_id
|
||||||
|
start_page = self._download_webpage(webpage_url, display_id)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
direct_url = self._search_regex(
|
||||||
r'<td><strong>Session Name:?</strong></td>\s*<td>(.*?)</td>',
|
r's1\.addVariable\("file",\s*encodeURIComponent\("(/[^"]+)"\)\);',
|
||||||
webpage, 'title')
|
start_page, 'url', default=None)
|
||||||
|
if direct_url:
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<td><strong>Session Name:?</strong></td>\s*<td>(.*?)</td>',
|
||||||
|
start_page, 'title')
|
||||||
|
video_url = 'http://www.gdcvault.com' + direct_url
|
||||||
|
# resolve the url so that we can detect the correct extension
|
||||||
|
video_url = self._request_webpage(
|
||||||
|
HEADRequest(video_url), video_id).geturl()
|
||||||
|
|
||||||
PLAYER_REGEX = r'<iframe src=\"(?P<manifest_url>.*?)\".*?</iframe>'
|
return {
|
||||||
manifest_url = self._html_search_regex(
|
'id': video_id,
|
||||||
PLAYER_REGEX, webpage, 'manifest_url')
|
'display_id': display_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
}
|
||||||
|
|
||||||
partner_id = self._search_regex(
|
embed_url = KalturaIE._extract_url(start_page)
|
||||||
r'/p(?:artner_id)?/(\d+)', manifest_url, 'partner id',
|
if embed_url:
|
||||||
default='1670711')
|
embed_url = smuggle_url(embed_url, {'source_url': url})
|
||||||
|
ie_key = 'Kaltura'
|
||||||
|
else:
|
||||||
|
PLAYER_REGEX = r'<iframe src="(?P<xml_root>.+?)/(?:gdc-)?player.*?\.html.*?".*?</iframe>'
|
||||||
|
|
||||||
kaltura_id = self._search_regex(
|
xml_root = self._html_search_regex(
|
||||||
r'entry_id=(?P<id>(?:[^&])+)', manifest_url,
|
PLAYER_REGEX, start_page, 'xml root', default=None)
|
||||||
'kaltura id', group='id')
|
if xml_root is None:
|
||||||
|
# Probably need to authenticate
|
||||||
|
login_res = self._login(webpage_url, display_id)
|
||||||
|
if login_res is None:
|
||||||
|
self.report_warning('Could not login.')
|
||||||
|
else:
|
||||||
|
start_page = login_res
|
||||||
|
# Grab the url from the authenticated page
|
||||||
|
xml_root = self._html_search_regex(
|
||||||
|
PLAYER_REGEX, start_page, 'xml root')
|
||||||
|
|
||||||
|
xml_name = self._html_search_regex(
|
||||||
|
r'<iframe src=".*?\?xml(?:=|URL=xml/)(.+?\.xml).*?".*?</iframe>',
|
||||||
|
start_page, 'xml filename', default=None)
|
||||||
|
if not xml_name:
|
||||||
|
info = self._parse_html5_media_entries(url, start_page, video_id)[0]
|
||||||
|
info.update({
|
||||||
|
'title': remove_start(self._search_regex(
|
||||||
|
r'>Session Name:\s*<.*?>\s*<td>(.+?)</td>', start_page,
|
||||||
|
'title', default=None) or self._og_search_title(
|
||||||
|
start_page, default=None), 'GDC Vault - '),
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
embed_url = '%s/xml/%s' % (xml_root, xml_name)
|
||||||
|
ie_key = 'DigitallySpeaking'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': 'kaltura:%s:%s' % (partner_id, kaltura_id),
|
|
||||||
'ie_key': KalturaIE.ie_key(),
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'url': embed_url,
|
||||||
|
'ie_key': ie_key,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ from .viqeo import ViqeoIE
|
|||||||
from .expressen import ExpressenIE
|
from .expressen import ExpressenIE
|
||||||
from .zype import ZypeIE
|
from .zype import ZypeIE
|
||||||
from .odnoklassniki import OdnoklassnikiIE
|
from .odnoklassniki import OdnoklassnikiIE
|
||||||
|
from .vk import VKIE
|
||||||
from .kinja import KinjaEmbedIE
|
from .kinja import KinjaEmbedIE
|
||||||
from .gedidigital import GediDigitalIE
|
from .gedidigital import GediDigitalIE
|
||||||
from .rcs import RCSEmbedsIE
|
from .rcs import RCSEmbedsIE
|
||||||
@@ -2252,6 +2253,10 @@ class GenericIE(InfoExtractor):
|
|||||||
'playlist_mincount': 52,
|
'playlist_mincount': 52,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
# Sibnet embed (https://help.sibnet.ru/?sibnet_video_embed)
|
||||||
|
'url': 'https://phpbb3.x-tk.ru/bbcode-video-sibnet-t24.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
# WimTv embed player
|
# WimTv embed player
|
||||||
'url': 'http://www.msmotor.tv/wearefmi-pt-2-2021/',
|
'url': 'http://www.msmotor.tv/wearefmi-pt-2-2021/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -2370,7 +2375,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
parsed_url = compat_urlparse.urlparse(url)
|
parsed_url = compat_urlparse.urlparse(url)
|
||||||
if not parsed_url.scheme:
|
if not parsed_url.scheme:
|
||||||
default_search = self._downloader.params.get('default_search')
|
default_search = self.get_param('default_search')
|
||||||
if default_search is None:
|
if default_search is None:
|
||||||
default_search = 'fixup_error'
|
default_search = 'fixup_error'
|
||||||
|
|
||||||
@@ -2444,8 +2449,9 @@ class GenericIE(InfoExtractor):
|
|||||||
m = re.match(r'^(?P<type>audio|video|application(?=/(?:ogg$|(?:vnd\.apple\.|x-)?mpegurl)))/(?P<format_id>[^;\s]+)', content_type)
|
m = re.match(r'^(?P<type>audio|video|application(?=/(?:ogg$|(?:vnd\.apple\.|x-)?mpegurl)))/(?P<format_id>[^;\s]+)', content_type)
|
||||||
if m:
|
if m:
|
||||||
format_id = compat_str(m.group('format_id'))
|
format_id = compat_str(m.group('format_id'))
|
||||||
|
subtitles = {}
|
||||||
if format_id.endswith('mpegurl'):
|
if format_id.endswith('mpegurl'):
|
||||||
formats = self._extract_m3u8_formats(url, video_id, 'mp4')
|
formats, subtitles = self._extract_m3u8_formats_and_subtitles(url, video_id, 'mp4')
|
||||||
elif format_id == 'f4m':
|
elif format_id == 'f4m':
|
||||||
formats = self._extract_f4m_formats(url, video_id)
|
formats = self._extract_f4m_formats(url, video_id)
|
||||||
else:
|
else:
|
||||||
@@ -2457,10 +2463,11 @@ class GenericIE(InfoExtractor):
|
|||||||
info_dict['direct'] = True
|
info_dict['direct'] = True
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
|
info_dict['subtitles'] = subtitles
|
||||||
return info_dict
|
return info_dict
|
||||||
|
|
||||||
if not self._downloader.params.get('test', False) and not is_intentional:
|
if not self.get_param('test', False) and not is_intentional:
|
||||||
force = self._downloader.params.get('force_generic_extractor', False)
|
force = self.get_param('force_generic_extractor', False)
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'%s on generic information extractor.' % ('Forcing' if force else 'Falling back'))
|
'%s on generic information extractor.' % ('Forcing' if force else 'Falling back'))
|
||||||
|
|
||||||
@@ -2506,11 +2513,14 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Is it an RSS feed, a SMIL file, an XSPF playlist or a MPD manifest?
|
# Is it an RSS feed, a SMIL file, an XSPF playlist or a MPD manifest?
|
||||||
try:
|
try:
|
||||||
doc = compat_etree_fromstring(webpage.encode('utf-8'))
|
try:
|
||||||
|
doc = compat_etree_fromstring(webpage)
|
||||||
|
except compat_xml_parse_error:
|
||||||
|
doc = compat_etree_fromstring(webpage.encode('utf-8'))
|
||||||
if doc.tag == 'rss':
|
if doc.tag == 'rss':
|
||||||
return self._extract_rss(url, video_id, doc)
|
return self._extract_rss(url, video_id, doc)
|
||||||
elif doc.tag == 'SmoothStreamingMedia':
|
elif doc.tag == 'SmoothStreamingMedia':
|
||||||
info_dict['formats'] = self._parse_ism_formats(doc, url)
|
info_dict['formats'], info_dict['subtitles'] = self._parse_ism_formats_and_subtitles(doc, url)
|
||||||
self._sort_formats(info_dict['formats'])
|
self._sort_formats(info_dict['formats'])
|
||||||
return info_dict
|
return info_dict
|
||||||
elif re.match(r'^(?:{[^}]+})?smil$', doc.tag):
|
elif re.match(r'^(?:{[^}]+})?smil$', doc.tag):
|
||||||
@@ -2524,7 +2534,7 @@ class GenericIE(InfoExtractor):
|
|||||||
xspf_base_url=full_response.geturl()),
|
xspf_base_url=full_response.geturl()),
|
||||||
video_id)
|
video_id)
|
||||||
elif re.match(r'(?i)^(?:{[^}]+})?MPD$', doc.tag):
|
elif re.match(r'(?i)^(?:{[^}]+})?MPD$', doc.tag):
|
||||||
info_dict['formats'] = self._parse_mpd_formats(
|
info_dict['formats'], info_dict['subtitles'] = self._parse_mpd_formats_and_subtitles(
|
||||||
doc,
|
doc,
|
||||||
mpd_base_url=full_response.geturl().rpartition('/')[0],
|
mpd_base_url=full_response.geturl().rpartition('/')[0],
|
||||||
mpd_url=url)
|
mpd_url=url)
|
||||||
@@ -2798,6 +2808,11 @@ class GenericIE(InfoExtractor):
|
|||||||
if odnoklassniki_url:
|
if odnoklassniki_url:
|
||||||
return self.url_result(odnoklassniki_url, OdnoklassnikiIE.ie_key())
|
return self.url_result(odnoklassniki_url, OdnoklassnikiIE.ie_key())
|
||||||
|
|
||||||
|
# Look for sibnet embedded player
|
||||||
|
sibnet_urls = VKIE._extract_sibnet_urls(webpage)
|
||||||
|
if sibnet_urls:
|
||||||
|
return self.playlist_from_matches(sibnet_urls, video_id, video_title)
|
||||||
|
|
||||||
# Look for embedded ivi player
|
# Look for embedded ivi player
|
||||||
mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
|
mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
@@ -3449,6 +3464,9 @@ class GenericIE(InfoExtractor):
|
|||||||
'url': src,
|
'url': src,
|
||||||
'ext': (mimetype2ext(src_type)
|
'ext': (mimetype2ext(src_type)
|
||||||
or ext if ext in KNOWN_EXTENSIONS else 'mp4'),
|
or ext if ext in KNOWN_EXTENSIONS else 'mp4'),
|
||||||
|
'http_headers': {
|
||||||
|
'Referer': full_response.geturl(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if formats:
|
if formats:
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
@@ -3517,7 +3535,7 @@ class GenericIE(InfoExtractor):
|
|||||||
m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
|
m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
|
||||||
# We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
|
# We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
|
||||||
if m_video_type is not None:
|
if m_video_type is not None:
|
||||||
found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
|
found = filter_video(re.findall(r'<meta.*?property="og:(?:video|audio)".*?content="(.*?)"', webpage))
|
||||||
if not found:
|
if not found:
|
||||||
REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
|
REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
|
||||||
found = re.search(
|
found = re.search(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class GloboIE(InfoExtractor):
|
|||||||
video = self._download_json(
|
video = self._download_json(
|
||||||
'http://api.globovideos.com/videos/%s/playlist' % video_id,
|
'http://api.globovideos.com/videos/%s/playlist' % video_id,
|
||||||
video_id)['videos'][0]
|
video_id)['videos'][0]
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and video.get('encrypted') is True:
|
if not self.get_param('allow_unplayable_formats') and video.get('encrypted') is True:
|
||||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||||
|
|
||||||
title = video['title']
|
title = video['title']
|
||||||
|
|||||||
@@ -87,7 +87,14 @@ class HotStarBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
class HotStarIE(HotStarBaseIE):
|
class HotStarIE(HotStarBaseIE):
|
||||||
IE_NAME = 'hotstar'
|
IE_NAME = 'hotstar'
|
||||||
_VALID_URL = r'https?://(?:www\.)?hotstar\.com/.*(?P<id>\d{10})'
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://(?:www\.)?hotstar\.com(?:/in)?/(?!in/)
|
||||||
|
(?:
|
||||||
|
tv/(?:[^/?#]+/){3}|
|
||||||
|
(?!tv/)[^?#]+/
|
||||||
|
)?
|
||||||
|
(?P<id>\d{10})
|
||||||
|
'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# contentData
|
# contentData
|
||||||
'url': 'https://www.hotstar.com/can-you-not-spread-rumours/1000076273',
|
'url': 'https://www.hotstar.com/can-you-not-spread-rumours/1000076273',
|
||||||
@@ -141,7 +148,7 @@ class HotStarIE(HotStarBaseIE):
|
|||||||
|
|
||||||
title = video_data['title']
|
title = video_data['title']
|
||||||
|
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and video_data.get('drmProtected'):
|
if not self.get_param('allow_unplayable_formats') and video_data.get('drmProtected'):
|
||||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||||
|
|
||||||
headers = {'Referer': url}
|
headers = {'Referer': url}
|
||||||
@@ -235,3 +242,41 @@ class HotStarPlaylistIE(HotStarBaseIE):
|
|||||||
if video.get('contentId')]
|
if video.get('contentId')]
|
||||||
|
|
||||||
return self.playlist_result(entries, playlist_id)
|
return self.playlist_result(entries, playlist_id)
|
||||||
|
|
||||||
|
|
||||||
|
class HotStarSeriesIE(HotStarBaseIE):
|
||||||
|
IE_NAME = 'hotstar:series'
|
||||||
|
_VALID_URL = r'(?:https?://)(?:www\.)?hotstar\.com(?:/in)?/tv/[^/]+/(?P<id>\d{10})$'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.hotstar.com/in/tv/radhakrishn/1260000646',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1260000646',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 690,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.hotstar.com/tv/dancee-/1260050431',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1260050431',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 43,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
series_id = self._match_id(url)
|
||||||
|
headers = {
|
||||||
|
'x-country-code': 'IN',
|
||||||
|
'x-platform-code': 'PCTV',
|
||||||
|
}
|
||||||
|
detail_json = self._download_json('https://api.hotstar.com/o/v1/show/detail?contentId=' + series_id,
|
||||||
|
video_id=series_id, headers=headers)
|
||||||
|
id = compat_str(try_get(detail_json, lambda x: x['body']['results']['item']['id'], int))
|
||||||
|
item_json = self._download_json('https://api.hotstar.com/o/v1/tray/g/1/items?etid=0&tao=0&tas=10000&eid=' + id,
|
||||||
|
video_id=series_id, headers=headers)
|
||||||
|
entries = [
|
||||||
|
self.url_result(
|
||||||
|
'https://www.hotstar.com/%d' % video['contentId'],
|
||||||
|
ie=HotStarIE.ie_key(), video_id=video['contentId'])
|
||||||
|
for video in item_json['body']['results']['items']
|
||||||
|
if video.get('contentId')]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, series_id)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class ImgGamingBaseIE(InfoExtractor):
|
|||||||
domain, media_type, media_id, playlist_id = re.match(self._VALID_URL, url).groups()
|
domain, media_type, media_id, playlist_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
if playlist_id:
|
if playlist_id:
|
||||||
if self._downloader.params.get('noplaylist'):
|
if self.get_param('noplaylist'):
|
||||||
self.to_screen('Downloading just video %s because of --no-playlist' % media_id)
|
self.to_screen('Downloading just video %s because of --no-playlist' % media_id)
|
||||||
else:
|
else:
|
||||||
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
|
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class IviIE(InfoExtractor):
|
|||||||
content_format = f.get('content_format')
|
content_format = f.get('content_format')
|
||||||
if not f_url:
|
if not f_url:
|
||||||
continue
|
continue
|
||||||
if (not self._downloader.params.get('allow_unplayable_formats')
|
if (not self.get_param('allow_unplayable_formats')
|
||||||
and ('-MDRM-' in content_format or '-FPS-' in content_format)):
|
and ('-MDRM-' in content_format or '-FPS-' in content_format)):
|
||||||
continue
|
continue
|
||||||
formats.append({
|
formats.append({
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class KalturaIE(InfoExtractor):
|
|||||||
def _extract_urls(webpage):
|
def _extract_urls(webpage):
|
||||||
# Embed codes: https://knowledge.kaltura.com/embedding-kaltura-media-players-your-site
|
# Embed codes: https://knowledge.kaltura.com/embedding-kaltura-media-players-your-site
|
||||||
finditer = (
|
finditer = (
|
||||||
re.finditer(
|
list(re.finditer(
|
||||||
r"""(?xs)
|
r"""(?xs)
|
||||||
kWidget\.(?:thumb)?[Ee]mbed\(
|
kWidget\.(?:thumb)?[Ee]mbed\(
|
||||||
\{.*?
|
\{.*?
|
||||||
@@ -128,8 +128,8 @@ class KalturaIE(InfoExtractor):
|
|||||||
(?P<q2>['"])_?(?P<partner_id>(?:(?!(?P=q2)).)+)(?P=q2),.*?
|
(?P<q2>['"])_?(?P<partner_id>(?:(?!(?P=q2)).)+)(?P=q2),.*?
|
||||||
(?P<q3>['"])entry_?[Ii]d(?P=q3)\s*:\s*
|
(?P<q3>['"])entry_?[Ii]d(?P=q3)\s*:\s*
|
||||||
(?P<q4>['"])(?P<id>(?:(?!(?P=q4)).)+)(?P=q4)(?:,|\s*\})
|
(?P<q4>['"])(?P<id>(?:(?!(?P=q4)).)+)(?P=q4)(?:,|\s*\})
|
||||||
""", webpage)
|
""", webpage))
|
||||||
or re.finditer(
|
or list(re.finditer(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
(?P<q1>["'])
|
(?P<q1>["'])
|
||||||
(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
|
(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
|
||||||
@@ -142,16 +142,16 @@ class KalturaIE(InfoExtractor):
|
|||||||
\[\s*(?P<q2_1>["'])entry_?[Ii]d(?P=q2_1)\s*\]\s*=\s*
|
\[\s*(?P<q2_1>["'])entry_?[Ii]d(?P=q2_1)\s*\]\s*=\s*
|
||||||
)
|
)
|
||||||
(?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
|
(?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
|
||||||
''', webpage)
|
''', webpage))
|
||||||
or re.finditer(
|
or list(re.finditer(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
<(?:iframe[^>]+src|meta[^>]+\bcontent)=(?P<q1>["'])
|
<(?:iframe[^>]+src|meta[^>]+\bcontent)=(?P<q1>["'])\s*
|
||||||
(?:https?:)?//(?:(?:www|cdnapi(?:sec)?)\.)?kaltura\.com/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)
|
(?:https?:)?//(?:(?:www|cdnapi(?:sec)?)\.)?kaltura\.com/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)
|
||||||
(?:(?!(?P=q1)).)*
|
(?:(?!(?P=q1)).)*
|
||||||
[?&;]entry_id=(?P<id>(?:(?!(?P=q1))[^&])+)
|
[?&;]entry_id=(?P<id>(?:(?!(?P=q1))[^&])+)
|
||||||
(?:(?!(?P=q1)).)*
|
(?:(?!(?P=q1)).)*
|
||||||
(?P=q1)
|
(?P=q1)
|
||||||
''', webpage)
|
''', webpage))
|
||||||
)
|
)
|
||||||
urls = []
|
urls = []
|
||||||
for mobj in finditer:
|
for mobj in finditer:
|
||||||
@@ -309,7 +309,7 @@ class KalturaIE(InfoExtractor):
|
|||||||
if f.get('fileExt') == 'chun':
|
if f.get('fileExt') == 'chun':
|
||||||
continue
|
continue
|
||||||
# DRM-protected video, cannot be decrypted
|
# DRM-protected video, cannot be decrypted
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and f.get('fileExt') == 'wvm':
|
if not self.get_param('allow_unplayable_formats') and f.get('fileExt') == 'wvm':
|
||||||
continue
|
continue
|
||||||
if not f.get('fileExt'):
|
if not f.get('fileExt'):
|
||||||
# QT indicates QuickTime; some videos have broken fileExt
|
# QT indicates QuickTime; some videos have broken fileExt
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class LimelightBaseIE(InfoExtractor):
|
|||||||
stream_url = stream.get('url')
|
stream_url = stream.get('url')
|
||||||
if not stream_url or stream_url in urls:
|
if not stream_url or stream_url in urls:
|
||||||
continue
|
continue
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and stream.get('drmProtected'):
|
if not self.get_param('allow_unplayable_formats') and stream.get('drmProtected'):
|
||||||
continue
|
continue
|
||||||
urls.append(stream_url)
|
urls.append(stream_url)
|
||||||
ext = determine_ext(stream_url)
|
ext = determine_ext(stream_url)
|
||||||
@@ -160,7 +160,10 @@ class LimelightBaseIE(InfoExtractor):
|
|||||||
for mobile_url in mobile_item.get('mobileUrls', []):
|
for mobile_url in mobile_item.get('mobileUrls', []):
|
||||||
media_url = mobile_url.get('mobileUrl')
|
media_url = mobile_url.get('mobileUrl')
|
||||||
format_id = mobile_url.get('targetMediaPlatform')
|
format_id = mobile_url.get('targetMediaPlatform')
|
||||||
if not media_url or format_id in ('Widevine', 'SmoothStreaming') or media_url in urls:
|
if not media_url or media_url in urls:
|
||||||
|
continue
|
||||||
|
if (format_id in ('Widevine', 'SmoothStreaming')
|
||||||
|
and not self.get_param('allow_unplayable_formats', False)):
|
||||||
continue
|
continue
|
||||||
urls.append(media_url)
|
urls.append(media_url)
|
||||||
ext = determine_ext(media_url)
|
ext = determine_ext(media_url)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class LiTVIE(InfoExtractor):
|
|||||||
|
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
noplaylist = self._downloader.params.get('noplaylist')
|
noplaylist = self.get_param('noplaylist')
|
||||||
noplaylist_prompt = True
|
noplaylist_prompt = True
|
||||||
if 'force_noplaylist' in data:
|
if 'force_noplaylist' in data:
|
||||||
noplaylist = data['force_noplaylist']
|
noplaylist = data['force_noplaylist']
|
||||||
|
|||||||
@@ -15,33 +15,39 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class MedalTVIE(InfoExtractor):
|
class MedalTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?medal\.tv/clips/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?medal\.tv/clips/(?P<id>[^/?#&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://medal.tv/clips/34934644/3Is9zyGMoBMr',
|
'url': 'https://medal.tv/clips/2mA60jWAGQCBH',
|
||||||
'md5': '7b07b064331b1cf9e8e5c52a06ae68fa',
|
'md5': '7b07b064331b1cf9e8e5c52a06ae68fa',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '34934644',
|
'id': '2mA60jWAGQCBH',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Quad Cold',
|
'title': 'Quad Cold',
|
||||||
'description': 'Medal,https://medal.tv/desktop/',
|
'description': 'Medal,https://medal.tv/desktop/',
|
||||||
'uploader': 'MowgliSB',
|
'uploader': 'MowgliSB',
|
||||||
'timestamp': 1603165266,
|
'timestamp': 1603165266,
|
||||||
'upload_date': '20201020',
|
'upload_date': '20201020',
|
||||||
'uploader_id': 10619174,
|
'uploader_id': '10619174',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://medal.tv/clips/36787208',
|
'url': 'https://medal.tv/clips/2um24TWdty0NA',
|
||||||
'md5': 'b6dc76b78195fff0b4f8bf4a33ec2148',
|
'md5': 'b6dc76b78195fff0b4f8bf4a33ec2148',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '36787208',
|
'id': '2um24TWdty0NA',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'u tk me i tk u bigger',
|
'title': 'u tk me i tk u bigger',
|
||||||
'description': 'Medal,https://medal.tv/desktop/',
|
'description': 'Medal,https://medal.tv/desktop/',
|
||||||
'uploader': 'Mimicc',
|
'uploader': 'Mimicc',
|
||||||
'timestamp': 1605580939,
|
'timestamp': 1605580939,
|
||||||
'upload_date': '20201117',
|
'upload_date': '20201117',
|
||||||
'uploader_id': 5156321,
|
'uploader_id': '5156321',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://medal.tv/clips/37rMeFpryCC-9',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://medal.tv/clips/2WRj40tpY_EU9',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ _ID_RE = r'(?:[0-9a-f]{32,34}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0
|
|||||||
|
|
||||||
|
|
||||||
class MediasiteIE(InfoExtractor):
|
class MediasiteIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?xi)https?://[^/]+/Mediasite/(?:Play|Showcase/(?:default|livebroadcast)/Presentation)/(?P<id>%s)(?P<query>\?[^#]+|)' % _ID_RE
|
_VALID_URL = r'(?xi)https?://[^/]+/Mediasite/(?:Play|Showcase/[^/#?]+/Presentation)/(?P<id>%s)(?P<query>\?[^#]+|)' % _ID_RE
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271681e4f199af3c60d1f82869b1d',
|
'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271681e4f199af3c60d1f82869b1d',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
@@ -14,7 +15,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class MxplayerIE(InfoExtractor):
|
class MxplayerIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?mxplayer\.in/(?:show|movie)/(?:(?P<display_id>[-/a-z0-9]+)-)?(?P<id>[a-z0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?mxplayer\.in/(?:movie|show/[-\w]+/[-\w]+)/(?P<display_id>[-\w]+)-(?P<id>\w+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.mxplayer.in/movie/watch-knock-knock-hindi-dubbed-movie-online-b9fa28df3bfb8758874735bbd7d2655a?watch=true',
|
'url': 'https://www.mxplayer.in/movie/watch-knock-knock-hindi-dubbed-movie-online-b9fa28df3bfb8758874735bbd7d2655a?watch=true',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -117,7 +118,7 @@ class MxplayerIE(InfoExtractor):
|
|||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id.replace('/', '-'),
|
'display_id': display_id,
|
||||||
'title': video_dict['title'] or self._og_search_title(webpage),
|
'title': video_dict['title'] or self._og_search_title(webpage),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'description': video_dict.get('description'),
|
'description': video_dict.get('description'),
|
||||||
@@ -125,3 +126,46 @@ class MxplayerIE(InfoExtractor):
|
|||||||
'series': try_get(video_dict, lambda x: x['container']['container']['title']),
|
'series': try_get(video_dict, lambda x: x['container']['container']['title']),
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MxplayerShowIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?:https?://)(?:www\.)?mxplayer\.in/show/(?P<display_id>[-\w]+)-(?P<id>\w+)/?(?:$|[#?])'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.mxplayer.in/show/watch-chakravartin-ashoka-samrat-series-online-a8f44e3cc0814b5601d17772cedf5417',
|
||||||
|
'playlist_mincount': 440,
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a8f44e3cc0814b5601d17772cedf5417',
|
||||||
|
'title': 'Watch Chakravartin Ashoka Samrat Series Online',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
_API_SHOW_URL = "https://api.mxplay.com/v1/web/detail/tab/tvshowseasons?type=tv_show&id={}&device-density=2&platform=com.mxplay.desktop&content-languages=hi,en"
|
||||||
|
_API_EPISODES_URL = "https://api.mxplay.com/v1/web/detail/tab/tvshowepisodes?type=season&id={}&device-density=1&platform=com.mxplay.desktop&content-languages=hi,en&{}"
|
||||||
|
|
||||||
|
def _entries(self, show_id):
|
||||||
|
show_json = self._download_json(
|
||||||
|
self._API_SHOW_URL.format(show_id),
|
||||||
|
video_id=show_id, headers={'Referer': 'https://mxplayer.in'})
|
||||||
|
page_num = 0
|
||||||
|
for season in show_json.get('items') or []:
|
||||||
|
season_id = try_get(season, lambda x: x['id'], compat_str)
|
||||||
|
next_url = ''
|
||||||
|
while next_url is not None:
|
||||||
|
page_num += 1
|
||||||
|
season_json = self._download_json(
|
||||||
|
self._API_EPISODES_URL.format(season_id, next_url),
|
||||||
|
video_id=season_id,
|
||||||
|
headers={'Referer': 'https://mxplayer.in'},
|
||||||
|
note='Downloading JSON metadata page %d' % page_num)
|
||||||
|
for episode in season_json.get('items') or []:
|
||||||
|
video_url = episode['webUrl']
|
||||||
|
yield self.url_result(
|
||||||
|
'https://mxplayer.in%s' % video_url,
|
||||||
|
ie=MxplayerIE.ie_key(), video_id=video_url.split('-')[-1])
|
||||||
|
next_url = season_json.get('next')
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id, show_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
return self.playlist_result(
|
||||||
|
self._entries(show_id), playlist_id=show_id,
|
||||||
|
playlist_title=display_id.replace('-', ' ').title())
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class NBAWatchIE(NBAWatchBaseIE):
|
|||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
collection_id = compat_parse_qs(compat_urllib_parse_urlparse(url).query).get('collection', [None])[0]
|
collection_id = compat_parse_qs(compat_urllib_parse_urlparse(url).query).get('collection', [None])[0]
|
||||||
if collection_id:
|
if collection_id:
|
||||||
if self._downloader.params.get('noplaylist'):
|
if self.get_param('noplaylist'):
|
||||||
self.to_screen('Downloading just video %s because of --no-playlist' % display_id)
|
self.to_screen('Downloading just video %s because of --no-playlist' % display_id)
|
||||||
else:
|
else:
|
||||||
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % collection_id)
|
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % collection_id)
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ from ..utils import (
|
|||||||
|
|
||||||
class NebulaIE(InfoExtractor):
|
class NebulaIE(InfoExtractor):
|
||||||
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?watchnebula\.com/videos/(?P<id>[-\w]+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:watchnebula\.com|nebula\.app)/videos/(?P<id>[-\w]+)'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'https://watchnebula.com/videos/that-time-disney-remade-beauty-and-the-beast',
|
'url': 'https://nebula.app/videos/that-time-disney-remade-beauty-and-the-beast',
|
||||||
'md5': 'fe79c4df8b3aa2fea98a93d027465c7e',
|
'md5': 'fe79c4df8b3aa2fea98a93d027465c7e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '5c271b40b13fd613090034fd',
|
'id': '5c271b40b13fd613090034fd',
|
||||||
@@ -36,7 +36,7 @@ class NebulaIE(InfoExtractor):
|
|||||||
'skip': 'All Nebula content requires authentication',
|
'skip': 'All Nebula content requires authentication',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'https://watchnebula.com/videos/the-logistics-of-d-day-landing-craft-how-the-allies-got-ashore',
|
'url': 'https://nebula.app/videos/the-logistics-of-d-day-landing-craft-how-the-allies-got-ashore',
|
||||||
'md5': '6d4edd14ce65720fa63aba5c583fb328',
|
'md5': '6d4edd14ce65720fa63aba5c583fb328',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '5e7e78171aaf320001fbd6be',
|
'id': '5e7e78171aaf320001fbd6be',
|
||||||
@@ -54,7 +54,7 @@ class NebulaIE(InfoExtractor):
|
|||||||
'skip': 'All Nebula content requires authentication',
|
'skip': 'All Nebula content requires authentication',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'https://watchnebula.com/videos/money-episode-1-the-draw',
|
'url': 'https://nebula.app/videos/money-episode-1-the-draw',
|
||||||
'md5': '8c7d272910eea320f6f8e6d3084eecf5',
|
'md5': '8c7d272910eea320f6f8e6d3084eecf5',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '5e779ebdd157bc0001d1c75a',
|
'id': '5e779ebdd157bc0001d1c75a',
|
||||||
@@ -71,6 +71,10 @@ class NebulaIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'skip': 'All Nebula content requires authentication',
|
'skip': 'All Nebula content requires authentication',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://watchnebula.com/videos/money-episode-1-the-draw',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
_NETRC_MACHINE = 'watchnebula'
|
_NETRC_MACHINE = 'watchnebula'
|
||||||
|
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ class NetEaseMusicProgramIE(NetEaseMusicBaseIE):
|
|||||||
name = info['name']
|
name = info['name']
|
||||||
description = info['description']
|
description = info['description']
|
||||||
|
|
||||||
if not info['songs'] or self._downloader.params.get('noplaylist'):
|
if not info['songs'] or self.get_param('noplaylist'):
|
||||||
if info['songs']:
|
if info['songs']:
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'Downloading just the main audio %s because of --no-playlist'
|
'Downloading just the main audio %s because of --no-playlist'
|
||||||
|
|||||||
@@ -164,6 +164,11 @@ class NiconicoIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.|secure\.|sp\.)?nicovideo\.jp/watch/(?P<id>(?:[a-z]{2})?[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.|secure\.|sp\.)?nicovideo\.jp/watch/(?P<id>(?:[a-z]{2})?[0-9]+)'
|
||||||
_NETRC_MACHINE = 'niconico'
|
_NETRC_MACHINE = 'niconico'
|
||||||
|
|
||||||
|
_API_HEADERS = {
|
||||||
|
'X-Frontend-ID': '6',
|
||||||
|
'X-Frontend-Version': '0'
|
||||||
|
}
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
@@ -197,46 +202,48 @@ class NiconicoIE(InfoExtractor):
|
|||||||
|
|
||||||
video_id, video_src_id, audio_src_id = info_dict['url'].split(':')[1].split('/')
|
video_id, video_src_id, audio_src_id = info_dict['url'].split(':')[1].split('/')
|
||||||
|
|
||||||
# Get video webpage for API data.
|
api_data = (
|
||||||
webpage, handle = self._download_webpage_handle(
|
info_dict.get('_api_data')
|
||||||
'http://www.nicovideo.jp/watch/' + video_id, video_id)
|
or self._parse_json(
|
||||||
|
self._html_search_regex(
|
||||||
api_data = self._parse_json(self._html_search_regex(
|
'data-api-data="([^"]+)"',
|
||||||
'data-api-data="([^"]+)"', webpage,
|
self._download_webpage('http://www.nicovideo.jp/watch/' + video_id, video_id),
|
||||||
'API data', default='{}'), video_id)
|
'API data', default='{}'),
|
||||||
|
video_id))
|
||||||
|
|
||||||
session_api_data = try_get(api_data, lambda x: x['media']['delivery']['movie']['session'])
|
session_api_data = try_get(api_data, lambda x: x['media']['delivery']['movie']['session'])
|
||||||
session_api_endpoint = try_get(session_api_data, lambda x: x['urls'][0])
|
session_api_endpoint = try_get(session_api_data, lambda x: x['urls'][0])
|
||||||
|
|
||||||
# ping
|
def ping():
|
||||||
self._download_json(
|
status = try_get(
|
||||||
'https://nvapi.nicovideo.jp/v1/2ab0cbaa/watch', video_id,
|
self._download_json(
|
||||||
query={'t': try_get(api_data, lambda x: x['video']['dmcInfo']['tracking_id'])},
|
'https://nvapi.nicovideo.jp/v1/2ab0cbaa/watch', video_id,
|
||||||
headers={
|
query={'t': try_get(api_data, lambda x: x['media']['delivery']['trackingId'])},
|
||||||
'Origin': 'https://www.nicovideo.jp',
|
note='Acquiring permission for downloading video',
|
||||||
'Referer': 'https://www.nicovideo.jp/watch/' + video_id,
|
headers=self._API_HEADERS),
|
||||||
'X-Frontend-Id': '6',
|
lambda x: x['meta']['status'])
|
||||||
'X-Frontend-Version': '0'
|
if status != 200:
|
||||||
})
|
self.report_warning('Failed to acquire permission for playing video. The video may not download.')
|
||||||
|
|
||||||
yesno = lambda x: 'yes' if x else 'no'
|
yesno = lambda x: 'yes' if x else 'no'
|
||||||
|
|
||||||
# m3u8 (encryption)
|
# m3u8 (encryption)
|
||||||
if 'encryption' in (try_get(api_data, lambda x: x['media']['delivery']['movie']) or {}):
|
if try_get(api_data, lambda x: x['media']['delivery']['encryption']) is not None:
|
||||||
protocol = 'm3u8'
|
protocol = 'm3u8'
|
||||||
|
encryption = self._parse_json(session_api_data['token'], video_id)['hls_encryption']
|
||||||
session_api_http_parameters = {
|
session_api_http_parameters = {
|
||||||
'parameters': {
|
'parameters': {
|
||||||
'hls_parameters': {
|
'hls_parameters': {
|
||||||
'encryption': {
|
'encryption': {
|
||||||
'hls_encryption_v1': {
|
encryption: {
|
||||||
'encrypted_key': try_get(api_data, lambda x: x['video']['dmcInfo']['encryption']['hls_encryption_v1']['encrypted_key']),
|
'encrypted_key': try_get(api_data, lambda x: x['media']['delivery']['encryption']['encryptedKey']),
|
||||||
'key_uri': try_get(api_data, lambda x: x['video']['dmcInfo']['encryption']['hls_encryption_v1']['key_uri'])
|
'key_uri': try_get(api_data, lambda x: x['media']['delivery']['encryption']['keyUri'])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'transfer_preset': '',
|
'transfer_preset': '',
|
||||||
'use_ssl': yesno(session_api_endpoint['is_ssl']),
|
'use_ssl': yesno(session_api_endpoint['isSsl']),
|
||||||
'use_well_known_port': yesno(session_api_endpoint['is_well_known_port']),
|
'use_well_known_port': yesno(session_api_endpoint['isWellKnownPort']),
|
||||||
'segment_duration': 6000
|
'segment_duration': 6000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +317,8 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'url': session_api_endpoint['url'] + '/' + session_response['data']['session']['id'] + '?_format=json&_method=PUT',
|
'url': session_api_endpoint['url'] + '/' + session_response['data']['session']['id'] + '?_format=json&_method=PUT',
|
||||||
'data': json.dumps(session_response['data']),
|
'data': json.dumps(session_response['data']),
|
||||||
# interval, convert milliseconds to seconds, then halve to make a buffer.
|
# interval, convert milliseconds to seconds, then halve to make a buffer.
|
||||||
'interval': float_or_none(session_api_data.get('heartbeatLifetime'), scale=2000),
|
'interval': float_or_none(session_api_data.get('heartbeatLifetime'), scale=3000),
|
||||||
|
'ping': ping
|
||||||
}
|
}
|
||||||
|
|
||||||
return info_dict, heartbeat_info_dict
|
return info_dict, heartbeat_info_dict
|
||||||
@@ -400,7 +408,7 @@ class NiconicoIE(InfoExtractor):
|
|||||||
# Get HTML5 videos info
|
# Get HTML5 videos info
|
||||||
quality_info = try_get(api_data, lambda x: x['media']['delivery']['movie'])
|
quality_info = try_get(api_data, lambda x: x['media']['delivery']['movie'])
|
||||||
if not quality_info:
|
if not quality_info:
|
||||||
raise ExtractorError('The video can\'t downloaded.', expected=True)
|
raise ExtractorError('The video can\'t be downloaded', expected=True)
|
||||||
|
|
||||||
for audio_quality in quality_info.get('audios') or {}:
|
for audio_quality in quality_info.get('audios') or {}:
|
||||||
for video_quality in quality_info.get('videos') or {}:
|
for video_quality in quality_info.get('videos') or {}:
|
||||||
@@ -412,9 +420,7 @@ class NiconicoIE(InfoExtractor):
|
|||||||
# Get flv/swf info
|
# Get flv/swf info
|
||||||
timestamp = None
|
timestamp = None
|
||||||
video_real_url = try_get(api_data, lambda x: x['video']['smileInfo']['url'])
|
video_real_url = try_get(api_data, lambda x: x['video']['smileInfo']['url'])
|
||||||
if not video_real_url:
|
if video_real_url:
|
||||||
self.report_warning('Unable to obtain smile video information')
|
|
||||||
else:
|
|
||||||
is_economy = video_real_url.endswith('low')
|
is_economy = video_real_url.endswith('low')
|
||||||
|
|
||||||
if is_economy:
|
if is_economy:
|
||||||
@@ -486,14 +492,12 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'filesize': filesize
|
'filesize': filesize
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(formats) == 0:
|
|
||||||
raise ExtractorError('Unable to find video info.')
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
# Start extracting information
|
# Start extracting information
|
||||||
title = (
|
title = (
|
||||||
get_video_info_web(['originalTitle', 'title'])
|
get_video_info_xml('title') # prefer to get the untranslated original title
|
||||||
|
or get_video_info_web(['originalTitle', 'title'])
|
||||||
or self._og_search_title(webpage, default=None)
|
or self._og_search_title(webpage, default=None)
|
||||||
or self._html_search_regex(
|
or self._html_search_regex(
|
||||||
r'<span[^>]+class="videoHeaderTitle"[^>]*>([^<]+)</span>',
|
r'<span[^>]+class="videoHeaderTitle"[^>]*>([^<]+)</span>',
|
||||||
@@ -507,7 +511,9 @@ class NiconicoIE(InfoExtractor):
|
|||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
self._html_search_regex(r'<meta property="og:image" content="([^"]+)">', webpage, 'thumbnail data', default=None)
|
self._html_search_regex(r'<meta property="og:image" content="([^"]+)">', webpage, 'thumbnail data', default=None)
|
||||||
or get_video_info_web(['thumbnail_url', 'largeThumbnailURL', 'thumbnailURL'])
|
or dict_get( # choose highest from 720p to 240p
|
||||||
|
get_video_info_web('thumbnail'),
|
||||||
|
['ogp', 'player', 'largeUrl', 'middleUrl', 'url'])
|
||||||
or self._html_search_meta('image', webpage, 'thumbnail', default=None)
|
or self._html_search_meta('image', webpage, 'thumbnail', default=None)
|
||||||
or video_detail.get('thumbnail'))
|
or video_detail.get('thumbnail'))
|
||||||
|
|
||||||
@@ -582,6 +588,7 @@ class NiconicoIE(InfoExtractor):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
'_api_data': api_data,
|
||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
@@ -616,24 +623,19 @@ class NiconicoPlaylistIE(InfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
_API_HEADERS = {
|
||||||
|
'X-Frontend-ID': '6',
|
||||||
|
'X-Frontend-Version': '0'
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
list_id = self._match_id(url)
|
list_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, list_id)
|
|
||||||
|
|
||||||
header = self._parse_json(self._html_search_regex(
|
|
||||||
r'data-common-header="([^"]+)"', webpage,
|
|
||||||
'webpage header'), list_id)
|
|
||||||
frontendId = header.get('initConfig').get('frontendId')
|
|
||||||
frontendVersion = header.get('initConfig').get('frontendVersion')
|
|
||||||
|
|
||||||
def get_page_data(pagenum, pagesize):
|
def get_page_data(pagenum, pagesize):
|
||||||
return self._download_json(
|
return self._download_json(
|
||||||
'http://nvapi.nicovideo.jp/v2/mylists/' + list_id, list_id,
|
'http://nvapi.nicovideo.jp/v2/mylists/' + list_id, list_id,
|
||||||
query={'page': 1 + pagenum, 'pageSize': pagesize},
|
query={'page': 1 + pagenum, 'pageSize': pagesize},
|
||||||
headers={
|
headers=self._API_HEADERS).get('data').get('mylist')
|
||||||
'X-Frontend-Id': frontendId,
|
|
||||||
'X-Frontend-Version': frontendVersion,
|
|
||||||
}).get('data').get('mylist')
|
|
||||||
|
|
||||||
data = get_page_data(0, 1)
|
data = get_page_data(0, 1)
|
||||||
title = data.get('name')
|
title = data.get('name')
|
||||||
@@ -669,20 +671,20 @@ class NiconicoUserIE(InfoExtractor):
|
|||||||
'playlist_mincount': 101,
|
'playlist_mincount': 101,
|
||||||
}
|
}
|
||||||
_API_URL = "https://nvapi.nicovideo.jp/v1/users/%s/videos?sortKey=registeredAt&sortOrder=desc&pageSize=%s&page=%s"
|
_API_URL = "https://nvapi.nicovideo.jp/v1/users/%s/videos?sortKey=registeredAt&sortOrder=desc&pageSize=%s&page=%s"
|
||||||
_api_headers = {
|
|
||||||
'X-Frontend-ID': '6',
|
|
||||||
'X-Frontend-Version': '0',
|
|
||||||
'X-Niconico-Language': 'en-us'
|
|
||||||
}
|
|
||||||
_PAGE_SIZE = 100
|
_PAGE_SIZE = 100
|
||||||
|
|
||||||
|
_API_HEADERS = {
|
||||||
|
'X-Frontend-ID': '6',
|
||||||
|
'X-Frontend-Version': '0'
|
||||||
|
}
|
||||||
|
|
||||||
def _entries(self, list_id, ):
|
def _entries(self, list_id, ):
|
||||||
total_count = 1
|
total_count = 1
|
||||||
count = page_num = 0
|
count = page_num = 0
|
||||||
while count < total_count:
|
while count < total_count:
|
||||||
json_parsed = self._download_json(
|
json_parsed = self._download_json(
|
||||||
self._API_URL % (list_id, self._PAGE_SIZE, page_num + 1), list_id,
|
self._API_URL % (list_id, self._PAGE_SIZE, page_num + 1), list_id,
|
||||||
headers=self._api_headers,
|
headers=self._API_HEADERS,
|
||||||
note='Downloading JSON metadata%s' % (' page %d' % page_num if page_num else ''))
|
note='Downloading JSON metadata%s' % (' page %d' % page_num if page_num else ''))
|
||||||
if not page_num:
|
if not page_num:
|
||||||
total_count = int_or_none(json_parsed['data'].get('totalCount'))
|
total_count = int_or_none(json_parsed['data'].get('totalCount'))
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class NineCNineMediaIE(InfoExtractor):
|
|||||||
'$include': '[HasClosedCaptions]',
|
'$include': '[HasClosedCaptions]',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (not self._downloader.params.get('allow_unplayable_formats')
|
if (not self.get_param('allow_unplayable_formats')
|
||||||
and try_get(content_package, lambda x: x['Constraints']['Security']['Type'])):
|
and try_get(content_package, lambda x: x['Constraints']['Security']['Type'])):
|
||||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class NineNowIE(InfoExtractor):
|
|||||||
|
|
||||||
video_data = common_data['video']
|
video_data = common_data['video']
|
||||||
|
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and video_data.get('drm'):
|
if not self.get_param('allow_unplayable_formats') and video_data.get('drm'):
|
||||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||||
|
|
||||||
brightcove_id = video_data.get('brightcoveId') or 'ref:' + video_data['referenceId']
|
brightcove_id = video_data.get('brightcoveId') or 'ref:' + video_data['referenceId']
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class NPOIE(NPOBaseIE):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if not formats:
|
if not formats:
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and drm:
|
if not self.get_param('allow_unplayable_formats') and drm:
|
||||||
self.raise_no_formats('This video is DRM protected.', expected=True)
|
self.raise_no_formats('This video is DRM protected.', expected=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class NYTimesBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
urls = []
|
urls = []
|
||||||
formats = []
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
for video in video_data.get('renditions', []):
|
for video in video_data.get('renditions', []):
|
||||||
video_url = video.get('url')
|
video_url = video.get('url')
|
||||||
format_id = video.get('type')
|
format_id = video.get('type')
|
||||||
@@ -54,9 +55,11 @@ class NYTimesBaseIE(InfoExtractor):
|
|||||||
urls.append(video_url)
|
urls.append(video_url)
|
||||||
ext = mimetype2ext(video.get('mimetype')) or determine_ext(video_url)
|
ext = mimetype2ext(video.get('mimetype')) or determine_ext(video_url)
|
||||||
if ext == 'm3u8':
|
if ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
video_url, video_id, 'mp4', 'm3u8_native',
|
video_url, video_id, 'mp4', 'm3u8_native',
|
||||||
m3u8_id=format_id or 'hls', fatal=False))
|
m3u8_id=format_id or 'hls', fatal=False)
|
||||||
|
formats.extend(m3u8_fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, m3u8_subs)
|
||||||
elif ext == 'mpd':
|
elif ext == 'mpd':
|
||||||
continue
|
continue
|
||||||
# formats.extend(self._extract_mpd_formats(
|
# formats.extend(self._extract_mpd_formats(
|
||||||
@@ -96,6 +99,7 @@ class NYTimesBaseIE(InfoExtractor):
|
|||||||
'uploader': video_data.get('byline'),
|
'uploader': video_data.get('byline'),
|
||||||
'duration': float_or_none(video_data.get('duration'), 1000),
|
'duration': float_or_none(video_data.get('duration'), 1000),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,8 +247,7 @@ class OdnoklassnikiIE(InfoExtractor):
|
|||||||
m3u8_url = metadata.get('hlsMasterPlaylistUrl')
|
m3u8_url = metadata.get('hlsMasterPlaylistUrl')
|
||||||
if m3u8_url:
|
if m3u8_url:
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
m3u8_url, video_id, 'mp4', entry_protocol='m3u8',
|
m3u8_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
m3u8_id='hls', fatal=False))
|
|
||||||
rtmp_url = metadata.get('rtmpUrl')
|
rtmp_url = metadata.get('rtmpUrl')
|
||||||
if rtmp_url:
|
if rtmp_url:
|
||||||
formats.append({
|
formats.append({
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class OnetChannelIE(OnetBaseIE):
|
|||||||
video_id = remove_start(current_clip_info['ckmId'], 'mvp:')
|
video_id = remove_start(current_clip_info['ckmId'], 'mvp:')
|
||||||
video_name = url_basename(current_clip_info['url'])
|
video_name = url_basename(current_clip_info['url'])
|
||||||
|
|
||||||
if self._downloader.params.get('noplaylist'):
|
if self.get_param('noplaylist'):
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'Downloading just video %s because of --no-playlist' % video_name)
|
'Downloading just video %s because of --no-playlist' % video_name)
|
||||||
return self._extract_from_id(video_id, webpage)
|
return self._extract_from_id(video_id, webpage)
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class ORFRadioIE(InfoExtractor):
|
|||||||
duration = end - start if end and start else None
|
duration = end - start if end and start else None
|
||||||
entries.append({
|
entries.append({
|
||||||
'id': loop_stream_id.replace('.mp3', ''),
|
'id': loop_stream_id.replace('.mp3', ''),
|
||||||
'url': 'http://loopstream01.apa.at/?channel=%s&id=%s' % (self._LOOP_STATION, loop_stream_id),
|
'url': 'https://loopstream01.apa.at/?channel=%s&id=%s' % (self._LOOP_STATION, loop_stream_id),
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': clean_html(data.get('subtitle')),
|
'description': clean_html(data.get('subtitle')),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
try_get,
|
||||||
|
unified_timestamp,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ParlviewIE(InfoExtractor):
|
||||||
|
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?parlview\.aph\.gov\.au/(?:[^/]+)?\bvideoID=(?P<id>\d{6})'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://parlview.aph.gov.au/mediaPlayer.php?videoID=542661',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '542661',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Australia's Family Law System [Part 2]",
|
||||||
|
'duration': 5799,
|
||||||
|
'description': 'md5:7099883b391619dbae435891ca871a62',
|
||||||
|
'timestamp': 1621430700,
|
||||||
|
'upload_date': '20210519',
|
||||||
|
'uploader': 'Joint Committee',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://parlview.aph.gov.au/mediaPlayer.php?videoID=539936',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
_API_URL = 'https://parlview.aph.gov.au/api_v3/1/playback/getUniversalPlayerConfig?videoID=%s&format=json'
|
||||||
|
_MEDIA_INFO_URL = 'https://parlview.aph.gov.au/ajaxPlayer.php?videoID=%s&tabNum=4&action=loadTab'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
media = self._download_json(self._API_URL % video_id, video_id).get('media')
|
||||||
|
timestamp = try_get(media, lambda x: x['timeMap']['source']['timecode_offsets'][0], compat_str) or '/'
|
||||||
|
|
||||||
|
stream = try_get(media, lambda x: x['renditions'][0], dict)
|
||||||
|
if not stream:
|
||||||
|
self.raise_no_formats('No streams were detected')
|
||||||
|
elif stream.get('streamType') != 'VOD':
|
||||||
|
self.raise_no_formats('Unknown type of stream was detected: "%s"' % str(stream.get('streamType')))
|
||||||
|
formats = self._extract_m3u8_formats(stream['url'], video_id, 'mp4', 'm3u8_native')
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
media_info = self._download_webpage(
|
||||||
|
self._MEDIA_INFO_URL % video_id, video_id, note='Downloading media info', fatal=False)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': url,
|
||||||
|
'title': self._html_search_regex(r'<h2>([^<]+)<', webpage, 'title', fatal=False),
|
||||||
|
'formats': formats,
|
||||||
|
'duration': int_or_none(media.get('duration')),
|
||||||
|
'timestamp': unified_timestamp(timestamp.split('/', 1)[1].replace('_', ' ')),
|
||||||
|
'description': self._html_search_regex(
|
||||||
|
r'<div[^>]+class="descripti?on"[^>]*>[^>]+<strong>[^>]+>[^>]+>([^<]+)',
|
||||||
|
webpage, 'description', fatal=False),
|
||||||
|
'uploader': self._html_search_regex(
|
||||||
|
r'<td>[^>]+>Channel:[^>]+>([^<]+)', media_info, 'channel', fatal=False),
|
||||||
|
'thumbnail': media.get('staticImage'),
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .vimeo import VimeoIE
|
||||||
|
|
||||||
|
from ..compat import compat_urllib_parse_unquote
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
clean_html,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
@@ -11,6 +14,7 @@ from ..utils import (
|
|||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
try_get,
|
try_get,
|
||||||
|
url_or_none
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,6 +67,20 @@ class PatreonIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.patreon.com/posts/743933',
|
'url': 'https://www.patreon.com/posts/743933',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.patreon.com/posts/kitchen-as-seen-51706779',
|
||||||
|
'md5': '96656690071f6d64895866008484251b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '555089736',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'KITCHEN AS SEEN ON DEEZ NUTS EXTENDED!',
|
||||||
|
'uploader': 'Cold Ones',
|
||||||
|
'thumbnail': 're:^https?://.*$',
|
||||||
|
'upload_date': '20210526',
|
||||||
|
'description': 'md5:557a409bd79d3898689419094934ba79',
|
||||||
|
'uploader_id': '14936315',
|
||||||
|
},
|
||||||
|
'skip': 'Patron-only content'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
# Currently Patreon exposes download URL via hidden CSS, so login is not
|
# Currently Patreon exposes download URL via hidden CSS, so login is not
|
||||||
@@ -136,6 +154,19 @@ class PatreonIE(InfoExtractor):
|
|||||||
'uploader_url': user_attributes.get('url'),
|
'uploader_url': user_attributes.get('url'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if not info.get('url'):
|
||||||
|
# handle Vimeo embeds
|
||||||
|
if try_get(attributes, lambda x: x['embed']['provider']) == 'Vimeo':
|
||||||
|
embed_html = try_get(attributes, lambda x: x['embed']['html'])
|
||||||
|
v_url = url_or_none(compat_urllib_parse_unquote(
|
||||||
|
self._search_regex(r'src=(https%3A%2F%2Fplayer\.vimeo\.com.+)%3F', embed_html, 'vimeo url', fatal=False)))
|
||||||
|
if v_url:
|
||||||
|
info.update({
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': VimeoIE._smuggle_referrer(v_url, 'https://patreon.com'),
|
||||||
|
'ie_key': 'Vimeo',
|
||||||
|
})
|
||||||
|
|
||||||
if not info.get('url'):
|
if not info.get('url'):
|
||||||
embed_url = try_get(attributes, lambda x: x['embed']['url'])
|
embed_url = try_get(attributes, lambda x: x['embed']['url'])
|
||||||
if embed_url:
|
if embed_url:
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class PhilharmonieDeParisIE(InfoExtractor):
|
|||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id='hls', fatal=False))
|
m3u8_id='hls', fatal=False))
|
||||||
if not formats and not self._downloader.params.get('ignore_no_formats'):
|
if not formats and not self.get_param('ignore_no_formats'):
|
||||||
return
|
return
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
return {
|
return {
|
||||||
|
|||||||
+28
-23
@@ -9,8 +9,9 @@ from ..compat import compat_str
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
merge_dicts,
|
merge_dicts,
|
||||||
|
try_get,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
xpath_text,
|
urljoin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -27,10 +28,11 @@ class PhoenixIE(ZDFBaseIE):
|
|||||||
'title': 'Wohin führt der Protest in der Pandemie?',
|
'title': 'Wohin führt der Protest in der Pandemie?',
|
||||||
'description': 'md5:7d643fe7f565e53a24aac036b2122fbd',
|
'description': 'md5:7d643fe7f565e53a24aac036b2122fbd',
|
||||||
'duration': 1691,
|
'duration': 1691,
|
||||||
'timestamp': 1613906100,
|
'timestamp': 1613902500,
|
||||||
'upload_date': '20210221',
|
'upload_date': '20210221',
|
||||||
'uploader': 'Phoenix',
|
'uploader': 'Phoenix',
|
||||||
'channel': 'corona nachgehakt',
|
'series': 'corona nachgehakt',
|
||||||
|
'episode': 'Wohin führt der Protest in der Pandemie?',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# Youtube embed
|
# Youtube embed
|
||||||
@@ -79,50 +81,53 @@ class PhoenixIE(ZDFBaseIE):
|
|||||||
|
|
||||||
video_id = compat_str(video.get('basename') or video.get('content'))
|
video_id = compat_str(video.get('basename') or video.get('content'))
|
||||||
|
|
||||||
details = self._download_xml(
|
details = self._download_json(
|
||||||
'https://www.phoenix.de/php/mediaplayer/data/beitrags_details.php',
|
'https://www.phoenix.de/php/mediaplayer/data/beitrags_details.php',
|
||||||
video_id, 'Downloading details XML', query={
|
video_id, 'Downloading details JSON', query={
|
||||||
'ak': 'web',
|
'ak': 'web',
|
||||||
'ptmd': 'true',
|
'ptmd': 'true',
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'profile': 'player2',
|
'profile': 'player2',
|
||||||
})
|
})
|
||||||
|
|
||||||
title = title or xpath_text(
|
title = title or details['title']
|
||||||
details, './/information/title', 'title', fatal=True)
|
content_id = details['tracking']['nielsen']['content']['assetid']
|
||||||
content_id = xpath_text(
|
|
||||||
details, './/video/details/basename', 'content id', fatal=True)
|
|
||||||
|
|
||||||
info = self._extract_ptmd(
|
info = self._extract_ptmd(
|
||||||
'https://tmd.phoenix.de/tmd/2/ngplayer_2_3/vod/ptmd/phoenix/%s' % content_id,
|
'https://tmd.phoenix.de/tmd/2/ngplayer_2_3/vod/ptmd/phoenix/%s' % content_id,
|
||||||
content_id, None, url)
|
content_id, None, url)
|
||||||
|
|
||||||
timestamp = unified_timestamp(xpath_text(details, './/details/airtime'))
|
duration = int_or_none(try_get(
|
||||||
|
details, lambda x: x['tracking']['nielsen']['content']['length']))
|
||||||
|
timestamp = unified_timestamp(details.get('editorialDate'))
|
||||||
|
series = try_get(
|
||||||
|
details, lambda x: x['tracking']['nielsen']['content']['program'],
|
||||||
|
compat_str)
|
||||||
|
episode = title if details.get('contentType') == 'episode' else None
|
||||||
|
|
||||||
thumbnails = []
|
thumbnails = []
|
||||||
for node in details.findall('.//teaserimages/teaserimage'):
|
teaser_images = try_get(details, lambda x: x['teaserImageRef']['layouts'], dict) or {}
|
||||||
thumbnail_url = node.text
|
for thumbnail_key, thumbnail_url in teaser_images.items():
|
||||||
|
thumbnail_url = urljoin(url, thumbnail_url)
|
||||||
if not thumbnail_url:
|
if not thumbnail_url:
|
||||||
continue
|
continue
|
||||||
thumbnail = {
|
thumbnail = {
|
||||||
'url': thumbnail_url,
|
'url': thumbnail_url,
|
||||||
}
|
}
|
||||||
thumbnail_key = node.get('key')
|
m = re.match('^([0-9]+)x([0-9]+)$', thumbnail_key)
|
||||||
if thumbnail_key:
|
if m:
|
||||||
m = re.match('^([0-9]+)x([0-9]+)$', thumbnail_key)
|
thumbnail['width'] = int(m.group(1))
|
||||||
if m:
|
thumbnail['height'] = int(m.group(2))
|
||||||
thumbnail['width'] = int(m.group(1))
|
|
||||||
thumbnail['height'] = int(m.group(2))
|
|
||||||
thumbnails.append(thumbnail)
|
thumbnails.append(thumbnail)
|
||||||
|
|
||||||
return merge_dicts(info, {
|
return merge_dicts(info, {
|
||||||
'id': content_id,
|
'id': content_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': xpath_text(details, './/information/detail'),
|
'description': details.get('leadParagraph'),
|
||||||
'duration': int_or_none(xpath_text(details, './/details/lengthSec')),
|
'duration': duration,
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'uploader': xpath_text(details, './/details/channel'),
|
'uploader': details.get('tvService'),
|
||||||
'uploader_id': xpath_text(details, './/details/originChannelId'),
|
'series': series,
|
||||||
'channel': xpath_text(details, './/details/originChannelTitle'),
|
'episode': episode,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
smuggle_url,
|
||||||
|
try_get,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlayStuffIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?play\.stuff\.co\.nz/details/(?P<id>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://play.stuff.co.nz/details/608778ac1de1c4001a3fa09a',
|
||||||
|
'md5': 'c82d3669e5247c64bc382577843e5bd0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6250584958001',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Episode 1: Rotorua/Mt Maunganui/Tauranga',
|
||||||
|
'description': 'md5:c154bafb9f0dd02d01fd4100fb1c1913',
|
||||||
|
'uploader_id': '6005208634001',
|
||||||
|
'timestamp': 1619491027,
|
||||||
|
'upload_date': '20210427',
|
||||||
|
},
|
||||||
|
'add_ie': ['BrightcoveNew'],
|
||||||
|
}, {
|
||||||
|
# geo restricted, bypassable
|
||||||
|
'url': 'https://play.stuff.co.nz/details/_6155660351001',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_default/index.html?videoId=%s'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
state = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'__INITIAL_STATE__\s*=\s*({.+?})\s*;', webpage, 'state'),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
account_id = try_get(
|
||||||
|
state, lambda x: x['configurations']['accountId'],
|
||||||
|
compat_str) or '6005208634001'
|
||||||
|
player_id = try_get(
|
||||||
|
state, lambda x: x['configurations']['playerId'],
|
||||||
|
compat_str) or 'default'
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for item_id, video in state['items'].items():
|
||||||
|
if not isinstance(video, dict):
|
||||||
|
continue
|
||||||
|
asset_id = try_get(
|
||||||
|
video, lambda x: x['content']['attributes']['assetId'],
|
||||||
|
compat_str)
|
||||||
|
if not asset_id:
|
||||||
|
continue
|
||||||
|
entries.append(self.url_result(
|
||||||
|
smuggle_url(
|
||||||
|
self.BRIGHTCOVE_URL_TEMPLATE % (account_id, player_id, asset_id),
|
||||||
|
{'geo_countries': ['NZ']}),
|
||||||
|
'BrightcoveNew', video_id))
|
||||||
|
|
||||||
|
return self.playlist_result(entries, video_id)
|
||||||
@@ -337,11 +337,11 @@ query viewClip {
|
|||||||
# In order to minimize the number of calls to ViewClip API and reduce
|
# In order to minimize the number of calls to ViewClip API and reduce
|
||||||
# the probability of being throttled or banned by Pluralsight we will request
|
# the probability of being throttled or banned by Pluralsight we will request
|
||||||
# only single format until formats listing was explicitly requested.
|
# only single format until formats listing was explicitly requested.
|
||||||
if self._downloader.params.get('listformats', False):
|
if self.get_param('listformats', False):
|
||||||
allowed_qualities = ALLOWED_QUALITIES
|
allowed_qualities = ALLOWED_QUALITIES
|
||||||
else:
|
else:
|
||||||
def guess_allowed_qualities():
|
def guess_allowed_qualities():
|
||||||
req_format = self._downloader.params.get('format') or 'best'
|
req_format = self.get_param('format') or 'best'
|
||||||
req_format_split = req_format.split('-', 1)
|
req_format_split = req_format.split('-', 1)
|
||||||
if len(req_format_split) > 1:
|
if len(req_format_split) > 1:
|
||||||
req_ext, req_quality = req_format_split
|
req_ext, req_quality = req_format_split
|
||||||
@@ -349,7 +349,7 @@ query viewClip {
|
|||||||
for allowed_quality in ALLOWED_QUALITIES:
|
for allowed_quality in ALLOWED_QUALITIES:
|
||||||
if req_ext == allowed_quality.ext and req_quality in allowed_quality.qualities:
|
if req_ext == allowed_quality.ext and req_quality in allowed_quality.qualities:
|
||||||
return (AllowedQuality(req_ext, (req_quality, )), )
|
return (AllowedQuality(req_ext, (req_quality, )), )
|
||||||
req_ext = 'webm' if self._downloader.params.get('prefer_free_formats') else 'mp4'
|
req_ext = 'webm' if self.get_param('prefer_free_formats') else 'mp4'
|
||||||
return (AllowedQuality(req_ext, (best_quality, )), )
|
return (AllowedQuality(req_ext, (best_quality, )), )
|
||||||
allowed_qualities = guess_allowed_qualities()
|
allowed_qualities = guess_allowed_qualities()
|
||||||
|
|
||||||
|
|||||||
+35
-20
@@ -78,45 +78,60 @@ class PlutoTVIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _to_ad_free_formats(self, video_id, formats):
|
def _to_ad_free_formats(self, video_id, formats, subtitles):
|
||||||
ad_free_formats = []
|
ad_free_formats, ad_free_subtitles, m3u8_urls = [], {}, set()
|
||||||
m3u8_urls = set()
|
for fmt in formats:
|
||||||
for format in formats:
|
|
||||||
res = self._download_webpage(
|
res = self._download_webpage(
|
||||||
format.get('url'), video_id, note='Downloading m3u8 playlist',
|
fmt.get('url'), video_id, note='Downloading m3u8 playlist',
|
||||||
fatal=False)
|
fatal=False)
|
||||||
if not res:
|
if not res:
|
||||||
continue
|
continue
|
||||||
first_segment_url = re.search(
|
first_segment_url = re.search(
|
||||||
r'^(https?://.*/)0\-(end|[0-9]+)/[^/]+\.ts$', res,
|
r'^(https?://.*/)0\-(end|[0-9]+)/[^/]+\.ts$', res,
|
||||||
re.MULTILINE)
|
re.MULTILINE)
|
||||||
if not first_segment_url:
|
if first_segment_url:
|
||||||
|
m3u8_urls.add(
|
||||||
|
compat_urlparse.urljoin(first_segment_url.group(1), '0-end/master.m3u8'))
|
||||||
|
continue
|
||||||
|
first_segment_url = re.search(
|
||||||
|
r'^(https?://.*/).+\-0+\.ts$', res,
|
||||||
|
re.MULTILINE)
|
||||||
|
if first_segment_url:
|
||||||
|
m3u8_urls.add(
|
||||||
|
compat_urlparse.urljoin(first_segment_url.group(1), 'master.m3u8'))
|
||||||
continue
|
continue
|
||||||
m3u8_urls.add(
|
|
||||||
compat_urlparse.urljoin(first_segment_url.group(1), '0-end/master.m3u8'))
|
|
||||||
|
|
||||||
for m3u8_url in m3u8_urls:
|
for m3u8_url in m3u8_urls:
|
||||||
ad_free_formats.extend(
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
self._extract_m3u8_formats(
|
m3u8_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)
|
||||||
m3u8_url, video_id, 'mp4', 'm3u8_native',
|
ad_free_formats.extend(fmts)
|
||||||
m3u8_id='hls', fatal=False))
|
ad_free_subtitles = self._merge_subtitles(ad_free_subtitles, subs)
|
||||||
self._sort_formats(ad_free_formats)
|
if ad_free_formats:
|
||||||
return ad_free_formats
|
formats, subtitles = ad_free_formats, ad_free_subtitles
|
||||||
|
else:
|
||||||
|
self.report_warning('Unable to find ad-free formats')
|
||||||
|
return formats, subtitles
|
||||||
|
|
||||||
def _get_video_info(self, video_json, slug, series_name=None):
|
def _get_video_info(self, video_json, slug, series_name=None):
|
||||||
video_id = video_json.get('_id', slug)
|
video_id = video_json.get('_id', slug)
|
||||||
formats = []
|
formats, subtitles = [], {}
|
||||||
for video_url in try_get(video_json, lambda x: x['stitched']['urls'], list) or []:
|
for video_url in try_get(video_json, lambda x: x['stitched']['urls'], list) or []:
|
||||||
if video_url.get('type') != 'hls':
|
if video_url.get('type') != 'hls':
|
||||||
continue
|
continue
|
||||||
url = url_or_none(video_url.get('url'))
|
url = url_or_none(video_url.get('url'))
|
||||||
formats.extend(
|
|
||||||
self._extract_m3u8_formats(
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
url, video_id, 'mp4', 'm3u8_native',
|
url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)
|
||||||
m3u8_id='hls', fatal=False))
|
formats.extend(fmts)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, subs)
|
||||||
|
|
||||||
|
formats, subtitles = self._to_ad_free_formats(video_id, formats, subtitles)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': self._to_ad_free_formats(video_id, formats),
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
'title': video_json.get('name'),
|
'title': video_json.get('name'),
|
||||||
'description': video_json.get('description'),
|
'description': video_json.get('description'),
|
||||||
'duration': float_or_none(video_json.get('duration'), scale=1000),
|
'duration': float_or_none(video_json.get('duration'), scale=1000),
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class PokemonWatchIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# API call can be avoided entirely if we are listing formats
|
# API call can be avoided entirely if we are listing formats
|
||||||
if self._downloader.params.get('listformats', False):
|
if self.get_param('listformats', False):
|
||||||
return info
|
return info
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class ProSiebenSat1BaseIE(InfoExtractor):
|
|||||||
'ids': clip_id,
|
'ids': clip_id,
|
||||||
})[0]
|
})[0]
|
||||||
|
|
||||||
if not self._downloader.params.get('allow_unplayable_formats') and video.get('is_protected') is True:
|
if not self.get_param('allow_unplayable_formats') and video.get('is_protected') is True:
|
||||||
raise ExtractorError('This video is DRM protected.', expected=True)
|
raise ExtractorError('This video is DRM protected.', expected=True)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user