Compare commits

...
23 Commits
Author SHA1 Message Date
pukkandan 4524baf056 Release 2021.02.19 2021-02-20 03:14:01 +05:30
pukkandan bc2ca1bb75 Update to ytdl-commit-cf2dbec
https://github.com/ytdl-org/youtube-dl/commit/cf2dbec6301177a1fddf72862de05fa912d9869d

Except: [kakao] improve info extraction and detect geo restriction
https://github.com/ytdl-org/youtube-dl/commit/d8085580f63ad3b146a31712ff76cf41d5a4558a
2021-02-20 02:32:22 +05:30
pukkandan 5e41dca334 [viki] Fix extractor (Closes #91) 2021-02-19 18:21:29 +05:30
pukkandan 2a86f3da07 [build] Publish on PyPi only if token is set
This allows forks to easily build releases
:ci skip all
2021-02-19 17:04:25 +05:30
pukkandan a40258a259 [documentation] Remove --flat-videos
It does not work as documented
It was an experimental option that I forgot to remove when making the fork public

:ci skip all
2021-02-19 04:52:05 +05:30
pukkandan ba7bf12d89 [youtube] Fix for empty comment text (Closes #97) 2021-02-19 04:15:25 +05:30
pukkandan f983b87567 [formatsort] Remove misuse of 'preference'
'preference' is to be used only when the format is better that ALL qualities of a lower preference irrespective of ANY sorting order the user requests. See deezer.py for correct use of this

In the older sorting method, `preference`, `quality` and `language_preference` were functionally almost equivalent. So these disparities doesn't really matter there

Also, despite what the documentation says, the default for `preference` was actually 0 and not -1. I have tried to correct this and also account for it when converting `preference` to `quality`
2021-02-19 03:33:45 +05:30
pukkandan dca3ff4a5e [formatsort] Remove forced priority of quality
When making `FormatSort`, I misinterpreted the purpose `quality`
2021-02-19 00:12:21 +05:30
pukkandan da9be05edf [documentation] Better document --prefer-free-formats
Also added `--no-prefer-free-formats`
2021-02-18 23:52:32 +05:30
pukkandan 155d2b48c5 [formatsort] Prefer vp9.2 over other vp9 codecs
vp9.2 may contain HDR while vp9.0 doesn't
2021-02-18 23:52:31 +05:30
pukkandan 54f37eeabd [formatsort] Remove unnecessary field_preference from extractors
These were written with the old format sorting in mind and is no longer needed
2021-02-18 23:52:28 +05:30
pukkandan 9ba5705ac0 [youtube] Fix hashtag continuation
Eg: https://www.youtube.com/hashtag/youtube

:ci skip dl
2021-02-18 13:54:06 +05:30
pukkandan c2934512c2 Option --windows-filenames to force use of windows compatible filenames
* Also changed `--trim-file-name` to `--trim-filenames` to be similar to related options

Related: https://web.archive.org/web/20210217190806/https://old.reddit.com/r/youtubedl/comments/llc4o5/do_you_guys_also_have_this_error

:ci skip dl
2021-02-18 01:06:40 +05:30
shirt-devandGitHub 55e36f035c #93 Build improvements
* Lock all python package versions to the last officially supported releases for x86
* Bugfix for UNIX hash output
* Use wheels to avoid compilation of python packages
* Hash calculation on Windows now uses PowerShell rather than the legacy certutil

Authored-by: shirtjs <2660574+shirtjs@users.noreply.github.com>
2021-02-17 11:40:39 +05:30
pukkandan c86d5023d0 [youtube] Add more Invidious instances (Closes #92)
:ci skip dl
2021-02-17 04:40:55 +05:30
pukkandan 42bb0c59f8 [MoveFiles] Fix when merger can't run
:ci skip dl
2021-02-17 00:42:27 +05:30
pukkandan c3e1f0c4f2 [contributors] update
Forgot to do it when making release

:ci skip dl
2021-02-16 17:09:54 +05:30
pukkandan 6b027907ce Don't raise parser.error when exiting for update 2021-02-16 17:04:53 +05:30
pukkandan f3b7c69377 [version] Set version number based on UTC time, not local time 2021-02-16 17:04:53 +05:30
Jody BruchonandGitHub 46261325be #89 [pyinst.py] Exclude vcruntime140.dll from UPX (#89)
Related: https://github.com/blackjack4494/yt-dlc/pull/182 (7b400ac40b)

Authored by: jbruchon
2021-02-16 16:41:47 +05:30
kurumigiandGitHub 78b9a616cc #90 [niconico] Extract channel and channel_id (Closes #77)
Authored by kurumigi
2021-02-16 16:19:37 +05:30
pukkandan 55b53b338b [ExtractAudio] Bugfix for 1de75fa129
Fixes: #58
:ci skip dl
2021-02-16 15:00:54 +05:30
pukkandan d16ab6ef1c [version] update
:ci skip dl
2021-02-16 04:17:55 +05:30
115 changed files with 1341 additions and 613 deletions
+3 -3
View File
@@ -21,7 +21,7 @@ assignees: ''
<!-- <!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc: Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc:
- First of, make sure you are using the latest version of yt-dlp. Run `youtube-dlc --version` and ensure your version is 2021.02.09. If it's not, see https://github.com/pukkandan/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 `youtube-dlc --version` and ensure your version is 2021.02.16. If it's not, see https://github.com/pukkandan/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/pukkandan/yt-dlp. - Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/pukkandan/yt-dlp.
- Search the bugtracker for similar issues: https://github.com/pukkandan/yt-dlp. DO NOT post duplicates. - Search the bugtracker for similar issues: https://github.com/pukkandan/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.02.09** - [ ] I've verified that I'm running yt-dlp version **2021.02.16**
- [ ] 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 youtube-dlc with (`youtube-dlc -v
[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.02.09 [debug] yt-dlp version 2021.02.16
[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 youtube-dlc: Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc:
- First of, make sure you are using the latest version of yt-dlp. Run `youtube-dlc --version` and ensure your version is 2021.02.09. If it's not, see https://github.com/pukkandan/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 `youtube-dlc --version` and ensure your version is 2021.02.16. If it's not, see https://github.com/pukkandan/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/pukkandan/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/pukkandan/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/pukkandan/yt-dlp. DO NOT post duplicates. - Search the bugtracker for similar site support requests: https://github.com/pukkandan/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.02.09** - [ ] I've verified that I'm running yt-dlp version **2021.02.16**
- [ ] 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 youtube-dlc: Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc:
- First of, make sure you are using the latest version of yt-dlp. Run `youtube-dlc --version` and ensure your version is 2021.02.09. If it's not, see https://github.com/pukkandan/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 `youtube-dlc --version` and ensure your version is 2021.02.16. If it's not, see https://github.com/pukkandan/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Search the bugtracker for similar site feature requests: https://github.com/pukkandan/yt-dlp. DO NOT post duplicates. - Search the bugtracker for similar site feature requests: https://github.com/pukkandan/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.02.09** - [ ] I've verified that I'm running yt-dlp version **2021.02.16**
- [ ] 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
+3 -3
View File
@@ -21,7 +21,7 @@ assignees: ''
<!-- <!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc: Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc:
- First of, make sure you are using the latest version of yt-dlp. Run `youtube-dlc --version` and ensure your version is 2021.02.09. If it's not, see https://github.com/pukkandan/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 `youtube-dlc --version` and ensure your version is 2021.02.16. If it's not, see https://github.com/pukkandan/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/pukkandan/yt-dlp. - Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/pukkandan/yt-dlp.
- Search the bugtracker for similar issues: https://github.com/pukkandan/yt-dlp. DO NOT post duplicates. - Search the bugtracker for similar issues: https://github.com/pukkandan/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.02.09** - [ ] I've verified that I'm running yt-dlp version **2021.02.16**
- [ ] 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 youtube-dlc with (`youtube-dlc -v
[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.02.09 [debug] yt-dlp version 2021.02.16
[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: {}
+2 -2
View File
@@ -21,13 +21,13 @@ assignees: ''
<!-- <!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc: Carefully read and work through this check list in order to prevent the most common mistakes and misuse of youtube-dlc:
- First of, make sure you are using the latest version of yt-dlp. Run `youtube-dlc --version` and ensure your version is 2021.02.09. If it's not, see https://github.com/pukkandan/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 `youtube-dlc --version` and ensure your version is 2021.02.16. If it's not, see https://github.com/pukkandan/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Search the bugtracker for similar feature requests: https://github.com/pukkandan/yt-dlp. DO NOT post duplicates. - Search the bugtracker for similar feature requests: https://github.com/pukkandan/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.02.09** - [ ] I've verified that I'm running yt-dlp version **2021.02.16**
- [ ] I've searched the bugtracker for similar feature requests including closed ones - [ ] I've searched the bugtracker for similar feature requests including closed ones
+12 -19
View File
@@ -55,8 +55,11 @@ jobs:
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
- name: Get SHA2-256SUMS for youtube-dlc - name: Get SHA2-256SUMS for youtube-dlc
id: sha2_file id: sha2_file
run: echo "::set-output name=sha2_unix::$(sha256sum youtube-dlc)" run: echo "::set-output name=sha2_unix::$(sha256sum youtube-dlc | awk '{print $1}')"
- name: Install dependencies for pypi - name: Install dependencies for pypi
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
if: "env.PYPI_TOKEN != ''"
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install setuptools wheel twine pip install setuptools wheel twine
@@ -64,6 +67,7 @@ jobs:
env: env:
TWINE_USERNAME: __token__ TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
if: "env.TWINE_PASSWORD != ''"
run: | run: |
rm -rf dist/* rm -rf dist/*
python setup.py sdist bdist_wheel python setup.py sdist bdist_wheel
@@ -84,6 +88,8 @@ jobs:
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.8' python-version: '3.8'
- name: Upgrade pip and enable wheel support
run: python -m pip install --upgrade pip setuptools wheel
- name: Install Requirements - name: Install Requirements
run: pip install pyinstaller mutagen pycryptodome run: pip install pyinstaller mutagen pycryptodome
- name: Bump version - name: Bump version
@@ -105,7 +111,7 @@ jobs:
asset_content_type: application/vnd.microsoft.portable-executable asset_content_type: application/vnd.microsoft.portable-executable
- name: Get SHA2-256SUMS for youtube-dlc.exe - name: Get SHA2-256SUMS for youtube-dlc.exe
id: sha2_file_win id: sha2_file_win
run: echo "::set-output name=sha2_windows::$(certUtil -hashfile dist\youtube-dlc.exe SHA256 | findstr -v :)" run: echo "::set-output name=sha2_windows::$((Get-FileHash dist\youtube-dlc.exe -Algorithm SHA256).Hash.ToLower())"
build_windows32: build_windows32:
@@ -123,10 +129,10 @@ jobs:
with: with:
python-version: '3.4.4' python-version: '3.4.4'
architecture: 'x86' architecture: 'x86'
- name: Install VS libs - name: Upgrade pip and enable wheel support
run: choco install vcexpress2010 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 pycryptodome run: pip install pyinstaller==3.5 mutagen==1.42.0 pycryptodome==3.9.4
- name: Bump version - name: Bump version
id: bump_version id: bump_version
run: python devscripts/update-version.py run: python devscripts/update-version.py
@@ -146,7 +152,7 @@ jobs:
asset_content_type: application/vnd.microsoft.portable-executable asset_content_type: application/vnd.microsoft.portable-executable
- name: Get SHA2-256SUMS for youtube-dlc_x86.exe - name: Get SHA2-256SUMS for youtube-dlc_x86.exe
id: sha2_file_win32 id: sha2_file_win32
run: echo "::set-output name=sha2_windows32::$(certUtil -hashfile dist\youtube-dlc_x86.exe SHA256 | findstr -v :)" run: echo "::set-output name=sha2_windows32::$((Get-FileHash dist\youtube-dlc_x86.exe -Algorithm SHA256).Hash.ToLower())"
- name: Make SHA2-256SUMS file - name: Make SHA2-256SUMS file
env: env:
SHA2_WINDOWS: ${{ needs.build_windows.outputs.sha2_windows }} SHA2_WINDOWS: ${{ needs.build_windows.outputs.sha2_windows }}
@@ -169,16 +175,3 @@ jobs:
asset_path: ./SHA2-256SUMS asset_path: ./SHA2-256SUMS
asset_name: SHA2-256SUMS asset_name: SHA2-256SUMS
asset_content_type: text/plain asset_content_type: text/plain
# update_version_badge:
# runs-on: ubuntu-latest
# needs: build_unix
# steps:
# - name: Create Version Badge
# uses: schneegans/dynamic-badges-action@v1.0.0
# with:
# auth: ${{ secrets.GIST_TOKEN }}
# gistID: c69cb23c3c5b3316248e52022790aa57
# filename: version.json
# label: Version
# message: ${{ needs.build_unix.outputs.ytdlc_version }}
+1
View File
@@ -36,6 +36,7 @@ README.txt
# Binary # Binary
youtube-dl youtube-dl
youtube-dlc youtube-dlc
youtube-dlc.zip
*.exe *.exe
# Downloaded # Downloaded
+4 -1
View File
@@ -17,4 +17,7 @@ alxnull
FelixFrog FelixFrog
Zocker1999NET Zocker1999NET
nao20010128nao nao20010128nao
shirt-dev shirt-dev
kurumigi
tsukumi
bbepis
+35 -10
View File
@@ -8,7 +8,7 @@
* Change "Merged with youtube-dl" version in Readme.md if needed * Change "Merged with youtube-dl" version in Readme.md if needed
* Commit to master as `Release <version>` * Commit to master as `Release <version>`
* Push to origin/release - build task will now run * Push to origin/release - build task will now run
* Update version.py using devscripts\update-version.py (be wary of timezones) * 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
@@ -17,25 +17,50 @@
--> -->
### 2021.02.19
* **Merge youtube-dl:** Upto [commit/cf2dbec](https://github.com/ytdl-org/youtube-dl/commit/cf2dbec6301177a1fddf72862de05fa912d9869d) (except kakao)
* [viki] Fix extractor
* [niconico] Extract `channel` and `channel_id` by [kurumigi](https://github.com/kurumigi)
* [youtube] Multiple page support for hashtag URLs
* [youtube] Add more invidious instances
* [youtube] Fix comment extraction when comment text is empty
* Option `--windows-filenames` to force use of windows compatible filenames
* [ExtractAudio] Bugfix
* Don't raise `parser.error` when exiting for update
* [MoveFiles] Fix for when merger can't run
* Changed `--trim-file-name` to `--trim-filenames` to be similar to related options
* Format Sort improvements:
* Prefer `vp9.2` more than other `vp9` codecs
* Remove forced priority of `quality`
* Remove unnecessary `field_preference` and misuse of `preference` from extractors
* Build improvements:
* Fix hash output by [shirt](https://github.com/shirt-dev)
* Lock python package versions for x86 and use `wheels` by [shirt](https://github.com/shirt-dev)
* Exclude `vcruntime140.dll` from UPX by [jbruchon](https://github.com/jbruchon)
* Set version number based on UTC time, not local time
* Publish on PyPi only if token is set
* [documentation] Better document `--prefer-free-formats` and add `--no-prefer-free-format`
### 2021.02.15 ### 2021.02.15
* **Merge youtube-dl:** Upto [2021.02.10](https://github.com/ytdl-org/youtube-dl/releases/tag/2021.02.10) (except archive.org) * **Merge youtube-dl:** Upto [2021.02.10](https://github.com/ytdl-org/youtube-dl/releases/tag/2021.02.10) (except archive.org)
* [niconico] Improved extraction and support encrypted/SMILE movies * [niconico] Improved extraction and support encrypted/SMILE movies by [kurumigi](https://github.com/kurumigi), [tsukumi](https://github.com/tsukumi), [bbepis](https://github.com/bbepis), [pukkandan](https://github.com/pukkandan)
* Fix HLS AES-128 with multiple keys in external downloaders * Fix HLS AES-128 with multiple keys in external downloaders by [shirt](https://github.com/shirt-dev)
* [youtube_live_chat] Fix by using POST API * [youtube_live_chat] Fix by using POST API by [siikamiika](https://github.com/siikamiika)
* [rumble] Add support for video page * [rumble] Add support for video page
* Option to allow downloading unplayable video formats (`--allow-unplayable-formats`) * Option `--allow-unplayable-formats` to allow downloading unplayable video formats
* [ExtractAudio] Don't re-encode when file is already in a common audio format * [ExtractAudio] Don't re-encode when file is already in a common audio format
* Change optional dependency to `pycryptodome`
* [youtube] Fix search continuations * [youtube] Fix search continuations
* [youtube] Fix for new accounts * [youtube] Fix for new accounts
* Improve build/updater: * Improve build/updater: by [pukkandan](https://github.com/pukkandan) and [shirt](https://github.com/shirt-dev)
* Fix SHA256 calculation in build and implement hash checking for updater * Fix SHA256 calculation in build and implement hash checking for updater
* Exit immediately in windows once the update process starts * Exit immediately in windows once the update process starts
* Fix updater for `x86.exe` * Fix updater for `x86.exe`
* Updater looks for both `yt-dlp` and `youtube-dlc` in releases for future-proofing * Updater looks for both `yt-dlp` and `youtube-dlc` in releases for future-proofing
* Fix issue with unicode filenames in aria2c * Change optional dependency to `pycryptodome`
* Fix issue with unicode filenames in aria2c by [shirt](https://github.com/shirt-dev)
* Fix `allow_playlist_files` not being correctly passed through * Fix `allow_playlist_files` not being correctly passed through
* Fix for empty HTTP head requests * Fix for empty HTTP head requests by [shirt](https://github.com/shirt-dev)
* Fix `get_executable_path` in UNIX * Fix `get_executable_path` in UNIX
* [sponskrub] Print ffmpeg output and errors to terminal * [sponskrub] Print ffmpeg output and errors to terminal
* `__real_download` should be false when ffmpeg unavailable and no download * `__real_download` should be false when ffmpeg unavailable and no download
@@ -82,7 +107,7 @@
### 2021.01.29 ### 2021.01.29
* **Features from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl)**: Co-authored by [animelover1984](https://github.com/animelover1984) and [bbepis](https://github.com/bbepis) * **Features from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl)**: by [animelover1984](https://github.com/animelover1984) and [bbepis](https://github.com/bbepis)
* Add `--get-comments` * Add `--get-comments`
* [youtube] Extract comments * [youtube] Extract comments
* [billibilli] Added BiliBiliSearchIE, BilibiliChannelIE * [billibilli] Added BiliBiliSearchIE, BilibiliChannelIE
+16 -9
View File
@@ -177,7 +177,6 @@ Then simply type this
containing directory containing directory
--flat-playlist Do not extract the videos of a playlist, --flat-playlist Do not extract the videos of a playlist,
only list them only list them
--flat-videos Do not resolve the video urls
--no-flat-playlist Extract the videos of a playlist --no-flat-playlist Extract the videos of a playlist
--mark-watched Mark videos watched (YouTube only) --mark-watched Mark videos watched (YouTube only)
--no-mark-watched Do not mark videos watched --no-mark-watched Do not mark videos watched
@@ -361,6 +360,12 @@ Then simply type this
filenames filenames
--no-restrict-filenames Allow Unicode characters, "&" and spaces in --no-restrict-filenames Allow Unicode characters, "&" and spaces in
filenames (default) filenames (default)
--windows-filenames Force filenames to be windows compatible
--no-windows-filenames Make filenames windows compatible only if
using windows (default)
--trim-filenames LENGTH Limit the filename length (excluding
extension) to the specified number of
characters
-w, --no-overwrites Do not overwrite any files -w, --no-overwrites Do not overwrite any files
--force-overwrites Overwrite all video and metadata files. --force-overwrites Overwrite all video and metadata files.
This option includes --no-continue This option includes --no-continue
@@ -411,8 +416,6 @@ Then simply type this
may change may change
--no-cache-dir Disable filesystem caching --no-cache-dir Disable filesystem caching
--rm-cache-dir Delete all filesystem cache files --rm-cache-dir Delete all filesystem cache files
--trim-file-name LENGTH Limit the filename length (extension
excluded)
## Thumbnail Images: ## Thumbnail Images:
--write-thumbnail Write thumbnail image to disk --write-thumbnail Write thumbnail image to disk
@@ -516,8 +519,12 @@ Then simply type this
--no-audio-multistreams Only one audio stream is downloaded for --no-audio-multistreams Only one audio stream is downloaded for
each output file (default) each output file (default)
--all-formats Download all available video formats --all-formats Download all available video formats
--prefer-free-formats Prefer free video formats over non-free --prefer-free-formats Prefer video formats with free containers
formats of same quality over non-free ones of same quality. Use
with "-S ext" to strictly prefer free
containers irrespective of quality
--no-prefer-free-formats Don't give any special preference to free
containers (default)
-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 --list-formats-as-table Present the output of -F in tabular form
@@ -978,10 +985,10 @@ You can change the criteria for being considered the `best` by using `-S` (`--fo
- `hasaud`: Gives priority to formats that has a audio stream - `hasaud`: Gives priority to formats that has a audio stream
- `ie_pref`: The format preference as given by the extractor - `ie_pref`: The format preference as given by the extractor
- `lang`: Language preference as given by the extractor - `lang`: Language preference as given by the extractor
- `quality`: The quality of the format. This is a metadata field available in some websites - `quality`: The quality of the format as given by the extractor
- `source`: Preference of the source as given by the extractor - `source`: Preference of the source as given by the extractor
- `proto`: Protocol used for download (`https`/`ftps` > `http`/`ftp` > `m3u8-native` > `m3u8` > `http-dash-segments` > other > `mms`/`rtsp` > unknown > `f4f`/`f4m`) - `proto`: Protocol used for download (`https`/`ftps` > `http`/`ftp` > `m3u8-native` > `m3u8` > `http-dash-segments` > other > `mms`/`rtsp` > unknown > `f4f`/`f4m`)
- `vcodec`: Video Codec (`av01` > `vp9` > `h265` > `h264` > `vp8` > `h263` > `theora` > other > unknown) - `vcodec`: Video Codec (`av01` > `vp9.2` > `vp9` > `h265` > `h264` > `vp8` > `h263` > `theora` > other > unknown)
- `acodec`: Audio Codec (`opus` > `vorbis` > `aac` > `mp4a` > `mp3` > `ac3` > `dts` > other > unknown) - `acodec`: Audio Codec (`opus` > `vorbis` > `aac` > `mp4a` > `mp3` > `ac3` > `dts` > other > unknown)
- `codec`: Equivalent to `vcodec,acodec` - `codec`: Equivalent to `vcodec,acodec`
- `vext`: Video Extension (`mp4` > `webm` > `flv` > other > unknown). If `--prefer-free-formats` is used, `webm` is prefered. - `vext`: Video Extension (`mp4` > `webm` > `flv` > other > unknown). If `--prefer-free-formats` is used, `webm` is prefered.
@@ -1000,9 +1007,9 @@ You can change the criteria for being considered the `best` by using `-S` (`--fo
- `br`: Equivalent to using `tbr,vbr,abr` - `br`: Equivalent to using `tbr,vbr,abr`
- `asr`: Audio sample rate in Hz - `asr`: Audio sample rate in Hz
Note that any other **numerical** field made available by the extractor can also be used. All fields, unless specified otherwise, are sorted in decending order. To reverse this, prefix the field with a `+`. Eg: `+res` prefers format with the smallest resolution. Additionally, you can suffix a prefered value for the fields, seperated by a `:`. Eg: `res:720` prefers larger videos, but no larger than 720p and the smallest video if there are no videos less than 720p. For `codec` and `ext`, you can provide two prefered values, the first for video and the second for audio. Eg: `+codec:avc:m4a` (equivalent to `+vcodec:avc,+acodec:m4a`) sets the video codec preference to `h264` > `h265` > `vp9` > `vp8` > `h263` > `theora` and audio codec preference to `mp4a` > `aac` > `vorbis` > `opus` > `mp3` > `ac3` > `dts`. You can also make the sorting prefer the nearest values to the provided by using `~` as the delimiter. Eg: `filesize~1G` prefers the format with filesize closest to 1 GiB. Note that any other **numerical** field made available by the extractor can also be used. All fields, unless specified otherwise, are sorted in decending order. To reverse this, prefix the field with a `+`. Eg: `+res` prefers format with the smallest resolution. Additionally, you can suffix a prefered value for the fields, seperated by a `:`. Eg: `res:720` prefers larger videos, but no larger than 720p and the smallest video if there are no videos less than 720p. For `codec` and `ext`, you can provide two prefered values, the first for video and the second for audio. Eg: `+codec:avc:m4a` (equivalent to `+vcodec:avc,+acodec:m4a`) sets the video codec preference to `h264` > `h265` > `vp9` > `vp9.2` > `av01` > `vp8` > `h263` > `theora` and audio codec preference to `mp4a` > `aac` > `vorbis` > `opus` > `mp3` > `ac3` > `dts`. You can also make the sorting prefer the nearest values to the provided by using `~` as the delimiter. Eg: `filesize~1G` prefers the format with filesize closest to 1 GiB.
The fields `hasvid`, `ie_pref`, `lang`, `quality` are always given highest priority in sorting, irrespective of the user-defined order. This behaviour can be changed by using `--force-format-sort`. Apart from these, the default order used is: `res,fps,codec:vp9,size,br,asr,proto,ext,hasaud,source,id`. Note that the extractors may override this default order, but they cannot override the user-provided order. The fields `hasvid`, `ie_pref`, `lang` are always given highest priority in sorting, irrespective of the user-defined order. This behaviour can be changed by using `--force-format-sort`. Apart from these, the default order used is: `quality,res,fps,codec:vp9.2,size,br,asr,proto,ext,hasaud,source,id`. Note that the extractors may override this default order, but they cannot override the user-provided order.
If your format selector is `worst`, the last item is selected after sorting. This means it will select the format that is worst in all repects. Most of the time, what you actually want is the video with the smallest filesize instead. So it is generally better to use `-f best -S +size,+br,+res,+fps`. If your format selector is `worst`, the last item is selected after sorting. This means it will select the format that is worst in all repects. Most of the time, what you actually want is the video with the smallest filesize instead. So it is generally better to use `-f best -S +size,+br,+res,+fps`.
+1 -1
View File
@@ -13,7 +13,7 @@ old_version_list = old_version.split(".", 4)
old_ver = '.'.join(old_version_list[:3]) old_ver = '.'.join(old_version_list[:3])
old_rev = old_version_list[3] if len(old_version_list) > 3 else '' old_rev = old_version_list[3] if len(old_version_list) > 3 else ''
ver = datetime.now().strftime("%Y.%m.%d") ver = datetime.utcnow().strftime("%Y.%m.%d")
rev = str(int(old_rev or 0) + 1) if old_ver == ver else '' rev = str(int(old_rev or 0) + 1) if old_ver == ver else ''
VERSION = '.'.join((ver, rev)) if rev else ver VERSION = '.'.join((ver, rev)) if rev else ver
+8
View File
@@ -5,6 +5,7 @@
- **23video** - **23video**
- **24video** - **24video**
- **3qsdn**: 3Q SDN - **3qsdn**: 3Q SDN
- **3sat**
- **4tube** - **4tube**
- **56.com** - **56.com**
- **5min** - **5min**
@@ -222,6 +223,7 @@
- **curiositystream** - **curiositystream**
- **curiositystream:collection** - **curiositystream:collection**
- **CWTV** - **CWTV**
- **DagelijkseKost**: dagelijksekost.een.be
- **DailyMail** - **DailyMail**
- **dailymotion** - **dailymotion**
- **dailymotion:playlist** - **dailymotion:playlist**
@@ -244,6 +246,7 @@
- **DiscoveryGo** - **DiscoveryGo**
- **DiscoveryGoPlaylist** - **DiscoveryGoPlaylist**
- **DiscoveryNetworksDe** - **DiscoveryNetworksDe**
- **DiscoveryPlus**
- **DiscoveryVR** - **DiscoveryVR**
- **Disney** - **Disney**
- **dlive:stream** - **dlive:stream**
@@ -370,6 +373,7 @@
- **HentaiStigma** - **HentaiStigma**
- **hetklokhuis** - **hetklokhuis**
- **hgtv.com:show** - **hgtv.com:show**
- **HGTVDe**
- **HiDive** - **HiDive**
- **HistoricFilms** - **HistoricFilms**
- **history:player** - **history:player**
@@ -854,6 +858,9 @@
- **ShahidShow** - **ShahidShow**
- **Shared**: shared.sx - **Shared**: shared.sx
- **ShowRoomLive** - **ShowRoomLive**
- **simplecast**
- **simplecast:episode**
- **simplecast:podcast**
- **Sina** - **Sina**
- **sky.it** - **sky.it**
- **sky:news** - **sky:news**
@@ -1231,6 +1238,7 @@
- **ZattooLive** - **ZattooLive**
- **ZDF-3sat** - **ZDF-3sat**
- **ZDFChannel** - **ZDFChannel**
- **Zhihu**
- **zingmp3**: mp3.zing.vn - **zingmp3**: mp3.zing.vn
- **zoom** - **zoom**
- **Zype** - **Zype**
+1
View File
@@ -73,6 +73,7 @@ PyInstaller.__main__.run([
'--exclude-module=ytdlp_plugins', '--exclude-module=ytdlp_plugins',
'--hidden-import=mutagen', '--hidden-import=mutagen',
'--hidden-import=Crypto', '--hidden-import=Crypto',
'--upx-exclude=vcruntime140.dll',
'youtube_dlc/__main__.py', 'youtube_dlc/__main__.py',
]) ])
SetVersion('dist/youtube-dlc%s.exe' % _x86, VERSION_FILE) SetVersion('dist/youtube-dlc%s.exe' % _x86, VERSION_FILE)
+14 -5
View File
@@ -12,6 +12,7 @@ from test.helper import FakeYDL
from youtube_dlc.extractor import ( from youtube_dlc.extractor import (
YoutubePlaylistIE, YoutubePlaylistIE,
YoutubeTabIE,
YoutubeIE, YoutubeIE,
) )
@@ -57,14 +58,22 @@ class TestYoutubeLists(unittest.TestCase):
entries = result['entries'] entries = result['entries']
self.assertEqual(len(entries), 100) self.assertEqual(len(entries), 100)
def test_youtube_flat_playlist_titles(self): def test_youtube_flat_playlist_extraction(self):
dl = FakeYDL() dl = FakeYDL()
dl.params['extract_flat'] = True dl.params['extract_flat'] = True
ie = YoutubePlaylistIE(dl) ie = YoutubeTabIE(dl)
result = ie.extract('https://www.youtube.com/playlist?list=PL-KKIb8rvtMSrAO9YFbeM6UQrAqoFTUWv') result = ie.extract('https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc')
self.assertIsPlaylist(result) self.assertIsPlaylist(result)
for entry in result['entries']: entries = list(result['entries'])
self.assertTrue(entry.get('title')) self.assertTrue(len(entries) == 1)
video = entries[0]
self.assertEqual(video['_type'], 'url_transparent')
self.assertEqual(video['ie_key'], 'Youtube')
self.assertEqual(video['id'], 'BaW_jenozKc')
self.assertEqual(video['url'], 'BaW_jenozKc')
self.assertEqual(video['title'], 'youtube-dl test video "\'/\\ä↭𝕐')
self.assertEqual(video['duration'], 10)
self.assertEqual(video['uploader'], 'Philipp Hagemeister')
if __name__ == '__main__': if __name__ == '__main__':
+16 -11
View File
@@ -189,11 +189,15 @@ class YoutubeDL(object):
into a single file into a single file
allow_multiple_audio_streams: Allow multiple audio streams to be merged allow_multiple_audio_streams: Allow multiple audio streams to be merged
into a single file into a single file
paths: Dictionary of output paths. The allowed keys are 'home'
'temp' and the keys of OUTTMPL_TYPES (in utils.py)
outtmpl: Dictionary of templates for output names. Allowed keys outtmpl: Dictionary of templates for output names. Allowed keys
are 'default' and the keys of OUTTMPL_TYPES (in utils.py) are 'default' and the keys of OUTTMPL_TYPES (in utils.py).
A string a also accepted for backward compatibility
outtmpl_na_placeholder: Placeholder for unavailable meta fields. outtmpl_na_placeholder: Placeholder for unavailable meta fields.
restrictfilenames: Do not allow "&" and spaces in file names restrictfilenames: Do not allow "&" and spaces in file names
trim_file_name: Limit length of filename (extension excluded) trim_file_name: Limit length of filename (extension excluded)
windowsfilenames: Force the filenames to be windows compatible
ignoreerrors: Do not stop on download errors ignoreerrors: Do not stop on download errors
(Default True when running youtube-dlc, (Default True when running youtube-dlc,
but False when directly accessing YoutubeDL class) but False when directly accessing YoutubeDL class)
@@ -868,13 +872,6 @@ class YoutubeDL(object):
sub_ext = fn_groups[-2] sub_ext = fn_groups[-2]
filename = '.'.join(filter(None, [fn_groups[0][:trim_file_name], sub_ext, ext])) filename = '.'.join(filter(None, [fn_groups[0][:trim_file_name], sub_ext, ext]))
# Temporary fix for #4787
# 'Treat' all problem characters by passing filename through preferredencoding
# to workaround encoding issues with subprocess on python2 @ Windows
if sys.version_info < (3, 0) and sys.platform == 'win32':
filename = encodeFilename(filename, True).decode(preferredencoding())
filename = sanitize_path(filename)
return filename return filename
except ValueError as err: except ValueError as err:
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')') self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
@@ -901,7 +898,14 @@ class YoutubeDL(object):
assert isinstance(homepath, compat_str) assert isinstance(homepath, compat_str)
subdir = expand_path(paths.get(dir_type, '').strip()) if dir_type else '' subdir = expand_path(paths.get(dir_type, '').strip()) if dir_type else ''
assert isinstance(subdir, compat_str) assert isinstance(subdir, compat_str)
return sanitize_path(os.path.join(homepath, subdir, filename)) path = os.path.join(homepath, subdir, filename)
# Temporary fix for #4787
# 'Treat' all problem characters by passing filename through preferredencoding
# to workaround encoding issues with subprocess on python2 @ Windows
if sys.version_info < (3, 0) and sys.platform == 'win32':
path = encodeFilename(path, True).decode(preferredencoding())
return sanitize_path(path, force=self.params.get('windowsfilenames'))
def _match_entry(self, info_dict, incomplete): def _match_entry(self, info_dict, incomplete):
""" Returns None if the file should be downloaded """ """ Returns None if the file should be downloaded """
@@ -2360,6 +2364,9 @@ class YoutubeDL(object):
info_dict['__files_to_merge'] = downloaded info_dict['__files_to_merge'] = downloaded
# Even if there were no downloads, it is being merged only now # Even if there were no downloads, it is being merged only now
info_dict['__real_download'] = True info_dict['__real_download'] = True
else:
for file in downloaded:
files_to_move[file] = None
else: else:
# Just a single file # Just a single file
dl_filename = existing_file(full_filename, temp_filename) dl_filename = existing_file(full_filename, temp_filename)
@@ -2713,8 +2720,6 @@ class YoutubeDL(object):
if f.get('preference') is None or f['preference'] >= -1000] if f.get('preference') is None or f['preference'] >= -1000]
header_line = ['format code', 'extension', 'resolution', 'note'] header_line = ['format code', 'extension', 'resolution', 'note']
# if len(formats) > 1:
# table[-1][-1] += (' ' if table[-1][-1] else '') + '(best)'
self.to_screen( self.to_screen(
'[info] Available formats for %s:\n%s' % (info_dict['id'], render_table( '[info] Available formats for %s:\n%s' % (info_dict['id'], render_table(
header_line, header_line,
+2 -1
View File
@@ -440,6 +440,7 @@ def _real_main(argv=None):
'autonumber_size': opts.autonumber_size, 'autonumber_size': opts.autonumber_size,
'autonumber_start': opts.autonumber_start, 'autonumber_start': opts.autonumber_start,
'restrictfilenames': opts.restrictfilenames, 'restrictfilenames': opts.restrictfilenames,
'windowsfilenames': opts.windowsfilenames,
'ignoreerrors': opts.ignoreerrors, 'ignoreerrors': opts.ignoreerrors,
'force_generic_extractor': opts.force_generic_extractor, 'force_generic_extractor': opts.force_generic_extractor,
'ratelimit': opts.ratelimit, 'ratelimit': opts.ratelimit,
@@ -560,7 +561,7 @@ def _real_main(argv=None):
# 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 update_self(ydl.to_screen, opts.verbose, ydl._opener):
if actual_use: if actual_use:
parser.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()
# Maybe do nothing # Maybe do nothing
+1 -1
View File
@@ -66,7 +66,7 @@ class AdobeTVBaseIE(InfoExtractor):
if original_filename.startswith('s3://') and not s3_extracted: if original_filename.startswith('s3://') and not s3_extracted:
formats.append({ formats.append({
'format_id': 'original', 'format_id': 'original',
'preference': 1, 'quality': 1,
'url': original_filename.replace('s3://', 'https://s3.amazonaws.com/'), 'url': original_filename.replace('s3://', 'https://s3.amazonaws.com/'),
}) })
s3_extracted = True s3_extracted = True
+1 -1
View File
@@ -67,7 +67,7 @@ class AluraIE(InfoExtractor):
f['height'] = int('720' if m.group('res') == 'hd' else '480') f['height'] = int('720' if m.group('res') == 'hd' else '480')
formats.extend(video_format) formats.extend(video_format)
self._sort_formats(formats, field_preference=('height', 'width', 'tbr', 'format_id')) self._sort_formats(formats)
return { return {
'id': video_id, 'id': video_id,
+1 -1
View File
@@ -125,7 +125,7 @@ class AolIE(YahooIE):
'height': int_or_none(qs.get('h', [None])[0]), 'height': int_or_none(qs.get('h', [None])[0]),
}) })
formats.append(f) formats.append(f)
self._sort_formats(formats, ('width', 'height', 'tbr', 'format_id')) self._sort_formats(formats)
return { return {
'id': video_id, 'id': video_id,
+1 -2
View File
@@ -72,8 +72,7 @@ class AparatIE(InfoExtractor):
r'(\d+)[pP]', label or '', 'height', r'(\d+)[pP]', label or '', 'height',
default=None)), default=None)),
}) })
self._sort_formats( self._sort_formats(formats)
formats, field_preference=('height', 'width', 'tbr', 'format_id'))
info = self._search_json_ld(webpage, video_id, default={}) info = self._search_json_ld(webpage, video_id, default={})
+2 -7
View File
@@ -129,10 +129,6 @@ class ArcPublishingIE(InfoExtractor):
if all([f.get('acodec') == 'none' for f in m3u8_formats]): if all([f.get('acodec') == 'none' for f in m3u8_formats]):
continue continue
for f in m3u8_formats: for f in m3u8_formats:
if f.get('acodec') == 'none':
f['preference'] = -40
elif f.get('vcodec') == 'none':
f['preference'] = -50
height = f.get('height') height = f.get('height')
if not height: if not height:
continue continue
@@ -150,10 +146,9 @@ class ArcPublishingIE(InfoExtractor):
'height': int_or_none(s.get('height')), 'height': int_or_none(s.get('height')),
'filesize': int_or_none(s.get('filesize')), 'filesize': int_or_none(s.get('filesize')),
'url': s_url, 'url': s_url,
'preference': -1, 'quality': -10,
}) })
self._sort_formats( self._sort_formats(formats)
formats, ('preference', 'width', 'height', 'vbr', 'filesize', 'tbr', 'ext', 'format_id'))
subtitles = {} subtitles = {}
for subtitle in (try_get(video, lambda x: x['subtitles']['urls'], list) or []): for subtitle in (try_get(video, lambda x: x['subtitles']['urls'], list) or []):
+33 -11
View File
@@ -324,20 +324,42 @@ class ARDIE(InfoExtractor):
formats = [] formats = []
for a in video_node.findall('.//asset'): for a in video_node.findall('.//asset'):
file_name = xpath_text(a, './fileName', default=None)
if not file_name:
continue
format_type = a.attrib.get('type')
format_url = url_or_none(file_name)
if format_url:
ext = determine_ext(file_name)
if ext == 'm3u8':
formats.extend(self._extract_m3u8_formats(
format_url, display_id, 'mp4', entry_protocol='m3u8_native',
m3u8_id=format_type or 'hls', fatal=False))
continue
elif ext == 'f4m':
formats.extend(self._extract_f4m_formats(
update_url_query(format_url, {'hdcore': '3.7.0'}),
display_id, f4m_id=format_type or 'hds', fatal=False))
continue
f = { f = {
'format_id': a.attrib['type'], 'format_id': format_type,
'width': int_or_none(a.find('./frameWidth').text), 'width': int_or_none(xpath_text(a, './frameWidth')),
'height': int_or_none(a.find('./frameHeight').text), 'height': int_or_none(xpath_text(a, './frameHeight')),
'vbr': int_or_none(a.find('./bitrateVideo').text), 'vbr': int_or_none(xpath_text(a, './bitrateVideo')),
'abr': int_or_none(a.find('./bitrateAudio').text), 'abr': int_or_none(xpath_text(a, './bitrateAudio')),
'vcodec': a.find('./codecVideo').text, 'vcodec': xpath_text(a, './codecVideo'),
'tbr': int_or_none(a.find('./totalBitrate').text), 'tbr': int_or_none(xpath_text(a, './totalBitrate')),
} }
if a.find('./serverPrefix').text: server_prefix = xpath_text(a, './serverPrefix', default=None)
f['url'] = a.find('./serverPrefix').text if server_prefix:
f['playpath'] = a.find('./fileName').text f.update({
'url': server_prefix,
'playpath': file_name,
})
else: else:
f['url'] = a.find('./fileName').text if not format_url:
continue
f['url'] = format_url
formats.append(f) formats.append(f)
self._sort_formats(formats) self._sort_formats(formats)
+3 -2
View File
@@ -150,7 +150,6 @@ class ArteTVIE(ArteTVBaseIE):
format = { format = {
'format_id': format_id, 'format_id': format_id,
'preference': -10 if f.get('videoFormat') == 'M3U8' else None,
'language_preference': lang_pref, 'language_preference': lang_pref,
'format_note': '%s, %s' % (f.get('versionCode'), f.get('versionLibelle')), 'format_note': '%s, %s' % (f.get('versionCode'), f.get('versionLibelle')),
'width': int_or_none(f.get('width')), 'width': int_or_none(f.get('width')),
@@ -168,7 +167,9 @@ class ArteTVIE(ArteTVBaseIE):
formats.append(format) formats.append(format)
self._sort_formats(formats) # For this extractor, quality only represents the relative quality
# with respect to other formats with the same resolution
self._sort_formats(formats, ('res', 'quality'))
return { return {
'id': player_info.get('VID') or video_id, 'id': player_info.get('VID') or video_id,
-2
View File
@@ -69,12 +69,10 @@ class BeatportIE(InfoExtractor):
'vcodec': 'none', 'vcodec': 'none',
} }
if ext == 'mp3': if ext == 'mp3':
fmt['preference'] = 0
fmt['acodec'] = 'mp3' fmt['acodec'] = 'mp3'
fmt['abr'] = 96 fmt['abr'] = 96
fmt['asr'] = 44100 fmt['asr'] = 44100
elif ext == 'mp4': elif ext == 'mp4':
fmt['preference'] = 1
fmt['acodec'] = 'aac' fmt['acodec'] = 'aac'
fmt['abr'] = 96 fmt['abr'] = 96
fmt['asr'] = 44100 fmt['asr'] = 44100
+1 -1
View File
@@ -203,7 +203,7 @@ class BiliBiliIE(InfoExtractor):
formats.append({ formats.append({
'url': backup_url, 'url': backup_url,
# backup URLs have lower priorities # backup URLs have lower priorities
'preference': -2 if 'hd.mp4' in backup_url else -3, 'quality': -2 if 'hd.mp4' in backup_url else -3,
}) })
for a_format in formats: for a_format in formats:
+1 -1
View File
@@ -23,7 +23,7 @@ class BokeCCBaseIE(InfoExtractor):
formats = [{ formats = [{
'format_id': format_id, 'format_id': format_id,
'url': quality.find('./copy').attrib['playurl'], 'url': quality.find('./copy').attrib['playurl'],
'preference': int(quality.attrib['value']), 'quality': int(quality.attrib['value']),
} for quality in info_xml.findall('./video/quality')] } for quality in info_xml.findall('./video/quality')]
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -47,7 +47,7 @@ class BpbIE(InfoExtractor):
quality = 'high' if '_high' in video_url else 'low' quality = 'high' if '_high' in video_url else 'low'
formats.append({ formats.append({
'url': video_url, 'url': video_url,
'preference': 10 if quality == 'high' else 0, 'quality': 10 if quality == 'high' else 0,
'format_note': quality, 'format_note': quality,
'format_id': '%s-%s' % (quality, determine_ext(video_url)), 'format_id': '%s-%s' % (quality, determine_ext(video_url)),
}) })
+1 -1
View File
@@ -82,7 +82,7 @@ class CamModelsIE(InfoExtractor):
f.update({ f.update({
'ext': 'mp4', 'ext': 'mp4',
# hls skips fragments, preferring rtmp # hls skips fragments, preferring rtmp
'preference': -1, 'quality': -10,
}) })
else: else:
continue continue
+1 -1
View File
@@ -89,7 +89,7 @@ class CanalplusIE(InfoExtractor):
# the secret extracted from ya function in http://player.canalplus.fr/common/js/canalPlayer.js # the secret extracted from ya function in http://player.canalplus.fr/common/js/canalPlayer.js
'url': format_url + '?secret=pqzerjlsmdkjfoiuerhsdlfknaes', 'url': format_url + '?secret=pqzerjlsmdkjfoiuerhsdlfknaes',
'format_id': format_id, 'format_id': format_id,
'preference': preference(format_id), 'quality': preference(format_id),
}) })
self._sort_formats(formats) self._sort_formats(formats)
+53 -3
View File
@@ -7,19 +7,21 @@ from .common import InfoExtractor
from .gigya import GigyaBaseIE from .gigya import GigyaBaseIE
from ..compat import compat_HTTPError from ..compat import compat_HTTPError
from ..utils import ( from ..utils import (
extract_attributes,
ExtractorError, ExtractorError,
strip_or_none, clean_html,
extract_attributes,
float_or_none, float_or_none,
get_element_by_class,
int_or_none, int_or_none,
merge_dicts, merge_dicts,
str_or_none, str_or_none,
strip_or_none,
url_or_none, url_or_none,
) )
class CanvasIE(InfoExtractor): class CanvasIE(InfoExtractor):
_VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet|vrt(?:video|nieuws)|sporza)/assets/(?P<id>[^/?#&]+)' _VALID_URL = r'https?://mediazone\.vrt\.be/api/v1/(?P<site_id>canvas|een|ketnet|vrt(?:video|nieuws)|sporza|dako)/assets/(?P<id>[^/?#&]+)'
_TESTS = [{ _TESTS = [{
'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475', 'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/md-ast-4ac54990-ce66-4d00-a8ca-9eac86f4c475',
'md5': '68993eda72ef62386a15ea2cf3c93107', 'md5': '68993eda72ef62386a15ea2cf3c93107',
@@ -332,3 +334,51 @@ class VrtNUIE(GigyaBaseIE):
'display_id': display_id, 'display_id': display_id,
'season_number': int_or_none(page.get('episode_season')), 'season_number': int_or_none(page.get('episode_season')),
}) })
class DagelijkseKostIE(InfoExtractor):
IE_DESC = 'dagelijksekost.een.be'
_VALID_URL = r'https?://dagelijksekost\.een\.be/gerechten/(?P<id>[^/?#&]+)'
_TEST = {
'url': 'https://dagelijksekost.een.be/gerechten/hachis-parmentier-met-witloof',
'md5': '30bfffc323009a3e5f689bef6efa2365',
'info_dict': {
'id': 'md-ast-27a4d1ff-7d7b-425e-b84f-a4d227f592fa',
'display_id': 'hachis-parmentier-met-witloof',
'ext': 'mp4',
'title': 'Hachis parmentier met witloof',
'description': 'md5:9960478392d87f63567b5b117688cdc5',
'thumbnail': r're:^https?://.*\.jpg$',
'duration': 283.02,
},
'expected_warnings': ['is not a supported codec'],
}
def _real_extract(self, url):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id)
title = strip_or_none(get_element_by_class(
'dish-metadata__title', webpage
) or self._html_search_meta(
'twitter:title', webpage))
description = clean_html(get_element_by_class(
'dish-description', webpage)
) or self._html_search_meta(
('description', 'twitter:description', 'og:description'),
webpage)
video_id = self._html_search_regex(
r'data-url=(["\'])(?P<id>(?:(?!\1).)+)\1', webpage, 'video id',
group='id')
return {
'_type': 'url_transparent',
'url': 'https://mediazone.vrt.be/api/v1/dako/assets/%s' % video_id,
'ie_key': CanvasIE.ie_key(),
'id': video_id,
'display_id': display_id,
'title': title,
'description': description,
}
+5 -2
View File
@@ -1,12 +1,14 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import calendar
import datetime import datetime
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
clean_html, clean_html,
extract_timezone,
int_or_none, int_or_none,
parse_duration, parse_duration,
parse_resolution, parse_resolution,
@@ -97,8 +99,9 @@ class CCMAIE(InfoExtractor):
timestamp = None timestamp = None
data_utc = try_get(informacio, lambda x: x['data_emissio']['utc']) data_utc = try_get(informacio, lambda x: x['data_emissio']['utc'])
try: try:
timestamp = datetime.datetime.strptime( timezone, data_utc = extract_timezone(data_utc)
data_utc, '%Y-%d-%mT%H:%M:%S%z').timestamp() timestamp = calendar.timegm((datetime.datetime.strptime(
data_utc, '%Y-%d-%mT%H:%M:%S') - timezone).timetuple())
except TypeError: except TypeError:
pass pass
+1 -1
View File
@@ -162,7 +162,7 @@ class CCTVIE(InfoExtractor):
'url': video_url, 'url': video_url,
'format_id': 'http', 'format_id': 'http',
'quality': quality, 'quality': quality,
'preference': -1, 'source_preference': -10
}) })
hls_url = try_get(data, lambda x: x['hls_url'], compat_str) hls_url = try_get(data, lambda x: x['hls_url'], compat_str)
+26 -22
View File
@@ -1367,12 +1367,12 @@ class InfoExtractor(object):
regex = r' *((?P<reverse>\+)?(?P<field>[a-zA-Z0-9_]+)((?P<seperator>[~:])(?P<limit>.*?))?)? *$' regex = r' *((?P<reverse>\+)?(?P<field>[a-zA-Z0-9_]+)((?P<seperator>[~:])(?P<limit>.*?))?)? *$'
default = ('hidden', 'hasvid', 'ie_pref', 'lang', 'quality', default = ('hidden', 'hasvid', 'ie_pref', 'lang', 'quality',
'res', 'fps', 'codec:vp9', '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', 'has_audio', 'source', 'format_id') # These must not be aliases
settings = { settings = {
'vcodec': {'type': 'ordered', 'regex': True, 'vcodec': {'type': 'ordered', 'regex': True,
'order': ['av0?1', 'vp9', '(h265|he?vc?)', '(h264|avc)', 'vp8', '(mp4v|h263)', 'theora', '', None, 'none']}, 'order': ['av0?1', 'vp0?9.2', 'vp0?9', '[hx]265|he?vc?', '[hx]264|avc', 'vp0?8', 'mp4v|h263', 'theora', '', None, 'none']},
'acodec': {'type': 'ordered', 'regex': True, 'acodec': {'type': 'ordered', 'regex': True,
'order': ['opus', 'vorbis', 'aac', 'mp?4a?', 'mp3', 'e?a?c-?3', 'dts', '', None, 'none']}, 'order': ['opus', 'vorbis', 'aac', 'mp?4a?', 'mp3', 'e?a?c-?3', 'dts', '', None, 'none']},
'proto': {'type': 'ordered', 'regex': True, 'field': 'protocol', 'proto': {'type': 'ordered', 'regex': True, 'field': 'protocol',
@@ -1384,11 +1384,11 @@ class InfoExtractor(object):
'order': ('m4a', 'aac', 'mp3', 'ogg', 'opus', 'webm', '', 'none'), 'order': ('m4a', 'aac', 'mp3', 'ogg', 'opus', 'webm', '', 'none'),
'order_free': ('opus', 'ogg', 'webm', 'm4a', 'mp3', 'aac', '', 'none')}, 'order_free': ('opus', 'ogg', 'webm', 'm4a', 'mp3', 'aac', '', 'none')},
'hidden': {'visible': False, 'forced': True, 'type': 'extractor', 'max': -1000}, 'hidden': {'visible': False, 'forced': True, 'type': 'extractor', 'max': -1000},
'ie_pref': {'priority': True, 'type': 'extractor', 'field': 'extractor_preference'}, 'ie_pref': {'priority': True, 'type': 'extractor'},
'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', 'type': 'extractor', 'field': 'language_preference'},
'quality': {'priority': True, 'convert': 'float_none'}, 'quality': {'convert': 'float_none', 'type': 'extractor'},
'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'},
@@ -1399,7 +1399,7 @@ class InfoExtractor(object):
'vbr': {'convert': 'float_none'}, 'vbr': {'convert': 'float_none'},
'abr': {'convert': 'float_none'}, 'abr': {'convert': 'float_none'},
'asr': {'convert': 'float_none'}, 'asr': {'convert': 'float_none'},
'source': {'convert': 'ignore', 'field': 'source_preference'}, 'source': {'convert': 'ignore', 'type': 'extractor', 'field': 'source_preference'},
'codec': {'type': 'combined', 'field': ('vcodec', 'acodec')}, 'codec': {'type': 'combined', 'field': ('vcodec', 'acodec')},
'br': {'type': 'combined', 'field': ('tbr', 'vbr', 'abr'), 'same_limit': True}, 'br': {'type': 'combined', 'field': ('tbr', 'vbr', 'abr'), 'same_limit': True},
@@ -1469,13 +1469,12 @@ class InfoExtractor(object):
elif conversion == 'bytes': elif conversion == 'bytes':
return FileDownloader.parse_bytes(value) return FileDownloader.parse_bytes(value)
elif conversion == 'order': elif conversion == 'order':
order_free = self._get_field_setting(field, 'order_free') order_list = (self._use_free_order and self._get_field_setting(field, 'order_free')) or self._get_field_setting(field, 'order')
order_list = order_free if order_free and self._use_free_order else self._get_field_setting(field, 'order')
use_regex = self._get_field_setting(field, 'regex') use_regex = self._get_field_setting(field, 'regex')
list_length = len(order_list) list_length = len(order_list)
empty_pos = order_list.index('') if '' in order_list else list_length + 1 empty_pos = order_list.index('') if '' in order_list else list_length + 1
if use_regex and value is not None: if use_regex and value is not None:
for (i, regex) in enumerate(order_list): for i, regex in enumerate(order_list):
if regex and re.match(regex, value): if regex and re.match(regex, value):
return list_length - i return list_length - i
return list_length - empty_pos # not in list return list_length - empty_pos # not in list
@@ -1544,7 +1543,7 @@ class InfoExtractor(object):
def print_verbose_info(self, to_screen): def print_verbose_info(self, to_screen):
to_screen('[debug] Sort order given by user: %s' % ','.join(self._sort_user)) to_screen('[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)) to_screen('[debug] Sort order given by extractor: %s' % ', '.join(self._sort_extractor))
to_screen('[debug] Formats sorted by: %s' % ', '.join(['%s%s%s' % ( to_screen('[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 ':',
@@ -1561,7 +1560,7 @@ class InfoExtractor(object):
if type == 'extractor': if type == 'extractor':
maximum = self._get_field_setting(field, 'max') maximum = self._get_field_setting(field, 'max')
if value is None or (maximum is not None and value >= maximum): if value is None or (maximum is not None and value >= maximum):
value = 0 value = -1
elif type == 'boolean': elif type == 'boolean':
in_list = self._get_field_setting(field, 'in_list') in_list = self._get_field_setting(field, 'in_list')
not_in_list = self._get_field_setting(field, 'not_in_list') not_in_list = self._get_field_setting(field, 'not_in_list')
@@ -1694,7 +1693,7 @@ class InfoExtractor(object):
self.to_screen(msg) self.to_screen(msg)
time.sleep(timeout) time.sleep(timeout)
def _extract_f4m_formats(self, manifest_url, video_id, preference=None, f4m_id=None, def _extract_f4m_formats(self, manifest_url, video_id, preference=None, quality=None, f4m_id=None,
transform_source=lambda s: fix_xml_ampersands(s).strip(), transform_source=lambda s: fix_xml_ampersands(s).strip(),
fatal=True, m3u8_id=None, data=None, headers={}, query={}): fatal=True, m3u8_id=None, data=None, headers={}, query={}):
manifest = self._download_xml( manifest = self._download_xml(
@@ -1709,10 +1708,10 @@ class InfoExtractor(object):
return [] return []
return self._parse_f4m_formats( return self._parse_f4m_formats(
manifest, manifest_url, video_id, preference=preference, f4m_id=f4m_id, manifest, manifest_url, video_id, preference=preference, quality=quality, f4m_id=f4m_id,
transform_source=transform_source, fatal=fatal, m3u8_id=m3u8_id) transform_source=transform_source, fatal=fatal, m3u8_id=m3u8_id)
def _parse_f4m_formats(self, manifest, manifest_url, video_id, preference=None, f4m_id=None, def _parse_f4m_formats(self, manifest, manifest_url, video_id, preference=None, quality=None, f4m_id=None,
transform_source=lambda s: fix_xml_ampersands(s).strip(), transform_source=lambda s: fix_xml_ampersands(s).strip(),
fatal=True, m3u8_id=None): fatal=True, m3u8_id=None):
if not isinstance(manifest, compat_etree_Element) and not fatal: if not isinstance(manifest, compat_etree_Element) and not fatal:
@@ -1777,7 +1776,7 @@ class InfoExtractor(object):
ext = determine_ext(manifest_url) ext = determine_ext(manifest_url)
if ext == 'f4m': if ext == 'f4m':
f4m_formats = self._extract_f4m_formats( f4m_formats = self._extract_f4m_formats(
manifest_url, video_id, preference=preference, f4m_id=f4m_id, manifest_url, video_id, preference=preference, quality=quality, f4m_id=f4m_id,
transform_source=transform_source, fatal=fatal) transform_source=transform_source, fatal=fatal)
# Sometimes stream-level manifest contains single media entry that # Sometimes stream-level manifest contains single media entry that
# does not contain any quality metadata (e.g. http://matchtv.ru/#live-player). # does not contain any quality metadata (e.g. http://matchtv.ru/#live-player).
@@ -1797,7 +1796,7 @@ class InfoExtractor(object):
elif ext == 'm3u8': elif ext == 'm3u8':
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
manifest_url, video_id, 'mp4', preference=preference, manifest_url, video_id, 'mp4', preference=preference,
m3u8_id=m3u8_id, fatal=fatal)) quality=quality, m3u8_id=m3u8_id, fatal=fatal))
continue continue
formats.append({ formats.append({
'format_id': format_id, 'format_id': format_id,
@@ -1810,22 +1809,24 @@ class InfoExtractor(object):
'height': height, 'height': height,
'vcodec': vcodec, 'vcodec': vcodec,
'preference': preference, 'preference': preference,
'quality': quality,
}) })
return formats return formats
def _m3u8_meta_format(self, m3u8_url, ext=None, preference=None, m3u8_id=None): def _m3u8_meta_format(self, m3u8_url, ext=None, preference=None, quality=None, m3u8_id=None):
return { return {
'format_id': '-'.join(filter(None, [m3u8_id, 'meta'])), 'format_id': '-'.join(filter(None, [m3u8_id, 'meta'])),
'url': m3u8_url, 'url': m3u8_url,
'ext': ext, 'ext': ext,
'protocol': 'm3u8', 'protocol': 'm3u8',
'preference': preference - 100 if preference else -100, 'preference': preference - 100 if preference else -100,
'quality': quality,
'resolution': 'multiple', 'resolution': 'multiple',
'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, m3u8_url, video_id, ext=None,
entry_protocol='m3u8', preference=None, entry_protocol='m3u8', preference=None, quality=None,
m3u8_id=None, note=None, errnote=None, m3u8_id=None, note=None, errnote=None,
fatal=True, live=False, data=None, headers={}, fatal=True, live=False, data=None, headers={},
query={}): query={}):
@@ -1843,10 +1844,10 @@ class InfoExtractor(object):
return self._parse_m3u8_formats( return self._parse_m3u8_formats(
m3u8_doc, m3u8_url, ext=ext, entry_protocol=entry_protocol, m3u8_doc, m3u8_url, ext=ext, entry_protocol=entry_protocol,
preference=preference, m3u8_id=m3u8_id, live=live) preference=preference, quality=quality, m3u8_id=m3u8_id, live=live)
def _parse_m3u8_formats(self, m3u8_doc, m3u8_url, ext=None, def _parse_m3u8_formats(self, m3u8_doc, m3u8_url, ext=None,
entry_protocol='m3u8', preference=None, entry_protocol='m3u8', preference=None, quality=None,
m3u8_id=None, live=False): m3u8_id=None, live=False):
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
return [] return []
@@ -1884,6 +1885,7 @@ class InfoExtractor(object):
'ext': ext, 'ext': ext,
'protocol': entry_protocol, 'protocol': entry_protocol,
'preference': preference, 'preference': preference,
'quality': quality,
}] }]
groups = {} groups = {}
@@ -1912,6 +1914,7 @@ class InfoExtractor(object):
'ext': ext, 'ext': ext,
'protocol': entry_protocol, 'protocol': entry_protocol,
'preference': preference, 'preference': preference,
'quality': quality,
} }
if media_type == 'AUDIO': if media_type == 'AUDIO':
f['vcodec'] = 'none' f['vcodec'] = 'none'
@@ -1971,6 +1974,7 @@ class InfoExtractor(object):
'fps': float_or_none(last_stream_inf.get('FRAME-RATE')), 'fps': float_or_none(last_stream_inf.get('FRAME-RATE')),
'protocol': entry_protocol, 'protocol': entry_protocol,
'preference': preference, 'preference': preference,
'quality': quality,
} }
resolution = last_stream_inf.get('RESOLUTION') resolution = last_stream_inf.get('RESOLUTION')
if resolution: if resolution:
@@ -2678,7 +2682,7 @@ class InfoExtractor(object):
}) })
return formats return formats
def _parse_html5_media_entries(self, base_url, webpage, video_id, m3u8_id=None, m3u8_entry_protocol='m3u8', mpd_id=None, preference=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):
return urljoin(base_url, item_url) return urljoin(base_url, item_url)
@@ -2701,7 +2705,7 @@ class InfoExtractor(object):
formats = self._extract_m3u8_formats( formats = self._extract_m3u8_formats(
full_url, video_id, ext='mp4', full_url, video_id, ext='mp4',
entry_protocol=m3u8_entry_protocol, m3u8_id=m3u8_id, entry_protocol=m3u8_entry_protocol, m3u8_id=m3u8_id,
preference=preference, fatal=False) preference=preference, quality=quality, fatal=False)
elif ext == 'mpd': elif ext == 'mpd':
is_plain_url = False is_plain_url = False
formats = self._extract_mpd_formats( formats = self._extract_mpd_formats(
+3 -3
View File
@@ -87,7 +87,7 @@ class CoubIE(InfoExtractor):
'filesize': int_or_none(item.get('size')), 'filesize': int_or_none(item.get('size')),
'vcodec': 'none' if kind == 'audio' else None, 'vcodec': 'none' if kind == 'audio' else None,
'quality': quality_key(quality), 'quality': quality_key(quality),
'preference': preference_key(HTML5), 'source_preference': preference_key(HTML5),
}) })
iphone_url = file_versions.get(IPHONE, {}).get('url') iphone_url = file_versions.get(IPHONE, {}).get('url')
@@ -95,7 +95,7 @@ class CoubIE(InfoExtractor):
formats.append({ formats.append({
'url': iphone_url, 'url': iphone_url,
'format_id': IPHONE, 'format_id': IPHONE,
'preference': preference_key(IPHONE), 'source_preference': preference_key(IPHONE),
}) })
mobile_url = file_versions.get(MOBILE, {}).get('audio_url') mobile_url = file_versions.get(MOBILE, {}).get('audio_url')
@@ -103,7 +103,7 @@ class CoubIE(InfoExtractor):
formats.append({ formats.append({
'url': mobile_url, 'url': mobile_url,
'format_id': '%s-audio' % MOBILE, 'format_id': '%s-audio' % MOBILE,
'preference': preference_key(MOBILE), 'source_preference': preference_key(MOBILE),
}) })
self._sort_formats(formats) self._sort_formats(formats)
+6 -10
View File
@@ -473,15 +473,11 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
stream.get('url'), video_id, stream.get('format'), stream.get('url'), video_id, stream.get('format'),
audio_lang, hardsub_lang) audio_lang, hardsub_lang)
for f in vrv_formats: for f in vrv_formats:
if not hardsub_lang: f['language_preference'] = 1 if audio_lang == language else 0
f['preference'] = 1 f['quality'] = (
language_preference = 0 1 if not hardsub_lang
if audio_lang == language: else 0 if hardsub_lang == language
language_preference += 1 else -1)
if hardsub_lang == language:
language_preference += 1
if language_preference:
f['language_preference'] = language_preference
formats.extend(vrv_formats) formats.extend(vrv_formats)
if not formats: if not formats:
available_fmts = [] available_fmts = []
@@ -571,7 +567,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
'ext': 'flv', 'ext': 'flv',
}) })
formats.append(format_info) formats.append(format_info)
self._sort_formats(formats, ('preference', 'language_preference', 'height', 'width', 'tbr', 'fps')) self._sort_formats(formats)
metadata = self._call_rpc_api( metadata = self._call_rpc_api(
'VideoPlayer_GetMediaMetadata', video_id, 'VideoPlayer_GetMediaMetadata', video_id,
+1 -2
View File
@@ -82,6 +82,7 @@ class DigitallySpeakingIE(InfoExtractor):
'play_path': remove_end(audio.get('url'), '.flv'), 'play_path': remove_end(audio.get('url'), '.flv'),
'ext': 'flv', 'ext': 'flv',
'vcodec': 'none', 'vcodec': 'none',
'quality': 1,
'format_id': audio.get('code'), 'format_id': audio.get('code'),
}) })
slide_video_path = xpath_text(metadata, './slideVideo', fatal=True) slide_video_path = xpath_text(metadata, './slideVideo', fatal=True)
@@ -91,7 +92,6 @@ class DigitallySpeakingIE(InfoExtractor):
'ext': 'flv', 'ext': 'flv',
'format_note': 'slide deck video', 'format_note': 'slide deck video',
'quality': -2, 'quality': -2,
'preference': -2,
'format_id': 'slides', 'format_id': 'slides',
'acodec': 'none', 'acodec': 'none',
}) })
@@ -102,7 +102,6 @@ class DigitallySpeakingIE(InfoExtractor):
'ext': 'flv', 'ext': 'flv',
'format_note': 'speaker video', 'format_note': 'speaker video',
'quality': -1, 'quality': -1,
'preference': -1,
'format_id': 'speaker', 'format_id': 'speaker',
}) })
return formats return formats
+128 -30
View File
@@ -1,6 +1,7 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import json
import re import re
from .common import InfoExtractor from .common import InfoExtractor
@@ -10,11 +11,13 @@ from ..utils import (
ExtractorError, ExtractorError,
float_or_none, float_or_none,
int_or_none, int_or_none,
strip_or_none,
unified_timestamp, unified_timestamp,
) )
class DPlayIE(InfoExtractor): class DPlayIE(InfoExtractor):
_PATH_REGEX = r'/(?P<id>[^/]+/[^/?#]+)'
_VALID_URL = r'''(?x)https?:// _VALID_URL = r'''(?x)https?://
(?P<domain> (?P<domain>
(?:www\.)?(?P<host>d (?:www\.)?(?P<host>d
@@ -24,7 +27,7 @@ class DPlayIE(InfoExtractor):
) )
)| )|
(?P<subdomain_country>es|it)\.dplay\.com (?P<subdomain_country>es|it)\.dplay\.com
)/[^/]+/(?P<id>[^/]+/[^/?#]+)''' )/[^/]+''' + _PATH_REGEX
_TESTS = [{ _TESTS = [{
# non geo restricted, via secure api, unsigned download hls URL # non geo restricted, via secure api, unsigned download hls URL
@@ -151,56 +154,79 @@ class DPlayIE(InfoExtractor):
'only_matching': True, 'only_matching': True,
}] }]
def _process_errors(self, e, geo_countries):
info = self._parse_json(e.cause.read().decode('utf-8'), None)
error = info['errors'][0]
error_code = error.get('code')
if error_code == 'access.denied.geoblocked':
self.raise_geo_restricted(countries=geo_countries)
elif error_code in ('access.denied.missingpackage', 'invalid.token'):
raise ExtractorError(
'This video is only available for registered users. You may want to use --cookies.', expected=True)
raise ExtractorError(info['errors'][0]['detail'], expected=True)
def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
headers['Authorization'] = 'Bearer ' + self._download_json(
disco_base + 'token', display_id, 'Downloading token',
query={
'realm': realm,
})['data']['attributes']['token']
def _download_video_playback_info(self, disco_base, video_id, headers):
streaming = self._download_json(
disco_base + 'playback/videoPlaybackInfo/' + video_id,
video_id, headers=headers)['data']['attributes']['streaming']
streaming_list = []
for format_id, format_dict in streaming.items():
streaming_list.append({
'type': format_id,
'url': format_dict.get('url'),
})
return streaming_list
def _get_disco_api_info(self, url, display_id, disco_host, realm, country): def _get_disco_api_info(self, url, display_id, disco_host, realm, country):
geo_countries = [country.upper()] geo_countries = [country.upper()]
self._initialize_geo_bypass({ self._initialize_geo_bypass({
'countries': geo_countries, 'countries': geo_countries,
}) })
disco_base = 'https://%s/' % disco_host disco_base = 'https://%s/' % disco_host
token = self._download_json(
disco_base + 'token', display_id, 'Downloading token',
query={
'realm': realm,
})['data']['attributes']['token']
headers = { headers = {
'Referer': url, 'Referer': url,
'Authorization': 'Bearer ' + token,
} }
video = self._download_json( self._update_disco_api_headers(headers, disco_base, display_id, realm)
disco_base + 'content/videos/' + display_id, display_id, try:
headers=headers, query={ video = self._download_json(
'fields[channel]': 'name', disco_base + 'content/videos/' + display_id, display_id,
'fields[image]': 'height,src,width', headers=headers, query={
'fields[show]': 'name', 'fields[channel]': 'name',
'fields[tag]': 'name', 'fields[image]': 'height,src,width',
'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration', 'fields[show]': 'name',
'include': 'images,primaryChannel,show,tags' 'fields[tag]': 'name',
}) 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration',
'include': 'images,primaryChannel,show,tags'
})
except ExtractorError as e:
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400:
self._process_errors(e, geo_countries)
raise
video_id = video['data']['id'] video_id = video['data']['id']
info = video['data']['attributes'] info = video['data']['attributes']
title = info['name'].strip() title = info['name'].strip()
formats = [] formats = []
try: try:
streaming = self._download_json( streaming = self._download_video_playback_info(
disco_base + 'playback/videoPlaybackInfo/' + video_id, disco_base, video_id, headers)
display_id, headers=headers)['data']['attributes']['streaming']
except ExtractorError as e: except ExtractorError as e:
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403: if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
info = self._parse_json(e.cause.read().decode('utf-8'), display_id) self._process_errors(e, geo_countries)
error = info['errors'][0]
error_code = error.get('code')
if error_code == 'access.denied.geoblocked':
self.raise_geo_restricted(countries=geo_countries)
elif error_code == 'access.denied.missingpackage':
self.raise_login_required()
raise ExtractorError(info['errors'][0]['detail'], expected=True)
raise raise
for format_id, format_dict in streaming.items(): for format_dict in streaming:
if not isinstance(format_dict, dict): if not isinstance(format_dict, dict):
continue continue
format_url = format_dict.get('url') format_url = format_dict.get('url')
if not format_url: if not format_url:
continue continue
format_id = format_dict.get('type')
ext = determine_ext(format_url) ext = determine_ext(format_url)
if format_id == 'dash' or ext == 'mpd': if format_id == 'dash' or ext == 'mpd':
formats.extend(self._extract_mpd_formats( formats.extend(self._extract_mpd_formats(
@@ -248,7 +274,7 @@ class DPlayIE(InfoExtractor):
'id': video_id, 'id': video_id,
'display_id': display_id, 'display_id': display_id,
'title': title, 'title': title,
'description': info.get('description'), 'description': strip_or_none(info.get('description')),
'duration': float_or_none(info.get('videoDuration'), 1000), 'duration': float_or_none(info.get('videoDuration'), 1000),
'timestamp': unified_timestamp(info.get('publishStart')), 'timestamp': unified_timestamp(info.get('publishStart')),
'series': series, 'series': series,
@@ -268,3 +294,75 @@ class DPlayIE(InfoExtractor):
host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com' host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com'
return self._get_disco_api_info( return self._get_disco_api_info(
url, display_id, host, 'dplay' + country, country) url, display_id, host, 'dplay' + country, country)
class DiscoveryPlusIE(DPlayIE):
_VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/video' + DPlayIE._PATH_REGEX
_TESTS = [{
'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family',
'info_dict': {
'id': '1140794',
'display_id': 'property-brothers-forever-home/food-and-family',
'ext': 'mp4',
'title': 'Food and Family',
'description': 'The brothers help a Richmond family expand their single-level home.',
'duration': 2583.113,
'timestamp': 1609304400,
'upload_date': '20201230',
'creator': 'HGTV',
'series': 'Property Brothers: Forever Home',
'season_number': 1,
'episode_number': 1,
},
'skip': 'Available for Premium users',
}]
def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
headers['x-disco-client'] = 'WEB:UNKNOWN:dplus_us:15.0.0'
def _download_video_playback_info(self, disco_base, video_id, headers):
return self._download_json(
disco_base + 'playback/v3/videoPlaybackInfo',
video_id, headers=headers, data=json.dumps({
'deviceInfo': {
'adBlocker': False,
},
'videoId': video_id,
'wisteriaProperties': {
'platform': 'desktop',
},
}).encode('utf-8'))['data']['attributes']['streaming']
def _real_extract(self, url):
display_id = self._match_id(url)
return self._get_disco_api_info(
url, display_id, 'us1-prod-direct.discoveryplus.com', 'go', 'us')
class HGTVDeIE(DPlayIE):
_VALID_URL = r'https?://de\.hgtv\.com/sendungen' + DPlayIE._PATH_REGEX
_TESTS = [{
'url': 'https://de.hgtv.com/sendungen/tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette/',
'info_dict': {
'id': '151205',
'display_id': 'tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette',
'ext': 'mp4',
'title': 'Wer braucht schon eine Toilette',
'description': 'md5:05b40a27e7aed2c9172de34d459134e2',
'duration': 1177.024,
'timestamp': 1595705400,
'upload_date': '20200725',
'creator': 'HGTV',
'series': 'Tiny House - klein, aber oho',
'season_number': 3,
'episode_number': 3,
},
'params': {
'format': 'bestvideo',
},
}]
def _real_extract(self, url):
display_id = self._match_id(url)
return self._get_disco_api_info(
url, display_id, 'eu1-prod.disco-api.com', 'hgtv', 'de')
+193
View File
@@ -0,0 +1,193 @@
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import (
int_or_none,
unified_strdate,
xpath_text,
determine_ext,
float_or_none,
ExtractorError,
)
class DreiSatIE(InfoExtractor):
IE_NAME = '3sat'
_GEO_COUNTRIES = ['DE']
_VALID_URL = r'https?://(?:www\.)?3sat\.de/mediathek/(?:(?:index|mediathek)\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)'
_TESTS = [
{
'url': 'http://www.3sat.de/mediathek/index.php?mode=play&obj=45918',
'md5': 'be37228896d30a88f315b638900a026e',
'info_dict': {
'id': '45918',
'ext': 'mp4',
'title': 'Waidmannsheil',
'description': 'md5:cce00ca1d70e21425e72c86a98a56817',
'uploader': 'SCHWEIZWEIT',
'uploader_id': '100000210',
'upload_date': '20140913'
},
'params': {
'skip_download': True, # m3u8 downloads
}
},
{
'url': 'http://www.3sat.de/mediathek/mediathek.php?mode=play&obj=51066',
'only_matching': True,
},
]
def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None, transform_rtmp_url=None):
param_groups = {}
for param_group in smil.findall(self._xpath_ns('./head/paramGroup', namespace)):
group_id = param_group.get(self._xpath_ns(
'id', 'http://www.w3.org/XML/1998/namespace'))
params = {}
for param in param_group:
params[param.get('name')] = param.get('value')
param_groups[group_id] = params
formats = []
for video in smil.findall(self._xpath_ns('.//video', namespace)):
src = video.get('src')
if not src:
continue
bitrate = int_or_none(self._search_regex(r'_(\d+)k', src, 'bitrate', None)) or float_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
group_id = video.get('paramGroup')
param_group = param_groups[group_id]
for proto in param_group['protocols'].split(','):
formats.append({
'url': '%s://%s' % (proto, param_group['host']),
'app': param_group['app'],
'play_path': src,
'ext': 'flv',
'format_id': '%s-%d' % (proto, bitrate),
'tbr': bitrate,
})
self._sort_formats(formats)
return formats
def extract_from_xml_url(self, video_id, xml_url):
doc = self._download_xml(
xml_url, video_id,
note='Downloading video info',
errnote='Failed to download video info')
status_code = xpath_text(doc, './status/statuscode')
if status_code and status_code != 'ok':
if status_code == 'notVisibleAnymore':
message = 'Video %s is not available' % video_id
else:
message = '%s returned error: %s' % (self.IE_NAME, status_code)
raise ExtractorError(message, expected=True)
title = xpath_text(doc, './/information/title', 'title', True)
urls = []
formats = []
for fnode in doc.findall('.//formitaeten/formitaet'):
video_url = xpath_text(fnode, 'url')
if not video_url or video_url in urls:
continue
urls.append(video_url)
is_available = 'http://www.metafilegenerator' not in video_url
geoloced = 'static_geoloced_online' in video_url
if not is_available or geoloced:
continue
format_id = fnode.attrib['basetype']
format_m = re.match(r'''(?x)
(?P<vcodec>[^_]+)_(?P<acodec>[^_]+)_(?P<container>[^_]+)_
(?P<proto>[^_]+)_(?P<index>[^_]+)_(?P<indexproto>[^_]+)
''', format_id)
ext = determine_ext(video_url, None) or format_m.group('container')
if ext == 'meta':
continue
elif ext == 'smil':
formats.extend(self._extract_smil_formats(
video_url, video_id, fatal=False))
elif ext == 'm3u8':
# the certificates are misconfigured (see
# https://github.com/ytdl-org/youtube-dl/issues/8665)
if video_url.startswith('https://'):
continue
formats.extend(self._extract_m3u8_formats(
video_url, video_id, 'mp4', 'm3u8_native',
m3u8_id=format_id, fatal=False))
elif ext == 'f4m':
formats.extend(self._extract_f4m_formats(
video_url, video_id, f4m_id=format_id, fatal=False))
else:
quality = xpath_text(fnode, './quality')
if quality:
format_id += '-' + quality
abr = int_or_none(xpath_text(fnode, './audioBitrate'), 1000)
vbr = int_or_none(xpath_text(fnode, './videoBitrate'), 1000)
tbr = int_or_none(self._search_regex(
r'_(\d+)k', video_url, 'bitrate', None))
if tbr and vbr and not abr:
abr = tbr - vbr
formats.append({
'format_id': format_id,
'url': video_url,
'ext': ext,
'acodec': format_m.group('acodec'),
'vcodec': format_m.group('vcodec'),
'abr': abr,
'vbr': vbr,
'tbr': tbr,
'width': int_or_none(xpath_text(fnode, './width')),
'height': int_or_none(xpath_text(fnode, './height')),
'filesize': int_or_none(xpath_text(fnode, './filesize')),
'protocol': format_m.group('proto').lower(),
})
geolocation = xpath_text(doc, './/details/geolocation')
if not formats and geolocation and geolocation != 'none':
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
self._sort_formats(formats)
thumbnails = []
for node in doc.findall('.//teaserimages/teaserimage'):
thumbnail_url = node.text
if not thumbnail_url:
continue
thumbnail = {
'url': thumbnail_url,
}
thumbnail_key = node.get('key')
if thumbnail_key:
m = re.match('^([0-9]+)x([0-9]+)$', thumbnail_key)
if m:
thumbnail['width'] = int(m.group(1))
thumbnail['height'] = int(m.group(2))
thumbnails.append(thumbnail)
upload_date = unified_strdate(xpath_text(doc, './/details/airtime'))
return {
'id': video_id,
'title': title,
'description': xpath_text(doc, './/information/detail'),
'duration': int_or_none(xpath_text(doc, './/details/lengthSec')),
'thumbnails': thumbnails,
'uploader': xpath_text(doc, './/details/originChannelTitle'),
'uploader_id': xpath_text(doc, './/details/originChannelId'),
'upload_date': upload_date,
'formats': formats,
}
def _real_extract(self, url):
video_id = self._match_id(url)
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?id=%s' % video_id
return self.extract_from_xml_url(video_id, details_url)
+2 -2
View File
@@ -242,7 +242,7 @@ class DRTVIE(InfoExtractor):
elif target == 'HLS': elif target == 'HLS':
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
uri, video_id, 'mp4', entry_protocol='m3u8_native', uri, video_id, 'mp4', entry_protocol='m3u8_native',
preference=preference, m3u8_id=format_id, quality=preference, m3u8_id=format_id,
fatal=False)) fatal=False))
else: else:
bitrate = link.get('Bitrate') bitrate = link.get('Bitrate')
@@ -254,7 +254,7 @@ class DRTVIE(InfoExtractor):
'tbr': int_or_none(bitrate), 'tbr': int_or_none(bitrate),
'ext': link.get('FileFormat'), 'ext': link.get('FileFormat'),
'vcodec': 'none' if kind == 'AudioResource' else None, 'vcodec': 'none' if kind == 'AudioResource' else None,
'preference': preference, 'quality': preference,
}) })
subtitles_list = asset.get('SubtitlesList') or asset.get('Subtitleslist') subtitles_list = asset.get('SubtitlesList') or asset.get('Subtitleslist')
if isinstance(subtitles_list, list): if isinstance(subtitles_list, list):
+1 -1
View File
@@ -154,7 +154,7 @@ class ESPNIE(OnceIE):
'tbr': int(mobj.group(3)), 'tbr': int(mobj.group(3)),
}) })
if source_id == 'mezzanine': if source_id == 'mezzanine':
f['preference'] = 1 f['quality'] = 1
formats.append(f) formats.append(f)
links = clip.get('links', {}) links = clip.get('links', {})
+18 -6
View File
@@ -182,6 +182,7 @@ from .canvas import (
CanvasIE, CanvasIE,
CanvasEenIE, CanvasEenIE,
VrtNUIE, VrtNUIE,
DagelijkseKostIE,
) )
from .carambatv import ( from .carambatv import (
CarambaTVIE, CarambaTVIE,
@@ -309,7 +310,12 @@ from .douyutv import (
DouyuShowIE, DouyuShowIE,
DouyuTVIE, DouyuTVIE,
) )
from .dplay import DPlayIE from .dplay import (
DPlayIE,
DiscoveryPlusIE,
HGTVDeIE,
)
from .dreisat import DreiSatIE
from .drbonanza import DRBonanzaIE from .drbonanza import DRBonanzaIE
from .drtuber import DrTuberIE from .drtuber import DrTuberIE
from .drtv import ( from .drtv import (
@@ -1107,6 +1113,11 @@ from .shared import (
VivoIE, VivoIE,
) )
from .showroomlive import ShowRoomLiveIE from .showroomlive import ShowRoomLiveIE
from .simplecast import (
SimplecastIE,
SimplecastEpisodeIE,
SimplecastPodcastIE,
)
from .sina import SinaIE from .sina import SinaIE
from .sixplay import SixPlayIE from .sixplay import SixPlayIE
from .skyit import ( from .skyit import (
@@ -1165,11 +1176,6 @@ from .spike import (
BellatorIE, BellatorIE,
ParamountNetworkIE, ParamountNetworkIE,
) )
from .storyfire import (
StoryFireIE,
StoryFireUserIE,
StoryFireSeriesIE,
)
from .stitcher import StitcherIE from .stitcher import StitcherIE
from .sport5 import Sport5IE from .sport5 import Sport5IE
from .sportbox import SportBoxIE from .sportbox import SportBoxIE
@@ -1193,6 +1199,11 @@ from .srgssr import (
from .srmediathek import SRMediathekIE from .srmediathek import SRMediathekIE
from .stanfordoc import StanfordOpenClassroomIE from .stanfordoc import StanfordOpenClassroomIE
from .steam import SteamIE from .steam import SteamIE
from .storyfire import (
StoryFireIE,
StoryFireUserIE,
StoryFireSeriesIE,
)
from .streamable import StreamableIE from .streamable import StreamableIE
from .streamcloud import StreamcloudIE from .streamcloud import StreamcloudIE
from .streamcz import StreamCZIE from .streamcz import StreamCZIE
@@ -1652,6 +1663,7 @@ from .zattoo import (
ZattooLiveIE, ZattooLiveIE,
) )
from .zdf import ZDFIE, ZDFChannelIE from .zdf import ZDFIE, ZDFChannelIE
from .zhihu import ZhihuIE
from .zingmp3 import ZingMp3IE from .zingmp3 import ZingMp3IE
from .zoom import ZoomIE from .zoom import ZoomIE
from .zype import ZypeIE from .zype import ZypeIE
+1 -1
View File
@@ -619,7 +619,7 @@ class FacebookIE(InfoExtractor):
formats.append({ formats.append({
'format_id': '%s_%s_%s' % (format_id, quality, src_type), 'format_id': '%s_%s_%s' % (format_id, quality, src_type),
'url': src, 'url': src,
'preference': preference, 'quality': preference,
}) })
extract_dash_manifest(f[0], formats) extract_dash_manifest(f[0], formats)
subtitles_src = f[0].get('subtitles_src') subtitles_src = f[0].get('subtitles_src')
+1 -1
View File
@@ -104,7 +104,7 @@ class FirstTVIE(InfoExtractor):
'tbr': tbr, 'tbr': tbr,
'source_preference': quality(f.get('name')), 'source_preference': quality(f.get('name')),
# quality metadata of http formats may be incorrect # quality metadata of http formats may be incorrect
'preference': -1, 'preference': -10,
}) })
# m3u8 URL format is reverse engineered from [1] (search for # m3u8 URL format is reverse engineered from [1] (search for
# master.m3u8). dashEdges (that is currently balancer-vod.1tv.ru) # master.m3u8). dashEdges (that is currently balancer-vod.1tv.ru)
+1 -1
View File
@@ -88,7 +88,7 @@ class FlickrIE(InfoExtractor):
formats.append({ formats.append({
'format_id': stream_type, 'format_id': stream_type,
'url': stream['_content'], 'url': stream['_content'],
'preference': preference(stream_type), 'quality': preference(stream_type),
}) })
self._sort_formats(formats) self._sort_formats(formats)
+16
View File
@@ -133,6 +133,7 @@ from .bitchute import BitChuteIE
from .rumble import RumbleEmbedIE from .rumble import RumbleEmbedIE
from .arcpublishing import ArcPublishingIE from .arcpublishing import ArcPublishingIE
from .medialaan import MedialaanIE from .medialaan import MedialaanIE
from .simplecast import SimplecastIE
class GenericIE(InfoExtractor): class GenericIE(InfoExtractor):
@@ -2240,6 +2241,15 @@ class GenericIE(InfoExtractor):
'duration': 159, 'duration': 159,
}, },
}, },
{
# Simplecast player embed
'url': 'https://www.bio.org/podcast',
'info_dict': {
'id': 'podcast',
'title': 'I AM BIO Podcast | BIO',
},
'playlist_mincount': 52,
},
] ]
def report_following_redirect(self, new_url): def report_following_redirect(self, new_url):
@@ -2794,6 +2804,12 @@ class GenericIE(InfoExtractor):
return self.playlist_from_matches( return self.playlist_from_matches(
matches, video_id, video_title, getter=unescapeHTML, ie='FunnyOrDie') matches, video_id, video_title, getter=unescapeHTML, ie='FunnyOrDie')
# Look for Simplecast embeds
simplecast_urls = SimplecastIE._extract_urls(webpage)
if simplecast_urls:
return self.playlist_from_matches(
simplecast_urls, video_id, video_title)
# Look for BBC iPlayer embed # Look for BBC iPlayer embed
matches = re.findall(r'setPlaylist\("(https?://www\.bbc\.co\.uk/iplayer/[^/]+/[\da-z]{8})"\)', webpage) matches = re.findall(r'setPlaylist\("(https?://www\.bbc\.co\.uk/iplayer/[^/]+/[\da-z]{8})"\)', webpage)
if matches: if matches:
+1 -1
View File
@@ -236,7 +236,7 @@ class GoIE(AdobePassIE):
if re.search(r'(?:/mp4/source/|_source\.mp4)', asset_url): if re.search(r'(?:/mp4/source/|_source\.mp4)', asset_url):
f.update({ f.update({
'format_id': ('%s-' % format_id if format_id else '') + 'SOURCE', 'format_id': ('%s-' % format_id if format_id else '') + 'SOURCE',
'preference': 1, 'quality': 1,
}) })
else: else:
mobj = re.search(r'/(\d+)x(\d+)/', asset_url) mobj = re.search(r'/(\d+)x(\d+)/', asset_url)
+1 -1
View File
@@ -115,7 +115,7 @@ class HearThisAtIE(InfoExtractor):
'vcodec': 'none', 'vcodec': 'none',
'ext': ext, 'ext': ext,
'url': download_url, 'url': download_url,
'preference': 2, # Usually better quality 'quality': 2, # Usually better quality
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -100,7 +100,7 @@ class IGNIE(IGNBaseIE):
formats.append({ formats.append({
'ext': determine_ext(mezzanine_url, 'mp4'), 'ext': determine_ext(mezzanine_url, 'mp4'),
'format_id': 'mezzanine', 'format_id': 'mezzanine',
'preference': 1, 'quality': 1,
'url': mezzanine_url, 'url': mezzanine_url,
}) })
+1 -1
View File
@@ -72,7 +72,7 @@ class ImgurIE(InfoExtractor):
gif_json, video_id, transform_source=js_to_json) gif_json, video_id, transform_source=js_to_json)
formats.append({ formats.append({
'format_id': 'gif', 'format_id': 'gif',
'preference': -10, 'preference': -10, # gifs are worse than videos
'width': width, 'width': width,
'height': height, 'height': height,
'ext': 'gif', 'ext': 'gif',
+1 -1
View File
@@ -373,7 +373,7 @@ class IqiyiIE(InfoExtractor):
'url': stream['m3utx'], 'url': stream['m3utx'],
'format_id': vd, 'format_id': vd,
'ext': 'mp4', 'ext': 'mp4',
'preference': self._FORMATS_MAP.get(vd, -1), 'quality': self._FORMATS_MAP.get(vd, -1),
'protocol': 'm3u8_native', 'protocol': 'm3u8_native',
}) })
+1 -1
View File
@@ -49,7 +49,7 @@ class KuwoBaseIE(InfoExtractor):
'url': song_url, 'url': song_url,
'format_id': file_format['format'], 'format_id': file_format['format'],
'format': file_format['format'], 'format': file_format['format'],
'preference': file_format['preference'], 'quality': file_format['preference'],
'abr': file_format.get('abr'), 'abr': file_format.get('abr'),
}) })
+1 -1
View File
@@ -185,7 +185,7 @@ class LeIE(InfoExtractor):
f['height'] = int_or_none(format_id[:-1]) f['height'] = int_or_none(format_id[:-1])
formats.append(f) formats.append(f)
self._sort_formats(formats, ('height', 'quality', 'format_id')) self._sort_formats(formats, ('res', 'quality'))
publish_time = parse_iso8601(self._html_search_regex( publish_time = parse_iso8601(self._html_search_regex(
r'发布时间&nbsp;([^<>]+) ', page, 'publish time', default=None), r'发布时间&nbsp;([^<>]+) ', page, 'publish time', default=None),
+1 -1
View File
@@ -201,7 +201,7 @@ class LifeEmbedIE(InfoExtractor):
formats.append({ formats.append({
'url': original_url, 'url': original_url,
'format_id': determine_ext(original_url, None), 'format_id': determine_ext(original_url, None),
'preference': 1, 'quality': 1,
}) })
playlist = self._parse_json( playlist = self._parse_json(
+1 -1
View File
@@ -175,7 +175,7 @@ class LimelightBaseIE(InfoExtractor):
formats.append({ formats.append({
'url': media_url, 'url': media_url,
'format_id': format_id, 'format_id': format_id,
'preference': -1, 'quality': -10,
'ext': ext, 'ext': ext,
}) })
+4 -1
View File
@@ -124,7 +124,10 @@ class LinkedInLearningIE(LinkedInLearningBaseIE):
streaming_url, video_slug, 'mp4', streaming_url, video_slug, 'mp4',
'm3u8_native', m3u8_id='hls', fatal=False)) 'm3u8_native', m3u8_id='hls', fatal=False))
self._sort_formats(formats, ('width', 'height', 'source_preference', 'tbr', 'abr')) # It seems like this would be correctly handled by default
# However, unless someone can confirm this, the old
# behaviour is being kept as-is
self._sort_formats(formats, ('res', 'source_preference'))
return { return {
'id': self._get_video_id(video_data, course_slug, video_slug), 'id': self._get_video_id(video_data, course_slug, video_slug),
+1 -1
View File
@@ -143,7 +143,7 @@ class LiveLeakIE(InfoExtractor):
formats.append({ formats.append({
'format_id': format_id, 'format_id': format_id,
'url': orig_url, 'url': orig_url,
'preference': 1, 'quality': 1,
}) })
self._sort_formats(formats) self._sort_formats(formats)
info_dict['formats'] = formats info_dict['formats'] = formats
+1 -1
View File
@@ -84,7 +84,7 @@ class LivestreamIE(InfoExtractor):
'format_id': 'smil_%d' % tbr, 'format_id': 'smil_%d' % tbr,
'ext': 'flv', 'ext': 'flv',
'tbr': tbr, 'tbr': tbr,
'preference': -1000, 'preference': -1000, # Strictly inferior than all other formats?
}) })
return formats return formats
+1 -1
View File
@@ -67,7 +67,7 @@ class MassengeschmackTVIE(InfoExtractor):
'vcodec': 'none' if format_id.startswith('Audio') else None, 'vcodec': 'none' if format_id.startswith('Audio') else None,
}) })
self._sort_formats(formats, ('width', 'height', 'filesize', 'tbr')) self._sort_formats(formats)
return { return {
'id': episode, 'id': episode,
+2 -2
View File
@@ -137,11 +137,11 @@ class MDRIE(InfoExtractor):
if ext == 'm3u8': if ext == 'm3u8':
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
video_url, video_id, 'mp4', entry_protocol='m3u8_native', video_url, video_id, 'mp4', entry_protocol='m3u8_native',
preference=0, m3u8_id='HLS', fatal=False)) quality=1, m3u8_id='HLS', fatal=False))
elif ext == 'f4m': elif ext == 'f4m':
formats.extend(self._extract_f4m_formats( formats.extend(self._extract_f4m_formats(
video_url + '?hdcore=3.7.0&plugin=aasp-3.7.0.39.44', video_id, video_url + '?hdcore=3.7.0&plugin=aasp-3.7.0.39.44', video_id,
preference=0, f4m_id='HDS', fatal=False)) quality=1, f4m_id='HDS', fatal=False))
else: else:
media_type = xpath_text(asset, './mediaType', 'media type', default='MP4') media_type = xpath_text(asset, './mediaType', 'media type', default='MP4')
vbr = int_or_none(xpath_text(asset, './bitrateVideo', 'vbr'), 1000) vbr = int_or_none(xpath_text(asset, './bitrateVideo', 'vbr'), 1000)
+1 -1
View File
@@ -206,7 +206,7 @@ class MediasiteIE(InfoExtractor):
# disprefer 'secondary' streams # disprefer 'secondary' streams
if stream_type != 0: if stream_type != 0:
for fmt in stream_formats: for fmt in stream_formats:
fmt['preference'] = -1 fmt['quality'] = -10
thumbnail_url = Stream.get('ThumbnailUrl') thumbnail_url = Stream.get('ThumbnailUrl')
if thumbnail_url: if thumbnail_url:
+1 -1
View File
@@ -132,7 +132,7 @@ class MSNIE(InfoExtractor):
'width': int_or_none(file_.get('width')), 'width': int_or_none(file_.get('width')),
'height': int_or_none(file_.get('height')), 'height': int_or_none(file_.get('height')),
'vbr': int_or_none(self._search_regex(r'_(\d+)\.mp4', format_url, 'vbr', default=None)), 'vbr': int_or_none(self._search_regex(r'_(\d+)\.mp4', format_url, 'vbr', default=None)),
'preference': 1 if format_id == '1001' else None, 'quality': 1 if format_id == '1001' else None,
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -61,7 +61,7 @@ class MuenchenTVIE(InfoExtractor):
'tbr': int_or_none(s.get('label')), 'tbr': int_or_none(s.get('label')),
'ext': 'mp4', 'ext': 'mp4',
'format_id': format_id, 'format_id': format_id,
'preference': -100 if '.smil' in s['file'] else 0, 'preference': -100 if '.smil' in s['file'] else 0, # Strictly inferior than all other formats?
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -303,7 +303,7 @@ class NBABaseIE(NBACVPBaseIE):
formats.append({ formats.append({
'format_id': 'source', 'format_id': 'source',
'url': source_url, 'url': source_url,
'preference': 1, 'quality': 1,
}) })
m3u8_url = video.get('m3u8') m3u8_url = video.get('m3u8')
+22 -2
View File
@@ -546,11 +546,29 @@ class NiconicoIE(InfoExtractor):
webpage_url = get_video_info_web('watch_url') or url webpage_url = get_video_info_web('watch_url') or url
# for channel movie and community movie
channel_id = try_get(
api_data,
(lambda x: x['channel']['globalId'],
lambda x: x['community']['globalId']))
channel = try_get(
api_data,
(lambda x: x['channel']['name'],
lambda x: x['community']['name']))
# Note: cannot use api_data.get('owner', {}) because owner may be set to "null" # Note: cannot use api_data.get('owner', {}) because owner may be set to "null"
# in the JSON, which will cause None to be returned instead of {}. # in the JSON, which will cause None to be returned instead of {}.
owner = try_get(api_data, lambda x: x.get('owner'), dict) or {} owner = try_get(api_data, lambda x: x.get('owner'), dict) or {}
uploader_id = get_video_info_web(['ch_id', 'user_id']) or owner.get('id') uploader_id = (
uploader = get_video_info_web(['ch_name', 'user_nickname']) or owner.get('nickname') get_video_info_web(['ch_id', 'user_id'])
or owner.get('id')
or channel_id
)
uploader = (
get_video_info_web(['ch_name', 'user_nickname'])
or owner.get('nickname')
or channel
)
return { return {
'id': video_id, 'id': video_id,
@@ -561,6 +579,8 @@ class NiconicoIE(InfoExtractor):
'uploader': uploader, 'uploader': uploader,
'timestamp': timestamp, 'timestamp': timestamp,
'uploader_id': uploader_id, 'uploader_id': uploader_id,
'channel': channel,
'channel_id': channel_id,
'view_count': view_count, 'view_count': view_count,
'comment_count': comment_count, 'comment_count': comment_count,
'duration': duration, 'duration': duration,
+9 -4
View File
@@ -2,10 +2,11 @@ from __future__ import unicode_literals
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
determine_ext,
ExtractorError, ExtractorError,
determine_ext,
int_or_none, int_or_none,
try_get, try_get,
unescapeHTML,
url_or_none, url_or_none,
) )
@@ -14,7 +15,7 @@ class NineGagIE(InfoExtractor):
IE_NAME = '9gag' IE_NAME = '9gag'
_VALID_URL = r'https?://(?:www\.)?9gag\.com/gag/(?P<id>[^/?&#]+)' _VALID_URL = r'https?://(?:www\.)?9gag\.com/gag/(?P<id>[^/?&#]+)'
_TEST = { _TESTS = [{
'url': 'https://9gag.com/gag/ae5Ag7B', 'url': 'https://9gag.com/gag/ae5Ag7B',
'info_dict': { 'info_dict': {
'id': 'ae5Ag7B', 'id': 'ae5Ag7B',
@@ -29,7 +30,11 @@ class NineGagIE(InfoExtractor):
'dislike_count': int, 'dislike_count': int,
'comment_count': int, 'comment_count': int,
} }
} }, {
# HTML escaped title
'url': 'https://9gag.com/gag/av5nvyb',
'only_matching': True,
}]
def _real_extract(self, url): def _real_extract(self, url):
post_id = self._match_id(url) post_id = self._match_id(url)
@@ -43,7 +48,7 @@ class NineGagIE(InfoExtractor):
'The given url does not contain a video', 'The given url does not contain a video',
expected=True) expected=True)
title = post['title'] title = unescapeHTML(post['title'])
duration = None duration = None
formats = [] formats = []
+1 -1
View File
@@ -183,7 +183,7 @@ class NocoIE(InfoExtractor):
'filesize': int_or_none(fmt.get('filesize')), 'filesize': int_or_none(fmt.get('filesize')),
'format_note': qualities[format_id].get('quality_name'), 'format_note': qualities[format_id].get('quality_name'),
'quality': qualities[format_id].get('priority'), 'quality': qualities[format_id].get('priority'),
'preference': preference, 'language_preference': preference,
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -425,7 +425,7 @@ class NPOIE(NPOBaseIE):
stream_url, video_id, fatal=False) stream_url, video_id, fatal=False)
# f4m downloader downloads only piece of live stream # f4m downloader downloads only piece of live stream
for f4m_format in f4m_formats: for f4m_format in f4m_formats:
f4m_format['preference'] = -1 f4m_format['preference'] = -5
formats.extend(f4m_formats) formats.extend(f4m_formats)
elif stream_type == 'hls': elif stream_type == 'hls':
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
+1 -1
View File
@@ -62,7 +62,7 @@ class NTVDeIE(InfoExtractor):
m3u8_url = compat_urlparse.urljoin('http://video.n-tv.de', vdata['videoM3u8']) m3u8_url = compat_urlparse.urljoin('http://video.n-tv.de', vdata['videoM3u8'])
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
m3u8_url, video_id, ext='mp4', entry_protocol='m3u8_native', m3u8_url, video_id, ext='mp4', entry_protocol='m3u8_native',
preference=0, m3u8_id='hls', fatal=False)) quality=1, m3u8_id='hls', fatal=False))
self._sort_formats(formats) self._sort_formats(formats)
return { return {
+1 -1
View File
@@ -72,7 +72,7 @@ class NYTimesBaseIE(InfoExtractor):
'tbr': int_or_none(video.get('bitrate'), 1000) or None, 'tbr': int_or_none(video.get('bitrate'), 1000) or None,
'ext': ext, 'ext': ext,
}) })
self._sort_formats(formats, ('height', 'width', 'filesize', 'tbr', 'fps', 'format_id')) self._sort_formats(formats)
thumbnails = [] thumbnails = []
for image in video_data.get('images', []): for image in video_data.get('images', []):
+1 -1
View File
@@ -55,7 +55,7 @@ class OraTVIE(InfoExtractor):
formats.append({ formats.append({
'url': http_template % q, 'url': http_template % q,
'format_id': q, 'format_id': q,
'preference': preference(q), 'quality': preference(q),
}) })
self._sort_formats(formats) self._sort_formats(formats)
else: else:
+2 -2
View File
@@ -78,7 +78,7 @@ class PicartoIE(InfoExtractor):
update_url_query( update_url_query(
'https://%s/hls/%s/index.m3u8' 'https://%s/hls/%s/index.m3u8'
% (edge_ep, channel_id), params), % (edge_ep, channel_id), params),
channel_id, 'mp4', preference=preference, channel_id, 'mp4', quality=preference,
m3u8_id='-'.join(format_id), fatal=False)) m3u8_id='-'.join(format_id), fatal=False))
continue continue
elif tech_type == 'video/mp4' or tech_label == 'MP4': elif tech_type == 'video/mp4' or tech_label == 'MP4':
@@ -88,7 +88,7 @@ class PicartoIE(InfoExtractor):
'https://%s/mp4/%s.mp4' % (edge_ep, channel_id), 'https://%s/mp4/%s.mp4' % (edge_ep, channel_id),
params), params),
'format_id': '-'.join(format_id), 'format_id': '-'.join(format_id),
'preference': preference, 'quality': preference,
}) })
else: else:
# rtmp format does not seem to work # rtmp format does not seem to work
+1 -2
View File
@@ -54,8 +54,7 @@ class PinterestBaseIE(InfoExtractor):
'height': int_or_none(format_dict.get('height')), 'height': int_or_none(format_dict.get('height')),
'duration': duration, 'duration': duration,
}) })
self._sort_formats( self._sort_formats(formats)
formats, field_preference=('height', 'width', 'tbr', 'format_id'))
description = data.get('description') or data.get('description_html') or data.get('seo_description') description = data.get('description') or data.get('description_html') or data.get('seo_description')
timestamp = unified_timestamp(data.get('created_at')) timestamp = unified_timestamp(data.get('created_at'))
+1 -1
View File
@@ -150,7 +150,7 @@ class PlaytvakIE(InfoExtractor):
ext = 'mp4' ext = 'mp4'
# Some streams have mp3 audio which does not play # Some streams have mp3 audio which does not play
# well with ffmpeg filter aac_adtstoasc # well with ffmpeg filter aac_adtstoasc
preference = -1 preference = -10
elif format_ == 'adobe': # f4m manifest fails with 404 in 80% of requests elif format_ == 'adobe': # f4m manifest fails with 404 in 80% of requests
continue continue
else: # Other formats not supported yet else: # Other formats not supported yet
+1 -1
View File
@@ -121,7 +121,7 @@ class QQMusicIE(InfoExtractor):
% (details['prefix'], mid, details['ext'], vkey, guid), % (details['prefix'], mid, details['ext'], vkey, guid),
'format': format_id, 'format': format_id,
'format_id': format_id, 'format_id': format_id,
'preference': details['preference'], 'quality': details['preference'],
'abr': details.get('abr'), 'abr': details.get('abr'),
}) })
self._check_formats(formats, mid) self._check_formats(formats, mid)
+1 -1
View File
@@ -43,7 +43,7 @@ class RadioFranceIE(InfoExtractor):
'format_id': fm[0], 'format_id': fm[0],
'url': fm[1], 'url': fm[1],
'vcodec': 'none', 'vcodec': 'none',
'preference': i, 'quality': i,
} }
for i, fm in for i, fm in
enumerate(re.findall(r"([a-z0-9]+)\s*:\s*'([^']+)'", formats_str)) enumerate(re.findall(r"([a-z0-9]+)\s*:\s*'([^']+)'", formats_str))
+1 -1
View File
@@ -40,7 +40,7 @@ class RedditIE(InfoExtractor):
'https://v.redd.it/%s/DASHPlaylist.mpd' % video_id, video_id, 'https://v.redd.it/%s/DASHPlaylist.mpd' % video_id, video_id,
mpd_id='dash', fatal=False)) mpd_id='dash', fatal=False))
self._sort_formats(formats, ('height', 'width')) self._sort_formats(formats)
return { return {
'id': video_id, 'id': video_id,
+1 -1
View File
@@ -93,7 +93,7 @@ class RTL2IE(InfoExtractor):
'flash_version': 'LNX 11,2,202,429', 'flash_version': 'LNX 11,2,202,429',
'rtmp_conn': rtmp_conn, 'rtmp_conn': rtmp_conn,
'no_resume': True, 'no_resume': True,
'preference': 1, 'quality': 1,
}) })
m3u8_url = video_info.get('streamurl_hls') m3u8_url = video_info.get('streamurl_hls')
+1 -1
View File
@@ -45,7 +45,7 @@ class RTPIE(InfoExtractor):
if file_key: if file_key:
formats.append({ formats.append({
'url': 'https://cdn-ondemand.rtp.pt' + file_key, 'url': 'https://cdn-ondemand.rtp.pt' + file_key,
'preference': 1, 'quality': 1,
}) })
self._sort_formats(formats) self._sort_formats(formats)
else: else:
+2 -2
View File
@@ -180,11 +180,11 @@ class RUTVIE(InfoExtractor):
'rtmp_live': True, 'rtmp_live': True,
'ext': 'flv', 'ext': 'flv',
'vbr': int(quality), 'vbr': int(quality),
'preference': preference, 'quality': preference,
} }
elif transport == 'm3u8': elif transport == 'm3u8':
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
url, video_id, 'mp4', preference=preference, m3u8_id='hls')) url, video_id, 'mp4', quality=preference, m3u8_id='hls'))
continue continue
else: else:
fmt = { fmt = {
+3 -1
View File
@@ -80,7 +80,9 @@ class SendtoNewsIE(InfoExtractor):
'format_id': '%s-%d' % (determine_protocol(f), tbr), 'format_id': '%s-%d' % (determine_protocol(f), tbr),
'tbr': tbr, 'tbr': tbr,
}) })
self._sort_formats(info_dict['formats'], ('tbr', 'height', 'width', 'format_id')) # 'tbr' was explicitly set to be prefered over 'height' originally,
# So this is being kept unless someone can confirm this is unnecessary
self._sort_formats(info_dict['formats'], ('tbr', 'res'))
thumbnails = [] thumbnails = []
if video.get('thumbnailUrl'): if video.get('thumbnailUrl'):
+160
View File
@@ -0,0 +1,160 @@
# coding: utf-8
from __future__ import unicode_literals
import re
from .common import InfoExtractor
from ..utils import (
clean_podcast_url,
int_or_none,
parse_iso8601,
strip_or_none,
try_get,
urlencode_postdata,
)
class SimplecastBaseIE(InfoExtractor):
_UUID_REGEX = r'[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}'
_API_BASE = 'https://api.simplecast.com/'
def _call_api(self, path_tmpl, video_id):
return self._download_json(
self._API_BASE + path_tmpl % video_id, video_id)
def _call_search_api(self, resource, resource_id, resource_url):
return self._download_json(
'https://api.simplecast.com/%ss/search' % resource, resource_id,
data=urlencode_postdata({'url': resource_url}))
def _parse_episode(self, episode):
episode_id = episode['id']
title = episode['title'].strip()
audio_file = episode.get('audio_file') or {}
audio_file_url = audio_file.get('url') or episode.get('audio_file_url') or episode['enclosure_url']
season = episode.get('season') or {}
season_href = season.get('href')
season_id = None
if season_href:
season_id = self._search_regex(
r'https?://api.simplecast.com/seasons/(%s)' % self._UUID_REGEX,
season_href, 'season id', default=None)
webpage_url = episode.get('episode_url')
channel_url = None
if webpage_url:
channel_url = self._search_regex(
r'(https?://[^/]+\.simplecast\.com)',
webpage_url, 'channel url', default=None)
return {
'id': episode_id,
'display_id': episode.get('slug'),
'title': title,
'url': clean_podcast_url(audio_file_url),
'webpage_url': webpage_url,
'channel_url': channel_url,
'series': try_get(episode, lambda x: x['podcast']['title']),
'season_number': int_or_none(season.get('number')),
'season_id': season_id,
'thumbnail': episode.get('image_url'),
'episode_id': episode_id,
'episode_number': int_or_none(episode.get('number')),
'description': strip_or_none(episode.get('description')),
'timestamp': parse_iso8601(episode.get('published_at')),
'duration': int_or_none(episode.get('duration')),
'filesize': int_or_none(audio_file.get('size') or episode.get('audio_file_size')),
}
class SimplecastIE(SimplecastBaseIE):
IE_NAME = 'simplecast'
_VALID_URL = r'https?://(?:api\.simplecast\.com/episodes|player\.simplecast\.com)/(?P<id>%s)' % SimplecastBaseIE._UUID_REGEX
_COMMON_TEST_INFO = {
'display_id': 'errant-signal-chris-franklin-new-wave-video-essays',
'id': 'b6dc49a2-9404-4853-9aa9-9cfc097be876',
'ext': 'mp3',
'title': 'Errant Signal - Chris Franklin & New Wave Video Essays',
'episode_number': 1,
'episode_id': 'b6dc49a2-9404-4853-9aa9-9cfc097be876',
'description': 'md5:34752789d3d2702e2d2c975fbd14f357',
'season_number': 1,
'season_id': 'e23df0da-bae4-4531-8bbf-71364a88dc13',
'series': 'The RE:BIND.io Podcast',
'duration': 5343,
'timestamp': 1580979475,
'upload_date': '20200206',
'webpage_url': r're:^https?://the-re-bind-io-podcast\.simplecast\.com/episodes/errant-signal-chris-franklin-new-wave-video-essays',
'channel_url': r're:^https?://the-re-bind-io-podcast\.simplecast\.com$',
}
_TESTS = [{
'url': 'https://api.simplecast.com/episodes/b6dc49a2-9404-4853-9aa9-9cfc097be876',
'md5': '8c93be7be54251bf29ee97464eabe61c',
'info_dict': _COMMON_TEST_INFO,
}, {
'url': 'https://player.simplecast.com/b6dc49a2-9404-4853-9aa9-9cfc097be876',
'only_matching': True,
}]
@staticmethod
def _extract_urls(webpage):
return re.findall(
r'''(?x)<iframe[^>]+src=["\']
(
https?://(?:embed\.simplecast\.com/[0-9a-f]{8}|
player\.simplecast\.com/%s
))''' % SimplecastBaseIE._UUID_REGEX, webpage)
def _real_extract(self, url):
episode_id = self._match_id(url)
episode = self._call_api('episodes/%s', episode_id)
return self._parse_episode(episode)
class SimplecastEpisodeIE(SimplecastBaseIE):
IE_NAME = 'simplecast:episode'
_VALID_URL = r'https?://(?!api\.)[^/]+\.simplecast\.com/episodes/(?P<id>[^/?&#]+)'
_TEST = {
'url': 'https://the-re-bind-io-podcast.simplecast.com/episodes/errant-signal-chris-franklin-new-wave-video-essays',
'md5': '8c93be7be54251bf29ee97464eabe61c',
'info_dict': SimplecastIE._COMMON_TEST_INFO,
}
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
episode = self._call_search_api(
'episode', mobj.group(1), mobj.group(0))
return self._parse_episode(episode)
class SimplecastPodcastIE(SimplecastBaseIE):
IE_NAME = 'simplecast:podcast'
_VALID_URL = r'https?://(?!(?:api|cdn|embed|feeds|player)\.)(?P<id>[^/]+)\.simplecast\.com(?!/episodes/[^/?&#]+)'
_TESTS = [{
'url': 'https://the-re-bind-io-podcast.simplecast.com',
'playlist_mincount': 33,
'info_dict': {
'id': '07d28d26-7522-42eb-8c53-2bdcfc81c43c',
'title': 'The RE:BIND.io Podcast',
},
}, {
'url': 'https://the-re-bind-io-podcast.simplecast.com/episodes',
'only_matching': True,
}]
def _real_extract(self, url):
subdomain = self._match_id(url)
site = self._call_search_api('site', subdomain, url)
podcast = site['podcast']
podcast_id = podcast['id']
podcast_title = podcast.get('title')
def entries():
episodes = self._call_api('podcasts/%s/episodes', podcast_id)
for episode in (episodes.get('collection') or []):
info = self._parse_episode(episode)
info['series'] = podcast_title
yield info
return self.playlist_result(entries(), podcast_id, podcast_title)
+1 -1
View File
@@ -99,7 +99,7 @@ class SinaIE(InfoExtractor):
formats.append({ formats.append({
'format_id': quality_id, 'format_id': quality_id,
'url': update_url_query(file_api, {'vid': file_id}), 'url': update_url_query(file_api, {'vid': file_id}),
'preference': preference(quality_id), 'quality': preference(quality_id),
'ext': 'mp4', 'ext': 'mp4',
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -422,7 +422,7 @@ class SoundcloudIE(InfoExtractor):
'ext': urlhandle_detect_ext(urlh) or 'mp3', 'ext': urlhandle_detect_ext(urlh) or 'mp3',
'filesize': int_or_none(urlh.headers.get('Content-Length')), 'filesize': int_or_none(urlh.headers.get('Content-Length')),
'url': format_url, 'url': format_url,
'preference': 10, 'quality': 10,
}) })
def invalid_url(url): def invalid_url(url):
+1 -1
View File
@@ -129,7 +129,7 @@ class SpankBangIE(InfoExtractor):
format_url = format_url[0] format_url = format_url[0]
extract_format(format_id, format_url) extract_format(format_id, format_url)
self._sort_formats(formats, field_preference=('preference', 'height', 'width', 'fps', 'tbr', 'format_id')) self._sort_formats(formats)
info = self._search_json_ld(webpage, video_id, default={}) info = self._search_json_ld(webpage, video_id, default={})
+1 -1
View File
@@ -108,7 +108,7 @@ class SpankwireIE(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))
self._sort_formats(formats, ('height', 'tbr', 'width', 'format_id')) self._sort_formats(formats)
view_count = str_to_int(video.get('viewed')) view_count = str_to_int(video.get('viewed'))
+1 -1
View File
@@ -89,7 +89,7 @@ class SRGSSRIE(InfoExtractor):
formats.append({ formats.append({
'format_id': format_id, 'format_id': format_id,
'url': asset_url, 'url': asset_url,
'preference': preference(quality), 'quality': preference(quality),
'ext': 'flv' if protocol == 'RTMP' else None, 'ext': 'flv' if protocol == 'RTMP' else None,
}) })
self._sort_formats(formats) self._sort_formats(formats)
+105 -209
View File
@@ -1,255 +1,151 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
import itertools import functools
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import (
# HEADRequest,
int_or_none,
OnDemandPagedList,
smuggle_url,
)
class StoryFireIE(InfoExtractor): class StoryFireBaseIE(InfoExtractor):
_VALID_URL = r'(?:(?:https?://(?:www\.)?storyfire\.com/video-details)|(?:https://storyfire.app.link))/(?P<id>[^/\s]+)' _VALID_URL_BASE = r'https?://(?:www\.)?storyfire\.com/'
_TESTS = [{
def _call_api(self, path, video_id, resource, query=None):
return self._download_json(
'https://storyfire.com/app/%s/%s' % (path, video_id), video_id,
'Downloading %s JSON metadata' % resource, query=query)
def _parse_video(self, video):
title = video['title']
vimeo_id = self._search_regex(
r'https?://player\.vimeo\.com/external/(\d+)',
video['vimeoVideoURL'], 'vimeo id')
# video_url = self._request_webpage(
# HEADRequest(video['vimeoVideoURL']), video_id).geturl()
# formats = []
# for v_url, suffix in [(video_url, '_sep'), (video_url.replace('/sep/video/', '/video/'), '')]:
# formats.extend(self._extract_m3u8_formats(
# v_url, video_id, 'mp4', 'm3u8_native',
# m3u8_id='hls' + suffix, fatal=False))
# formats.extend(self._extract_mpd_formats(
# v_url.replace('.m3u8', '.mpd'), video_id,
# mpd_id='dash' + suffix, fatal=False))
# self._sort_formats(formats)
uploader_id = video.get('hostID')
return {
'_type': 'url_transparent',
'id': vimeo_id,
'title': title,
'description': video.get('description'),
'url': smuggle_url(
'https://player.vimeo.com/video/' + vimeo_id, {
'http_headers': {
'Referer': 'https://storyfire.com/',
}
}),
# 'formats': formats,
'thumbnail': video.get('storyImage'),
'view_count': int_or_none(video.get('views')),
'like_count': int_or_none(video.get('likesCount')),
'comment_count': int_or_none(video.get('commentsCount')),
'duration': int_or_none(video.get('videoDuration')),
'timestamp': int_or_none(video.get('publishDate')),
'uploader': video.get('username'),
'uploader_id': uploader_id,
'uploader_url': 'https://storyfire.com/user/%s/video' % uploader_id if uploader_id else None,
'episode_number': int_or_none(video.get('episodeNumber') or video.get('episode_number')),
}
class StoryFireIE(StoryFireBaseIE):
_VALID_URL = StoryFireBaseIE._VALID_URL_BASE + r'video-details/(?P<id>[0-9a-f]{24})'
_TEST = {
'url': 'https://storyfire.com/video-details/5df1d132b6378700117f9181', 'url': 'https://storyfire.com/video-details/5df1d132b6378700117f9181',
'md5': '560953bfca81a69003cfa5e53ac8a920', 'md5': 'caec54b9e4621186d6079c7ec100c1eb',
'info_dict': { 'info_dict': {
'id': '5df1d132b6378700117f9181', 'id': '378954662',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Buzzfeed Teaches You About Memes', 'title': 'Buzzfeed Teaches You About Memes',
'uploader_id': 'ntZAJFECERSgqHSxzonV5K2E89s1', 'uploader_id': 'ntZAJFECERSgqHSxzonV5K2E89s1',
'timestamp': 1576129028, 'timestamp': 1576129028,
'description': 'Mocking Buzzfeed\'s meme lesson. Reuploaded from YouTube because of their new policies', 'description': 'md5:0b4e28021548e144bed69bb7539e62ea',
'uploader': 'whang!', 'uploader': 'whang!',
'upload_date': '20191212', 'upload_date': '20191212',
'duration': 418,
'view_count': int,
'like_count': int,
'comment_count': int,
}, },
'params': {'format': 'bestvideo'} # There are no merged formats in the playlist. 'params': {
}, { 'skip_download': True,
'url': 'https://storyfire.app.link/5GxAvWOQr8', # Alternate URL format, with unrelated short ID
'md5': '7a2dc6d60c4889edfed459c620fe690d',
'info_dict': {
'id': '5f1e11ecd78a57b6c702001d',
'ext': 'm4a',
'title': 'Weird Nintendo Prototype Leaks',
'description': 'A stream taking a look at some weird Nintendo Prototypes with Luigi in Mario 64 and weird Yoshis',
'timestamp': 1595808576,
'upload_date': '20200727',
'uploader': 'whang!',
'uploader_id': 'ntZAJFECERSgqHSxzonV5K2E89s1',
}, },
'params': {'format': 'bestaudio'} # Verifying audio extraction 'expected_warnings': ['Unable to download JSON metadata']
}]
_aformats = {
'audio-medium-audio': {'acodec': 'aac', 'abr': 125, 'preference': -10},
'audio-high-audio': {'acodec': 'aac', 'abr': 254, 'preference': -1},
} }
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id) video = self._call_api(
'generic/video-detail', video_id, 'video')['video']
# Extracting the json blob is mandatory to proceed with extraction. return self._parse_video(video)
jsontext = self._html_search_regex(
r'<script id="__NEXT_DATA__" type="application/json">(.+?)</script>',
webpage, 'json_data')
json = self._parse_json(jsontext, video_id)
# The currentVideo field in the json is mandatory
# because it contains the only link to the m3u playlist
video = json['props']['initialState']['video']['currentVideo']
videourl = video['vimeoVideoURL'] # Video URL is mandatory
# Extract other fields from the json in an error tolerant fashion
# ID may be incorrect (on short URL format), correct it.
parsed_id = video.get('_id')
if parsed_id:
video_id = parsed_id
title = video.get('title')
description = video.get('description')
thumbnail = video.get('storyImage')
views = video.get('views')
likes = video.get('likesCount')
comments = video.get('commentsCount')
duration = video.get('videoDuration')
publishdate = video.get('publishDate') # Apparently epoch time, day only
uploader = video.get('username')
uploader_id = video.get('hostID')
# Construct an uploader URL
uploader_url = None
if uploader_id:
uploader_url = "https://storyfire.com/user/%s/video" % uploader_id
# Collect root playlist to determine formats
formats = self._extract_m3u8_formats(
videourl, video_id, 'mp4', 'm3u8_native')
# Modify formats to fill in missing information about audio codecs
for format in formats:
aformat = self._aformats.get(format['format_id'])
if aformat:
format['acodec'] = aformat['acodec']
format['abr'] = aformat['abr']
format['preference'] = aformat['preference']
format['ext'] = 'm4a'
self._sort_formats(formats)
return {
'id': video_id,
'title': title,
'description': description,
'ext': "mp4",
'url': videourl,
'formats': formats,
'thumbnail': thumbnail,
'view_count': views,
'like_count': likes,
'comment_count': comments,
'duration': duration,
'timestamp': publishdate,
'uploader': uploader,
'uploader_id': uploader_id,
'uploader_url': uploader_url,
}
class StoryFireUserIE(InfoExtractor): class StoryFireUserIE(StoryFireBaseIE):
_VALID_URL = r'https?://(?:www\.)?storyfire\.com/user/(?P<id>[^/\s]+)/video' _VALID_URL = StoryFireBaseIE._VALID_URL_BASE + r'user/(?P<id>[^/]+)/video'
_TESTS = [{ _TEST = {
'url': 'https://storyfire.com/user/ntZAJFECERSgqHSxzonV5K2E89s1/video',
'info_dict': {
'id': 'ntZAJFECERSgqHSxzonV5K2E89s1',
'title': 'whang!',
},
'playlist_mincount': 18
}, {
'url': 'https://storyfire.com/user/UQ986nFxmAWIgnkZQ0ftVhq4nOk2/video', 'url': 'https://storyfire.com/user/UQ986nFxmAWIgnkZQ0ftVhq4nOk2/video',
'info_dict': { 'info_dict': {
'id': 'UQ986nFxmAWIgnkZQ0ftVhq4nOk2', 'id': 'UQ986nFxmAWIgnkZQ0ftVhq4nOk2',
'title': 'McJuggerNuggets',
}, },
'playlist_mincount': 143 'playlist_mincount': 151,
}
_PAGE_SIZE = 20
}] def _fetch_page(self, user_id, page):
videos = self._call_api(
# Generator for fetching playlist items 'publicVideos', user_id, 'page %d' % (page + 1), {
def _enum_videos(self, baseurl, user_id, firstjson): 'skip': page * self._PAGE_SIZE,
totalVideos = int(firstjson['videosCount']) })['videos']
haveVideos = 0 for video in videos:
json = firstjson yield self._parse_video(video)
for page in itertools.count(1):
for video in json['videos']:
id = video['_id']
url = "https://storyfire.com/video-details/%s" % id
haveVideos += 1
yield {
'_type': 'url',
'id': id,
'url': url,
'ie_key': 'StoryFire',
'title': video.get('title'),
'description': video.get('description'),
'view_count': video.get('views'),
'comment_count': video.get('commentsCount'),
'duration': video.get('videoDuration'),
'timestamp': video.get('publishDate'),
}
# Are there more pages we could fetch?
if haveVideos < totalVideos:
pageurl = baseurl + ("%i" % haveVideos)
json = self._download_json(pageurl, user_id,
note='Downloading page %s' % page)
# Are there any videos in the new json?
videos = json.get('videos')
if not videos or len(videos) == 0:
break # no videos
else:
break # We have fetched all the videos, stop
def _real_extract(self, url): def _real_extract(self, url):
user_id = self._match_id(url) user_id = self._match_id(url)
entries = OnDemandPagedList(functools.partial(
baseurl = "https://storyfire.com/app/publicVideos/%s?skip=" % user_id self._fetch_page, user_id), self._PAGE_SIZE)
return self.playlist_result(entries, user_id)
# Download first page to ensure it can be downloaded, and get user information if available.
firstpage = baseurl + "0"
firstjson = self._download_json(firstpage, user_id)
title = None
videos = firstjson.get('videos')
if videos and len(videos):
title = videos[1].get('username')
return {
'_type': 'playlist',
'entries': self._enum_videos(baseurl, user_id, firstjson),
'id': user_id,
'title': title,
}
class StoryFireSeriesIE(InfoExtractor): class StoryFireSeriesIE(StoryFireBaseIE):
_VALID_URL = r'https?://(?:www\.)?storyfire\.com/write/series/stories/(?P<id>[^/\s]+)' _VALID_URL = StoryFireBaseIE._VALID_URL_BASE + r'write/series/stories/(?P<id>[^/?&#]+)'
_TESTS = [{ _TESTS = [{
'url': 'https://storyfire.com/write/series/stories/-Lq6MsuIHLODO6d2dDkr/', 'url': 'https://storyfire.com/write/series/stories/-Lq6MsuIHLODO6d2dDkr/',
'info_dict': { 'info_dict': {
'id': '-Lq6MsuIHLODO6d2dDkr', 'id': '-Lq6MsuIHLODO6d2dDkr',
}, },
'playlist_mincount': 13 'playlist_mincount': 13,
}, { }, {
'url': 'https://storyfire.com/write/series/stories/the_mortal_one/', 'url': 'https://storyfire.com/write/series/stories/the_mortal_one/',
'info_dict': { 'info_dict': {
'id': 'the_mortal_one', 'id': 'the_mortal_one',
}, },
'playlist_count': 0 # This playlist has entries, but no videos. 'playlist_count': 0,
}, {
'url': 'https://storyfire.com/write/series/stories/story_time',
'info_dict': {
'id': 'story_time',
},
'playlist_mincount': 10
}] }]
# Generator for returning playlist items def _extract_videos(self, stories):
# This object is substantially different than the one in the user videos page above for story in stories.values():
def _enum_videos(self, jsonlist): if story.get('hasVideo'):
for video in jsonlist: yield self._parse_video(story)
id = video['_id']
if video.get('hasVideo'): # Boolean element
url = "https://storyfire.com/video-details/%s" % id
yield {
'_type': 'url',
'id': id,
'url': url,
'ie_key': 'StoryFire',
'title': video.get('title'),
'description': video.get('description'),
'view_count': video.get('views'),
'likes_count': video.get('likesCount'),
'comment_count': video.get('commentsCount'),
'duration': video.get('videoDuration'),
'timestamp': video.get('publishDate'),
}
def _real_extract(self, url): def _real_extract(self, url):
list_id = self._match_id(url) series_id = self._match_id(url)
stories = self._call_api(
listurl = "https://storyfire.com/app/seriesStories/%s/list" % list_id 'seriesStories', series_id, 'series stories')
json = self._download_json(listurl, list_id) return self.playlist_result(self._extract_videos(stories), series_id)
return {
'_type': 'playlist',
'entries': self._enum_videos(json),
'id': list_id
}
+1 -1
View File
@@ -70,7 +70,7 @@ class Tele13IE(InfoExtractor):
formats.append({ formats.append({
'url': format_url, 'url': format_url,
'format_id': f.get('label'), 'format_id': f.get('label'),
'preference': preference(f.get('label')), 'quality': preference(f.get('label')),
'ext': ext, 'ext': ext,
}) })
urls.append(format_url) urls.append(format_url)
+1 -1
View File
@@ -57,7 +57,7 @@ class TeleMBIE(InfoExtractor):
'app': rtmp.group('app'), 'app': rtmp.group('app'),
'player_url': 'http://p.jwpcdn.com/6/10/jwplayer.flash.swf', 'player_url': 'http://p.jwpcdn.com/6/10/jwplayer.flash.swf',
'page_url': 'http://www.telemb.be', 'page_url': 'http://www.telemb.be',
'preference': -1, 'preference': -10,
}) })
formats.append(fmt) formats.append(fmt)
self._sort_formats(formats) self._sort_formats(formats)
+4 -6
View File
@@ -133,12 +133,10 @@ class ThreeQSDNIE(InfoExtractor):
'vcodec': 'none' if height == 0 else None, 'vcodec': 'none' if height == 0 else None,
'width': width, 'width': width,
}) })
for f in formats: # It seems like this would be correctly handled by default
if f.get('acodec') == 'none': # However, unless someone can confirm this, the old
f['preference'] = -40 # behaviour is being kept as-is
elif f.get('vcodec') == 'none': self._sort_formats(formats, ('res', 'source_preference'))
f['preference'] = -50
self._sort_formats(formats, ('preference', 'width', 'height', 'source_preference', 'tbr', 'vbr', 'abr', 'ext', 'format_id'))
subtitles = {} subtitles = {}
for subtitle in (config.get('subtitles') or []): for subtitle in (config.get('subtitles') or []):
+1 -1
View File
@@ -405,7 +405,7 @@ class UdemyIE(InfoExtractor):
if f.get('url'): if f.get('url'):
formats.append(f) formats.append(f)
self._sort_formats(formats, field_preference=('height', 'width', 'tbr', 'format_id')) self._sort_formats(formats)
return { return {
'id': video_id, 'id': video_id,
+1 -1
View File
@@ -91,7 +91,7 @@ class UMGDeIE(InfoExtractor):
if not formats: if not formats:
for format_id in (867, 836, 940): for format_id in (867, 836, 940):
add_m3u8_format(format_id) add_m3u8_format(format_id)
self._sort_formats(formats, ('width', 'height', 'filesize', 'tbr')) self._sort_formats(formats)
return { return {
'id': video_id, 'id': video_id,
-1
View File
@@ -110,7 +110,6 @@ class UOLIE(InfoExtractor):
'format_id': format_id, 'format_id': format_id,
'url': f_url, 'url': f_url,
'quality': quality(format_id), 'quality': quality(format_id),
'preference': -1,
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1 -1
View File
@@ -44,7 +44,7 @@ class UrortIE(InfoExtractor):
'ext': f['FileType'], 'ext': f['FileType'],
'format_id': '%s-%s' % (f['FileType'], f.get('Quality', '')), 'format_id': '%s-%s' % (f['FileType'], f.get('Quality', '')),
'url': 'http://p3urort.blob.core.windows.net/tracks/%s' % f['FileRef'], 'url': 'http://p3urort.blob.core.windows.net/tracks/%s' % f['FileRef'],
'preference': 3 if f['FileType'] == 'mp3' else 2, 'quality': 3 if f['FileType'] == 'mp3' else 2,
} for f in s['Files']] } for f in s['Files']]
self._sort_formats(formats) self._sort_formats(formats)
e = { e = {
+15 -11
View File
@@ -4,21 +4,22 @@ 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 (
determine_ext, determine_ext,
float_or_none, float_or_none,
int_or_none,
parse_age_limit, parse_age_limit,
qualities, qualities,
random_birthday, random_birthday,
try_get,
unified_timestamp, unified_timestamp,
urljoin, urljoin,
) )
class VideoPressIE(InfoExtractor): class VideoPressIE(InfoExtractor):
_VALID_URL = r'https?://videopress\.com/embed/(?P<id>[\da-zA-Z]+)' _ID_REGEX = r'[\da-zA-Z]{8}'
_PATH_REGEX = r'video(?:\.word)?press\.com/embed/'
_VALID_URL = r'https?://%s(?P<id>%s)' % (_PATH_REGEX, _ID_REGEX)
_TESTS = [{ _TESTS = [{
'url': 'https://videopress.com/embed/kUJmAcSf', 'url': 'https://videopress.com/embed/kUJmAcSf',
'md5': '706956a6c875873d51010921310e4bc6', 'md5': '706956a6c875873d51010921310e4bc6',
@@ -36,35 +37,36 @@ class VideoPressIE(InfoExtractor):
# 17+, requires birth_* params # 17+, requires birth_* params
'url': 'https://videopress.com/embed/iH3gstfZ', 'url': 'https://videopress.com/embed/iH3gstfZ',
'only_matching': True, 'only_matching': True,
}, {
'url': 'https://video.wordpress.com/embed/kUJmAcSf',
'only_matching': True,
}] }]
@staticmethod @staticmethod
def _extract_urls(webpage): def _extract_urls(webpage):
return re.findall( return re.findall(
r'<iframe[^>]+src=["\']((?:https?://)?videopress\.com/embed/[\da-zA-Z]+)', r'<iframe[^>]+src=["\']((?:https?://)?%s%s)' % (VideoPressIE._PATH_REGEX, VideoPressIE._ID_REGEX),
webpage) webpage)
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
query = random_birthday('birth_year', 'birth_month', 'birth_day') query = random_birthday('birth_year', 'birth_month', 'birth_day')
query['fields'] = 'description,duration,file_url_base,files,height,original,poster,rating,title,upload_date,width'
video = self._download_json( video = self._download_json(
'https://public-api.wordpress.com/rest/v1.1/videos/%s' % video_id, 'https://public-api.wordpress.com/rest/v1.1/videos/%s' % video_id,
video_id, query=query) video_id, query=query)
title = video['title'] title = video['title']
def base_url(scheme): file_url_base = video.get('file_url_base') or {}
return try_get( base_url = file_url_base.get('https') or file_url_base.get('http')
video, lambda x: x['file_url_base'][scheme], compat_str)
base_url = base_url('https') or base_url('http')
QUALITIES = ('std', 'dvd', 'hd') QUALITIES = ('std', 'dvd', 'hd')
quality = qualities(QUALITIES) quality = qualities(QUALITIES)
formats = [] formats = []
for format_id, f in video['files'].items(): for format_id, f in (video.get('files') or {}).items():
if not isinstance(f, dict): if not isinstance(f, dict):
continue continue
for ext, path in f.items(): for ext, path in f.items():
@@ -75,12 +77,14 @@ class VideoPressIE(InfoExtractor):
'ext': determine_ext(path, ext), 'ext': determine_ext(path, ext),
'quality': quality(format_id), 'quality': quality(format_id),
}) })
original_url = try_get(video, lambda x: x['original'], compat_str) original_url = video.get('original')
if original_url: if original_url:
formats.append({ formats.append({
'url': original_url, 'url': original_url,
'format_id': 'original', 'format_id': 'original',
'quality': len(QUALITIES), 'quality': len(QUALITIES),
'width': int_or_none(video.get('width')),
'height': int_or_none(video.get('height')),
}) })
self._sort_formats(formats) self._sort_formats(formats)
+1
View File
@@ -181,6 +181,7 @@ class VidmeIE(InfoExtractor):
'url': format_url, 'url': format_url,
'width': int_or_none(f.get('width')), 'width': int_or_none(f.get('width')),
'height': int_or_none(f.get('height')), 'height': int_or_none(f.get('height')),
# Clips should never be prefered over full video
'preference': 0 if f.get('type', '').endswith( 'preference': 0 if f.get('type', '').endswith(
'clip') else 1, 'clip') else 1,
}) })
+1 -1
View File
@@ -134,7 +134,7 @@ class ViewLiftEmbedIE(ViewLiftBaseIE):
if hls_url: if hls_url:
formats.extend(self._extract_m3u8_formats( formats.extend(self._extract_m3u8_formats(
hls_url, film_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)) hls_url, film_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
self._sort_formats(formats, ('height', 'tbr', 'format_id')) self._sort_formats(formats)
info = { info = {
'id': film_id, 'id': film_id,
+62 -27
View File
@@ -22,6 +22,7 @@ from ..utils import (
parse_iso8601, parse_iso8601,
sanitized_Request, sanitized_Request,
std_headers, std_headers,
try_get,
) )
@@ -31,7 +32,7 @@ class VikiBaseIE(InfoExtractor):
_API_URL_TEMPLATE = 'https://api.viki.io%s&sig=%s' _API_URL_TEMPLATE = 'https://api.viki.io%s&sig=%s'
_APP = '100005a' _APP = '100005a'
_APP_VERSION = '2.2.5.1428709186' _APP_VERSION = '6.0.0'
_APP_SECRET = 'MM_d*yP@`&1@]@!AVrXf_o-HVEnoTnm$O-ti4[G~$JDI/Dc-&piU&z&5.;:}95=Iad' _APP_SECRET = 'MM_d*yP@`&1@]@!AVrXf_o-HVEnoTnm$O-ti4[G~$JDI/Dc-&piU&z&5.;:}95=Iad'
_GEO_BYPASS = False _GEO_BYPASS = False
@@ -42,7 +43,7 @@ class VikiBaseIE(InfoExtractor):
_ERRORS = { _ERRORS = {
'geo': 'Sorry, this content is not available in your region.', 'geo': 'Sorry, this content is not available in your region.',
'upcoming': 'Sorry, this content is not yet available.', 'upcoming': 'Sorry, this content is not yet available.',
# 'paywall': 'paywall', 'paywall': 'Sorry, this content is only available to Viki Pass Plus subscribers',
} }
def _prepare_call(self, path, timestamp=None, post_data=None): def _prepare_call(self, path, timestamp=None, post_data=None):
@@ -63,14 +64,25 @@ class VikiBaseIE(InfoExtractor):
def _call_api(self, path, video_id, note, timestamp=None, post_data=None): def _call_api(self, path, video_id, note, timestamp=None, post_data=None):
resp = self._download_json( resp = self._download_json(
self._prepare_call(path, timestamp, post_data), video_id, note) self._prepare_call(path, timestamp, post_data),
video_id, note,
headers={
'x-client-user-agent': std_headers['User-Agent'],
'x-viki-as-id': self._APP,
'x-viki-app-ver': self._APP_VERSION,
})
error = resp.get('error') error = resp.get('error')
if error: if error:
if error == 'invalid timestamp': if error == 'invalid timestamp':
resp = self._download_json( resp = self._download_json(
self._prepare_call(path, int(resp['current_timestamp']), post_data), self._prepare_call(path, int(resp['current_timestamp']), post_data),
video_id, '%s (retry)' % note) video_id, '%s (retry)' % note,
headers={
'x-client-user-agent': std_headers['User-Agent'],
'x-viki-as-id': self._APP,
'x-viki-app-ver': self._APP_VERSION,
})
error = resp.get('error') error = resp.get('error')
if error: if error:
self._raise_error(resp['error']) self._raise_error(resp['error'])
@@ -83,11 +95,13 @@ class VikiBaseIE(InfoExtractor):
expected=True) expected=True)
def _check_errors(self, data): def _check_errors(self, data):
for reason, status in data.get('blocking', {}).items(): for reason, status in (data.get('blocking') or {}).items():
if status and reason in self._ERRORS: if status and reason in self._ERRORS:
message = self._ERRORS[reason] message = self._ERRORS[reason]
if reason == 'geo': if reason == 'geo':
self.raise_geo_restricted(msg=message) self.raise_geo_restricted(msg=message)
elif reason == 'paywall':
self.raise_login_required(message)
raise ExtractorError('%s said: %s' % ( raise ExtractorError('%s said: %s' % (
self.IE_NAME, message), expected=True) self.IE_NAME, message), expected=True)
@@ -132,13 +146,19 @@ class VikiIE(VikiBaseIE):
'info_dict': { 'info_dict': {
'id': '1023585v', 'id': '1023585v',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Heirs Episode 14', 'title': 'Heirs - Episode 14',
'uploader': 'SBS', 'uploader': 'SBS Contents Hub',
'description': 'md5:c4b17b9626dd4b143dcc4d855ba3474e', 'timestamp': 1385047627,
'upload_date': '20131121', 'upload_date': '20131121',
'age_limit': 13, 'age_limit': 13,
'duration': 3570,
'episode_number': 14,
},
'params': {
'format': 'bestvideo',
}, },
'skip': 'Blocked in the US', 'skip': 'Blocked in the US',
'expected_warnings': ['Unknown MIME type image/jpeg in DASH manifest'],
}, { }, {
# clip # clip
'url': 'http://www.viki.com/videos/1067139v-the-avengers-age-of-ultron-press-conference', 'url': 'http://www.viki.com/videos/1067139v-the-avengers-age-of-ultron-press-conference',
@@ -154,7 +174,8 @@ class VikiIE(VikiBaseIE):
'uploader': 'Arirang TV', 'uploader': 'Arirang TV',
'like_count': int, 'like_count': int,
'age_limit': 0, 'age_limit': 0,
} },
'skip': 'Sorry. There was an error loading this video',
}, { }, {
'url': 'http://www.viki.com/videos/1048879v-ankhon-dekhi', 'url': 'http://www.viki.com/videos/1048879v-ankhon-dekhi',
'info_dict': { 'info_dict': {
@@ -172,7 +193,7 @@ class VikiIE(VikiBaseIE):
}, { }, {
# episode # episode
'url': 'http://www.viki.com/videos/44699v-boys-over-flowers-episode-1', 'url': 'http://www.viki.com/videos/44699v-boys-over-flowers-episode-1',
'md5': '94e0e34fd58f169f40c184f232356cfe', 'md5': '0a53dc252e6e690feccd756861495a8c',
'info_dict': { 'info_dict': {
'id': '44699v', 'id': '44699v',
'ext': 'mp4', 'ext': 'mp4',
@@ -184,6 +205,10 @@ class VikiIE(VikiBaseIE):
'uploader': 'group8', 'uploader': 'group8',
'like_count': int, 'like_count': int,
'age_limit': 13, 'age_limit': 13,
'episode_number': 1,
},
'params': {
'format': 'bestvideo',
}, },
'expected_warnings': ['Unknown MIME type image/jpeg in DASH manifest'], 'expected_warnings': ['Unknown MIME type image/jpeg in DASH manifest'],
}, { }, {
@@ -210,7 +235,7 @@ class VikiIE(VikiBaseIE):
}, { }, {
# non-English description # non-English description
'url': 'http://www.viki.com/videos/158036v-love-in-magic', 'url': 'http://www.viki.com/videos/158036v-love-in-magic',
'md5': 'adf9e321a0ae5d0aace349efaaff7691', 'md5': '41faaba0de90483fb4848952af7c7d0d',
'info_dict': { 'info_dict': {
'id': '158036v', 'id': '158036v',
'ext': 'mp4', 'ext': 'mp4',
@@ -221,6 +246,10 @@ class VikiIE(VikiBaseIE):
'title': 'Love In Magic', 'title': 'Love In Magic',
'age_limit': 13, 'age_limit': 13,
}, },
'params': {
'format': 'bestvideo',
},
'expected_warnings': ['Unknown MIME type image/jpeg in DASH manifest'],
}] }]
def _real_extract(self, url): def _real_extract(self, url):
@@ -230,29 +259,27 @@ class VikiIE(VikiBaseIE):
'https://www.viki.com/api/videos/' + video_id, 'https://www.viki.com/api/videos/' + video_id,
video_id, 'Downloading video JSON', headers={ video_id, 'Downloading video JSON', headers={
'x-client-user-agent': std_headers['User-Agent'], 'x-client-user-agent': std_headers['User-Agent'],
'x-viki-app-ver': '4.0.57', 'x-viki-as-id': self._APP,
'x-viki-app-ver': self._APP_VERSION,
}) })
video = resp['video'] video = resp['video']
self._check_errors(video) self._check_errors(video)
title = self.dict_selection(video.get('titles', {}), 'en', allow_fallback=False) title = self.dict_selection(video.get('titles', {}), 'en', allow_fallback=False)
episode_number = int_or_none(video.get('number'))
if not title: if not title:
title = 'Episode %d' % video.get('number') if video.get('type') == 'episode' else video.get('id') or video_id title = 'Episode %d' % episode_number if video.get('type') == 'episode' else video.get('id') or video_id
container_titles = video.get('container', {}).get('titles', {}) container_titles = try_get(video, lambda x: x['container']['titles'], dict) or {}
container_title = self.dict_selection(container_titles, 'en') container_title = self.dict_selection(container_titles, 'en')
title = '%s - %s' % (container_title, title) title = '%s - %s' % (container_title, title)
description = self.dict_selection(video.get('descriptions', {}), 'en') description = self.dict_selection(video.get('descriptions', {}), 'en')
duration = int_or_none(video.get('duration')) like_count = int_or_none(try_get(video, lambda x: x['likes']['count']))
timestamp = parse_iso8601(video.get('created_at'))
uploader = video.get('author')
like_count = int_or_none(video.get('likes', {}).get('count'))
age_limit = parse_age_limit(video.get('rating'))
thumbnails = [] thumbnails = []
for thumbnail_id, thumbnail in video.get('images', {}).items(): for thumbnail_id, thumbnail in (video.get('images') or {}).items():
thumbnails.append({ thumbnails.append({
'id': thumbnail_id, 'id': thumbnail_id,
'url': thumbnail.get('url'), 'url': thumbnail.get('url'),
@@ -263,7 +290,12 @@ class VikiIE(VikiBaseIE):
# New way to fetch subtitles # New way to fetch subtitles
new_video = self._download_json( new_video = self._download_json(
'https://www.viki.com/api/videos/%s' % video_id, video_id, 'https://www.viki.com/api/videos/%s' % video_id, video_id,
'Downloading new video JSON to get subtitles', fatal=False) 'Downloading new video JSON to get subtitles', fatal=False,
headers={
'x-client-user-agent': std_headers['User-Agent'],
'x-viki-as-id': self._APP,
'x-viki-app-ver': self._APP_VERSION,
})
for sub in new_video.get('streamSubtitles').get('dash'): for sub in new_video.get('streamSubtitles').get('dash'):
subtitles[sub.get('srclang')] = [{ subtitles[sub.get('srclang')] = [{
'ext': 'vtt', 'ext': 'vtt',
@@ -272,7 +304,7 @@ class VikiIE(VikiBaseIE):
}] }]
except AttributeError: except AttributeError:
# fall-back to the old way if there isn't a streamSubtitles attribute # fall-back to the old way if there isn't a streamSubtitles attribute
for subtitle_lang, _ in video.get('subtitle_completions', {}).items(): for subtitle_lang, _ in (video.get('subtitle_completions') or {}).items():
subtitles[subtitle_lang] = [{ subtitles[subtitle_lang] = [{
'ext': subtitles_format, 'ext': subtitles_format,
'url': self._prepare_call( 'url': self._prepare_call(
@@ -283,13 +315,15 @@ class VikiIE(VikiBaseIE):
'id': video_id, 'id': video_id,
'title': title, 'title': title,
'description': description, 'description': description,
'duration': duration, 'duration': int_or_none(video.get('duration')),
'timestamp': timestamp, 'timestamp': parse_iso8601(video.get('created_at')),
'uploader': uploader, 'uploader': video.get('author'),
'uploader_url': video.get('author_url'),
'like_count': like_count, 'like_count': like_count,
'age_limit': age_limit, 'age_limit': parse_age_limit(video.get('rating')),
'thumbnails': thumbnails, 'thumbnails': thumbnails,
'subtitles': subtitles, 'subtitles': subtitles,
'episode_number': episode_number,
} }
formats = [] formats = []
@@ -383,7 +417,7 @@ class VikiChannelIE(VikiBaseIE):
'info_dict': { 'info_dict': {
'id': '50c', 'id': '50c',
'title': 'Boys Over Flowers', 'title': 'Boys Over Flowers',
'description': 'md5:ecd3cff47967fe193cff37c0bec52790', 'description': 'md5:804ce6e7837e1fd527ad2f25420f4d59',
}, },
'playlist_mincount': 71, 'playlist_mincount': 71,
}, { }, {
@@ -394,6 +428,7 @@ class VikiChannelIE(VikiBaseIE):
'description': 'md5:05bf5471385aa8b21c18ad450e350525', 'description': 'md5:05bf5471385aa8b21c18ad450e350525',
}, },
'playlist_count': 127, 'playlist_count': 127,
'skip': 'Page not found',
}, { }, {
'url': 'http://www.viki.com/news/24569c-showbiz-korea', 'url': 'http://www.viki.com/news/24569c-showbiz-korea',
'only_matching': True, 'only_matching': True,
+17 -15
View File
@@ -116,7 +116,8 @@ class VimeoBaseInfoExtractor(InfoExtractor):
def _vimeo_sort_formats(self, formats): def _vimeo_sort_formats(self, formats):
# Bitrates are completely broken. Single m3u8 may contain entries in kbps and bps # Bitrates are completely broken. Single m3u8 may contain entries in kbps and bps
# at the same time without actual units specified. This lead to wrong sorting. # at the same time without actual units specified. This lead to wrong sorting.
self._sort_formats(formats, field_preference=('preference', 'height', 'width', 'fps', 'tbr', 'format_id')) # But since yt-dlp prefers 'res,fps' anyway, 'field_preference' is not needed
self._sort_formats(formats)
def _parse_config(self, config, video_id): def _parse_config(self, config, video_id):
video_data = config['video'] video_data = config['video']
@@ -178,16 +179,9 @@ class VimeoBaseInfoExtractor(InfoExtractor):
formats.append({ formats.append({
'format_id': 'live-archive-source', 'format_id': 'live-archive-source',
'url': live_archive_source_url, 'url': live_archive_source_url,
'preference': 1, 'quality': 10,
}) })
# Reduntant code! This is already done in common.py
# for f in formats:
# if f.get('vcodec') == 'none':
# f['preference'] = -50
# elif f.get('acodec') == 'none':
# f['preference'] = -40
subtitles = {} subtitles = {}
text_tracks = config['request'].get('text_tracks') text_tracks = config['request'].get('text_tracks')
if text_tracks: if text_tracks:
@@ -227,10 +221,12 @@ class VimeoBaseInfoExtractor(InfoExtractor):
'is_live': is_live, 'is_live': is_live,
} }
def _extract_original_format(self, url, video_id): def _extract_original_format(self, url, video_id, unlisted_hash=None):
query = {'action': 'load_download_config'}
if unlisted_hash:
query['unlisted_hash'] = unlisted_hash
download_data = self._download_json( download_data = self._download_json(
url, video_id, fatal=False, url, video_id, fatal=False, query=query,
query={'action': 'load_download_config'},
headers={'X-Requested-With': 'XMLHttpRequest'}) headers={'X-Requested-With': 'XMLHttpRequest'})
if download_data: if download_data:
source_file = download_data.get('source_file') source_file = download_data.get('source_file')
@@ -250,7 +246,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
'height': int_or_none(source_file.get('height')), 'height': int_or_none(source_file.get('height')),
'filesize': parse_filesize(source_file.get('size')), 'filesize': parse_filesize(source_file.get('size')),
'format_id': source_name, 'format_id': source_name,
'preference': 1, 'quality': 1,
} }
@@ -510,6 +506,11 @@ class VimeoIE(VimeoBaseInfoExtractor):
{ {
'url': 'https://vimeo.com/160743502/abd0e13fb4', 'url': 'https://vimeo.com/160743502/abd0e13fb4',
'only_matching': True, 'only_matching': True,
},
{
# requires passing unlisted_hash(a52724358e) to load_download_config request
'url': 'https://vimeo.com/392479337/a52724358e',
'only_matching': True,
} }
# https://gettingthingsdone.com/workflowmap/ # https://gettingthingsdone.com/workflowmap/
# vimeo embed with check-password page protected by Referer header # vimeo embed with check-password page protected by Referer header
@@ -674,7 +675,8 @@ class VimeoIE(VimeoBaseInfoExtractor):
if config.get('view') == 4: if config.get('view') == 4:
config = self._verify_player_video_password(redirect_url, video_id, headers) config = self._verify_player_video_password(redirect_url, video_id, headers)
vod = config.get('video', {}).get('vod', {}) video = config.get('video') or {}
vod = video.get('vod') or {}
def is_rented(): def is_rented():
if '>You rented this title.<' in webpage: if '>You rented this title.<' in webpage:
@@ -734,7 +736,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
formats = [] formats = []
source_format = self._extract_original_format( source_format = self._extract_original_format(
'https://vimeo.com/' + video_id, video_id) 'https://vimeo.com/' + video_id, video_id, video.get('unlisted_hash'))
if source_format: if source_format:
formats.append(source_format) formats.append(source_format)
+1 -1
View File
@@ -70,7 +70,7 @@ class VzaarIE(InfoExtractor):
f = { f = {
'url': source_url, 'url': source_url,
'format_id': 'http', 'format_id': 'http',
'preference': 1, 'quality': 1,
} }
if 'audio' in source_url: if 'audio' in source_url:
f.update({ f.update({

Some files were not shown because too many files have changed in this diff Show More