mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-08-09 13:48:35 +03:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f703a88055 | ||
|
|
a353beba83 | ||
|
|
052e135029 | ||
|
|
cb89cfc14b | ||
|
|
060ac76257 | ||
|
|
063c409dfb | ||
|
|
767b02a99b | ||
|
|
f45e6c1126 | ||
|
|
3944e7af92 | ||
|
|
ad34b2951e | ||
|
|
c8fa48fd94 | ||
|
|
2fd226f6a7 | ||
|
|
3ba7740dd8 | ||
|
|
29b208f6f9 | ||
|
|
e4d666d27b |
@@ -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.07.07. 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.07.21. 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.07.07**
|
- [ ] I've verified that I'm running yt-dlp version **2021.07.21**
|
||||||
- [ ] 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_jenozKc']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKc']
|
||||||
[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.07.07
|
[debug] yt-dlp version 2021.07.21
|
||||||
[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.07.07. 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.07.21. 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.07.07**
|
- [ ] I've verified that I'm running yt-dlp version **2021.07.21**
|
||||||
- [ ] 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.07.07. 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.07.21. 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.07.07**
|
- [ ] I've verified that I'm running yt-dlp version **2021.07.21**
|
||||||
- [ ] 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.07.07. 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.07.21. 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.07.07**
|
- [ ] I've verified that I'm running yt-dlp version **2021.07.21**
|
||||||
- [ ] 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_jenozKc']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKc']
|
||||||
[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.07.07
|
[debug] yt-dlp version 2021.07.21
|
||||||
[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.07.07. 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.07.21. 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.07.07**
|
- [ ] I've verified that I'm running yt-dlp version **2021.07.21**
|
||||||
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
- [ ] I've searched the bugtracker for similar feature requests including closed ones
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04]
|
os: [ubuntu-18.04]
|
||||||
# py3.9 is in quick-test
|
# py3.9 is in quick-test
|
||||||
python-version: [3.7, 3.8, pypy-3.6, pypy-3.7]
|
python-version: [3.7, 3.8, 3.10-dev, pypy-3.6, pypy-3.7]
|
||||||
run-tests-ext: [sh]
|
run-tests-ext: [sh]
|
||||||
include:
|
include:
|
||||||
# atleast one of the tests must be in windows
|
# atleast one of the tests must be in windows
|
||||||
@@ -23,11 +23,9 @@ jobs:
|
|||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install nose
|
- name: Install pytest
|
||||||
run: pip install nose
|
run: pip install pytest
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
continue-on-error: False
|
continue-on-error: False
|
||||||
env:
|
run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} core
|
||||||
YTDL_TEST_SET: core
|
|
||||||
run: ./devscripts/run_tests.${{ matrix.run-tests-ext }}
|
|
||||||
# Linter is in quick-test
|
# Linter is in quick-test
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04]
|
os: [ubuntu-18.04]
|
||||||
python-version: [3.7, 3.8, 3.9, pypy-3.6, pypy-3.7]
|
python-version: [3.7, 3.8, 3.9, 3.10-dev, pypy-3.6, pypy-3.7]
|
||||||
run-tests-ext: [sh]
|
run-tests-ext: [sh]
|
||||||
include:
|
include:
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
@@ -21,10 +21,8 @@ jobs:
|
|||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install nose
|
- name: Install pytest
|
||||||
run: pip install nose
|
run: pip install pytest
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} download
|
||||||
YTDL_TEST_SET: download
|
|
||||||
run: ./devscripts/run_tests.${{ matrix.run-tests-ext }}
|
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install test requirements
|
- name: Install test requirements
|
||||||
run: pip install nose pycryptodome
|
run: pip install pytest pycryptodome
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
run: ./devscripts/run_tests.sh core
|
||||||
YTDL_TEST_SET: core
|
|
||||||
run: ./devscripts/run_tests.sh
|
|
||||||
flake8:
|
flake8:
|
||||||
name: Linter
|
name: Linter
|
||||||
if: "!contains(github.event.head_commit.message, 'ci skip all')"
|
if: "!contains(github.event.head_commit.message, 'ci skip all')"
|
||||||
@@ -30,4 +28,4 @@ jobs:
|
|||||||
- name: Install flake8
|
- name: Install flake8
|
||||||
run: pip install flake8
|
run: pip install flake8
|
||||||
- name: Run flake8
|
- name: Run flake8
|
||||||
run: flake8 .
|
run: flake8 .
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ cookies.txt
|
|||||||
# Python
|
# Python
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
|
.pytest_cache
|
||||||
wine-py2exe/
|
wine-py2exe/
|
||||||
py2exe.log
|
py2exe.log
|
||||||
build/
|
build/
|
||||||
@@ -79,6 +80,7 @@ README.txt
|
|||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zsh
|
*.zsh
|
||||||
*.spec
|
*.spec
|
||||||
|
test/testdata/player-*.js
|
||||||
|
|
||||||
# Binary
|
# Binary
|
||||||
/youtube-dl
|
/youtube-dl
|
||||||
|
|||||||
+3
-2
@@ -81,16 +81,17 @@ To run the test, simply invoke your favorite test runner, or execute a test file
|
|||||||
python -m unittest discover
|
python -m unittest discover
|
||||||
python test/test_download.py
|
python test/test_download.py
|
||||||
nosetests
|
nosetests
|
||||||
|
pytest
|
||||||
|
|
||||||
See item 6 of [new extractor tutorial](#adding-support-for-a-new-site) for how to run extractor specific test cases.
|
See item 6 of [new extractor tutorial](#adding-support-for-a-new-site) for how to run extractor specific test cases.
|
||||||
|
|
||||||
If you want to create a build of youtube-dl yourself, you'll need
|
If you want to create a build of youtube-dl yourself, you'll need
|
||||||
|
|
||||||
* python
|
* python3
|
||||||
* make (only GNU make is supported)
|
* make (only GNU make is supported)
|
||||||
* pandoc
|
* pandoc
|
||||||
* zip
|
* zip
|
||||||
* nosetests
|
* pytest
|
||||||
|
|
||||||
### Adding support for a new site
|
### Adding support for a new site
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,20 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### 2021.07.24
|
||||||
|
|
||||||
|
* [youtube:tab] Extract video duration early
|
||||||
|
* [downloader] Pass `info_dict` to `progress_hook`s
|
||||||
|
* [youtube] Fix age-gated videos for API clients when cookies are supplied by [colethedj](https://github.com/colethedj)
|
||||||
|
* [youtube] Disable `get_video_info` age-gate workaround - This endpoint seems to be completely dead
|
||||||
|
* [youtube] Try all clients even if age-gated
|
||||||
|
* [youtube] Fix subtitles only being extracted from the first client
|
||||||
|
* [youtube] Simplify `_get_text`
|
||||||
|
* [cookies] bugfix for microsoft edge on macOS
|
||||||
|
* [cookies] Handle `sqlite` `ImportError` gracefully by [mbway](https://github.com/mbway)
|
||||||
|
* [cookies] Handle errors when importing `keyring`
|
||||||
|
|
||||||
### 2021.07.21
|
### 2021.07.21
|
||||||
|
|
||||||
* **Add option `--cookies-from-browser`** to load cookies from a browser by [mbway](https://github.com/mbway)
|
* **Add option `--cookies-from-browser`** to load cookies from a browser by [mbway](https://github.com/mbway)
|
||||||
@@ -53,6 +67,7 @@
|
|||||||
* [youtube:tab] Fix channels tab
|
* [youtube:tab] Fix channels tab
|
||||||
* [youtube:tab] Extract playlist availability by [colethedj](https://github.com/colethedj)
|
* [youtube:tab] Extract playlist availability by [colethedj](https://github.com/colethedj)
|
||||||
* **[youtube:comments] Move comment extraction to new API** by [colethedj](https://github.com/colethedj)
|
* **[youtube:comments] Move comment extraction to new API** by [colethedj](https://github.com/colethedj)
|
||||||
|
* Adds extractor-args `comment_sort` (`top`/`new`), `max_comments`, `max_comment_depth`
|
||||||
* [youtube:comments] Fix `is_favorited`, improve `like_count` parsing by [colethedj](https://github.com/colethedj)
|
* [youtube:comments] Fix `is_favorited`, improve `like_count` parsing by [colethedj](https://github.com/colethedj)
|
||||||
* [BravoTV] Improve metadata extraction by [kevinoconnor7](https://github.com/kevinoconnor7)
|
* [BravoTV] Improve metadata extraction by [kevinoconnor7](https://github.com/kevinoconnor7)
|
||||||
* [crunchyroll:playlist] Force http
|
* [crunchyroll:playlist] Force http
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pypi-files: AUTHORS Changelog.md LICENSE README.md README.txt supportedsites com
|
|||||||
.PHONY: all clean install test tar pypi-files completions ot offlinetest codetest supportedsites
|
.PHONY: all clean install test tar pypi-files completions ot offlinetest codetest supportedsites
|
||||||
|
|
||||||
clean-test:
|
clean-test:
|
||||||
rm -rf *.dump *.part* *.ytdl *.info.json *.mp4 *.m4a *.flv *.mp3 *.avi *.mkv *.webm *.3gp *.wav *.ape *.swf *.jpg *.png *.frag *.frag.urls *.frag.aria2
|
rm -rf *.dump *.part* *.ytdl *.info.json *.mp4 *.m4a *.flv *.mp3 *.avi *.mkv *.webm *.3gp *.wav *.ape *.swf *.jpg *.png *.frag *.frag.urls *.frag.aria2 test/testdata/player-*.js
|
||||||
clean-dist:
|
clean-dist:
|
||||||
rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS .mailmap
|
rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS .mailmap
|
||||||
clean-cache:
|
clean-cache:
|
||||||
@@ -49,23 +49,11 @@ codetest:
|
|||||||
flake8 .
|
flake8 .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
#nosetests --with-coverage --cover-package=yt_dlp --cover-html --verbose --processes 4 test
|
$(PYTHON) -m pytest
|
||||||
nosetests --verbose test
|
|
||||||
$(MAKE) codetest
|
$(MAKE) codetest
|
||||||
|
|
||||||
# Keep this list in sync with devscripts/run_tests.sh
|
|
||||||
offlinetest: codetest
|
offlinetest: codetest
|
||||||
$(PYTHON) -m nose --verbose test \
|
$(PYTHON) -m pytest -k "not download"
|
||||||
--exclude test_age_restriction.py \
|
|
||||||
--exclude test_download.py \
|
|
||||||
--exclude test_iqiyi_sdk_interpreter.py \
|
|
||||||
--exclude test_overwrites.py \
|
|
||||||
--exclude test_socks.py \
|
|
||||||
--exclude test_subtitles.py \
|
|
||||||
--exclude test_write_annotations.py \
|
|
||||||
--exclude test_youtube_lists.py \
|
|
||||||
--exclude test_youtube_signature.py \
|
|
||||||
--exclude test_post_hooks.py
|
|
||||||
|
|
||||||
yt-dlp: yt_dlp/*.py yt_dlp/*/*.py
|
yt-dlp: yt_dlp/*.py yt_dlp/*/*.py
|
||||||
mkdir -p zip
|
mkdir -p zip
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ The major new features from the latest release of [blackjack4494/yt-dlc](https:/
|
|||||||
* All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) supports downloading multiple pages of content
|
* All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) supports downloading multiple pages of content
|
||||||
* Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works
|
* Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works
|
||||||
* Mixes supports downloading multiple pages of content
|
* Mixes supports downloading multiple pages of content
|
||||||
* Partial workarounds for age-gate and throttling issues
|
* Partial workaround for throttling issue
|
||||||
* 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
|
* `255kbps` audio is extracted from youtube music if premium cookies are given
|
||||||
* Youtube music Albums, channels etc can be downloaded
|
* Youtube music Albums, channels etc can be downloaded
|
||||||
@@ -215,7 +215,7 @@ You can also build the executable without any version info or metadata by using:
|
|||||||
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
|
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`, `pytest`
|
||||||
Then simply run `make`. You can also run `make yt-dlp` instead to compile only the binary without updating any of the additional files
|
Then simply run `make`. You can also run `make yt-dlp` instead to compile only the binary without updating any of the additional files
|
||||||
|
|
||||||
**Note**: In either platform, `devscripts\update-version.py` can be used to automatically update the version number
|
**Note**: In either platform, `devscripts\update-version.py` can be used to automatically update the version number
|
||||||
|
|||||||
+10
-11
@@ -1,17 +1,16 @@
|
|||||||
|
@setlocal
|
||||||
@echo off
|
@echo off
|
||||||
|
cd /d %~dp0..
|
||||||
|
|
||||||
rem Keep this list in sync with the `offlinetest` target in Makefile
|
if ["%~1"]==[""] (
|
||||||
set DOWNLOAD_TESTS="age_restriction^|download^|iqiyi_sdk_interpreter^|socks^|subtitles^|write_annotations^|youtube_lists^|youtube_signature^|post_hooks"
|
set "test_set="
|
||||||
|
) else if ["%~1"]==["core"] (
|
||||||
if "%YTDL_TEST_SET%" == "core" (
|
set "test_set=-k "not download""
|
||||||
set test_set="-I test_("%DOWNLOAD_TESTS%")\.py"
|
) else if ["%~1"]==["download"] (
|
||||||
set multiprocess_args=""
|
set "test_set=-k download"
|
||||||
) else if "%YTDL_TEST_SET%" == "download" (
|
|
||||||
set test_set="-I test_(?!"%DOWNLOAD_TESTS%").+\.py"
|
|
||||||
set multiprocess_args="--processes=4 --process-timeout=540"
|
|
||||||
) else (
|
) else (
|
||||||
echo YTDL_TEST_SET is not set or invalid
|
echo.Invalid test type "%~1". Use "core" ^| "download"
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
nosetests test --verbose %test_set:"=% %multiprocess_args:"=%
|
pytest %test_set%
|
||||||
|
|||||||
+13
-20
@@ -1,22 +1,15 @@
|
|||||||
#!/bin/bash
|
#!/bin/sh
|
||||||
|
|
||||||
# Keep this list in sync with the `offlinetest` target in Makefile
|
if [ -z $1 ]; then
|
||||||
DOWNLOAD_TESTS="age_restriction|download|iqiyi_sdk_interpreter|overwrites|socks|subtitles|write_annotations|youtube_lists|youtube_signature|post_hooks"
|
test_set='test'
|
||||||
|
elif [ $1 = 'core' ]; then
|
||||||
|
test_set='not download'
|
||||||
|
elif [ $1 = 'download' ]; then
|
||||||
|
test_set='download'
|
||||||
|
else
|
||||||
|
echo 'Invalid test type "'$1'". Use "core" | "download"'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
test_set=""
|
echo python3 -m pytest -k $test_set
|
||||||
multiprocess_args=""
|
python3 -m pytest -k "$test_set"
|
||||||
|
|
||||||
case "$YTDL_TEST_SET" in
|
|
||||||
core)
|
|
||||||
test_set="-I test_($DOWNLOAD_TESTS)\.py"
|
|
||||||
;;
|
|
||||||
download)
|
|
||||||
test_set="-I test_(?!$DOWNLOAD_TESTS).+\.py"
|
|
||||||
multiprocess_args="--processes=4 --process-timeout=540"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
nosetests test --verbose $test_set $multiprocess_args
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[pytest]
|
||||||
|
addopts = -ra -v --strict-markers
|
||||||
|
markers =
|
||||||
|
download
|
||||||
@@ -22,6 +22,14 @@ from yt_dlp.utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if "pytest" in sys.modules:
|
||||||
|
import pytest
|
||||||
|
is_download_test = pytest.mark.download
|
||||||
|
else:
|
||||||
|
def is_download_test(testClass):
|
||||||
|
return testClass
|
||||||
|
|
||||||
|
|
||||||
def get_params(override=None):
|
def get_params(override=None):
|
||||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
"parameters.json")
|
"parameters.json")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"check_formats": false,
|
||||||
"consoletitle": false,
|
"consoletitle": false,
|
||||||
"continuedl": true,
|
"continuedl": true,
|
||||||
"forcedescription": false,
|
"forcedescription": false,
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ class InfoExtractorTestRequestHandler(compat_http_server.BaseHTTPRequestHandler)
|
|||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
class TestIE(InfoExtractor):
|
class DummyIE(InfoExtractor):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestInfoExtractor(unittest.TestCase):
|
class TestInfoExtractor(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.ie = TestIE(FakeYDL())
|
self.ie = DummyIE(FakeYDL())
|
||||||
|
|
||||||
def test_ie_key(self):
|
def test_ie_key(self):
|
||||||
self.assertEqual(get_info_extractor(YoutubeIE.ie_key()), YoutubeIE)
|
self.assertEqual(get_info_extractor(YoutubeIE.ie_key()), YoutubeIE)
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ 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 test.helper import try_rm
|
from test.helper import try_rm, is_download_test
|
||||||
|
|
||||||
|
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
|
|
||||||
@@ -32,6 +31,7 @@ def _download_restricted(url, filename, age):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestAgeRestriction(unittest.TestCase):
|
class TestAgeRestriction(unittest.TestCase):
|
||||||
def _assert_restricted(self, url, filename, age, old_age=None):
|
def _assert_restricted(self, url, filename, age, old_age=None):
|
||||||
self.assertTrue(_download_restricted(url, filename, old_age))
|
self.assertTrue(_download_restricted(url, filename, old_age))
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
|
|
||||||
from test.helper import (
|
from test.helper import (
|
||||||
assertGreaterEqual,
|
assertGreaterEqual,
|
||||||
|
expect_info_dict,
|
||||||
expect_warnings,
|
expect_warnings,
|
||||||
get_params,
|
get_params,
|
||||||
gettestcases,
|
gettestcases,
|
||||||
expect_info_dict,
|
is_download_test,
|
||||||
try_rm,
|
|
||||||
report_warning,
|
report_warning,
|
||||||
|
try_rm,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ def _file_md5(fn):
|
|||||||
defs = gettestcases()
|
defs = gettestcases()
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestDownload(unittest.TestCase):
|
class TestDownload(unittest.TestCase):
|
||||||
# Parallel testing in nosetests. See
|
# Parallel testing in nosetests. See
|
||||||
# http://nose.readthedocs.org/en/latest/doc_tests/test_multiprocess/multiprocess.html
|
# http://nose.readthedocs.org/en/latest/doc_tests/test_multiprocess/multiprocess.html
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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 test.helper import FakeYDL
|
from test.helper import FakeYDL, is_download_test
|
||||||
from yt_dlp.extractor import IqiyiIE
|
from yt_dlp.extractor import IqiyiIE
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ class WarningLogger(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestIqiyiSDKInterpreter(unittest.TestCase):
|
class TestIqiyiSDKInterpreter(unittest.TestCase):
|
||||||
def test_iqiyi_sdk_interpreter(self):
|
def test_iqiyi_sdk_interpreter(self):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 test.helper import get_params, try_rm
|
from test.helper import get_params, try_rm, is_download_test
|
||||||
import yt_dlp.YoutubeDL
|
import yt_dlp.YoutubeDL
|
||||||
from yt_dlp.utils import DownloadError
|
from yt_dlp.utils import DownloadError
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ TEST_ID = 'gr51aVj-mLg'
|
|||||||
EXPECTED_NAME = 'gr51aVj-mLg'
|
EXPECTED_NAME = 'gr51aVj-mLg'
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestPostHooks(unittest.TestCase):
|
class TestPostHooks(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.stored_name_1 = None
|
self.stored_name_1 = None
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import subprocess
|
|||||||
from test.helper import (
|
from test.helper import (
|
||||||
FakeYDL,
|
FakeYDL,
|
||||||
get_params,
|
get_params,
|
||||||
|
is_download_test,
|
||||||
)
|
)
|
||||||
from yt_dlp.compat import (
|
from yt_dlp.compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
@@ -21,6 +22,7 @@ from yt_dlp.compat import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestMultipleSocks(unittest.TestCase):
|
class TestMultipleSocks(unittest.TestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _check_params(attrs):
|
def _check_params(attrs):
|
||||||
@@ -76,6 +78,7 @@ class TestMultipleSocks(unittest.TestCase):
|
|||||||
params['secondary_server_ip'])
|
params['secondary_server_ip'])
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestSocks(unittest.TestCase):
|
class TestSocks(unittest.TestCase):
|
||||||
_SKIP_SOCKS_TEST = True
|
_SKIP_SOCKS_TEST = True
|
||||||
|
|
||||||
|
|||||||
+18
-1
@@ -7,7 +7,7 @@ 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 test.helper import FakeYDL, md5
|
from test.helper import FakeYDL, md5, is_download_test
|
||||||
|
|
||||||
|
|
||||||
from yt_dlp.extractor import (
|
from yt_dlp.extractor import (
|
||||||
@@ -30,6 +30,7 @@ from yt_dlp.extractor import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class BaseTestSubtitles(unittest.TestCase):
|
class BaseTestSubtitles(unittest.TestCase):
|
||||||
url = None
|
url = None
|
||||||
IE = None
|
IE = None
|
||||||
@@ -55,6 +56,7 @@ class BaseTestSubtitles(unittest.TestCase):
|
|||||||
return dict((l, sub_info['data']) for l, sub_info in subtitles.items())
|
return dict((l, sub_info['data']) for l, sub_info in subtitles.items())
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestYoutubeSubtitles(BaseTestSubtitles):
|
class TestYoutubeSubtitles(BaseTestSubtitles):
|
||||||
url = 'QRS8MkLhQmM'
|
url = 'QRS8MkLhQmM'
|
||||||
IE = YoutubeIE
|
IE = YoutubeIE
|
||||||
@@ -111,6 +113,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
|
|||||||
self.assertFalse(subtitles)
|
self.assertFalse(subtitles)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestDailymotionSubtitles(BaseTestSubtitles):
|
class TestDailymotionSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.dailymotion.com/video/xczg00'
|
url = 'http://www.dailymotion.com/video/xczg00'
|
||||||
IE = DailymotionIE
|
IE = DailymotionIE
|
||||||
@@ -134,6 +137,7 @@ class TestDailymotionSubtitles(BaseTestSubtitles):
|
|||||||
self.assertFalse(subtitles)
|
self.assertFalse(subtitles)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestTedSubtitles(BaseTestSubtitles):
|
class TestTedSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html'
|
url = 'http://www.ted.com/talks/dan_dennett_on_our_consciousness.html'
|
||||||
IE = TEDIE
|
IE = TEDIE
|
||||||
@@ -149,6 +153,7 @@ class TestTedSubtitles(BaseTestSubtitles):
|
|||||||
self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
|
self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestVimeoSubtitles(BaseTestSubtitles):
|
class TestVimeoSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://vimeo.com/76979871'
|
url = 'http://vimeo.com/76979871'
|
||||||
IE = VimeoIE
|
IE = VimeoIE
|
||||||
@@ -170,6 +175,7 @@ class TestVimeoSubtitles(BaseTestSubtitles):
|
|||||||
self.assertFalse(subtitles)
|
self.assertFalse(subtitles)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestWallaSubtitles(BaseTestSubtitles):
|
class TestWallaSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://vod.walla.co.il/movie/2705958/the-yes-men'
|
url = 'http://vod.walla.co.il/movie/2705958/the-yes-men'
|
||||||
IE = WallaIE
|
IE = WallaIE
|
||||||
@@ -191,6 +197,7 @@ class TestWallaSubtitles(BaseTestSubtitles):
|
|||||||
self.assertFalse(subtitles)
|
self.assertFalse(subtitles)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestCeskaTelevizeSubtitles(BaseTestSubtitles):
|
class TestCeskaTelevizeSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.ceskatelevize.cz/ivysilani/10600540290-u6-uzasny-svet-techniky'
|
url = 'http://www.ceskatelevize.cz/ivysilani/10600540290-u6-uzasny-svet-techniky'
|
||||||
IE = CeskaTelevizeIE
|
IE = CeskaTelevizeIE
|
||||||
@@ -212,6 +219,7 @@ class TestCeskaTelevizeSubtitles(BaseTestSubtitles):
|
|||||||
self.assertFalse(subtitles)
|
self.assertFalse(subtitles)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestLyndaSubtitles(BaseTestSubtitles):
|
class TestLyndaSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.lynda.com/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html'
|
url = 'http://www.lynda.com/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html'
|
||||||
IE = LyndaIE
|
IE = LyndaIE
|
||||||
@@ -224,6 +232,7 @@ class TestLyndaSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['en']), '09bbe67222259bed60deaa26997d73a7')
|
self.assertEqual(md5(subtitles['en']), '09bbe67222259bed60deaa26997d73a7')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestNPOSubtitles(BaseTestSubtitles):
|
class TestNPOSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.npo.nl/nos-journaal/28-08-2014/POW_00722860'
|
url = 'http://www.npo.nl/nos-journaal/28-08-2014/POW_00722860'
|
||||||
IE = NPOIE
|
IE = NPOIE
|
||||||
@@ -236,6 +245,7 @@ class TestNPOSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4')
|
self.assertEqual(md5(subtitles['nl']), 'fc6435027572b63fb4ab143abd5ad3f4')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestMTVSubtitles(BaseTestSubtitles):
|
class TestMTVSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.cc.com/video-clips/p63lk0/adam-devine-s-house-party-chasing-white-swans'
|
url = 'http://www.cc.com/video-clips/p63lk0/adam-devine-s-house-party-chasing-white-swans'
|
||||||
IE = ComedyCentralIE
|
IE = ComedyCentralIE
|
||||||
@@ -251,6 +261,7 @@ class TestMTVSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['en']), '78206b8d8a0cfa9da64dc026eea48961')
|
self.assertEqual(md5(subtitles['en']), '78206b8d8a0cfa9da64dc026eea48961')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestNRKSubtitles(BaseTestSubtitles):
|
class TestNRKSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1'
|
url = 'http://tv.nrk.no/serie/ikke-gjoer-dette-hjemme/DMPV73000411/sesong-2/episode-1'
|
||||||
IE = NRKTVIE
|
IE = NRKTVIE
|
||||||
@@ -263,6 +274,7 @@ class TestNRKSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['no']), '544fa917d3197fcbee64634559221cc2')
|
self.assertEqual(md5(subtitles['no']), '544fa917d3197fcbee64634559221cc2')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestRaiPlaySubtitles(BaseTestSubtitles):
|
class TestRaiPlaySubtitles(BaseTestSubtitles):
|
||||||
IE = RaiPlayIE
|
IE = RaiPlayIE
|
||||||
|
|
||||||
@@ -283,6 +295,7 @@ class TestRaiPlaySubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['it']), '4b3264186fbb103508abe5311cfcb9cd')
|
self.assertEqual(md5(subtitles['it']), '4b3264186fbb103508abe5311cfcb9cd')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestVikiSubtitles(BaseTestSubtitles):
|
class TestVikiSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.viki.com/videos/1060846v-punch-episode-18'
|
url = 'http://www.viki.com/videos/1060846v-punch-episode-18'
|
||||||
IE = VikiIE
|
IE = VikiIE
|
||||||
@@ -295,6 +308,7 @@ class TestVikiSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['en']), '53cb083a5914b2d84ef1ab67b880d18a')
|
self.assertEqual(md5(subtitles['en']), '53cb083a5914b2d84ef1ab67b880d18a')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestThePlatformSubtitles(BaseTestSubtitles):
|
class TestThePlatformSubtitles(BaseTestSubtitles):
|
||||||
# from http://www.3playmedia.com/services-features/tools/integrations/theplatform/
|
# from http://www.3playmedia.com/services-features/tools/integrations/theplatform/
|
||||||
# (see http://theplatform.com/about/partners/type/subtitles-closed-captioning/)
|
# (see http://theplatform.com/about/partners/type/subtitles-closed-captioning/)
|
||||||
@@ -309,6 +323,7 @@ class TestThePlatformSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['en']), '97e7670cbae3c4d26ae8bcc7fdd78d4b')
|
self.assertEqual(md5(subtitles['en']), '97e7670cbae3c4d26ae8bcc7fdd78d4b')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestThePlatformFeedSubtitles(BaseTestSubtitles):
|
class TestThePlatformFeedSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://feed.theplatform.com/f/7wvmTC/msnbc_video-p-test?form=json&pretty=true&range=-40&byGuid=n_hardball_5biden_140207'
|
url = 'http://feed.theplatform.com/f/7wvmTC/msnbc_video-p-test?form=json&pretty=true&range=-40&byGuid=n_hardball_5biden_140207'
|
||||||
IE = ThePlatformFeedIE
|
IE = ThePlatformFeedIE
|
||||||
@@ -321,6 +336,7 @@ class TestThePlatformFeedSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['en']), '48649a22e82b2da21c9a67a395eedade')
|
self.assertEqual(md5(subtitles['en']), '48649a22e82b2da21c9a67a395eedade')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestRtveSubtitles(BaseTestSubtitles):
|
class TestRtveSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.rtve.es/alacarta/videos/los-misterios-de-laura/misterios-laura-capitulo-32-misterio-del-numero-17-2-parte/2428621/'
|
url = 'http://www.rtve.es/alacarta/videos/los-misterios-de-laura/misterios-laura-capitulo-32-misterio-del-numero-17-2-parte/2428621/'
|
||||||
IE = RTVEALaCartaIE
|
IE = RTVEALaCartaIE
|
||||||
@@ -335,6 +351,7 @@ class TestRtveSubtitles(BaseTestSubtitles):
|
|||||||
self.assertEqual(md5(subtitles['es']), '69e70cae2d40574fb7316f31d6eb7fca')
|
self.assertEqual(md5(subtitles['es']), '69e70cae2d40574fb7316f31d6eb7fca')
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestDemocracynowSubtitles(BaseTestSubtitles):
|
class TestDemocracynowSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.democracynow.org/shows/2015/7/3'
|
url = 'http://www.democracynow.org/shows/2015/7/3'
|
||||||
IE = DemocracynowIE
|
IE = DemocracynowIE
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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 test.helper import get_params, try_rm
|
from test.helper import get_params, try_rm, is_download_test
|
||||||
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
@@ -38,6 +38,7 @@ ANNOTATIONS_FILE = TEST_ID + '.annotations.xml'
|
|||||||
EXPECTED_ANNOTATIONS = ['Speech bubble', 'Note', 'Title', 'Spotlight', 'Label']
|
EXPECTED_ANNOTATIONS = ['Speech bubble', 'Note', 'Title', 'Spotlight', 'Label']
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestAnnotations(unittest.TestCase):
|
class TestAnnotations(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Clear old files
|
# Clear old files
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 test.helper import FakeYDL
|
from test.helper import FakeYDL, is_download_test
|
||||||
|
|
||||||
|
|
||||||
from yt_dlp.extractor import (
|
from yt_dlp.extractor import (
|
||||||
@@ -17,6 +17,7 @@ from yt_dlp.extractor import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestYoutubeLists(unittest.TestCase):
|
class TestYoutubeLists(unittest.TestCase):
|
||||||
def assertIsPlaylist(self, info):
|
def assertIsPlaylist(self, info):
|
||||||
"""Make sure the info has '_type' set to 'playlist'"""
|
"""Make sure the info has '_type' set to 'playlist'"""
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import io
|
|||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from test.helper import FakeYDL
|
from test.helper import FakeYDL, is_download_test
|
||||||
from yt_dlp.extractor import YoutubeIE
|
from yt_dlp.extractor import YoutubeIE
|
||||||
from yt_dlp.compat import compat_str, compat_urlretrieve
|
from yt_dlp.compat import compat_str, compat_urlretrieve
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ _TESTS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestPlayerInfo(unittest.TestCase):
|
class TestPlayerInfo(unittest.TestCase):
|
||||||
def test_youtube_extract_player_info(self):
|
def test_youtube_extract_player_info(self):
|
||||||
PLAYER_URLS = (
|
PLAYER_URLS = (
|
||||||
@@ -87,6 +88,7 @@ class TestPlayerInfo(unittest.TestCase):
|
|||||||
self.assertEqual(player_id, expected_player_id)
|
self.assertEqual(player_id, expected_player_id)
|
||||||
|
|
||||||
|
|
||||||
|
@is_download_test
|
||||||
class TestSignature(unittest.TestCase):
|
class TestSignature(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py26,py27,py33,py34,py35
|
envlist = py26,py27,py33,py34,py35
|
||||||
|
|
||||||
|
# Needed?
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
nose
|
nose
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ class YoutubeDL(object):
|
|||||||
progress, with a dictionary with the entries
|
progress, with a dictionary with the entries
|
||||||
* status: One of "downloading", "error", or "finished".
|
* status: One of "downloading", "error", or "finished".
|
||||||
Check this first and ignore unknown values.
|
Check this first and ignore unknown values.
|
||||||
|
* info_dict: The extracted info_dict
|
||||||
|
|
||||||
If status is one of "downloading", or "finished", the
|
If status is one of "downloading", or "finished", the
|
||||||
following properties may also be present:
|
following properties may also be present:
|
||||||
|
|||||||
+31
-6
@@ -2,7 +2,6 @@ import ctypes
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sqlite3
|
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -16,6 +15,7 @@ from yt_dlp.compat import (
|
|||||||
compat_cookiejar_Cookie,
|
compat_cookiejar_Cookie,
|
||||||
)
|
)
|
||||||
from yt_dlp.utils import (
|
from yt_dlp.utils import (
|
||||||
|
bug_reports_message,
|
||||||
bytes_to_intlist,
|
bytes_to_intlist,
|
||||||
expand_path,
|
expand_path,
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
@@ -23,6 +23,15 @@ from yt_dlp.utils import (
|
|||||||
YoutubeDLCookieJar,
|
YoutubeDLCookieJar,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sqlite3
|
||||||
|
SQLITE_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
# although sqlite3 is part of the standard library, it is possible to compile python without
|
||||||
|
# sqlite support. See: https://github.com/yt-dlp/yt-dlp/issues/544
|
||||||
|
SQLITE_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
CRYPTO_AVAILABLE = True
|
CRYPTO_AVAILABLE = True
|
||||||
@@ -32,8 +41,17 @@ except ImportError:
|
|||||||
try:
|
try:
|
||||||
import keyring
|
import keyring
|
||||||
KEYRING_AVAILABLE = True
|
KEYRING_AVAILABLE = True
|
||||||
|
KEYRING_UNAVAILABLE_REASON = f'due to unknown reasons{bug_reports_message()}'
|
||||||
except ImportError:
|
except ImportError:
|
||||||
KEYRING_AVAILABLE = False
|
KEYRING_AVAILABLE = False
|
||||||
|
KEYRING_UNAVAILABLE_REASON = (
|
||||||
|
'as the `keyring` module is not installed. '
|
||||||
|
'Please install by running `python3 -m pip install keyring`. '
|
||||||
|
'Depending on your platform, additional packages may be required '
|
||||||
|
'to access the keyring; see https://pypi.org/project/keyring')
|
||||||
|
except Exception as _err:
|
||||||
|
KEYRING_AVAILABLE = False
|
||||||
|
KEYRING_UNAVAILABLE_REASON = 'as the `keyring` module could not be initialized: %s' % _err
|
||||||
|
|
||||||
|
|
||||||
CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi'}
|
CHROMIUM_BASED_BROWSERS = {'brave', 'chrome', 'chromium', 'edge', 'opera', 'vivaldi'}
|
||||||
@@ -90,6 +108,10 @@ def extract_cookies_from_browser(browser_name, profile=None, logger=YDLLogger())
|
|||||||
|
|
||||||
def _extract_firefox_cookies(profile, logger):
|
def _extract_firefox_cookies(profile, logger):
|
||||||
logger.info('Extracting cookies from firefox')
|
logger.info('Extracting cookies from firefox')
|
||||||
|
if not SQLITE_AVAILABLE:
|
||||||
|
logger.warning('Cannot extract cookies from firefox without sqlite3 support. '
|
||||||
|
'Please use a python interpreter compiled with sqlite3 support')
|
||||||
|
return YoutubeDLCookieJar()
|
||||||
|
|
||||||
if profile is None:
|
if profile is None:
|
||||||
search_root = _firefox_browser_dir()
|
search_root = _firefox_browser_dir()
|
||||||
@@ -179,7 +201,7 @@ def _get_chromium_based_browser_settings(browser_name):
|
|||||||
'brave': 'Brave',
|
'brave': 'Brave',
|
||||||
'chrome': 'Chrome',
|
'chrome': 'Chrome',
|
||||||
'chromium': 'Chromium',
|
'chromium': 'Chromium',
|
||||||
'edge': 'Mirosoft Edge' if sys.platform == 'darwin' else 'Chromium',
|
'edge': 'Microsoft Edge' if sys.platform == 'darwin' else 'Chromium',
|
||||||
'opera': 'Opera' if sys.platform == 'darwin' else 'Chromium',
|
'opera': 'Opera' if sys.platform == 'darwin' else 'Chromium',
|
||||||
'vivaldi': 'Vivaldi' if sys.platform == 'darwin' else 'Chrome',
|
'vivaldi': 'Vivaldi' if sys.platform == 'darwin' else 'Chrome',
|
||||||
}[browser_name]
|
}[browser_name]
|
||||||
@@ -195,6 +217,12 @@ def _get_chromium_based_browser_settings(browser_name):
|
|||||||
|
|
||||||
def _extract_chrome_cookies(browser_name, profile, logger):
|
def _extract_chrome_cookies(browser_name, profile, logger):
|
||||||
logger.info('Extracting cookies from {}'.format(browser_name))
|
logger.info('Extracting cookies from {}'.format(browser_name))
|
||||||
|
|
||||||
|
if not SQLITE_AVAILABLE:
|
||||||
|
logger.warning(('Cannot extract cookies from {} without sqlite3 support. '
|
||||||
|
'Please use a python interpreter compiled with sqlite3 support').format(browser_name))
|
||||||
|
return YoutubeDLCookieJar()
|
||||||
|
|
||||||
config = _get_chromium_based_browser_settings(browser_name)
|
config = _get_chromium_based_browser_settings(browser_name)
|
||||||
|
|
||||||
if profile is None:
|
if profile is None:
|
||||||
@@ -322,10 +350,7 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
|
|||||||
|
|
||||||
elif version == b'v11':
|
elif version == b'v11':
|
||||||
if self._v11_key is None:
|
if self._v11_key is None:
|
||||||
self._logger.warning('cannot decrypt cookie as the `keyring` module is not installed. '
|
self._logger.warning(f'cannot decrypt cookie {KEYRING_UNAVAILABLE_REASON}', only_once=True)
|
||||||
'Please install by running `python3 -m pip install keyring`. '
|
|
||||||
'Note that depending on your platform, additional packages may be required '
|
|
||||||
'to access the keyring, see https://pypi.org/project/keyring', only_once=True)
|
|
||||||
return None
|
return None
|
||||||
return _decrypt_aes_cbc(ciphertext, self._v11_key, self._logger)
|
return _decrypt_aes_cbc(ciphertext, self._v11_key, self._logger)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import division, unicode_literals
|
from __future__ import division, unicode_literals
|
||||||
|
|
||||||
|
import copy
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -360,7 +361,7 @@ class FileDownloader(object):
|
|||||||
'filename': filename,
|
'filename': filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'total_bytes': os.path.getsize(encodeFilename(filename)),
|
'total_bytes': os.path.getsize(encodeFilename(filename)),
|
||||||
})
|
}, info_dict)
|
||||||
return True, False
|
return True, False
|
||||||
|
|
||||||
if subtitle is False:
|
if subtitle is False:
|
||||||
@@ -388,7 +389,16 @@ class FileDownloader(object):
|
|||||||
"""Real download process. Redefine in subclasses."""
|
"""Real download process. Redefine in subclasses."""
|
||||||
raise NotImplementedError('This method must be implemented by subclasses')
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
def _hook_progress(self, status):
|
def _hook_progress(self, status, info_dict):
|
||||||
|
if not self._progress_hooks:
|
||||||
|
return
|
||||||
|
info_dict = dict(info_dict)
|
||||||
|
for key in ('__original_infodict', '__postprocessors'):
|
||||||
|
info_dict.pop(key, None)
|
||||||
|
# youtube-dl passes the same status object to all the hooks.
|
||||||
|
# Some third party scripts seems to be relying on this.
|
||||||
|
# So keep this behavior if possible
|
||||||
|
status['info_dict'] = copy.deepcopy(info_dict)
|
||||||
for ph in self._progress_hooks:
|
for ph in self._progress_hooks:
|
||||||
ph(status)
|
ph(status)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class DashSegmentsFD(FragmentFD):
|
|||||||
if real_downloader:
|
if real_downloader:
|
||||||
self._prepare_external_frag_download(ctx)
|
self._prepare_external_frag_download(ctx)
|
||||||
else:
|
else:
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
fragments_to_download = []
|
fragments_to_download = []
|
||||||
frag_index = 0
|
frag_index = 0
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class ExternalFD(FileDownloader):
|
|||||||
'downloaded_bytes': fsize,
|
'downloaded_bytes': fsize,
|
||||||
'total_bytes': fsize,
|
'total_bytes': fsize,
|
||||||
})
|
})
|
||||||
self._hook_progress(status)
|
self._hook_progress(status, info_dict)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.to_stderr('\n')
|
self.to_stderr('\n')
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ class F4mFD(FragmentFD):
|
|||||||
|
|
||||||
base_url_parsed = compat_urllib_parse_urlparse(base_url)
|
base_url_parsed = compat_urllib_parse_urlparse(base_url)
|
||||||
|
|
||||||
self._start_frag_download(ctx)
|
self._start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
frag_index = 0
|
frag_index = 0
|
||||||
while fragments_list:
|
while fragments_list:
|
||||||
@@ -434,6 +434,6 @@ class F4mFD(FragmentFD):
|
|||||||
msg = 'Missed %d fragments' % (fragments_list[0][1] - (frag_i + 1))
|
msg = 'Missed %d fragments' % (fragments_list[0][1] - (frag_i + 1))
|
||||||
self.report_warning(msg)
|
self.report_warning(msg)
|
||||||
|
|
||||||
self._finish_frag_download(ctx)
|
self._finish_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -83,9 +83,9 @@ class FragmentFD(FileDownloader):
|
|||||||
headers = info_dict.get('http_headers')
|
headers = info_dict.get('http_headers')
|
||||||
return sanitized_Request(url, None, headers) if headers else url
|
return sanitized_Request(url, None, headers) if headers else url
|
||||||
|
|
||||||
def _prepare_and_start_frag_download(self, ctx):
|
def _prepare_and_start_frag_download(self, ctx, info_dict):
|
||||||
self._prepare_frag_download(ctx)
|
self._prepare_frag_download(ctx)
|
||||||
self._start_frag_download(ctx)
|
self._start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
def __do_ytdl_file(self, ctx):
|
def __do_ytdl_file(self, 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'] == '-' and not self.params.get('_no_ytdl_file')
|
||||||
@@ -219,7 +219,7 @@ class FragmentFD(FileDownloader):
|
|||||||
'complete_frags_downloaded_bytes': resume_len,
|
'complete_frags_downloaded_bytes': resume_len,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _start_frag_download(self, ctx):
|
def _start_frag_download(self, ctx, info_dict):
|
||||||
resume_len = ctx['complete_frags_downloaded_bytes']
|
resume_len = ctx['complete_frags_downloaded_bytes']
|
||||||
total_frags = ctx['total_frags']
|
total_frags = ctx['total_frags']
|
||||||
# This dict stores the download progress, it's updated by the progress
|
# This dict stores the download progress, it's updated by the progress
|
||||||
@@ -248,6 +248,7 @@ class FragmentFD(FileDownloader):
|
|||||||
time_now = time.time()
|
time_now = time.time()
|
||||||
state['elapsed'] = time_now - start
|
state['elapsed'] = time_now - start
|
||||||
frag_total_bytes = s.get('total_bytes') or 0
|
frag_total_bytes = s.get('total_bytes') or 0
|
||||||
|
s['fragment_info_dict'] = s.pop('info_dict', {})
|
||||||
if not ctx['live']:
|
if not ctx['live']:
|
||||||
estimated_size = (
|
estimated_size = (
|
||||||
(ctx['complete_frags_downloaded_bytes'] + frag_total_bytes)
|
(ctx['complete_frags_downloaded_bytes'] + frag_total_bytes)
|
||||||
@@ -270,13 +271,13 @@ class FragmentFD(FileDownloader):
|
|||||||
state['speed'] = s.get('speed') or ctx.get('speed')
|
state['speed'] = s.get('speed') or ctx.get('speed')
|
||||||
ctx['speed'] = state['speed']
|
ctx['speed'] = state['speed']
|
||||||
ctx['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
|
ctx['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
|
||||||
self._hook_progress(state)
|
self._hook_progress(state, info_dict)
|
||||||
|
|
||||||
ctx['dl'].add_progress_hook(frag_progress_hook)
|
ctx['dl'].add_progress_hook(frag_progress_hook)
|
||||||
|
|
||||||
return start
|
return start
|
||||||
|
|
||||||
def _finish_frag_download(self, ctx):
|
def _finish_frag_download(self, ctx, info_dict):
|
||||||
ctx['dest_stream'].close()
|
ctx['dest_stream'].close()
|
||||||
if self.__do_ytdl_file(ctx):
|
if self.__do_ytdl_file(ctx):
|
||||||
ytdl_filename = encodeFilename(self.ytdl_filename(ctx['filename']))
|
ytdl_filename = encodeFilename(self.ytdl_filename(ctx['filename']))
|
||||||
@@ -303,7 +304,7 @@ class FragmentFD(FileDownloader):
|
|||||||
'filename': ctx['filename'],
|
'filename': ctx['filename'],
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'elapsed': elapsed,
|
'elapsed': elapsed,
|
||||||
})
|
}, info_dict)
|
||||||
|
|
||||||
def _prepare_external_frag_download(self, ctx):
|
def _prepare_external_frag_download(self, ctx):
|
||||||
if 'live' not in ctx:
|
if 'live' not in ctx:
|
||||||
@@ -421,5 +422,5 @@ class FragmentFD(FileDownloader):
|
|||||||
if not result:
|
if not result:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._finish_frag_download(ctx)
|
self._finish_frag_download(ctx, info_dict)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class HlsFD(FragmentFD):
|
|||||||
if real_downloader:
|
if real_downloader:
|
||||||
self._prepare_external_frag_download(ctx)
|
self._prepare_external_frag_download(ctx)
|
||||||
else:
|
else:
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
extra_state = ctx.setdefault('extra_state', {})
|
extra_state = ctx.setdefault('extra_state', {})
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class HttpFD(FileDownloader):
|
|||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'downloaded_bytes': ctx.resume_len,
|
'downloaded_bytes': ctx.resume_len,
|
||||||
'total_bytes': ctx.resume_len,
|
'total_bytes': ctx.resume_len,
|
||||||
})
|
}, info_dict)
|
||||||
raise SucceedDownload()
|
raise SucceedDownload()
|
||||||
else:
|
else:
|
||||||
# The length does not match, we start the download over
|
# The length does not match, we start the download over
|
||||||
@@ -310,7 +310,7 @@ class HttpFD(FileDownloader):
|
|||||||
'eta': eta,
|
'eta': eta,
|
||||||
'speed': speed,
|
'speed': speed,
|
||||||
'elapsed': now - ctx.start_time,
|
'elapsed': now - ctx.start_time,
|
||||||
})
|
}, info_dict)
|
||||||
|
|
||||||
if data_len is not None and byte_counter == data_len:
|
if data_len is not None and byte_counter == data_len:
|
||||||
break
|
break
|
||||||
@@ -357,7 +357,7 @@ class HttpFD(FileDownloader):
|
|||||||
'filename': ctx.filename,
|
'filename': ctx.filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'elapsed': time.time() - ctx.start_time,
|
'elapsed': time.time() - ctx.start_time,
|
||||||
})
|
}, info_dict)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ class IsmFD(FragmentFD):
|
|||||||
'total_frags': len(segments),
|
'total_frags': len(segments),
|
||||||
}
|
}
|
||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
extra_state = ctx.setdefault('extra_state', {
|
extra_state = ctx.setdefault('extra_state', {
|
||||||
'ism_track_written': False,
|
'ism_track_written': False,
|
||||||
@@ -284,6 +284,6 @@ class IsmFD(FragmentFD):
|
|||||||
self.report_error('giving up after %s fragment retries' % fragment_retries)
|
self.report_error('giving up after %s fragment retries' % fragment_retries)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._finish_frag_download(ctx)
|
self._finish_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ body > figure > img {
|
|||||||
'total_frags': len(fragments),
|
'total_frags': len(fragments),
|
||||||
}
|
}
|
||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
extra_state = ctx.setdefault('extra_state', {
|
extra_state = ctx.setdefault('extra_state', {
|
||||||
'header_written': False,
|
'header_written': False,
|
||||||
@@ -198,5 +198,5 @@ body > figure > img {
|
|||||||
|
|
||||||
ctx['dest_stream'].write(
|
ctx['dest_stream'].write(
|
||||||
b'--%b--\r\n\r\n' % frag_boundary.encode('us-ascii'))
|
b'--%b--\r\n\r\n' % frag_boundary.encode('us-ascii'))
|
||||||
self._finish_frag_download(ctx)
|
self._finish_frag_download(ctx, info_dict)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class RtmpFD(FileDownloader):
|
|||||||
'eta': eta,
|
'eta': eta,
|
||||||
'elapsed': time_now - start,
|
'elapsed': time_now - start,
|
||||||
'speed': speed,
|
'speed': speed,
|
||||||
})
|
}, info_dict)
|
||||||
cursor_in_new_line = False
|
cursor_in_new_line = False
|
||||||
else:
|
else:
|
||||||
# no percent for live streams
|
# no percent for live streams
|
||||||
@@ -82,7 +82,7 @@ class RtmpFD(FileDownloader):
|
|||||||
'status': 'downloading',
|
'status': 'downloading',
|
||||||
'elapsed': time_now - start,
|
'elapsed': time_now - start,
|
||||||
'speed': speed,
|
'speed': speed,
|
||||||
})
|
}, info_dict)
|
||||||
cursor_in_new_line = False
|
cursor_in_new_line = False
|
||||||
elif self.params.get('verbose', False):
|
elif self.params.get('verbose', False):
|
||||||
if not cursor_in_new_line:
|
if not cursor_in_new_line:
|
||||||
@@ -208,7 +208,7 @@ class RtmpFD(FileDownloader):
|
|||||||
'filename': filename,
|
'filename': filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
'elapsed': time.time() - started,
|
'elapsed': time.time() - started,
|
||||||
})
|
}, info_dict)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.to_stderr('\n')
|
self.to_stderr('\n')
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class RtspFD(FileDownloader):
|
|||||||
'total_bytes': fsize,
|
'total_bytes': fsize,
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'status': 'finished',
|
'status': 'finished',
|
||||||
})
|
}, info_dict)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.to_stderr('\n')
|
self.to_stderr('\n')
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class YoutubeLiveChatFD(FragmentFD):
|
|||||||
self.report_error('giving up after %s fragment retries' % fragment_retries)
|
self.report_error('giving up after %s fragment retries' % fragment_retries)
|
||||||
return False, None, None, None
|
return False, None, None, None
|
||||||
|
|
||||||
self._prepare_and_start_frag_download(ctx)
|
self._prepare_and_start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
success, raw_fragment = dl_fragment(info_dict['url'])
|
success, raw_fragment = dl_fragment(info_dict['url'])
|
||||||
if not success:
|
if not success:
|
||||||
@@ -196,7 +196,7 @@ class YoutubeLiveChatFD(FragmentFD):
|
|||||||
if test:
|
if test:
|
||||||
break
|
break
|
||||||
|
|
||||||
self._finish_frag_download(ctx)
|
self._finish_frag_download(ctx, info_dict)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
+72
-69
@@ -691,7 +691,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
alert_type = alert.get('type')
|
alert_type = alert.get('type')
|
||||||
if not alert_type:
|
if not alert_type:
|
||||||
continue
|
continue
|
||||||
message = cls._get_text(alert.get('text'))
|
message = cls._get_text(alert, 'text')
|
||||||
if message:
|
if message:
|
||||||
yield alert_type, message
|
yield alert_type, message
|
||||||
|
|
||||||
@@ -721,23 +721,26 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
return badges
|
return badges
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_text(data, getter=None, max_runs=None):
|
def _get_text(data, *path_list, max_runs=None):
|
||||||
for get in variadic(getter):
|
for path in path_list or [None]:
|
||||||
d = try_get(data, get) if get is not None else data
|
if path is None:
|
||||||
text = try_get(d, lambda x: x['simpleText'], compat_str)
|
obj = [data]
|
||||||
if text:
|
else:
|
||||||
return text
|
obj = traverse_obj(data, path, default=[])
|
||||||
runs = try_get(d, lambda x: x['runs'], list) or []
|
if not any(key is ... or isinstance(key, (list, tuple)) for key in variadic(path)):
|
||||||
if not runs and isinstance(d, list):
|
obj = [obj]
|
||||||
runs = d
|
for item in obj:
|
||||||
|
text = try_get(item, lambda x: x['simpleText'], compat_str)
|
||||||
|
if text:
|
||||||
|
return text
|
||||||
|
runs = try_get(item, lambda x: x['runs'], list) or []
|
||||||
|
if not runs and isinstance(item, list):
|
||||||
|
runs = item
|
||||||
|
|
||||||
def get_runs(runs):
|
runs = runs[:min(len(runs), max_runs or len(runs))]
|
||||||
for run in runs[:min(len(runs), max_runs or len(runs))]:
|
text = ''.join(traverse_obj(runs, (..., 'text'), expected_type=str, default=[]))
|
||||||
yield try_get(run, lambda x: x['text'], compat_str) or ''
|
if text:
|
||||||
|
return text
|
||||||
text = ''.join(get_runs(runs))
|
|
||||||
if text:
|
|
||||||
return text
|
|
||||||
|
|
||||||
def _extract_response(self, item_id, query, note='Downloading API JSON', headers=None,
|
def _extract_response(self, item_id, query, note='Downloading API JSON', headers=None,
|
||||||
ytcfg=None, check_get_keys=None, ep='browse', fatal=True, api_hostname=None,
|
ytcfg=None, check_get_keys=None, ep='browse', fatal=True, api_hostname=None,
|
||||||
@@ -804,15 +807,16 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
def _extract_video(self, renderer):
|
def _extract_video(self, renderer):
|
||||||
video_id = renderer.get('videoId')
|
video_id = renderer.get('videoId')
|
||||||
title = self._get_text(renderer.get('title'))
|
title = self._get_text(renderer, 'title')
|
||||||
description = self._get_text(renderer.get('descriptionSnippet'))
|
description = self._get_text(renderer, 'descriptionSnippet')
|
||||||
duration = parse_duration(self._get_text(renderer.get('lengthText')))
|
duration = parse_duration(self._get_text(
|
||||||
view_count_text = self._get_text(renderer.get('viewCountText')) or ''
|
renderer, 'lengthText', ('thumbnailOverlays', ..., 'thumbnailOverlayTimeStatusRenderer', 'text')))
|
||||||
|
view_count_text = self._get_text(renderer, 'viewCountText') or ''
|
||||||
view_count = str_to_int(self._search_regex(
|
view_count = str_to_int(self._search_regex(
|
||||||
r'^([\d,]+)', re.sub(r'\s', '', view_count_text),
|
r'^([\d,]+)', re.sub(r'\s', '', view_count_text),
|
||||||
'view count', default=None))
|
'view count', default=None))
|
||||||
|
|
||||||
uploader = self._get_text(renderer, (lambda x: x['ownerText'], lambda x: x['shortBylineText']))
|
uploader = self._get_text(renderer, 'ownerText', 'shortBylineText')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url',
|
'_type': 'url',
|
||||||
@@ -2028,8 +2032,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
data,
|
data,
|
||||||
('engagementPanels', ..., 'engagementPanelSectionListRenderer', 'content', 'macroMarkersListRenderer', 'contents'),
|
('engagementPanels', ..., 'engagementPanelSectionListRenderer', 'content', 'macroMarkersListRenderer', 'contents'),
|
||||||
expected_type=list, default=[])
|
expected_type=list, default=[])
|
||||||
chapter_time = lambda chapter: parse_duration(self._get_text(chapter.get('timeDescription')))
|
chapter_time = lambda chapter: parse_duration(self._get_text(chapter, 'timeDescription'))
|
||||||
chapter_title = lambda chapter: self._get_text(chapter.get('title'))
|
chapter_title = lambda chapter: self._get_text(chapter, 'title')
|
||||||
|
|
||||||
return next((
|
return next((
|
||||||
filter(None, (
|
filter(None, (
|
||||||
@@ -2083,14 +2087,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
if not comment_id:
|
if not comment_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
text = self._get_text(comment_renderer.get('contentText'))
|
text = self._get_text(comment_renderer, 'contentText')
|
||||||
|
|
||||||
# note: timestamp is an estimate calculated from the current time and time_text
|
# note: timestamp is an estimate calculated from the current time and time_text
|
||||||
time_text = self._get_text(comment_renderer.get('publishedTimeText')) or ''
|
time_text = self._get_text(comment_renderer, 'publishedTimeText') or ''
|
||||||
time_text_dt = self.parse_time_text(time_text)
|
time_text_dt = self.parse_time_text(time_text)
|
||||||
if isinstance(time_text_dt, datetime.datetime):
|
if isinstance(time_text_dt, datetime.datetime):
|
||||||
timestamp = calendar.timegm(time_text_dt.timetuple())
|
timestamp = calendar.timegm(time_text_dt.timetuple())
|
||||||
author = self._get_text(comment_renderer.get('authorText'))
|
author = self._get_text(comment_renderer, 'authorText')
|
||||||
author_id = try_get(comment_renderer,
|
author_id = try_get(comment_renderer,
|
||||||
lambda x: x['authorEndpoint']['browseEndpoint']['browseId'], compat_str)
|
lambda x: x['authorEndpoint']['browseEndpoint']['browseId'], compat_str)
|
||||||
|
|
||||||
@@ -2125,7 +2129,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
for content in contents:
|
for content in contents:
|
||||||
comments_header_renderer = try_get(content, lambda x: x['commentsHeaderRenderer'])
|
comments_header_renderer = try_get(content, lambda x: x['commentsHeaderRenderer'])
|
||||||
expected_comment_count = parse_count(self._get_text(
|
expected_comment_count = parse_count(self._get_text(
|
||||||
comments_header_renderer, (lambda x: x['countText'], lambda x: x['commentsCount']), max_runs=1))
|
comments_header_renderer, 'countText', 'commentsCount', max_runs=1))
|
||||||
|
|
||||||
if expected_comment_count:
|
if expected_comment_count:
|
||||||
comment_counts[1] = expected_comment_count
|
comment_counts[1] = expected_comment_count
|
||||||
@@ -2343,7 +2347,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
'playbackContext': {
|
'playbackContext': {
|
||||||
'contentPlaybackContext': context
|
'contentPlaybackContext': context
|
||||||
},
|
},
|
||||||
'contentCheckOk': True
|
'contentCheckOk': True,
|
||||||
|
'racyCheckOk': True
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2389,21 +2394,22 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
) or None
|
) or None
|
||||||
|
|
||||||
def _extract_age_gated_player_response(self, client, video_id, ytcfg, identity_token, player_url, initial_pr):
|
def _extract_age_gated_player_response(self, client, video_id, ytcfg, identity_token, player_url, initial_pr):
|
||||||
gvi_client = self._YT_CLIENTS.get(f'_{client}_agegate')
|
# get_video_info endpoint seems to be completely dead
|
||||||
if not gvi_client:
|
gvi_client = None # self._YT_CLIENTS.get(f'_{client}_agegate')
|
||||||
|
if gvi_client:
|
||||||
|
pr = self._parse_json(traverse_obj(
|
||||||
|
compat_parse_qs(self._download_webpage(
|
||||||
|
self.http_scheme() + '//www.youtube.com/get_video_info', video_id,
|
||||||
|
'Refetching age-gated %s info webpage' % gvi_client.lower(),
|
||||||
|
'unable to download video info webpage', fatal=False,
|
||||||
|
query=self._get_video_info_params(video_id, client=gvi_client))),
|
||||||
|
('player_response', 0), expected_type=str) or '{}', video_id)
|
||||||
|
if pr:
|
||||||
|
return pr
|
||||||
|
self.report_warning('Falling back to embedded-only age-gate workaround')
|
||||||
|
|
||||||
|
if not self._YT_CLIENTS.get(f'_{client}_embedded'):
|
||||||
return
|
return
|
||||||
|
|
||||||
pr = self._parse_json(traverse_obj(
|
|
||||||
compat_parse_qs(self._download_webpage(
|
|
||||||
self.http_scheme() + '//www.youtube.com/get_video_info', video_id,
|
|
||||||
'Refetching age-gated %s info webpage' % gvi_client.lower(),
|
|
||||||
'unable to download video info webpage', fatal=False,
|
|
||||||
query=self._get_video_info_params(video_id, client=gvi_client))),
|
|
||||||
('player_response', 0), expected_type=str) or '{}', video_id)
|
|
||||||
if pr:
|
|
||||||
return pr
|
|
||||||
|
|
||||||
self.report_warning('Falling back to embedded-only age-gate workaround')
|
|
||||||
embed_webpage = None
|
embed_webpage = None
|
||||||
if client == 'web' and 'configs' not in self._configuration_arg('player_skip'):
|
if client == 'web' and 'configs' not in self._configuration_arg('player_skip'):
|
||||||
embed_webpage = self._download_webpage(
|
embed_webpage = self._download_webpage(
|
||||||
@@ -2442,12 +2448,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
webpage, self._YT_INITIAL_PLAYER_RESPONSE_RE,
|
webpage, self._YT_INITIAL_PLAYER_RESPONSE_RE,
|
||||||
video_id, 'initial player response')
|
video_id, 'initial player response')
|
||||||
|
|
||||||
age_gated = False
|
|
||||||
for client in clients:
|
for client in clients:
|
||||||
player_ytcfg = master_ytcfg if client == 'web' else {}
|
player_ytcfg = master_ytcfg if client == 'web' else {}
|
||||||
if age_gated:
|
if client == 'web' and initial_pr:
|
||||||
pr = None
|
|
||||||
elif client == 'web' and initial_pr:
|
|
||||||
pr = initial_pr
|
pr = initial_pr
|
||||||
else:
|
else:
|
||||||
if client == 'web_music' and 'configs' not in self._configuration_arg('player_skip'):
|
if client == 'web_music' and 'configs' not in self._configuration_arg('player_skip'):
|
||||||
@@ -2459,8 +2462,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
client, video_id, player_ytcfg or master_ytcfg, player_ytcfg, identity_token, player_url, initial_pr)
|
client, video_id, player_ytcfg or master_ytcfg, player_ytcfg, identity_token, player_url, initial_pr)
|
||||||
if pr:
|
if pr:
|
||||||
yield pr
|
yield pr
|
||||||
if age_gated or traverse_obj(pr, ('playabilityStatus', 'reason')) in self._AGE_GATE_REASONS:
|
if traverse_obj(pr, ('playabilityStatus', 'reason')) in self._AGE_GATE_REASONS:
|
||||||
age_gated = True
|
|
||||||
pr = self._extract_age_gated_player_response(
|
pr = self._extract_age_gated_player_response(
|
||||||
client, video_id, player_ytcfg or master_ytcfg, identity_token, player_url, initial_pr)
|
client, video_id, player_ytcfg or master_ytcfg, identity_token, player_url, initial_pr)
|
||||||
if pr:
|
if pr:
|
||||||
@@ -2847,7 +2849,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
'release_timestamp': live_starttime,
|
'release_timestamp': live_starttime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pctr = get_first(player_responses, ('captions', 'playerCaptionsTracklistRenderer'), expected_type=dict)
|
pctr = traverse_obj(player_responses, (..., 'captions', 'playerCaptionsTracklistRenderer'), expected_type=dict)
|
||||||
|
# Converted into dicts to remove duplicates
|
||||||
|
captions = {
|
||||||
|
sub.get('baseUrl'): sub
|
||||||
|
for sub in traverse_obj(pctr, (..., 'captionTracks', ...), default=[])}
|
||||||
|
translation_languages = {
|
||||||
|
lang.get('languageCode'): lang.get('languageName')
|
||||||
|
for lang in traverse_obj(pctr, (..., 'translationLanguages', ...), default=[])}
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
if pctr:
|
if pctr:
|
||||||
def process_language(container, base_url, lang_code, sub_name, query):
|
def process_language(container, base_url, lang_code, sub_name, query):
|
||||||
@@ -2862,8 +2871,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
'name': sub_name,
|
'name': sub_name,
|
||||||
})
|
})
|
||||||
|
|
||||||
for caption_track in (pctr.get('captionTracks') or []):
|
for base_url, caption_track in captions.items():
|
||||||
base_url = caption_track.get('baseUrl')
|
|
||||||
if not base_url:
|
if not base_url:
|
||||||
continue
|
continue
|
||||||
if caption_track.get('kind') != 'asr':
|
if caption_track.get('kind') != 'asr':
|
||||||
@@ -2874,18 +2882,17 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
continue
|
continue
|
||||||
process_language(
|
process_language(
|
||||||
subtitles, base_url, lang_code,
|
subtitles, base_url, lang_code,
|
||||||
try_get(caption_track, lambda x: x['name']['simpleText']),
|
traverse_obj(caption_track, ('name', 'simpleText')),
|
||||||
{})
|
{})
|
||||||
continue
|
continue
|
||||||
automatic_captions = {}
|
automatic_captions = {}
|
||||||
for translation_language in (pctr.get('translationLanguages') or []):
|
for trans_code, trans_name in translation_languages.items():
|
||||||
translation_language_code = translation_language.get('languageCode')
|
if not trans_code:
|
||||||
if not translation_language_code:
|
|
||||||
continue
|
continue
|
||||||
process_language(
|
process_language(
|
||||||
automatic_captions, base_url, translation_language_code,
|
automatic_captions, base_url, trans_code,
|
||||||
self._get_text(translation_language.get('languageName'), max_runs=1),
|
self._get_text(trans_name, max_runs=1),
|
||||||
{'tlang': translation_language_code})
|
{'tlang': trans_code})
|
||||||
info['automatic_captions'] = automatic_captions
|
info['automatic_captions'] = automatic_captions
|
||||||
info['subtitles'] = subtitles
|
info['subtitles'] = subtitles
|
||||||
|
|
||||||
@@ -2998,10 +3005,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
})
|
})
|
||||||
vsir = content.get('videoSecondaryInfoRenderer')
|
vsir = content.get('videoSecondaryInfoRenderer')
|
||||||
if vsir:
|
if vsir:
|
||||||
info['channel'] = self._get_text(try_get(
|
info['channel'] = self._get_text(vsir, ('owner', 'videoOwnerRenderer', 'title'))
|
||||||
vsir,
|
|
||||||
lambda x: x['owner']['videoOwnerRenderer']['title'],
|
|
||||||
dict))
|
|
||||||
rows = try_get(
|
rows = try_get(
|
||||||
vsir,
|
vsir,
|
||||||
lambda x: x['metadataRowContainer']['metadataRowContainerRenderer']['rows'],
|
lambda x: x['metadataRowContainer']['metadataRowContainerRenderer']['rows'],
|
||||||
@@ -3016,8 +3020,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
mrr_title = mrr.get('title')
|
mrr_title = mrr.get('title')
|
||||||
if not mrr_title:
|
if not mrr_title:
|
||||||
continue
|
continue
|
||||||
mrr_title = self._get_text(mrr['title'])
|
mrr_title = self._get_text(mrr, 'title')
|
||||||
mrr_contents_text = self._get_text(mrr['contents'][0])
|
mrr_contents_text = self._get_text(mrr, ('contents', 0))
|
||||||
if mrr_title == 'License':
|
if mrr_title == 'License':
|
||||||
info['license'] = mrr_contents_text
|
info['license'] = mrr_contents_text
|
||||||
elif not multiple_songs:
|
elif not multiple_songs:
|
||||||
@@ -3589,7 +3593,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
renderer = self._extract_basic_item_renderer(item)
|
renderer = self._extract_basic_item_renderer(item)
|
||||||
if not isinstance(renderer, dict):
|
if not isinstance(renderer, dict):
|
||||||
continue
|
continue
|
||||||
title = self._get_text(renderer.get('title'))
|
title = self._get_text(renderer, 'title')
|
||||||
|
|
||||||
# playlist
|
# playlist
|
||||||
playlist_id = renderer.get('playlistId')
|
playlist_id = renderer.get('playlistId')
|
||||||
@@ -3649,7 +3653,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
# will not work
|
# will not work
|
||||||
if skip_channels and '/channels?' in shelf_url:
|
if skip_channels and '/channels?' in shelf_url:
|
||||||
return
|
return
|
||||||
title = self._get_text(shelf_renderer, lambda x: x['title'])
|
title = self._get_text(shelf_renderer, 'title')
|
||||||
yield self.url_result(shelf_url, video_title=title)
|
yield self.url_result(shelf_url, video_title=title)
|
||||||
# Shelf may not contain shelf URL, fallback to extraction from content
|
# Shelf may not contain shelf URL, fallback to extraction from content
|
||||||
for entry in self._shelf_entries_from_content(shelf_renderer):
|
for entry in self._shelf_entries_from_content(shelf_renderer):
|
||||||
@@ -4023,8 +4027,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
renderer_dict, lambda x: x['privacyDropdownItemRenderer']['isSelected'], bool) or False
|
renderer_dict, lambda x: x['privacyDropdownItemRenderer']['isSelected'], bool) or False
|
||||||
if not is_selected:
|
if not is_selected:
|
||||||
continue
|
continue
|
||||||
label = self._get_text(
|
label = self._get_text(renderer_dict, ('privacyDropdownItemRenderer', 'label'))
|
||||||
try_get(renderer_dict, lambda x: x['privacyDropdownItemRenderer']['label'], dict) or [])
|
|
||||||
if label:
|
if label:
|
||||||
badge_labels.add(label.lower())
|
badge_labels.add(label.lower())
|
||||||
break
|
break
|
||||||
|
|||||||
+2
-2
@@ -3964,7 +3964,7 @@ def detect_exe_version(output, version_re=None, unrecognized='present'):
|
|||||||
return unrecognized
|
return unrecognized
|
||||||
|
|
||||||
|
|
||||||
class LazyList(collections.Sequence):
|
class LazyList(collections.abc.Sequence):
|
||||||
''' Lazy immutable list from an iterable
|
''' Lazy immutable list from an iterable
|
||||||
Note that slices of a LazyList are lists and not LazyList'''
|
Note that slices of a LazyList are lists and not LazyList'''
|
||||||
|
|
||||||
@@ -6313,4 +6313,4 @@ def traverse_dict(dictn, keys, casesense=True):
|
|||||||
|
|
||||||
|
|
||||||
def variadic(x, allowed_types=(str, bytes)):
|
def variadic(x, allowed_types=(str, bytes)):
|
||||||
return x if isinstance(x, collections.Iterable) and not isinstance(x, allowed_types) else (x,)
|
return x if isinstance(x, collections.abc.Iterable) and not isinstance(x, allowed_types) else (x,)
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '2021.07.07'
|
__version__ = '2021.07.21'
|
||||||
|
|||||||
Reference in New Issue
Block a user