Compare commits

..
43 Commits
Author SHA1 Message Date
pukkandan 65075de35b Release 2021.05.20 2021-05-20 19:20:07 +05:30
Lukas Anzingerandpukkandan 50755db160 Update to ytdl-commit-dfbbe29
[redbulltv] fix embed data extraction
https://github.com/ytdl-org/youtube-dl/commit/dfbbe2902fc67f0f93ee47a8077c148055c67a9b
2021-05-20 19:17:17 +05:30
king-millezandGitHub 5014558ab9 [parlview] Add extractor (#322)
Authored by: king-millez
2021-05-20 18:35:37 +05:30
pukkandan 28b0eb0f65 [cleanup] See desc
* Remove struct from `embedthumbnail`
* Use bullet lists in readme where numbered list don't make sense
* Fix error introduced in 9c2b75b561 when `ie_result` is `None`
2021-05-20 18:02:58 +05:30
pukkandan 95131b2176 [embedthumbnail] Add flac support and refactor mutagen code
https://github.com/ytdl-org/youtube-dl/pull/28894, https://github.com/ytdl-org/youtube-dl/pull/24310
Authored by: tripulse
2021-05-20 17:51:33 +05:30
pukkandan 2305e2e5c9 [options] Alias --write-comments, --no-write-comments
Closes: #264
2021-05-20 15:56:57 +05:30
coletdjnzandpukkandan 00ae27690d [youtube] Add html5=1 param to get_video_info page requests (#329)
Workaround for #319, https://github.com/ytdl-org/youtube-dl/issues/29086
Authored by: colethedj
2021-05-20 15:56:57 +05:30
pukkandan 9d5d4d64f8 [youtube] Better message when login required 2021-05-20 15:55:55 +05:30
king-millezandpukkandan 98784ef8d6 [audius:artist] Add extractor (#323)
Authored by: king-millez
2021-05-20 15:55:55 +05:30
pukkandan d3fc8074a4 [youtube] Sort audio-only formats correctly
Closes #317
2021-05-19 18:29:20 +05:30
pukkandan 9c2b75b561 Field additional_urls to download additional videos from metadata 2021-05-19 18:11:15 +05:30
pukkandan 856bb8f99d [downloader] Fix write_debug 2021-05-19 17:34:17 +05:30
pukkandan af32f40bf5 [test] Fix test_YoutubeDL.TestYoutubeDL
Test `test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries` was broken due to `__original_infodict` being added to the dict
2021-05-19 17:00:40 +05:30
pukkandan 4ec82a72bb Ensure post_extract and pre_process only run once
Previously, they ran once for each format requested
2021-05-19 16:48:22 +05:30
pukkandan 07cce701de [cleanup] linter, code formatting and readme 2021-05-19 16:48:20 +05:30
king-millezandGitHub 74e001af1d [tenplay] Fix extractor (#314)
Authored by: king-millez
2021-05-19 16:43:34 +05:30
pukkandan ff2751ac9c [youtube] Always extract maxresdefault thumbnail
Fixes: https://github.com/ytdl-org/youtube-dl/issues/29049
2021-05-18 19:31:17 +05:30
pukkandan abcdd12b26 [youtube:tab] Support youtube music MP pages 2021-05-18 19:31:08 +05:30
pukkandan 18db754858 [youtube:tab] Redirect UC channels that doesn't have a videos tab
Many topic URLs don't have a videos tab, but has an equivalent `UU` playlist.
If there is no playlist, fallback to using channel page
2021-05-18 19:31:07 +05:30
pukkandan fe03a6cdc8 [youtube:tab] Support youtube music VL and browse pages 2021-05-18 19:31:06 +05:30
pukkandan cd684175ad [youtube:tab] Support channel search
Fixes: https://github.com/ytdl-org/youtube-dl/issues/29071
2021-05-18 19:30:21 +05:30
pukkandan da692b7920 [cleanup] youtube tests 2021-05-18 18:10:15 +05:30
pukkandan 95c01b6c16 [youtube:tab] Show alerts only from the final webpage 2021-05-18 18:09:04 +05:30
pukkandan 6911e11edd [test:download] Only extract enough videos for playlist_mincount 2021-05-18 18:08:55 +05:30
pukkandan 5112f26a60 Add pl_thumbnail outtmpl key for playlist thumbnails
This should have been implemented in 681de68e9d, but I forgot
2021-05-18 17:12:20 +05:30
pukkandan a06916d98e [extractor] Add write_debug and get_param 2021-05-17 18:59:51 +05:30
pukkandan 681de68e9d Write thumbnail of playlist
Related: https://github.com/ytdl-org/youtube-dl/pull/28872, https://github.com/ytdl-org/youtube-dl/pull/28860
This is slightly different from the above PRs in that this downloads the playlist's thumbnail instead of the uploader's profile picture. But for youtube channel URLs these are the same
2021-05-17 18:24:17 +05:30
pukkandan 7aee40c13c Fix bug in listing subtitles
Bug introduced by: 2412044c90
2021-05-17 18:24:16 +05:30
coletdjnzGitHubcraftingmod, colethedj, pukkandan
9297939ec3 [Youtube] Extract more formats for music.youtube URLs (#311)
Based on: https://github.com/ytdl-org/youtube-dl/pull/28778, https://github.com/ytdl-org/youtube-dl/pull/26160

Co-authored-by: craftingmod, colethedj, pukkandan
2021-05-15 20:08:47 +05:30
pukkandan 774d79cc4c [youtube] Add language names
Co-authored by: nixxo, tpikonen
Based on: https://github.com/ytdl-org/youtube-dl/pull/26112
Closes: #310
2021-05-15 19:27:53 +05:30
pukkandan 2412044c90 Add field name for subtitles
Co-authored by: pukkandan, tpikonen

Based on: #310, https://github.com/ytdl-org/youtube-dl/pull/26112
2021-05-15 19:27:52 +05:30
pukkandan 120916dac2 [youtube] multiple subtitles in same language
Fixes: https://github.com/ytdl-org/youtube-dl/issues/21164
Related: #310, https://github.com/ytdl-org/youtube-dl/pull/26112
2021-05-15 19:27:48 +05:30
pukkandan fe346461ff Fix --check-formats when there is network error 2021-05-15 19:26:01 +05:30
pukkandan d2a1fad968 [compat] Fix py2 2021-05-14 13:35:13 +05:30
pukkandan 0fb983f62d [youtube] Extract audio language 2021-05-14 13:15:48 +05:30
pukkandan 53c18592d3 Add option --print
Deprecates: `--get-description`, `--get-duration`, `--get-filename`, `--get-format`, `--get-id`, `--get-thumbnail`, `--get-title`, `--get-url`
Closes #295
2021-05-14 13:15:47 +05:30
pukkandan e632bce2e4 [options] Refactor callbacks 2021-05-14 13:15:47 +05:30
pukkandan 0760b0a7e2 Standardize write_debug 2021-05-14 13:15:29 +05:30
pukkandan d908aa636a [cleanup] Fix typos 2021-05-11 23:34:40 +05:30
pukkandan 3d89341b47 [common] bugfix for when compat_opts is not given 2021-05-11 23:29:26 +05:30
pukkandan d8ec40b39f [rmcdecouverte] Generalize _VALID_URL
Closes #291
2021-05-11 18:57:55 +05:30
pukkandan 4171221823 Add compat-option no-attach-infojson 2021-05-11 14:25:31 +05:30
pukkandan eaeca38fc4 [version] update :ci skip all 2021-05-11 13:42:58 +05:30
85 changed files with 1173 additions and 571 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 yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.22. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/yt-dlp/yt-dlp.
- Search the bugtracker for similar issues: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
@@ -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've verified that I'm running yt-dlp version **2021.04.22**
- [ ] I've verified that I'm running yt-dlp version **2021.05.11**
- [ ] 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 searched the bugtracker for similar issues including closed ones
@@ -44,7 +44,7 @@ Add the `-v` flag to your command line you run yt-dlp with (`yt-dlp -v <your com
[debug] User config: []
[debug] 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] yt-dlp version 2021.04.22
[debug] yt-dlp version 2021.05.11
[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] Proxy map: {}
@@ -21,7 +21,7 @@ assignees: ''
<!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.22. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
- Make sure that site you are requesting is not dedicated to copyright infringement, see https://github.com/yt-dlp/yt-dlp. yt-dlp does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
- Search the bugtracker for similar site support requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
@@ -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've verified that I'm running yt-dlp version **2021.04.22**
- [ ] I've verified that I'm running yt-dlp version **2021.05.11**
- [ ] 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 searched the bugtracker for similar site support requests including closed ones
@@ -21,13 +21,13 @@ assignees: ''
<!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.22. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Search the bugtracker for similar site feature requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
- 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've verified that I'm running yt-dlp version **2021.04.22**
- [ ] I've verified that I'm running yt-dlp version **2021.05.11**
- [ ] 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 yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.22. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
- Make sure that all URLs and arguments with special characters are properly quoted or escaped as explained in https://github.com/yt-dlp/yt-dlp.
- Search the bugtracker for similar issues: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
@@ -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've verified that I'm running yt-dlp version **2021.04.22**
- [ ] I've verified that I'm running yt-dlp version **2021.05.11**
- [ ] 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 searched the bugtracker for similar bug reports including closed ones
@@ -46,7 +46,7 @@ Add the `-v` flag to your command line you run yt-dlp with (`yt-dlp -v <your com
[debug] User config: []
[debug] 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] yt-dlp version 2021.04.22
[debug] yt-dlp version 2021.05.11
[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] 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 yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.22. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.05.11. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Search the bugtracker for similar feature requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
- Finally, put x into all relevant boxes like this [x] (Dont forget to delete the empty space)
-->
- [ ] I'm reporting a feature request
- [ ] I've verified that I'm running yt-dlp version **2021.04.22**
- [ ] I've verified that I'm running yt-dlp version **2021.05.11**
- [ ] I've searched the bugtracker for similar feature requests including closed ones
+4
View File
@@ -44,3 +44,7 @@ Lamieur
tsukumijima
Hadi0609
b5eff52
craftingmod
tpikonen
tripulse
king-millez
+39 -4
View File
@@ -19,16 +19,51 @@
-->
### 2021.05.11
### 2021.05.20
**Youtube improvements**:
* Support youtube music `MP`, `VL` and `browse` pages
* Extract more formats for youtube music by @craftingmod, @colethedj, @pukkandan
* Extract multiple subtitles in same language by @pukkandan and @tpikonen
* Redirect channels that doesn't have a `videos` tab to their `UU` playlists
* Support in-channel search
* Sort audio-only formats correctly
* Always extract `maxresdefault` thumbnail
* Extract audio language
* Add subtitle language names by @nixxo and @tpikonen
* Show alerts only from the final webpage
* Add `html5=1` param to `get_video_info` page requests by @colethedj
* Better message when login required
**Add option `--print`**: to print any field/template
Deprecates: `--get-description`, `--get-duration`, `--get-filename`, `--get-format`, `--get-id`, `--get-thumbnail`, `--get-title`, `--get-url`
* Field `additional_urls` to download additional videos from metadata using [`--parse-metadata`](https://github.com/yt-dlp/yt-dlp#modifying-metadata)
* Merge youtube-dl: Upto [commit/dfbbe29](https://github.com/ytdl-org/youtube-dl/commit/dfbbe2902fc67f0f93ee47a8077c148055c67a9b)
* Write thumbnail of playlist and add `pl_thumbnail` outtmpl key
* [embedthumbnail] Add `flac` support and refactor `mutagen` code by @pukkandan and @tripulse
* [audius:artist] Add extractor by @king-millez
* [parlview] Add extractor by @king-millez
* [tenplay] Fix extractor by @king-millez
* [rmcdecouverte] Generalize `_VALID_URL`
* Add compat-option `no-attach-infojson`
* Add field `name` for subtitles
* Ensure `post_extract` and `pre_process` only run once
* Fix `--check-formats` when there is network error
* Standardize `write_debug` and `get_param`
* [options] Alias `--write-comments`, `--no-write-comments`
* [options] Refactor callbacks
* [test:download] Only extract enough videos for `playlist_mincount`
* [extractor] bugfix for when `compat_opts` is not given
* [cleanup] code formatting, youtube tests and readme
### 2021.05.11
* **Deprecate support for python versions < 3.6**
* **Subtitle extraction from manifests** by [fstirlitz](https://github.com/fstirlitz). See [be6202f12b97858b9d716e608394b51065d0419f](https://github.com/yt-dlp/yt-dlp/commit/be6202f12b97858b9d716e608394b51065d0419f) for details
* **Subtitle extraction from manifests** by [fstirlitz](https://github.com/fstirlitz). See [be6202f](https://github.com/yt-dlp/yt-dlp/commit/be6202f12b97858b9d716e608394b51065d0419f) for details
* **Improve output template:**
* Allow slicing lists/strings using `field.start:end:step`
* A field can also be used as offset like `field1+num+field2`
* A default value can be given using `field|default`
* Prevent invalid fields from causing errors
* Merge youtube-dl: Upto [commit/a726009](https://github.com/ytdl-org/youtube-dl/commit/a7260099873acc6dc7d76cafad2f6b139087afd0)
* **Merge youtube-dl**: Upto [commit/a726009](https://github.com/ytdl-org/youtube-dl/commit/a7260099873acc6dc7d76cafad2f6b139087afd0)
* **Remove options** `-l`, `-t`, `-A` completely and disable `--auto-number`, `--title`, `--literal`, `--id`
* [Plugins] Prioritize plugins over standard extractors and prevent plugins from overwriting the standard extractor classes
* [downloader] Fix `quiet` and `to_stderr`
@@ -63,7 +98,7 @@
* [zee5] Fix py2 compatibility
* Fix `playlist_index` and add `playlist_autonumber`. See [#302](https://github.com/yt-dlp/yt-dlp/issues/302) for details
* Add experimental option `--check-formats` to test the URLs before format selection
* Option `--compat-options` to revert some of yt-dlp's changes
* Option `--compat-options` to revert [some of yt-dlp's changes](https://github.com/yt-dlp/yt-dlp#differences-in-default-behavior)
* Deprecates `--list-formats-as-table`, `--list-formats-old`
* Fix number of digits in `%(playlist_index)s`
* Fix case sensitivity of format selector
+57 -41
View File
@@ -66,15 +66,17 @@ The major new features from the latest release of [blackjack4494/yt-dlc](https:/
* **[Format Sorting](#sorting-formats)**: The default format sorting options have been changed so that higher resolution and better codecs will be now preferred instead of simply using larger bitrate. Furthermore, you can now specify the sort order using `-S`. This allows for much easier format selection that what is possible by simply using `--format` ([examples](#format-selection-examples))
* **Merged with youtube-dl [commit/a726009](https://github.com/ytdl-org/youtube-dl/commit/a7260099873acc6dc7d76cafad2f6b139087afd0)**: (v2021.04.26) You get all the latest features and patches of [youtube-dl](https://github.com/ytdl-org/youtube-dl) in addition to all the features of [youtube-dlc](https://github.com/blackjack4494/yt-dlc)
* **Merged with youtube-dl [commit/dfbbe29](https://github.com/ytdl-org/youtube-dl/commit/dfbbe2902fc67f0f93ee47a8077c148055c67a9b)**: (v2021.05.16) You get all the latest features and patches of [youtube-dl](https://github.com/ytdl-org/youtube-dl) in addition to all the features of [youtube-dlc](https://github.com/blackjack4494/yt-dlc)
* **Merged with animelover1984/youtube-dl**: You get most of the features and improvements from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl) including `--get-comments`, `BiliBiliSearch`, `BilibiliChannel`, Embedding thumbnail in mp4/ogg/opus, playlist infojson etc. Note that the NicoNico improvements are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details.
* **Merged with animelover1984/youtube-dl**: You get most of the features and improvements from [animelover1984/youtube-dl](https://github.com/animelover1984/youtube-dl) including `--write-comments`, `BiliBiliSearch`, `BilibiliChannel`, Embedding thumbnail in mp4/ogg/opus, playlist infojson etc. Note that the NicoNico improvements are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details.
* **Youtube improvements**:
* All Youtube Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) works and supports downloading multiple pages of content
* Youtube search (`ytsearch:`, `ytsearchdate:`) along with Search URLs work
* Youtube mixes supports downloading multiple pages of content
* All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`) supports downloading multiple pages of content
* Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works
* Mixes supports downloading multiple pages of content
* Redirect channel's home URL automatically to `/video` to preserve the old behaviour
* `255kbps` audio is extracted from youtube music if premium cookies are given
* Youtube music Albums, channels etc can be downloaded
* **Split video by chapters**: Videos can be split into multiple files based on chapters using `--split-chapters`
@@ -82,9 +84,9 @@ The major new features from the latest release of [blackjack4494/yt-dlc](https:/
* **Aria2c with HLS/DASH**: You can use `aria2c` as the external downloader for DASH(mpd) and HLS(m3u8) formats
* **New extractors**: AnimeLab, Philo MSO, Rcs, Gedi, bitwave.tv, mildom, audius, zee5, mtv.it, wimtv, pluto.tv, niconico users, discoveryplus.in, mediathek, NFHSNetwork, nebula, ukcolumn, whowatch, MxplayerShow
* **New extractors**: AnimeLab, Philo MSO, Rcs, Gedi, bitwave.tv, mildom, audius, zee5, mtv.it, wimtv, pluto.tv, niconico users, discoveryplus.in, mediathek, NFHSNetwork, nebula, ukcolumn, whowatch, MxplayerShow, parlview (au)
* **Fixed extractors**: archive.org, roosterteeth.com, skyit, instagram, itv, SouthparkDe, spreaker, Vlive, akamai, ina, rumble, tennistv, amcnetworks, la7 podcasts, linuxacadamy, nitter, twitcasting, viu, crackle, curiositystream, mediasite, rmcdecouverte, sonyliv, tubi
* **Fixed extractors**: archive.org, roosterteeth.com, skyit, instagram, itv, SouthparkDe, spreaker, Vlive, akamai, ina, rumble, tennistv, amcnetworks, la7 podcasts, linuxacadamy, nitter, twitcasting, viu, crackle, curiositystream, mediasite, rmcdecouverte, sonyliv, tubi, tenplay
* **Subtitle extraction from manifests**: Subtitles can be extracted from streaming media manifests. See [be6202f12b97858b9d716e608394b51065d0419f](https://github.com/yt-dlp/yt-dlp/commit/be6202f12b97858b9d716e608394b51065d0419f) for details
@@ -114,24 +116,25 @@ If you are coming from [youtube-dl](https://github.com/ytdl-org/youtube-dl), the
Some of yt-dlp's default options are different from that of youtube-dl and youtube-dlc.
1. The options `--id`, `--auto-number` (`-A`), `--title` (`-t`) and `--literal` (`-l`), no longer work. See [removed options](#Removed) for details
1. `avconv` is not supported as as an alternative to `ffmpeg`
1. The default [output template](#output-template) is `%(title)s [%(id)s].%(ext)s`. There is no real reason for this change. This was changed before yt-dlp was ever made public and now there are no plans to change it back to `%(title)s.%(id)s.%(ext)s`. Instead, you may use `--compat-options filename`
1. The default [format sorting](sorting-formats) is different from youtube-dl and prefers higher resolution and better codecs rather than higher bitrates. You can use the `--format-sort` option to change this to any order you prefer, or use `--compat-options format-sort` to use youtube-dl's sorting order
1. The default format selector is `bv*+ba/b`. This means that if a combined video + audio format that is better than the best video-only format is found, the former will be prefered. Use `-f bv+ba/b` or `--compat-options format-spec` to revert this
1. Unlike youtube-dlc, yt-dlp does not allow merging multiple audio/video streams into one file by default (since this conflicts with the use of `-f bv*+ba`). If needed, this feature must be enabled using `--audio-multistreams` and `--video-multistreams`. You can also use `--compat-options multistreams` to enable both
1. `--ignore-errors` is enabled by default. Use `--abort-on-error` or `--compat-options abort-on-error` to abort on errors instead
1. When writing metadata files such as thumbnails, description or infojson, the same information (if available) is also written for playlists. Use `--no-write-playlist-metafiles` or `--compat-options no-playlist-metafiles` to not write these files
1. `playlist_index` behaves differently when used with options like `--playlist-reverse` and `--playlist-items`. See [#302](https://github.com/yt-dlp/yt-dlp/issues/302) for details. You can use `--compat-options playlist-index` if you want to keep the earlier behavior
1. The output of `-F` is listed in a new format. Use `--compat-options list-formats` to revert this
1. Youtube live chat (if available) is considered as a subtitle. Use `--sub-langs all,-live_chat` to download all subtitles except live chat. You can also use `--compat-options no-live-chat` to prevent live chat from downloading
1. Youtube channel URLs are automatically redirected to `/video`. Either append a `/featured` to the URL or use `--compat-options no-youtube-channel-redirect` to download only the videos in the home page
1. Unavailable videos are also listed for youtube playlists. Use `--compat-options no-youtube-unavailable-videos` to remove this
* The options `--id`, `--auto-number` (`-A`), `--title` (`-t`) and `--literal` (`-l`), no longer work. See [removed options](#Removed) for details
* `avconv` is not supported as as an alternative to `ffmpeg`
* The default [output template](#output-template) is `%(title)s [%(id)s].%(ext)s`. There is no real reason for this change. This was changed before yt-dlp was ever made public and now there are no plans to change it back to `%(title)s.%(id)s.%(ext)s`. Instead, you may use `--compat-options filename`
* The default [format sorting](sorting-formats) is different from youtube-dl and prefers higher resolution and better codecs rather than higher bitrates. You can use the `--format-sort` option to change this to any order you prefer, or use `--compat-options format-sort` to use youtube-dl's sorting order
* The default format selector is `bv*+ba/b`. This means that if a combined video + audio format that is better than the best video-only format is found, the former will be prefered. Use `-f bv+ba/b` or `--compat-options format-spec` to revert this
* Unlike youtube-dlc, yt-dlp does not allow merging multiple audio/video streams into one file by default (since this conflicts with the use of `-f bv*+ba`). If needed, this feature must be enabled using `--audio-multistreams` and `--video-multistreams`. You can also use `--compat-options multistreams` to enable both
* `--ignore-errors` is enabled by default. Use `--abort-on-error` or `--compat-options abort-on-error` to abort on errors instead
* When writing metadata files such as thumbnails, description or infojson, the same information (if available) is also written for playlists. Use `--no-write-playlist-metafiles` or `--compat-options no-playlist-metafiles` to not write these files
* `--add-metadata` attaches the `infojson` to `mkv` files in addition to writing the metadata when used with `--write-infojson`. Use `--compat-options no-attach-info-json` to revert this
* `playlist_index` behaves differently when used with options like `--playlist-reverse` and `--playlist-items`. See [#302](https://github.com/yt-dlp/yt-dlp/issues/302) for details. You can use `--compat-options playlist-index` if you want to keep the earlier behavior
* The output of `-F` is listed in a new format. Use `--compat-options list-formats` to revert this
* Youtube live chat (if available) is considered as a subtitle. Use `--sub-langs all,-live_chat` to download all subtitles except live chat. You can also use `--compat-options no-live-chat` to prevent live chat from downloading
* Youtube channel URLs are automatically redirected to `/video`. Either append a `/featured` to the URL or use `--compat-options no-youtube-channel-redirect` to download only the videos in the home page
* Unavailable videos are also listed for youtube playlists. Use `--compat-options no-youtube-unavailable-videos` to remove this
For ease of use, a few more compat options are available:
1. `--compat-options all` = Use all compat options
1. `--compat-options youtube-dl` = `--compat-options all,-multistreams`
1. `--compat-options youtube-dlc` = `--compat-options all,-no-live-chat,-no-youtube-channel-redirect`
* `--compat-options all`: Use all compat options
* `--compat-options youtube-dl`: Same as `--compat-options all,-multistreams`
* `--compat-options youtube-dlc`: Same as `--compat-options all,-no-live-chat,-no-youtube-channel-redirect`
# INSTALLATION
@@ -167,7 +170,7 @@ Python versions 3.6+ (CPython and PyPy) are officially supported. Other versions
On windows, [Microsoft Visual C++ 2010 Redistributable Package (x86)](https://www.microsoft.com/en-us/download/details.aspx?id=26999) is also necessary to run yt-dlp. You probably already have this, but if the executable throws an error due to missing `MSVCR100.dll` you need to install it.
Although there are no other required dependencies, `ffmpeg` and `ffprobe` are highly recommended. Other optional dependencies are `sponskrub`, `AtomicParsley`, `mutagen`, `pycryptodome` and any of the supported external downloaders. Note that the windows releases are already built with the python interpreter, mutagen and pycryptodome included.
Although there are no other required dependencies, `ffmpeg` and `ffprobe` are highly recommended. Other optional dependencies are `sponskrub`, `AtomicParsley`, `mutagen`, `pycryptodome`, `phantomjs` and any of the supported external downloaders. Note that the windows releases are already built with the python interpreter, mutagen and pycryptodome included.
### UPDATE
You can use `yt-dlp -U` to update if you are using the provided release.
@@ -482,10 +485,13 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
could still contain some personal
information (default)
--no-clean-infojson Write all fields to the infojson
--get-comments Retrieve video comments to be placed in the
.info.json file. The comments are fetched
even without this option if the extraction
is known to be quick
--write-comments Retrieve video comments to be placed in the
infojson. The comments are fetched even
without this option if the extraction is
known to be quick (Alias: --get-comments)
--no-write-comments Do not retrieve video comments unless the
extraction is known to be quick
(Alias: --no-get-comments)
--load-info-json FILE JSON file containing the video information
(created with the "--write-info-json"
option)
@@ -533,14 +539,9 @@ Then simply run `make`. You can also run `make yt-dlp` instead to compile only t
formats are found (default)
--skip-download Do not download the video but write all
related files (Alias: --no-download)
-g, --get-url Simulate, quiet but print URL
-e, --get-title Simulate, quiet but print title
--get-id Simulate, quiet but print id
--get-thumbnail Simulate, quiet but print thumbnail URL
--get-description Simulate, quiet but print video description
--get-duration Simulate, quiet but print video length
--get-filename Simulate, quiet but print output filename
--get-format Simulate, quiet but print output format
-O, --print TEMPLATE Simulate, quiet but print the given fields.
Either a field name or similar formatting
as the output template can be used
-j, --dump-json Simulate, quiet but print JSON information.
See "OUTPUT TEMPLATE" for a description of
available keys
@@ -822,7 +823,7 @@ You can configure yt-dlp by placing any supported command line option to a confi
* `~/yt-dlp.conf.txt`
Note that `~` points to `C:\Users\<user name>` on windows. Also, `%XDG_CONFIG_HOME%` defaults to `~/.config` if undefined
1. **System Configuration**: `/etc/yt-dlp.conf` or `/etc/yt-dlp.conf`
1. **System Configuration**: `/etc/yt-dlp.conf`
For example, with the following configuration file yt-dlp will always extract the audio, not copy the mtime, use a proxy and save all videos under `YouTube` directory in your home directory:
```
@@ -889,7 +890,7 @@ To summarize, the general syntax for a field is:
%(name[.keys][addition][>strf][|default])[flags][width][.precision][length]type
```
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation`, `infojson`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video.
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation`, `infojson`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video.
The available fields are:
@@ -911,7 +912,7 @@ The available fields are:
- `channel_id` (string): Id of the channel
- `location` (string): Physical location where the video was filmed
- `duration` (numeric): Length of the video in seconds
- `duration_string` (string): Length of the video (HH-mm-ss)
- `duration_string` (string): Length of the video (HH:mm:ss)
- `view_count` (numeric): How many users have watched the video on the platform
- `like_count` (numeric): Number of positive ratings of the video
- `dislike_count` (numeric): Number of negative ratings of the video
@@ -989,6 +990,11 @@ Available for `chapter:` prefix when using `--split-chapters` for videos with in
- `section_start` (numeric): Start time of the chapter in seconds
- `section_end` (numeric): End time of the chapter in seconds
Available only when used in `--print`:
- `urls` (string): The URLs of all requested formats, one in each line
- `filename` (string): Name of the video file. Note that the actual filename may be different due to post-processing. Use `--exec echo` to get the name after all postprocessing is complete
Each aforementioned sequence when referenced in an output template will be replaced by the actual value corresponding to the sequence name. Note that some of the sequences are not guaranteed to be present since they depend on the metadata obtained by a particular extractor. Such sequences will be replaced with placeholder value provided with `--output-na-placeholder` (`NA` by default).
For example for `-o %(title)s-%(id)s.%(ext)s` and an mp4 video with title `yt-dlp test video` and id `BaW_jenozKcj`, this will result in a `yt-dlp test video-BaW_jenozKcj.mp4` file created in the current directory.
@@ -1278,7 +1284,9 @@ The metadata obtained the the extractors can be modified by using `--parse-metad
Note that any field created by this can be used in the [output template](#output-template) and will also affect the media file's metadata added when using `--add-metadata`.
You can also use this to change only the metadata that is embedded in the media file. To do this, set the value of the corresponding field with a `meta_` prefix. For example, any value you set to `meta_description` field will be added to the `description` field in the file. You can use this to set a different "description" and "synopsis", for example.
This option also has a few special uses:
* You can use this to change the metadata that is embedded in the media file. To do this, set the value of the corresponding field with a `meta_` prefix. For example, any value you set to `meta_description` field will be added to the `description` field in the file. You can use this to set a different "description" and "synopsis", for example
* You can download an additional URL based on the metadata of the currently downloaded video. To do this, set the field `additional_urls` to the URL that you want to download. Eg: `--parse-metadata "description:(?P<additional_urls>https?://www\.vimeo\.com/\d+)` will download the first vimeo video found in the description
## Modifying metadata examples
@@ -1312,6 +1320,14 @@ These are all the deprecated options and the current alternative to achieve the
#### Not recommended
While these options still work, their use is not recommended since there are other alternatives to achieve the same
--get-description --print description
--get-duration --print duration_string
--get-filename --print filename
--get-format --print format
--get-id --print id
--get-thumbnail --print thumbnail
-e, --get-title --print title
-g, --get-url --print urls
--all-formats -f all
--all-subs --sub-langs all --write-subs
--autonumber-size NUMBER Use string formatting. Eg: %(autonumber)03d
@@ -1323,6 +1339,7 @@ While these options still work, their use is not recommended since there are oth
--list-formats-as-table --compat-options -list-formats [Default] (Alias: --no-list-formats-old)
--sponskrub-args ARGS --ppa "sponskrub:ARGS"
--test Used by developers for testing extractors. Not intended for the end user
--youtube-print-sig-code Used for testing youtube signatures
#### Old aliases
@@ -1353,7 +1370,6 @@ These options may no longer work as intended
--no-call-home Default
--include-ads No longer supported
--no-include-ads Default
--youtube-print-sig-code No longer supported
#### Removed
These options were deprecated since 2014 and have now been entirely removed
+3
View File
@@ -82,6 +82,7 @@
- **audiomack**
- **audiomack:album**
- **Audius**: Audius.co
- **audius:artist**: Audius.co profile/artist pages
- **audius:playlist**: Audius.co playlists
- **audius:track**: Audius track ID or API link. Prepend with "audius:"
- **AWAAN**
@@ -725,6 +726,7 @@
- **pandora.tv**: 판도라TV
- **ParamountNetwork**
- **parliamentlive.tv**: UK parliament videos
- **Parlview**
- **Patreon**
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
- **PearVideo**
@@ -748,6 +750,7 @@
- **play.fm**
- **player.sky.it**
- **PlayPlusTV**
- **PlayStuff**
- **PlaysTV**
- **Playtvak**: Playtvak.cz, iDNES.cz and Lidovky.cz
- **Playvid**
+33 -34
View File
@@ -1,41 +1,40 @@
{
"consoletitle": false,
"continuedl": true,
"forcedescription": false,
"forcefilename": false,
"forceformat": false,
"forcethumbnail": false,
"forcetitle": false,
"forceurl": false,
"consoletitle": false,
"continuedl": true,
"forcedescription": false,
"forcefilename": false,
"forceformat": false,
"forcethumbnail": false,
"forcetitle": false,
"forceurl": false,
"force_write_download_archive": false,
"format": "best",
"ignoreerrors": false,
"listformats": null,
"logtostderr": false,
"matchtitle": null,
"max_downloads": null,
"overwrites": null,
"nopart": false,
"noprogress": false,
"outtmpl": "%(id)s.%(ext)s",
"password": null,
"playlistend": -1,
"playliststart": 1,
"prefer_free_formats": false,
"quiet": false,
"ratelimit": null,
"rejecttitle": null,
"retries": 10,
"simulate": false,
"subtitleslang": null,
"ignoreerrors": false,
"listformats": null,
"logtostderr": false,
"matchtitle": null,
"max_downloads": null,
"overwrites": null,
"nopart": false,
"noprogress": false,
"outtmpl": "%(id)s.%(ext)s",
"password": null,
"playliststart": 1,
"prefer_free_formats": false,
"quiet": false,
"ratelimit": null,
"rejecttitle": null,
"retries": 10,
"simulate": false,
"subtitleslang": null,
"subtitlesformat": "best",
"test": true,
"updatetime": true,
"usenetrc": false,
"username": null,
"verbose": true,
"writedescription": false,
"writeinfojson": true,
"test": true,
"updatetime": true,
"usenetrc": false,
"username": null,
"verbose": true,
"writedescription": false,
"writeinfojson": true,
"writeannotations": false,
"writelink": false,
"writeurllink": false,
+1
View File
@@ -29,6 +29,7 @@ class YDL(FakeYDL):
self.msgs = []
def process_info(self, info_dict):
info_dict.pop('__original_infodict', None)
self.downloaded_info_dicts.append(info_dict)
def to_screen(self, msg):
+1
View File
@@ -121,6 +121,7 @@ def generator(test_case, tname):
params['outtmpl'] = tname + '_' + params['outtmpl']
if is_playlist and 'playlist' not in test_case:
params.setdefault('extract_flat', 'in_playlist')
params.setdefault('playlistend', test_case.get('playlist_mincount'))
params.setdefault('skip_download', True)
ydl = YoutubeDL(params, auto_init=False)
+118 -67
View File
@@ -48,7 +48,6 @@ from .utils import (
date_from_str,
DateRange,
DEFAULT_OUTTMPL,
OUTTMPL_TYPES,
determine_ext,
determine_protocol,
DOT_DESKTOP_LINK_TEMPLATE,
@@ -57,8 +56,8 @@ from .utils import (
DownloadError,
encode_compat_str,
encodeFilename,
error_to_compat_str,
EntryNotInPlaylist,
error_to_compat_str,
ExistingVideoReached,
expand_path,
ExtractorError,
@@ -77,6 +76,7 @@ from .utils import (
MaxDownloadsReached,
network_exceptions,
orderedSet,
OUTTMPL_TYPES,
PagedList,
parse_filesize,
PerRequestProxyHandler,
@@ -84,11 +84,12 @@ from .utils import (
PostProcessingError,
preferredencoding,
prepend_extension,
process_communicate_or_kill,
random_uuidv4,
register_socks_protocols,
RejectedVideoReached,
render_table,
replace_extension,
RejectedVideoReached,
SameFileError,
sanitize_filename,
sanitize_path,
@@ -109,7 +110,6 @@ from .utils import (
YoutubeDLCookieProcessor,
YoutubeDLHandler,
YoutubeDLRedirectHandler,
process_communicate_or_kill,
)
from .cache import Cache
from .extractor import (
@@ -177,13 +177,14 @@ class YoutubeDL(object):
verbose: Print additional info to stdout.
quiet: Do not print messages to stdout.
no_warnings: Do not print out anything for warnings.
forceurl: Force printing final URL.
forcetitle: Force printing title.
forceid: Force printing ID.
forcethumbnail: Force printing thumbnail URL.
forcedescription: Force printing description.
forcefilename: Force printing final filename.
forceduration: Force printing duration.
forceprint: A list of templates to force print
forceurl: Force printing final URL. (Deprecated)
forcetitle: Force printing title. (Deprecated)
forceid: Force printing ID. (Deprecated)
forcethumbnail: Force printing thumbnail URL. (Deprecated)
forcedescription: Force printing description. (Deprecated)
forcefilename: Force printing final filename. (Deprecated)
forceduration: Force printing duration. (Deprecated)
forcejson: Force printing info_dict as JSON.
dump_single_json: Force printing the info_dict of the whole playlist
(or video) as a single JSON line.
@@ -386,7 +387,7 @@ class YoutubeDL(object):
if True, otherwise use ffmpeg/avconv if False, otherwise
use downloader suggested by extractor if None.
compat_opts: Compatibility options. See "Differences in default behavior".
Note that only format-sort, format-spec, no-live-chat,
Note that only format-sort, format-spec, no-live-chat, no-attach-info-json
playlist-index, list-formats, no-youtube-channel-redirect
and no-youtube-unavailable-videos works when used via the API
@@ -542,8 +543,7 @@ class YoutubeDL(object):
def preload_download_archive(fn):
if fn is None:
return False
if self.params.get('verbose'):
self._write_string('[debug] Loading archive file %r\n' % fn)
self.write_debug('Loading archive file %r\n' % fn)
try:
with locked_file(fn, 'r', encoding='utf-8') as archive_file:
for line in archive_file:
@@ -649,17 +649,11 @@ class YoutubeDL(object):
for _ in range(line_count))
return res[:-len('\n')]
def to_screen(self, message, skip_eol=False):
"""Print message to stdout if not in quiet mode."""
return self.to_stdout(
message, skip_eol,
quiet=self.params.get('quiet', False))
def _write_string(self, s, out=None):
write_string(s, out=out, encoding=self.params.get('encoding'))
def to_stdout(self, message, skip_eol=False, quiet=False):
"""Print message to stdout if not in quiet mode."""
"""Print message to stdout"""
if self.params.get('logger'):
self.params['logger'].debug(message)
elif not quiet:
@@ -670,7 +664,7 @@ class YoutubeDL(object):
self._write_string(output, self._screen_file)
def to_stderr(self, message):
"""Print message to stderr."""
"""Print message to stderr"""
assert isinstance(message, compat_str)
if self.params.get('logger'):
self.params['logger'].error(message)
@@ -748,6 +742,11 @@ class YoutubeDL(object):
raise DownloadError(message, exc_info)
self._download_retcode = 1
def to_screen(self, message, skip_eol=False):
"""Print message to stdout if not in quiet mode"""
self.to_stdout(
message, skip_eol, quiet=self.params.get('quiet', False))
def report_warning(self, message):
'''
Print the message to stderr, it will be prefixed with 'WARNING:'
@@ -777,6 +776,16 @@ class YoutubeDL(object):
error_message = '%s %s' % (_msg_header, message)
self.trouble(error_message, tb)
def write_debug(self, message):
'''Log debug message or Print message to stderr'''
if not self.params.get('verbose', False):
return
message = '[debug] %s' % message
if self.params.get('logger'):
self.params['logger'].debug(message)
else:
self._write_string('%s\n' % message)
def report_file_already_downloaded(self, file_name):
"""Report file has already been fully downloaded."""
try:
@@ -812,7 +821,7 @@ class YoutubeDL(object):
# duration_string
template_dict['duration_string'] = ( # %(duration>%H-%M-%S)s is wrong if duration > 24hrs
formatSeconds(info_dict['duration'], '-')
formatSeconds(info_dict['duration'], '-' if sanitize else ':')
if info_dict.get('duration', None) is not None
else None)
@@ -1178,14 +1187,29 @@ class YoutubeDL(object):
if result_type == 'video':
self.add_extra_info(ie_result, extra_info)
return self.process_video_result(ie_result, download=download)
ie_result = self.process_video_result(ie_result, download=download)
additional_urls = (ie_result or {}).get('additional_urls')
if additional_urls:
# TODO: Improve MetadataFromFieldPP to allow setting a list
if isinstance(additional_urls, compat_str):
additional_urls = [additional_urls]
self.to_screen(
'[info] %s: %d additional URL(s) requested' % (ie_result['id'], len(additional_urls)))
self.write_debug('Additional URLs: "%s"' % '", "'.join(additional_urls))
ie_result['additional_entries'] = [
self.extract_info(
url, download, extra_info,
force_generic_extractor=self.params.get('force_generic_extractor'))
for url in additional_urls
]
return ie_result
elif result_type == 'url':
# We have to add extra_info to the results because it may be
# contained in a playlist
return self.extract_info(ie_result['url'],
download,
ie_key=ie_result.get('ie_key'),
extra_info=extra_info)
return self.extract_info(
ie_result['url'], download,
ie_key=ie_result.get('ie_key'),
extra_info=extra_info)
elif result_type == 'url_transparent':
# Use the information from the embedding page
info = self.extract_info(
@@ -1370,6 +1394,9 @@ class YoutubeDL(object):
except (OSError, IOError):
self.report_error('Cannot write playlist metadata to JSON file ' + infofn)
# TODO: This should be passed to ThumbnailsConvertor if necessary
self._write_thumbnails(ie_copy, self.prepare_filename(ie_copy, 'pl_thumbnail'))
if self.params.get('writedescription', False):
descfn = self.prepare_filename(ie_copy, 'pl_description')
if not self._ensure_dir_exists(encodeFilename(descfn)):
@@ -1717,9 +1744,13 @@ class YoutubeDL(object):
expand_path(paths.get('home', '').strip()),
expand_path(paths.get('temp', '').strip()),
'ytdl.%s.f%s.check-format' % (random_uuidv4(), f['format_id']))
dl, _ = self.dl(temp_file, f, test=True)
if os.path.exists(temp_file):
os.remove(temp_file)
try:
dl, _ = self.dl(temp_file, f, test=True)
except (ExtractorError, IOError, OSError, ValueError) + network_exceptions:
dl = False
finally:
if os.path.exists(temp_file):
os.remove(temp_file)
if dl:
yield f
else:
@@ -1928,7 +1959,8 @@ class YoutubeDL(object):
t.get('preference') if t.get('preference') is not None else -1,
t.get('width') if t.get('width') is not None else -1,
t.get('height') if t.get('height') is not None else -1,
t.get('id') if t.get('id') is not None else '', t.get('url')))
t.get('id') if t.get('id') is not None else '',
t.get('url')))
for i, t in enumerate(thumbnails):
t['url'] = sanitize_url(t['url'])
if t.get('width') and t.get('height'):
@@ -2072,6 +2104,9 @@ class YoutubeDL(object):
# element in the 'formats' field in info_dict is info_dict itself,
# which can't be exported to json
info_dict['formats'] = formats
info_dict, _ = self.pre_process(info_dict)
if self.params.get('listformats'):
if not info_dict.get('formats'):
raise ExtractorError('No video formats found', expected=True)
@@ -2081,8 +2116,7 @@ class YoutubeDL(object):
req_format = self.params.get('format')
if req_format is None:
req_format = self._default_format_spec(info_dict, download=download)
if self.params.get('verbose'):
self.to_screen('[debug] Default format spec: %s' % req_format)
self.write_debug('Default format spec: %s' % req_format)
format_selector = self.build_format_selector(req_format)
@@ -2120,14 +2154,13 @@ class YoutubeDL(object):
self.report_warning('Requested format is not available')
elif download:
self.to_screen(
'[info] %s: Downloading format(s) %s'
% (info_dict['id'], ", ".join([f['format_id'] for f in formats_to_download])))
if len(formats_to_download) > 1:
self.to_screen(
'[info] %s: Downloading video in %s formats'
% (info_dict['id'], len(formats_to_download)))
'[info] %s: Downloading %d format(s): %s' % (
info_dict['id'], len(formats_to_download),
", ".join([f['format_id'] for f in formats_to_download])))
for fmt in formats_to_download:
new_info = dict(info_dict)
# Save a reference to the original info_dict so that it can be modified in process_info if needed
new_info['__original_infodict'] = info_dict
new_info.update(fmt)
self.process_info(new_info)
# We update the info dict with the best quality format (backwards compatibility)
@@ -2172,6 +2205,7 @@ class YoutubeDL(object):
requested_langs = ['en']
else:
requested_langs = [list(all_sub_langs)[0]]
self.write_debug('Downloading subtitles: %s' % ', '.join(requested_langs))
formats_query = self.params.get('subtitlesformat', 'best')
formats_preference = formats_query.split('/') if formats_query else []
@@ -2198,32 +2232,43 @@ class YoutubeDL(object):
return subs
def __forced_printings(self, info_dict, filename, incomplete):
def print_mandatory(field):
def print_mandatory(field, actual_field=None):
if actual_field is None:
actual_field = field
if (self.params.get('force%s' % field, False)
and (not incomplete or info_dict.get(field) is not None)):
self.to_stdout(info_dict[field])
and (not incomplete or info_dict.get(actual_field) is not None)):
self.to_stdout(info_dict[actual_field])
def print_optional(field):
if (self.params.get('force%s' % field, False)
and info_dict.get(field) is not None):
self.to_stdout(info_dict[field])
info_dict = info_dict.copy()
if filename is not None:
info_dict['filename'] = filename
if info_dict.get('requested_formats') is not None:
# For RTMP URLs, also include the playpath
info_dict['urls'] = '\n'.join(f['url'] + f.get('play_path', '') for f in info_dict['requested_formats'])
elif 'url' in info_dict:
info_dict['urls'] = info_dict['url'] + info_dict.get('play_path', '')
for tmpl in self.params.get('forceprint', []):
if re.match(r'\w+$', tmpl):
tmpl = '%({})s'.format(tmpl)
tmpl, info_copy = self.prepare_outtmpl(tmpl, info_dict)
self.to_stdout(tmpl % info_copy)
print_mandatory('title')
print_mandatory('id')
if self.params.get('forceurl', False) and not incomplete:
if info_dict.get('requested_formats') is not None:
for f in info_dict['requested_formats']:
self.to_stdout(f['url'] + f.get('play_path', ''))
else:
# For RTMP URLs, also include the playpath
self.to_stdout(info_dict['url'] + info_dict.get('play_path', ''))
print_mandatory('url', 'urls')
print_optional('thumbnail')
print_optional('description')
if self.params.get('forcefilename', False) and filename is not None:
self.to_stdout(filename)
print_optional('filename')
if self.params.get('forceduration', False) and info_dict.get('duration') is not None:
self.to_stdout(formatSeconds(info_dict['duration']))
print_mandatory('format')
if self.params.get('forcejson', False):
self.post_extract(info_dict)
self.to_stdout(json.dumps(info_dict, default=repr))
@@ -2249,8 +2294,7 @@ class YoutubeDL(object):
if not test:
for ph in self._progress_hooks:
fd.add_progress_hook(ph)
if self.params.get('verbose'):
self.to_screen('[debug] Invoking downloader on %r' % info.get('url'))
self.write_debug('Invoking downloader on %r' % info.get('url'))
new_info = dict(info)
if new_info.get('http_headers') is None:
new_info['http_headers'] = self._calc_headers(new_info)
@@ -2280,8 +2324,6 @@ class YoutubeDL(object):
self.post_extract(info_dict)
self._num_downloads += 1
info_dict, _ = self.pre_process(info_dict)
# info_dict['_filename'] needs to be set for backward compatibility
info_dict['_filename'] = full_filename = self.prepare_filename(info_dict, warn=True)
temp_filename = self.prepare_filename(info_dict, 'temp')
@@ -2376,7 +2418,7 @@ class YoutubeDL(object):
self.dl(sub_filename, sub_info.copy(), subtitle=True)
sub_info['filepath'] = sub_filename
files_to_move[sub_filename] = sub_filename_final
except tuple([ExtractorError, IOError, OSError, ValueError] + network_exceptions) as err:
except (ExtractorError, IOError, OSError, ValueError) + network_exceptions as err:
self.report_warning('Unable to download subtitle for "%s": %s' %
(sub_lang, error_to_compat_str(err)))
continue
@@ -2719,6 +2761,7 @@ class YoutubeDL(object):
@staticmethod
def filter_requested_info(info_dict, actually_filter=True):
info_dict.pop('__original_infodict', None) # Always remove this
if not actually_filter:
info_dict['epoch'] = int(time.time())
return info_dict
@@ -2763,13 +2806,14 @@ class YoutubeDL(object):
actual_post_extract(video_dict or {})
return
if '__post_extractor' not in info_dict:
return
post_extractor = info_dict['__post_extractor']
if post_extractor:
info_dict.update(post_extractor().items())
del info_dict['__post_extractor']
return
post_extractor = info_dict.get('__post_extractor') or (lambda: {})
extra = post_extractor().items()
info_dict.update(extra)
info_dict.pop('__post_extractor', None)
original_infodict = info_dict.get('__original_infodict') or {}
original_infodict.update(extra)
original_infodict.pop('__post_extractor', None)
actual_post_extract(info_dict or {})
@@ -2982,10 +3026,17 @@ class YoutubeDL(object):
return
self.to_screen(
'Available %s for %s:' % (name, video_id))
def _row(lang, formats):
exts, names = zip(*((f['ext'], f.get('name', 'unknown')) for f in reversed(formats)))
if len(set(names)) == 1:
names = [] if names[0] == 'unknown' else names[:1]
return [lang, ', '.join(names), ', '.join(exts)]
self.to_screen(render_table(
['Language', 'formats'],
[[lang, ', '.join(f['ext'] for f in reversed(formats))]
for lang, formats in subtitles.items()]))
['Language', 'Name', 'Formats'],
[_row(lang, formats) for lang, formats in subtitles.items()],
hideEmpty=True))
def urlopen(self, req):
""" Start an HTTP download """
+3 -2
View File
@@ -260,7 +260,7 @@ def _real_main(argv=None):
all_compat_opts = [
'filename', 'format-sort', 'abort-on-error', 'format-spec', 'multistreams',
'no-playlist-metafiles', 'no-live-chat', 'playlist-index', 'list-formats',
'no-youtube-channel-redirect', 'no-youtube-unavailable-videos',
'no-youtube-channel-redirect', 'no-youtube-unavailable-videos', 'no-attach-info-json',
]
compat_opts = parse_compat_opts()
@@ -321,7 +321,7 @@ def _real_main(argv=None):
if re.match(MetadataFromFieldPP.regex, f) is None:
parser.error('invalid format string "%s" specified for --parse-metadata' % f)
any_getting = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
any_getting = opts.forceprint or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
any_printing = opts.print_json
download_archive_fn = expand_path(opts.download_archive) if opts.download_archive is not None else opts.download_archive
@@ -508,6 +508,7 @@ def _real_main(argv=None):
'forceduration': opts.getduration,
'forcefilename': opts.getfilename,
'forceformat': opts.getformat,
'forceprint': opts.forceprint,
'forcejson': opts.dumpjson or opts.print_json,
'dump_single_json': opts.dump_single_json,
'force_write_download_archive': opts.force_write_download_archive,
+4 -2
View File
@@ -164,6 +164,9 @@ class FileDownloader(object):
def report_error(self, *args, **kargs):
self.ydl.report_error(*args, **kargs)
def write_debug(self, *args, **kargs):
self.ydl.write_debug(*args, **kargs)
def slow_down(self, start_time, now, byte_counter):
"""Sleep if the download speed is over the rate limit."""
rate_limit = self.params.get('ratelimit')
@@ -402,5 +405,4 @@ class FileDownloader(object):
if exe is None:
exe = os.path.basename(str_args[0])
self.to_screen('[debug] %s command line: %s' % (
exe, shell_quote(str_args)))
self.write_debug('%s command line: %s' % (exe, shell_quote(str_args)))
+1 -1
View File
@@ -1414,7 +1414,7 @@ class AdobePassIE(InfoExtractor):
authn_token = None
if not authn_token:
# TODO add support for other TV Providers
mso_id = self._downloader.params.get('ap_mso')
mso_id = self.get_param('ap_mso')
if not mso_id:
raise_mvpd_required()
username, password = self._get_login_info('ap_username', 'ap_password', mso_id)
+1 -1
View File
@@ -323,7 +323,7 @@ class AfreecaTVIE(InfoExtractor):
'url': file_url,
'format_id': 'http',
}]
if not formats and not self._downloader.params.get('ignore_no_formats'):
if not formats and not self.get_param('ignore_no_formats'):
continue
self._sort_formats(formats)
file_info = common_entry.copy()
+28
View File
@@ -245,3 +245,31 @@ class AudiusPlaylistIE(AudiusBaseIE):
return self.playlist_result(entries, playlist_id,
playlist_data.get('playlist_name', title),
playlist_data.get('description'))
class AudiusProfileIE(AudiusPlaylistIE):
IE_NAME = 'audius:artist'
IE_DESC = 'Audius.co profile/artist pages'
_VALID_URL = r'https?://(?:www)?audius\.co/(?P<id>[^\/]+)/?(?:[?#]|$)'
_TEST = {
'url': 'https://audius.co/pzl/',
'info_dict': {
'id': 'ezRo7',
'description': 'TAMALE\n\nContact: officialpzl@gmail.com',
'title': 'pzl',
},
'playlist_count': 24,
}
def _real_extract(self, url):
self._select_api_base()
profile_id = self._match_id(url)
try:
_profile_data = self._api_request('/full/users/handle/' + profile_id, profile_id)
except ExtractorError as e:
raise ExtractorError('Could not download profile info; ' + str(e))
profile_audius_id = _profile_data[0]['id']
profile_bio = _profile_data[0].get('bio')
api_call = self._api_request('/full/users/handle/%s/tracks' % profile_id, profile_id)
return self.playlist_result(self._build_playlist(api_call), profile_audius_id, profile_id, profile_bio)
+1 -1
View File
@@ -1271,7 +1271,7 @@ class BBCIE(BBCCoUkIE):
entries = []
for num, media_meta in enumerate(medias, start=1):
formats, subtitles = self._extract_from_media_meta(media_meta, playlist_id)
if not formats and not self._downloader.params.get('ignore_no_formats'):
if not formats and not self.get_param('ignore_no_formats'):
continue
self._sort_formats(formats)
+2 -2
View File
@@ -153,7 +153,7 @@ class BiliBiliIE(InfoExtractor):
# Bilibili anthologies are similar to playlists but all videos share the same video ID as the anthology itself.
# If the video has no page argument, check to see if it's an anthology
if page_id is None:
if not self._downloader.params.get('noplaylist'):
if not self.get_param('noplaylist'):
r = self._extract_anthology_entries(bv_id, video_id, webpage)
if r is not None:
self.to_screen('Downloading anthology %s - add --no-playlist to just download video' % video_id)
@@ -299,7 +299,7 @@ class BiliBiliIE(InfoExtractor):
'tags': tags,
'raw_tags': raw_tags,
}
if self._downloader.params.get('getcomments', False):
if self.get_param('getcomments', False):
def get_comments():
comments = self._get_all_comment_pages(video_id)
return {
+2 -2
View File
@@ -478,7 +478,7 @@ class BrightcoveNewIE(AdobePassIE):
container = source.get('container')
ext = mimetype2ext(source.get('type'))
src = source.get('src')
skip_unplayable = not self._downloader.params.get('allow_unplayable_formats')
skip_unplayable = not self.get_param('allow_unplayable_formats')
# https://support.brightcove.com/playback-api-video-fields-reference#key_systems_object
if skip_unplayable and (container == 'WVM' or source.get('key_systems')):
num_drm_sources += 1
@@ -547,7 +547,7 @@ class BrightcoveNewIE(AdobePassIE):
error = errors[0]
self.raise_no_formats(
error.get('message') or error.get('error_subcode') or error['error_code'], expected=True)
elif (not self._downloader.params.get('allow_unplayable_formats')
elif (not self.get_param('allow_unplayable_formats')
and sources and num_drm_sources == len(sources)):
raise ExtractorError('This video is DRM protected.', expected=True)
+1 -1
View File
@@ -147,7 +147,7 @@ class CeskaTelevizeIE(InfoExtractor):
is_live = item.get('type') == 'LIVE'
formats = []
for format_id, stream_url in item.get('streamUrls', {}).items():
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and 'drmOnly=true' in stream_url):
continue
if 'playerType=flash' in stream_url:
+72 -62
View File
@@ -250,6 +250,8 @@ class InfoExtractor(object):
entry and one of:
* "data": The subtitles file contents
* "url": A URL pointing to the subtitles file
It can optionally also have:
* "name": Name or description of the subtitles
"ext" will be calculated from URL if missing
automatic_captions: Like 'subtitles'; contains automatically generated
captions instead of normal subtitles
@@ -420,6 +422,14 @@ class InfoExtractor(object):
_GEO_IP_BLOCKS = None
_WORKING = True
_LOGIN_HINTS = {
'any': 'Use --cookies, --username and --password or --netrc to provide account credentials',
'cookies': (
'Use --cookies for the authentication. '
'See https://github.com/ytdl-org/youtube-dl#how-do-i-pass-cookies-to-youtube-dl for how to pass cookies'),
'password': 'Use --username and --password or --netrc to provide account credentials',
}
def __init__(self, downloader=None):
"""Constructor. Receives an optional downloader."""
self._ready = False
@@ -489,7 +499,7 @@ class InfoExtractor(object):
if not self._x_forwarded_for_ip:
# Geo bypass mechanism is explicitly disabled by user
if not self._downloader.params.get('geo_bypass', True):
if not self.get_param('geo_bypass', True):
return
if not geo_bypass_context:
@@ -511,7 +521,7 @@ class InfoExtractor(object):
# Explicit IP block specified by user, use it right away
# regardless of whether extractor is geo bypassable or not
ip_block = self._downloader.params.get('geo_bypass_ip_block', None)
ip_block = self.get_param('geo_bypass_ip_block', None)
# Otherwise use random IP block from geo bypass context but only
# if extractor is known as geo bypassable
@@ -522,17 +532,15 @@ class InfoExtractor(object):
if ip_block:
self._x_forwarded_for_ip = GeoUtils.random_ipv4(ip_block)
if self._downloader.params.get('verbose', False):
self._downloader.to_screen(
'[debug] Using fake IP %s as X-Forwarded-For.'
% self._x_forwarded_for_ip)
self._downloader.write_debug(
'[debug] Using fake IP %s as X-Forwarded-For' % self._x_forwarded_for_ip)
return
# Path 2: bypassing based on country code
# Explicit country code specified by user, use it right away
# regardless of whether extractor is geo bypassable or not
country = self._downloader.params.get('geo_bypass_country', None)
country = self.get_param('geo_bypass_country', None)
# Otherwise use random country code from geo bypass context but
# only if extractor is known as geo bypassable
@@ -543,10 +551,8 @@ class InfoExtractor(object):
if country:
self._x_forwarded_for_ip = GeoUtils.random_ipv4(country)
if self._downloader.params.get('verbose', False):
self._downloader.to_screen(
'[debug] Using fake IP %s (%s) as X-Forwarded-For.'
% (self._x_forwarded_for_ip, country.upper()))
self._downloader.write_debug(
'Using fake IP %s (%s) as X-Forwarded-For' % (self._x_forwarded_for_ip, country.upper()))
def extract(self, url):
"""Extracts URL information and returns it in list of dicts."""
@@ -554,12 +560,15 @@ class InfoExtractor(object):
for _ in range(2):
try:
self.initialize()
self.write_debug('Extracting URL: %s' % url)
ie_result = self._real_extract(url)
if ie_result is None:
return None
if self._x_forwarded_for_ip:
ie_result['__x_forwarded_for_ip'] = self._x_forwarded_for_ip
subtitles = ie_result.get('subtitles')
if (subtitles and 'live_chat' in subtitles
and 'no-live-chat' in self._downloader.params.get('compat_opts')):
and 'no-live-chat' in self.get_param('compat_opts', [])):
del subtitles['live_chat']
return ie_result
except GeoRestrictedError as e:
@@ -574,9 +583,9 @@ class InfoExtractor(object):
raise ExtractorError('An extractor error has occurred.', cause=e)
def __maybe_fake_ip_and_retry(self, countries):
if (not self._downloader.params.get('geo_bypass_country', None)
if (not self.get_param('geo_bypass_country', None)
and self._GEO_BYPASS
and self._downloader.params.get('geo_bypass', True)
and self.get_param('geo_bypass', True)
and not self._x_forwarded_for_ip
and countries):
country_code = random.choice(countries)
@@ -630,7 +639,7 @@ class InfoExtractor(object):
See _download_webpage docstring for arguments specification.
"""
if not self._downloader._first_webpage_request:
sleep_interval = float_or_none(self._downloader.params.get('sleep_interval_requests')) or 0
sleep_interval = float_or_none(self.get_param('sleep_interval_requests')) or 0
if sleep_interval > 0:
self.to_screen('Sleeping %s seconds ...' % sleep_interval)
time.sleep(sleep_interval)
@@ -755,11 +764,11 @@ class InfoExtractor(object):
webpage_bytes = prefix + webpage_bytes
if not encoding:
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
if self._downloader.params.get('dump_intermediate_pages', False):
if self.get_param('dump_intermediate_pages', False):
self.to_screen('Dumping request to ' + urlh.geturl())
dump = base64.b64encode(webpage_bytes).decode('ascii')
self._downloader.to_screen(dump)
if self._downloader.params.get('write_pages', False):
if self.get_param('write_pages', False):
basen = '%s_%s' % (video_id, urlh.geturl())
if len(basen) > 240:
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
@@ -943,14 +952,22 @@ class InfoExtractor(object):
else:
self.report_warning(errmsg + str(ve))
def report_warning(self, msg, video_id=None):
def report_warning(self, msg, video_id=None, *args, **kwargs):
idstr = '' if video_id is None else '%s: ' % video_id
self._downloader.report_warning(
'[%s] %s%s' % (self.IE_NAME, idstr, msg))
'[%s] %s%s' % (self.IE_NAME, idstr, msg), *args, **kwargs)
def to_screen(self, msg):
def to_screen(self, msg, *args, **kwargs):
"""Print msg to screen, prefixing it with '[ie_name]'"""
self._downloader.to_screen('[%s] %s' % (self.IE_NAME, msg))
self._downloader.to_screen('[%s] %s' % (self.IE_NAME, msg), *args, **kwargs)
def write_debug(self, msg, *args, **kwargs):
self._downloader.write_debug('[%s] %s' % (self.IE_NAME, msg), *args, **kwargs)
def get_param(self, name, default=None, *args, **kwargs):
if self._downloader:
return self._downloader.params.get(name, default, *args, **kwargs)
return default
def report_extraction(self, id_or_name):
"""Report information extraction."""
@@ -969,23 +986,22 @@ class InfoExtractor(object):
self.to_screen('Logging in')
def raise_login_required(
self, msg='This video is only available for registered users', metadata_available=False):
if metadata_available and self._downloader.params.get('ignore_no_formats_error'):
self, msg='This video is only available for registered users',
metadata_available=False, method='any'):
if metadata_available and self.get_param('ignore_no_formats_error'):
self.report_warning(msg)
raise ExtractorError(
'%s. Use --cookies, --username and --password or --netrc to provide account credentials' % msg,
expected=True)
raise ExtractorError('%s. %s' % (msg, self._LOGIN_HINTS[method]), expected=True)
def raise_geo_restricted(
self, msg='This video is not available from your location due to geo restriction',
countries=None, metadata_available=False):
if metadata_available and self._downloader.params.get('ignore_no_formats_error'):
if metadata_available and self.get_param('ignore_no_formats_error'):
self.report_warning(msg)
else:
raise GeoRestrictedError(msg, countries=countries)
def raise_no_formats(self, msg, expected=False, video_id=None):
if expected and self._downloader.params.get('ignore_no_formats_error'):
if expected and self.get_param('ignore_no_formats_error'):
self.report_warning(msg, video_id)
else:
raise ExtractorError(msg, expected=expected, video_id=video_id)
@@ -1040,7 +1056,7 @@ class InfoExtractor(object):
if mobj:
break
if not self._downloader.params.get('no_color') and compat_os_name != 'nt' and sys.stderr.isatty():
if not self.get_param('no_color') and compat_os_name != 'nt' and sys.stderr.isatty():
_name = '\033[0;34m%s\033[0m' % name
else:
_name = name
@@ -1074,7 +1090,7 @@ class InfoExtractor(object):
password = None
netrc_machine = netrc_machine or self._NETRC_MACHINE
if self._downloader.params.get('usenetrc', False):
if self.get_param('usenetrc', False):
try:
info = netrc.netrc().authenticators(netrc_machine)
if info is not None:
@@ -1098,15 +1114,11 @@ class InfoExtractor(object):
value.
If there's no info available, return (None, None)
"""
if self._downloader is None:
return (None, None)
downloader_params = self._downloader.params
# Attempt to use provided username and password or .netrc data
if downloader_params.get(username_option) is not None:
username = downloader_params[username_option]
password = downloader_params[password_option]
username = self.get_param(username_option)
if username is not None:
password = self.get_param(password_option)
else:
username, password = self._get_netrc_login_info(netrc_machine)
@@ -1119,12 +1131,10 @@ class InfoExtractor(object):
currently just uses the command line option
If there's no info available, return None
"""
if self._downloader is None:
return None
downloader_params = self._downloader.params
if downloader_params.get('twofactor') is not None:
return downloader_params['twofactor']
tfa = self.get_param('twofactor')
if tfa is not None:
return tfa
return compat_getpass('Type %s and press [Return]: ' % note)
@@ -1594,12 +1604,12 @@ class InfoExtractor(object):
else limits[0] if has_limit and not has_multiple_limits
else None)
def print_verbose_info(self, to_screen):
def print_verbose_info(self, write_debug):
if self._sort_user:
to_screen('[debug] Sort order given by user: %s' % ', '.join(self._sort_user))
write_debug('Sort order given by user: %s' % ', '.join(self._sort_user))
if self._sort_extractor:
to_screen('[debug] Sort order given by extractor: %s' % ', '.join(self._sort_extractor))
to_screen('[debug] Formats sorted by: %s' % ', '.join(['%s%s%s' % (
write_debug('Sort order given by extractor: %s' % ', '.join(self._sort_extractor))
write_debug('Formats sorted by: %s' % ', '.join(['%s%s%s' % (
'+' if self._get_field_setting(field, 'reverse') else '', field,
'%s%s(%s)' % ('~' if self._get_field_setting(field, 'closest') else ':',
self._get_field_setting(field, 'limit_text'),
@@ -1685,13 +1695,13 @@ class InfoExtractor(object):
def _sort_formats(self, formats, field_preference=[]):
if not formats:
if self._downloader.params.get('ignore_no_formats_error'):
if self.get_param('ignore_no_formats_error'):
return
raise ExtractorError('No video formats found')
format_sort = self.FormatSort() # params and to_screen are taken from the downloader
format_sort.evaluate_params(self._downloader.params, field_preference)
if self._downloader.params.get('verbose', False):
format_sort.print_verbose_info(self._downloader.to_screen)
if self.get_param('verbose', False):
format_sort.print_verbose_info(self._downloader.write_debug)
formats.sort(key=lambda f: format_sort.calculate_preference(f))
def _check_formats(self, formats, video_id):
@@ -1730,7 +1740,7 @@ class InfoExtractor(object):
""" Either "http:" or "https:", depending on the user's preferences """
return (
'http:'
if self._downloader.params.get('prefer_insecure', False)
if self.get_param('prefer_insecure', False)
else 'https:')
def _proto_relative_url(self, url, scheme=None):
@@ -1924,7 +1934,7 @@ class InfoExtractor(object):
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
return [], {}
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and re.search(r'#EXT-X-SESSION-KEY:.*?URI="skd://', m3u8_doc)): # Apple FairPlay
return [], {}
@@ -1937,7 +1947,7 @@ class InfoExtractor(object):
if re.match(r'^https?://', u)
else compat_urlparse.urljoin(m3u8_url, u))
split_discontinuity = self._downloader.params.get('hls_split_discontinuity', False)
split_discontinuity = self.get_param('hls_split_discontinuity', False)
# References:
# 1. https://tools.ietf.org/html/draft-pantos-http-live-streaming-21
@@ -2480,7 +2490,7 @@ class InfoExtractor(object):
http://standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip
2. https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP
"""
if not self._downloader.params.get('dynamic_mpd', True):
if not self.get_param('dynamic_mpd', True):
if mpd_doc.get('type') == 'dynamic':
return [], {}
@@ -2550,7 +2560,7 @@ class InfoExtractor(object):
extract_Initialization(segment_template)
return ms_info
skip_unplayable = not self._downloader.params.get('allow_unplayable_formats')
skip_unplayable = not self.get_param('allow_unplayable_formats')
mpd_duration = parse_duration(mpd_doc.get('mediaPresentationDuration'))
formats = []
@@ -2799,7 +2809,7 @@ class InfoExtractor(object):
"""
if ism_doc.get('IsLive') == 'TRUE':
return [], {}
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and ism_doc.find('Protection') is not None):
return [], {}
@@ -3404,8 +3414,8 @@ class InfoExtractor(object):
return not any_restricted
def extract_subtitles(self, *args, **kwargs):
if (self._downloader.params.get('writesubtitles', False)
or self._downloader.params.get('listsubtitles')):
if (self.get_param('writesubtitles', False)
or self.get_param('listsubtitles')):
return self._get_subtitles(*args, **kwargs)
return {}
@@ -3440,8 +3450,8 @@ class InfoExtractor(object):
return target
def extract_automatic_captions(self, *args, **kwargs):
if (self._downloader.params.get('writeautomaticsub', False)
or self._downloader.params.get('listsubtitles')):
if (self.get_param('writeautomaticsub', False)
or self.get_param('listsubtitles')):
return self._get_automatic_captions(*args, **kwargs)
return {}
@@ -3449,9 +3459,9 @@ class InfoExtractor(object):
raise NotImplementedError('This method must be implemented by subclasses')
def mark_watched(self, *args, **kwargs):
if (self._downloader.params.get('mark_watched', False)
if (self.get_param('mark_watched', False)
and (self._get_login_info()[0] is not None
or self._downloader.params.get('cookiefile') is not None)):
or self.get_param('cookiefile') is not None)):
self._mark_watched(*args, **kwargs)
def _mark_watched(self, *args, **kwargs):
@@ -3459,7 +3469,7 @@ class InfoExtractor(object):
def geo_verification_headers(self):
headers = {}
geo_verification_proxy = self._downloader.params.get('geo_verification_proxy')
geo_verification_proxy = self.get_param('geo_verification_proxy')
if geo_verification_proxy:
headers['Ytdl-request-proxy'] = geo_verification_proxy
return headers
+2 -2
View File
@@ -26,8 +26,8 @@ class CommonMistakesIE(InfoExtractor):
'That doesn\'t make any sense. '
'Simply remove the parameter in your command or configuration.'
) % url
if not self._downloader.params.get('verbose'):
msg += ' Add -v to the command line to see what arguments and configuration yt-dlp got.'
if not self.get_param('verbose'):
msg += ' Add -v to the command line to see what arguments and configuration yt-dlp has'
raise ExtractorError(msg, expected=True)
+3 -3
View File
@@ -81,7 +81,7 @@ class CrackleIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
geo_bypass_country = self._downloader.params.get('geo_bypass_country', None)
geo_bypass_country = self.get_param('geo_bypass_country', None)
countries = orderedSet((geo_bypass_country, 'US', 'AU', 'CA', 'AS', 'FM', 'GU', 'MP', 'PR', 'PW', 'MH', 'VI', ''))
num_countries, num = len(countries) - 1, 0
@@ -128,8 +128,8 @@ class CrackleIE(InfoExtractor):
if isinstance(media.get('MediaURLs'), list):
break
ignore_no_formats = self._downloader.params.get('ignore_no_formats_error')
allow_unplayable_formats = self._downloader.params.get('allow_unplayable_formats')
ignore_no_formats = self.get_param('ignore_no_formats_error')
allow_unplayable_formats = self.get_param('allow_unplayable_formats')
if not media or (not media.get('MediaURLs') and not ignore_no_formats):
raise ExtractorError(
+4 -4
View File
@@ -42,7 +42,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
def _real_initialize(self):
cookies = self._get_dailymotion_cookies()
ff = self._get_cookie_value(cookies, 'ff')
self._FAMILY_FILTER = ff == 'on' if ff else age_restricted(18, self._downloader.params.get('age_limit'))
self._FAMILY_FILTER = ff == 'on' if ff else age_restricted(18, self.get_param('age_limit'))
self._set_dailymotion_cookie('ff', 'on' if self._FAMILY_FILTER else 'off')
def _call_api(self, object_type, xid, object_fields, note, filter_extra=None):
@@ -207,14 +207,14 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
video_id, playlist_id = re.match(self._VALID_URL, url).groups()
if playlist_id:
if not self._downloader.params.get('noplaylist'):
if not self.get_param('noplaylist'):
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
return self.url_result(
'http://www.dailymotion.com/playlist/' + playlist_id,
'DailymotionPlaylist', playlist_id)
self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
password = self._downloader.params.get('videopassword')
password = self.get_param('videopassword')
media = self._call_api(
'media', video_id, '''... on Video {
%s
@@ -232,7 +232,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
audienceCount
isOnAir
}''' % (self._COMMON_MEDIA_FIELDS, self._COMMON_MEDIA_FIELDS), 'Downloading media JSON metadata',
'password: "%s"' % self._downloader.params.get('videopassword') if password else None)
'password: "%s"' % self.get_param('videopassword') if password else None)
xid = media['xid']
metadata = self._download_json(
+1 -1
View File
@@ -158,7 +158,7 @@ class DaumListIE(InfoExtractor):
query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
if 'clipid' in query_dict:
clip_id = query_dict['clipid'][0]
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
self.to_screen('Downloading just video %s because of --no-playlist' % clip_id)
return self.url_result(DaumClipIE._URL_TEMPLATE % clip_id, 'DaumClip')
else:
+1 -1
View File
@@ -13,7 +13,7 @@ from ..utils import (
class DeezerBaseInfoExtractor(InfoExtractor):
def get_data(self, url):
if not self._downloader.params.get('test'):
if not self.get_param('test'):
self.report_warning('For now, this extractor only supports the 30 second previews. Patches welcome!')
mobj = re.match(self._VALID_URL, url)
+9 -12
View File
@@ -6,7 +6,7 @@ from .common import InfoExtractor
from ..compat import compat_urllib_parse_urlencode
from ..utils import (
ExtractorError,
unescapeHTML
merge_dicts,
)
@@ -24,7 +24,8 @@ class EroProfileIE(InfoExtractor):
'title': 'sexy babe softcore',
'thumbnail': r're:https?://.*\.jpg',
'age_limit': 18,
}
},
'skip': 'Video not found',
}, {
'url': 'http://www.eroprofile.com/m/videos/view/Try-It-On-Pee_cut_2-wmv-4shared-com-file-sharing-download-movie-file',
'md5': '1baa9602ede46ce904c431f5418d8916',
@@ -77,19 +78,15 @@ class EroProfileIE(InfoExtractor):
[r"glbUpdViews\s*\('\d*','(\d+)'", r'p/report/video/(\d+)'],
webpage, 'video id', default=None)
video_url = unescapeHTML(self._search_regex(
r'<source src="([^"]+)', webpage, 'video url'))
title = self._html_search_regex(
r'Title:</th><td>([^<]+)</td>', webpage, 'title')
thumbnail = self._search_regex(
r'onclick="showVideoPlayer\(\)"><img src="([^"]+)',
webpage, 'thumbnail', fatal=False)
(r'Title:</th><td>([^<]+)</td>', r'<h1[^>]*>(.+?)</h1>'),
webpage, 'title')
return {
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
return merge_dicts(info, {
'id': video_id,
'display_id': display_id,
'url': video_url,
'title': title,
'thumbnail': thumbnail,
'age_limit': 18,
}
})
+4 -1
View File
@@ -94,7 +94,8 @@ from .audiomack import AudiomackIE, AudiomackAlbumIE
from .audius import (
AudiusIE,
AudiusTrackIE,
AudiusPlaylistIE
AudiusPlaylistIE,
AudiusProfileIE,
)
from .awaan import (
AWAANIE,
@@ -952,6 +953,7 @@ from .palcomp3 import (
)
from .pandoratv import PandoraTVIE
from .parliamentliveuk import ParliamentLiveUKIE
from .parlview import ParlviewIE
from .patreon import PatreonIE
from .pbs import PBSIE
from .pearvideo import PearVideoIE
@@ -983,6 +985,7 @@ from .platzi import (
from .playfm import PlayFMIE
from .playplustv import PlayPlusTVIE
from .plays import PlaysTVIE
from .playstuff import PlayStuffIE
from .playtvak import PlaytvakIE
from .playvid import PlayvidIE
from .playwire import PlaywireIE
+17 -4
View File
@@ -126,6 +126,7 @@ from .viqeo import ViqeoIE
from .expressen import ExpressenIE
from .zype import ZypeIE
from .odnoklassniki import OdnoklassnikiIE
from .vk import VKIE
from .kinja import KinjaEmbedIE
from .gedidigital import GediDigitalIE
from .rcs import RCSEmbedsIE
@@ -2252,6 +2253,10 @@ class GenericIE(InfoExtractor):
'playlist_mincount': 52,
},
{
# Sibnet embed (https://help.sibnet.ru/?sibnet_video_embed)
'url': 'https://phpbb3.x-tk.ru/bbcode-video-sibnet-t24.html',
'only_matching': True,
}, {
# WimTv embed player
'url': 'http://www.msmotor.tv/wearefmi-pt-2-2021/',
'info_dict': {
@@ -2370,7 +2375,7 @@ class GenericIE(InfoExtractor):
parsed_url = compat_urlparse.urlparse(url)
if not parsed_url.scheme:
default_search = self._downloader.params.get('default_search')
default_search = self.get_param('default_search')
if default_search is None:
default_search = 'fixup_error'
@@ -2461,8 +2466,8 @@ class GenericIE(InfoExtractor):
info_dict['subtitles'] = subtitles
return info_dict
if not self._downloader.params.get('test', False) and not is_intentional:
force = self._downloader.params.get('force_generic_extractor', False)
if not self.get_param('test', False) and not is_intentional:
force = self.get_param('force_generic_extractor', False)
self.report_warning(
'%s on generic information extractor.' % ('Forcing' if force else 'Falling back'))
@@ -2803,6 +2808,11 @@ class GenericIE(InfoExtractor):
if odnoklassniki_url:
return self.url_result(odnoklassniki_url, OdnoklassnikiIE.ie_key())
# Look for sibnet embedded player
sibnet_urls = VKIE._extract_sibnet_urls(webpage)
if sibnet_urls:
return self.playlist_from_matches(sibnet_urls, video_id, video_title)
# Look for embedded ivi player
mobj = re.search(r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1', webpage)
if mobj is not None:
@@ -3454,6 +3464,9 @@ class GenericIE(InfoExtractor):
'url': src,
'ext': (mimetype2ext(src_type)
or ext if ext in KNOWN_EXTENSIONS else 'mp4'),
'http_headers': {
'Referer': full_response.geturl(),
},
})
if formats:
self._sort_formats(formats)
@@ -3522,7 +3535,7 @@ class GenericIE(InfoExtractor):
m_video_type = re.findall(r'<meta.*?property="og:video:type".*?content="video/(.*?)"', webpage)
# We only look in og:video if the MIME type is a video, don't try if it's a Flash player:
if m_video_type is not None:
found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
found = filter_video(re.findall(r'<meta.*?property="og:(?:video|audio)".*?content="(.*?)"', webpage))
if not found:
REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
found = re.search(
+1 -1
View File
@@ -96,7 +96,7 @@ class GloboIE(InfoExtractor):
video = self._download_json(
'http://api.globovideos.com/videos/%s/playlist' % video_id,
video_id)['videos'][0]
if not self._downloader.params.get('allow_unplayable_formats') and video.get('encrypted') is True:
if not self.get_param('allow_unplayable_formats') and video.get('encrypted') is True:
raise ExtractorError('This video is DRM protected.', expected=True)
title = video['title']
+1 -1
View File
@@ -141,7 +141,7 @@ class HotStarIE(HotStarBaseIE):
title = video_data['title']
if not self._downloader.params.get('allow_unplayable_formats') and video_data.get('drmProtected'):
if not self.get_param('allow_unplayable_formats') and video_data.get('drmProtected'):
raise ExtractorError('This video is DRM protected.', expected=True)
headers = {'Referer': url}
+1 -1
View File
@@ -65,7 +65,7 @@ class ImgGamingBaseIE(InfoExtractor):
domain, media_type, media_id, playlist_id = re.match(self._VALID_URL, url).groups()
if playlist_id:
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
self.to_screen('Downloading just video %s because of --no-playlist' % media_id)
else:
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % playlist_id)
+1 -1
View File
@@ -165,7 +165,7 @@ class IviIE(InfoExtractor):
content_format = f.get('content_format')
if not f_url:
continue
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and ('-MDRM-' in content_format or '-FPS-' in content_format)):
continue
formats.append({
+1 -1
View File
@@ -309,7 +309,7 @@ class KalturaIE(InfoExtractor):
if f.get('fileExt') == 'chun':
continue
# DRM-protected video, cannot be decrypted
if not self._downloader.params.get('allow_unplayable_formats') and f.get('fileExt') == 'wvm':
if not self.get_param('allow_unplayable_formats') and f.get('fileExt') == 'wvm':
continue
if not f.get('fileExt'):
# QT indicates QuickTime; some videos have broken fileExt
+2 -2
View File
@@ -98,7 +98,7 @@ class LimelightBaseIE(InfoExtractor):
stream_url = stream.get('url')
if not stream_url or stream_url in urls:
continue
if not self._downloader.params.get('allow_unplayable_formats') and stream.get('drmProtected'):
if not self.get_param('allow_unplayable_formats') and stream.get('drmProtected'):
continue
urls.append(stream_url)
ext = determine_ext(stream_url)
@@ -163,7 +163,7 @@ class LimelightBaseIE(InfoExtractor):
if not media_url or media_url in urls:
continue
if (format_id in ('Widevine', 'SmoothStreaming')
and not self._downloader.params.get('allow_unplayable_formats', False)):
and not self.get_param('allow_unplayable_formats', False)):
continue
urls.append(media_url)
ext = determine_ext(media_url)
+1 -1
View File
@@ -71,7 +71,7 @@ class LiTVIE(InfoExtractor):
video_id = self._match_id(url)
noplaylist = self._downloader.params.get('noplaylist')
noplaylist = self.get_param('noplaylist')
noplaylist_prompt = True
if 'force_noplaylist' in data:
noplaylist = data['force_noplaylist']
+1 -1
View File
@@ -167,7 +167,7 @@ class NBAWatchIE(NBAWatchBaseIE):
display_id = self._match_id(url)
collection_id = compat_parse_qs(compat_urllib_parse_urlparse(url).query).get('collection', [None])[0]
if collection_id:
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
self.to_screen('Downloading just video %s because of --no-playlist' % display_id)
else:
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % collection_id)
+1 -1
View File
@@ -405,7 +405,7 @@ class NetEaseMusicProgramIE(NetEaseMusicBaseIE):
name = info['name']
description = info['description']
if not info['songs'] or self._downloader.params.get('noplaylist'):
if not info['songs'] or self.get_param('noplaylist'):
if info['songs']:
self.to_screen(
'Downloading just the main audio %s because of --no-playlist'
+1 -1
View File
@@ -34,7 +34,7 @@ class NineCNineMediaIE(InfoExtractor):
'$include': '[HasClosedCaptions]',
})
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and try_get(content_package, lambda x: x['Constraints']['Security']['Type'])):
raise ExtractorError('This video is DRM protected.', expected=True)
+1 -1
View File
@@ -66,7 +66,7 @@ class NineNowIE(InfoExtractor):
video_data = common_data['video']
if not self._downloader.params.get('allow_unplayable_formats') and video_data.get('drm'):
if not self.get_param('allow_unplayable_formats') and video_data.get('drm'):
raise ExtractorError('This video is DRM protected.', expected=True)
brightcove_id = video_data.get('brightcoveId') or 'ref:' + video_data['referenceId']
+1 -1
View File
@@ -246,7 +246,7 @@ class NPOIE(NPOBaseIE):
})
if not formats:
if not self._downloader.params.get('allow_unplayable_formats') and drm:
if not self.get_param('allow_unplayable_formats') and drm:
self.raise_no_formats('This video is DRM protected.', expected=True)
return
+1 -1
View File
@@ -182,7 +182,7 @@ class OnetChannelIE(OnetBaseIE):
video_id = remove_start(current_clip_info['ckmId'], 'mvp:')
video_name = url_basename(current_clip_info['url'])
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
self.to_screen(
'Downloading just video %s because of --no-playlist' % video_name)
return self._extract_from_id(video_id, webpage)
+1 -1
View File
@@ -182,7 +182,7 @@ class ORFRadioIE(InfoExtractor):
duration = end - start if end and start else None
entries.append({
'id': loop_stream_id.replace('.mp3', ''),
'url': 'http://loopstream01.apa.at/?channel=%s&id=%s' % (self._LOOP_STATION, loop_stream_id),
'url': 'https://loopstream01.apa.at/?channel=%s&id=%s' % (self._LOOP_STATION, loop_stream_id),
'title': title,
'description': clean_html(data.get('subtitle')),
'duration': duration,
+68
View File
@@ -0,0 +1,68 @@
# coding: utf-8
from __future__ import unicode_literals
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
int_or_none,
try_get,
unified_timestamp,
)
class ParlviewIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?parlview\.aph\.gov\.au/(?:[^/]+)?\bvideoID=(?P<id>\d{6})'
_TESTS = [{
'url': 'https://parlview.aph.gov.au/mediaPlayer.php?videoID=542661',
'info_dict': {
'id': '542661',
'ext': 'mp4',
'title': "Australia's Family Law System [Part 2]",
'duration': 5799,
'description': 'md5:7099883b391619dbae435891ca871a62',
'timestamp': 1621430700,
'upload_date': '20210519',
'uploader': 'Joint Committee',
},
'params': {
'skip_download': True,
}
}, {
'url': 'https://parlview.aph.gov.au/mediaPlayer.php?videoID=539936',
'only_matching': True,
}]
_API_URL = 'https://parlview.aph.gov.au/api_v3/1/playback/getUniversalPlayerConfig?videoID=%s&format=json'
_MEDIA_INFO_URL = 'https://parlview.aph.gov.au/ajaxPlayer.php?videoID=%s&tabNum=4&action=loadTab'
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
media = self._download_json(self._API_URL % video_id, video_id).get('media')
timestamp = try_get(media, lambda x: x['timeMap']['source']['timecode_offsets'][0], compat_str) or '/'
stream = try_get(media, lambda x: x['renditions'][0], dict)
if not stream:
self.raise_no_formats('No streams were detected')
elif stream.get('streamType') != 'VOD':
self.raise_no_formats('Unknown type of stream was detected: "%s"' % str(stream.get('streamType')))
formats = self._extract_m3u8_formats(stream['url'], video_id, 'mp4', 'm3u8_native')
self._sort_formats(formats)
media_info = self._download_webpage(
self._MEDIA_INFO_URL % video_id, video_id, note='Downloading media info', fatal=False)
return {
'id': video_id,
'url': url,
'title': self._html_search_regex(r'<h2>([^<]+)<', webpage, 'title', fatal=False),
'formats': formats,
'duration': int_or_none(media.get('duration')),
'timestamp': unified_timestamp(timestamp.split('/', 1)[1].replace('_', ' ')),
'description': self._html_search_regex(
r'<div[^>]+class="descripti?on"[^>]*>[^>]+<strong>[^>]+>[^>]+>([^<]+)',
webpage, 'description', fatal=False),
'uploader': self._html_search_regex(
r'<td>[^>]+>Channel:[^>]+>([^<]+)', media_info, 'channel', fatal=False),
'thumbnail': media.get('staticImage'),
}
+1 -1
View File
@@ -79,7 +79,7 @@ class PhilharmonieDeParisIE(InfoExtractor):
formats.extend(self._extract_m3u8_formats(
m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
m3u8_id='hls', fatal=False))
if not formats and not self._downloader.params.get('ignore_no_formats'):
if not formats and not self.get_param('ignore_no_formats'):
return
self._sort_formats(formats)
return {
+28 -23
View File
@@ -9,8 +9,9 @@ from ..compat import compat_str
from ..utils import (
int_or_none,
merge_dicts,
try_get,
unified_timestamp,
xpath_text,
urljoin,
)
@@ -27,10 +28,11 @@ class PhoenixIE(ZDFBaseIE):
'title': 'Wohin führt der Protest in der Pandemie?',
'description': 'md5:7d643fe7f565e53a24aac036b2122fbd',
'duration': 1691,
'timestamp': 1613906100,
'timestamp': 1613902500,
'upload_date': '20210221',
'uploader': 'Phoenix',
'channel': 'corona nachgehakt',
'series': 'corona nachgehakt',
'episode': 'Wohin führt der Protest in der Pandemie?',
},
}, {
# Youtube embed
@@ -79,50 +81,53 @@ class PhoenixIE(ZDFBaseIE):
video_id = compat_str(video.get('basename') or video.get('content'))
details = self._download_xml(
details = self._download_json(
'https://www.phoenix.de/php/mediaplayer/data/beitrags_details.php',
video_id, 'Downloading details XML', query={
video_id, 'Downloading details JSON', query={
'ak': 'web',
'ptmd': 'true',
'id': video_id,
'profile': 'player2',
})
title = title or xpath_text(
details, './/information/title', 'title', fatal=True)
content_id = xpath_text(
details, './/video/details/basename', 'content id', fatal=True)
title = title or details['title']
content_id = details['tracking']['nielsen']['content']['assetid']
info = self._extract_ptmd(
'https://tmd.phoenix.de/tmd/2/ngplayer_2_3/vod/ptmd/phoenix/%s' % content_id,
content_id, None, url)
timestamp = unified_timestamp(xpath_text(details, './/details/airtime'))
duration = int_or_none(try_get(
details, lambda x: x['tracking']['nielsen']['content']['length']))
timestamp = unified_timestamp(details.get('editorialDate'))
series = try_get(
details, lambda x: x['tracking']['nielsen']['content']['program'],
compat_str)
episode = title if details.get('contentType') == 'episode' else None
thumbnails = []
for node in details.findall('.//teaserimages/teaserimage'):
thumbnail_url = node.text
teaser_images = try_get(details, lambda x: x['teaserImageRef']['layouts'], dict) or {}
for thumbnail_key, thumbnail_url in teaser_images.items():
thumbnail_url = urljoin(url, thumbnail_url)
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))
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)
return merge_dicts(info, {
'id': content_id,
'title': title,
'description': xpath_text(details, './/information/detail'),
'duration': int_or_none(xpath_text(details, './/details/lengthSec')),
'description': details.get('leadParagraph'),
'duration': duration,
'thumbnails': thumbnails,
'timestamp': timestamp,
'uploader': xpath_text(details, './/details/channel'),
'uploader_id': xpath_text(details, './/details/originChannelId'),
'channel': xpath_text(details, './/details/originChannelTitle'),
'uploader': details.get('tvService'),
'series': series,
'episode': episode,
})
+65
View File
@@ -0,0 +1,65 @@
from __future__ import unicode_literals
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
smuggle_url,
try_get,
)
class PlayStuffIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?play\.stuff\.co\.nz/details/(?P<id>[^/?#&]+)'
_TESTS = [{
'url': 'https://play.stuff.co.nz/details/608778ac1de1c4001a3fa09a',
'md5': 'c82d3669e5247c64bc382577843e5bd0',
'info_dict': {
'id': '6250584958001',
'ext': 'mp4',
'title': 'Episode 1: Rotorua/Mt Maunganui/Tauranga',
'description': 'md5:c154bafb9f0dd02d01fd4100fb1c1913',
'uploader_id': '6005208634001',
'timestamp': 1619491027,
'upload_date': '20210427',
},
'add_ie': ['BrightcoveNew'],
}, {
# geo restricted, bypassable
'url': 'https://play.stuff.co.nz/details/_6155660351001',
'only_matching': True,
}]
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_default/index.html?videoId=%s'
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
state = self._parse_json(
self._search_regex(
r'__INITIAL_STATE__\s*=\s*({.+?})\s*;', webpage, 'state'),
video_id)
account_id = try_get(
state, lambda x: x['configurations']['accountId'],
compat_str) or '6005208634001'
player_id = try_get(
state, lambda x: x['configurations']['playerId'],
compat_str) or 'default'
entries = []
for item_id, video in state['items'].items():
if not isinstance(video, dict):
continue
asset_id = try_get(
video, lambda x: x['content']['attributes']['assetId'],
compat_str)
if not asset_id:
continue
entries.append(self.url_result(
smuggle_url(
self.BRIGHTCOVE_URL_TEMPLATE % (account_id, player_id, asset_id),
{'geo_countries': ['NZ']}),
'BrightcoveNew', video_id))
return self.playlist_result(entries, video_id)
+3 -3
View File
@@ -337,11 +337,11 @@ query viewClip {
# In order to minimize the number of calls to ViewClip API and reduce
# the probability of being throttled or banned by Pluralsight we will request
# only single format until formats listing was explicitly requested.
if self._downloader.params.get('listformats', False):
if self.get_param('listformats', False):
allowed_qualities = ALLOWED_QUALITIES
else:
def guess_allowed_qualities():
req_format = self._downloader.params.get('format') or 'best'
req_format = self.get_param('format') or 'best'
req_format_split = req_format.split('-', 1)
if len(req_format_split) > 1:
req_ext, req_quality = req_format_split
@@ -349,7 +349,7 @@ query viewClip {
for allowed_quality in ALLOWED_QUALITIES:
if req_ext == allowed_quality.ext and req_quality in allowed_quality.qualities:
return (AllowedQuality(req_ext, (req_quality, )), )
req_ext = 'webm' if self._downloader.params.get('prefer_free_formats') else 'mp4'
req_ext = 'webm' if self.get_param('prefer_free_formats') else 'mp4'
return (AllowedQuality(req_ext, (best_quality, )), )
allowed_qualities = guess_allowed_qualities()
+1 -1
View File
@@ -109,7 +109,7 @@ class PlutoTVIE(InfoExtractor):
if ad_free_formats:
formats, subtitles = ad_free_formats, ad_free_subtitles
else:
self._downloader.report_warning('Unable to find ad-free formats')
self.report_warning('Unable to find ad-free formats')
return formats, subtitles
def _get_video_info(self, video_json, slug, series_name=None):
+1 -1
View File
@@ -112,7 +112,7 @@ class PokemonWatchIE(InfoExtractor):
}
# API call can be avoided entirely if we are listing formats
if self._downloader.params.get('listformats', False):
if self.get_param('listformats', False):
return info
webpage = self._download_webpage(url, video_id)
+1 -1
View File
@@ -34,7 +34,7 @@ class ProSiebenSat1BaseIE(InfoExtractor):
'ids': clip_id,
})[0]
if not self._downloader.params.get('allow_unplayable_formats') and video.get('is_protected') is True:
if not self.get_param('allow_unplayable_formats') and video.get('is_protected') is True:
raise ExtractorError('This video is DRM protected.', expected=True)
formats = []
+1 -1
View File
@@ -275,7 +275,7 @@ class RaiPlayIE(RaiBaseIE):
media = self._download_json(
base + '.json', video_id, 'Downloading video JSON')
if not self._downloader.params.get('allow_unplayable_formats'):
if not self.get_param('allow_unplayable_formats'):
if try_get(
media,
(lambda x: x['rights_management']['rights']['drm'],
+4 -2
View File
@@ -133,8 +133,10 @@ class RedBullEmbedIE(RedBullTVIE):
rrn_id = self._match_id(url)
asset_id = self._download_json(
'https://edge-graphql.crepo-production.redbullaws.com/v1/graphql',
rrn_id, headers={'API-KEY': 'e90a1ff11335423998b100c929ecc866'},
query={
rrn_id, headers={
'Accept': 'application/json',
'API-KEY': 'e90a1ff11335423998b100c929ecc866',
}, query={
'query': '''{
resource(id: "%s", enforceGeoBlocking: false) {
%s
+9 -2
View File
@@ -13,7 +13,7 @@ from ..utils import smuggle_url
class RMCDecouverteIE(InfoExtractor):
_VALID_URL = r'https?://rmcdecouverte\.bfmtv\.com/(?:[^/]+/(?P<id>[^?#/]+)|(?P<live_id>mediaplayer-direct))'
_VALID_URL = r'https?://rmcdecouverte\.bfmtv\.com/(?:[^?#]*_(?P<id>\d+)|mediaplayer-direct)/?(?:[#?]|$)'
_TESTS = [{
'url': 'https://rmcdecouverte.bfmtv.com/vestiges-de-guerre_22240/les-bunkers-secrets-domaha-beach_25303/',
@@ -45,6 +45,13 @@ class RMCDecouverteIE(InfoExtractor):
'skip_download': True,
},
'skip': 'only available for a week',
}, {
'url': 'https://rmcdecouverte.bfmtv.com/avions-furtifs-la-technologie-de-lextreme_10598',
'only_matching': True,
}, {
# The website accepts any URL as long as it has _\d+ at the end
'url': 'https://rmcdecouverte.bfmtv.com/any/thing/can/go/here/_10598',
'only_matching': True,
}, {
# live, geo restricted, bypassable
'url': 'https://rmcdecouverte.bfmtv.com/mediaplayer-direct/',
@@ -54,7 +61,7 @@ class RMCDecouverteIE(InfoExtractor):
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
display_id = mobj.group('id') or mobj.group('live_id')
display_id = mobj.group('id') or 'direct'
webpage = self._download_webpage(url, display_id)
brightcove_legacy_url = BrightcoveLegacyIE._extract_brightcove_url(webpage)
if brightcove_legacy_url:
+1 -1
View File
@@ -125,7 +125,7 @@ class RTBFIE(InfoExtractor):
})
mpd_url = data.get('urlDash')
if mpd_url and (self._downloader.params.get('allow_unplayable_formats') or not data.get('drm')):
if mpd_url and (self.get_param('allow_unplayable_formats') or not data.get('drm')):
formats.extend(self._extract_mpd_formats(
mpd_url, media_id, mpd_id='dash', fatal=False))
+1 -1
View File
@@ -200,7 +200,7 @@ class RuutuIE(InfoExtractor):
return node.get('value')
if not formats:
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and xpath_text(video_xml, './Clip/DRM', default=None)):
self.raise_no_formats('This video is DRM protected.', expected=True)
ns_st_cds = pv('ns_st_cds')
+7 -3
View File
@@ -21,6 +21,7 @@ from ..utils import (
class ShahidBaseIE(AWSIE):
_AWS_PROXY_HOST = 'api2.shahid.net'
_AWS_API_KEY = '2RRtuMHx95aNI1Kvtn2rChEuwsCogUd4samGPjLh'
_VALID_URL_BASE = r'https?://shahid\.mbc\.net/[a-z]{2}/'
def _handle_error(self, e):
fail_data = self._parse_json(
@@ -49,7 +50,7 @@ class ShahidBaseIE(AWSIE):
class ShahidIE(ShahidBaseIE):
_NETRC_MACHINE = 'shahid'
_VALID_URL = r'https?://shahid\.mbc\.net/ar/(?:serie|show|movie)s/[^/]+/(?P<type>episode|clip|movie)-(?P<id>\d+)'
_VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'(?:serie|show|movie)s/[^/]+/(?P<type>episode|clip|movie)-(?P<id>\d+)'
_TESTS = [{
'url': 'https://shahid.mbc.net/ar/shows/%D9%85%D8%AA%D8%AD%D9%81-%D8%A7%D9%84%D8%AF%D8%AD%D9%8A%D8%AD-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D9%83%D9%84%D9%8A%D8%A8-1/clip-816924',
'info_dict': {
@@ -73,6 +74,9 @@ class ShahidIE(ShahidBaseIE):
# shahid plus subscriber only
'url': 'https://shahid.mbc.net/ar/series/%D9%85%D8%B1%D8%A7%D9%8A%D8%A7-2011-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/episode-90511',
'only_matching': True
}, {
'url': 'https://shahid.mbc.net/en/shows/Ramez-Fi-Al-Shallal-season-1-episode-1/episode-359319',
'only_matching': True
}]
def _real_initialize(self):
@@ -114,7 +118,7 @@ class ShahidIE(ShahidBaseIE):
playout = self._call_api(
'playout/new/url/' + video_id, video_id)['playout']
if not self._downloader.params.get('allow_unplayable_formats') and playout.get('drm'):
if not self.get_param('allow_unplayable_formats') and playout.get('drm'):
raise ExtractorError('This video is DRM protected.', expected=True)
formats = self._extract_m3u8_formats(re.sub(
@@ -168,7 +172,7 @@ class ShahidIE(ShahidBaseIE):
class ShahidShowIE(ShahidBaseIE):
_VALID_URL = r'https?://shahid\.mbc\.net/ar/(?:show|serie)s/[^/]+/(?:show|series)-(?P<id>\d+)'
_VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'(?:show|serie)s/[^/]+/(?:show|series)-(?P<id>\d+)'
_TESTS = [{
'url': 'https://shahid.mbc.net/ar/shows/%D8%B1%D8%A7%D9%85%D8%B2-%D9%82%D8%B1%D8%B4-%D8%A7%D9%84%D8%A8%D8%AD%D8%B1/show-79187',
'info_dict': {
+6 -3
View File
@@ -86,10 +86,10 @@ class SharedIE(SharedBaseIE):
class VivoIE(SharedBaseIE):
IE_DESC = 'vivo.sx'
_VALID_URL = r'https?://vivo\.sx/(?P<id>[\da-z]{10})'
_VALID_URL = r'https?://vivo\.s[xt]/(?P<id>[\da-z]{10})'
_FILE_NOT_FOUND = '>The file you have requested does not exists or has been removed'
_TEST = {
_TESTS = [{
'url': 'http://vivo.sx/d7ddda0e78',
'md5': '15b3af41be0b4fe01f4df075c2678b2c',
'info_dict': {
@@ -98,7 +98,10 @@ class VivoIE(SharedBaseIE):
'title': 'Chicken',
'filesize': 515659,
},
}
}, {
'url': 'http://vivo.st/d7ddda0e78',
'only_matching': True,
}]
def _extract_title(self, webpage):
title = self._html_search_regex(
+1 -1
View File
@@ -75,7 +75,7 @@ class SonyLIVIE(InfoExtractor):
video_id = self._match_id(url)
content = self._call_api(
'1.5', 'IN/CONTENT/VIDEOURL/VOD/' + video_id, video_id)
if not self._downloader.params.get('allow_unplayable_formats') and content.get('isEncrypted'):
if not self.get_param('allow_unplayable_formats') and content.get('isEncrypted'):
raise ExtractorError('This video is DRM protected.', expected=True)
dash_url = content['videoURL']
headers = {
+2 -2
View File
@@ -63,7 +63,7 @@ class SportDeutschlandIE(InfoExtractor):
if len(videos) > 1:
playlist_id = compat_parse_qs(compat_urllib_parse_urlparse(url).query).get('playlistId', [None])[0]
if playlist_id:
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
videos = [videos[int(playlist_id)]]
self.to_screen('Downloading just a single video because of --no-playlist')
else:
@@ -77,7 +77,7 @@ class SportDeutschlandIE(InfoExtractor):
continue
formats = self._extract_m3u8_formats(
video_url.replace('.smil', '.m3u8'), video_id, 'mp4', fatal=False)
if not formats and not self._downloader.params.get('ignore_no_formats'):
if not formats and not self.get_param('ignore_no_formats'):
continue
yield {
'id': video_id,
+1 -1
View File
@@ -139,7 +139,7 @@ class SteamIE(InfoExtractor):
'format_id': ext + quality,
'url': video_url,
})
if not formats and not self._downloader.params.get('ignore_no_formats'):
if not formats and not self.get_param('ignore_no_formats'):
continue
entry['formats'] = formats
entries.append(entry)
+53 -34
View File
@@ -1,70 +1,89 @@
# coding: utf-8
from __future__ import unicode_literals
from datetime import datetime
import base64
from .common import InfoExtractor
from ..utils import (
HEADRequest,
parse_age_limit,
parse_iso8601,
# smuggle_url,
urlencode_postdata,
)
class TenPlayIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?10play\.com\.au/(?:[^/]+/)+(?P<id>tpv\d{6}[a-z]{5})'
_NETRC_MACHINE = '10play'
_TESTS = [{
'url': 'https://10play.com.au/masterchef/episodes/season-1/masterchef-s1-ep-1/tpv190718kwzga',
'url': 'https://10play.com.au/todd-sampsons-body-hack/episodes/season-4/episode-7/tpv200921kvngh',
'info_dict': {
'id': '6060533435001',
'id': '6192880312001',
'ext': 'mp4',
'title': 'MasterChef - S1 Ep. 1',
'description': 'md5:4fe7b78e28af8f2d900cd20d900ef95c',
'age_limit': 10,
'timestamp': 1240828200,
'upload_date': '20090427',
'uploader_id': '2199827728001',
'title': "Todd Sampson's Body Hack - S4 Ep. 2",
'description': 'md5:fa278820ad90f08ea187f9458316ac74',
'age_limit': 15,
'timestamp': 1600770600,
'upload_date': '20200922',
'uploader': 'Channel 10',
'uploader_id': '2199827728001'
},
'params': {
# 'format': 'bestvideo',
'skip_download': True,
}
}, {
'url': 'https://10play.com.au/how-to-stay-married/web-extras/season-1/terrys-talks-ep-1-embracing-change/tpv190915ylupc',
'only_matching': True,
}]
# BRIGHTCOVE_URL_TEMPLATE = 'https://players.brightcove.net/2199827728001/cN6vRtRQt_default/index.html?videoId=%s'
_GEO_BYPASS = False
_FASTLY_URL_TEMPL = 'https://10-selector.global.ssl.fastly.net/s/kYEXFC/media/%s?mbr=true&manifest=m3u&format=redirect'
_AUS_AGES = {
'G': 0,
'PG': 15,
'M': 15,
'MA': 15,
'R': 18,
'X': 18
}
def _get_bearer_token(self, video_id):
username, password = self._get_login_info()
if username is None or password is None:
self.raise_login_required('Your 10play account\'s details must be provided with --username and --password.')
_timestamp = datetime.now().strftime('%Y%m%d000000')
_auth_header = base64.b64encode(_timestamp.encode('ascii')).decode('ascii')
data = self._download_json('https://10play.com.au/api/user/auth', video_id, 'Getting bearer token', headers={
'X-Network-Ten-Auth': _auth_header,
}, data=urlencode_postdata({
'email': username,
'password': password,
}))
return "Bearer " + data['jwt']['accessToken']
def _real_extract(self, url):
content_id = self._match_id(url)
_token = self._get_bearer_token(content_id)
data = self._download_json(
'https://10play.com.au/api/video/' + content_id, content_id)
video = data.get('video') or {}
metadata = data.get('metaData') or {}
brightcove_id = video.get('videoId') or metadata['showContentVideoId']
# brightcove_url = smuggle_url(
# self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id,
# {'geo_countries': ['AU']})
'https://10play.com.au/api/v1/videos/' + content_id, content_id)
_video_url = self._download_json(
data.get('playbackApiEndpoint'), content_id, 'Downloading video JSON',
headers={'Authorization': _token}).get('source')
m3u8_url = self._request_webpage(HEADRequest(
self._FASTLY_URL_TEMPL % brightcove_id), brightcove_id).geturl()
_video_url), content_id).geturl()
if '10play-not-in-oz' in m3u8_url:
self.raise_geo_restricted(countries=['AU'])
formats = self._extract_m3u8_formats(m3u8_url, brightcove_id, 'mp4')
formats = self._extract_m3u8_formats(m3u8_url, content_id, 'mp4')
self._sort_formats(formats)
return {
# '_type': 'url_transparent',
# 'url': brightcove_url,
'formats': formats,
'id': brightcove_id,
'title': video.get('title') or metadata.get('pageContentName') or metadata['showContentName'],
'description': video.get('description'),
'age_limit': parse_age_limit(video.get('showRatingClassification') or metadata.get('showProgramClassification')),
'series': metadata.get('showName'),
'season': metadata.get('showContentSeason'),
'timestamp': parse_iso8601(metadata.get('contentPublishDate') or metadata.get('pageContentPublishDate')),
'thumbnail': video.get('poster'),
'id': data.get('altId') or content_id,
'title': data.get('title'),
'description': data.get('description'),
'age_limit': self._AUS_AGES[data.get('classification')],
'series': data.get('showName'),
'season': data.get('showContentSeason'),
'timestamp': data.get('published'),
'thumbnail': data.get('imageUrl'),
'uploader': 'Channel 10',
'uploader_id': '2199827728001',
# 'ie_key': 'BrightcoveNew',
}
+1 -1
View File
@@ -153,7 +153,7 @@ class ToggleIE(InfoExtractor):
})
if not formats:
for meta in (info.get('Metas') or []):
if (not self._downloader.params.get('allow_unplayable_formats')
if (not self.get_param('allow_unplayable_formats')
and meta.get('Key') == 'Encryption' and meta.get('Value') == '1'):
self.raise_no_formats(
'This video is DRM protected.', expected=True)
+1 -1
View File
@@ -74,7 +74,7 @@ class TouTvIE(RadioCanadaIE):
})
# IsDrm does not necessarily mean the video is DRM protected (see
# https://github.com/ytdl-org/youtube-dl/issues/13994).
if not self._downloader.params.get('allow_unplayable_formats') and metadata.get('IsDrm'):
if not self.get_param('allow_unplayable_formats') and metadata.get('IsDrm'):
self.report_warning('This video is probably DRM protected.', path)
video_id = metadata['IdMedia']
details = metadata['Details']
+1 -1
View File
@@ -69,7 +69,7 @@ class TVNowBaseIE(InfoExtractor):
if formats:
break
else:
if not self._downloader.params.get('allow_unplayable_formats') and info.get('isDrm'):
if not self.get_param('allow_unplayable_formats') and info.get('isDrm'):
raise ExtractorError(
'Video %s is DRM protected' % video_id, expected=True)
if info.get('geoblocked'):
+1 -1
View File
@@ -59,7 +59,7 @@ class TwitCastingIE(InfoExtractor):
def _real_extract(self, url):
uploader_id, video_id = re.match(self._VALID_URL, url).groups()
video_password = self._downloader.params.get('videopassword')
video_password = self.get_param('videopassword')
request_data = None
if video_password:
request_data = urlencode_postdata({
+1 -1
View File
@@ -324,7 +324,7 @@ class VikiIE(VikiBaseIE):
# Despite CODECS metadata in m3u8 all video-only formats
# are actually video+audio
for f in m3u8_formats:
if not self._downloader.params.get('allow_unplayable_formats') and '_drm/index_' in f['url']:
if not self.get_param('allow_unplayable_formats') and '_drm/index_' in f['url']:
continue
if f.get('acodec') == 'none' and f.get('vcodec') != 'none':
f['acodec'] = None
+3 -3
View File
@@ -76,7 +76,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
raise ExtractorError('Unable to log in')
def _get_video_password(self):
password = self._downloader.params.get('videopassword')
password = self.get_param('videopassword')
if password is None:
raise ExtractorError(
'This video is protected by a password, use the --video-password option',
@@ -603,7 +603,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
album_id, headers={'Authorization': 'jwt ' + jwt},
query={'fields': 'description,name,privacy'})
if try_get(album, lambda x: x['privacy']['view']) == 'password':
password = self._downloader.params.get('videopassword')
password = self.get_param('videopassword')
if not password:
raise ExtractorError(
'This album is protected by a password, use the --video-password option',
@@ -1058,7 +1058,7 @@ class VimeoAlbumIE(VimeoBaseInfoExtractor):
query={'fields': 'description,name,privacy'})
hashed_pass = None
if try_get(album, lambda x: x['privacy']['view']) == 'password':
password = self._downloader.params.get('videopassword')
password = self.get_param('videopassword')
if not password:
raise ExtractorError(
'This album is protected by a password, use the --video-password option',
+2 -2
View File
@@ -287,7 +287,7 @@ class ViuOTTIE(InfoExtractor):
raise ExtractorError('This video is not available in your region.', expected=True)
series_id = video_data.get('series_id')
if not self._downloader.params.get('noplaylist') and not idata.get('force_noplaylist'):
if not self.get_param('noplaylist') and not idata.get('force_noplaylist'):
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % series_id)
series = product_data.get('series', {})
product = series.get('product')
@@ -308,7 +308,7 @@ class ViuOTTIE(InfoExtractor):
return self.playlist_result(entries, series_id, series.get('name'), series.get('description'))
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
duration_limit = False
+11
View File
@@ -300,6 +300,13 @@ class VKIE(VKBaseIE):
'only_matching': True,
}]
@staticmethod
def _extract_sibnet_urls(webpage):
# https://help.sibnet.ru/?sibnet_video_embed
return [unescapeHTML(mobj.group('url')) for mobj in re.finditer(
r'<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//video\.sibnet\.ru/shell\.php\?.*?\bvideoid=\d+.*?)\1',
webpage)]
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
video_id = mobj.group('videoid')
@@ -408,6 +415,10 @@ class VKIE(VKBaseIE):
if odnoklassniki_url:
return self.url_result(odnoklassniki_url, OdnoklassnikiIE.ie_key())
sibnet_urls = self._extract_sibnet_urls(info_page)
if sibnet_urls:
return self.url_result(sibnet_urls[0])
m_opts = re.search(r'(?s)var\s+opts\s*=\s*({.+?});', info_page)
if m_opts:
m_opts_url = re.search(r"url\s*:\s*'((?!/\b)[^']+)", m_opts.group(1))
+1 -1
View File
@@ -136,7 +136,7 @@ class VLiveIE(VLiveBaseIE):
'author{nickname},channel{channelCode,channelName},officialVideo{commentCount,exposeStatus,likeCount,playCount,playTime,status,title,type,vodId},playlist{playlistSeq,totalCount,name}')
playlist = post.get('playlist')
if not playlist or self._downloader.params.get('noplaylist'):
if not playlist or self.get_param('noplaylist'):
if playlist:
self.to_screen(
'Downloading just video %s because of --no-playlist'
+1 -1
View File
@@ -41,7 +41,7 @@ class WakanimIE(InfoExtractor):
m3u8_url = urljoin(url, self._search_regex(
r'file\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage, 'm3u8 url',
group='url'))
if not self._downloader.params.get('allow_unplayable_formats'):
if not self.get_param('allow_unplayable_formats'):
# https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-content-protection-overview#streaming-urls
encryption = self._search_regex(
r'encryption%3D(c(?:enc|bc(?:s-aapl)?))',
+1 -1
View File
@@ -160,7 +160,7 @@ class YoukuIE(InfoExtractor):
'client_ts': time.time() / 1000,
}
video_password = self._downloader.params.get('videopassword')
video_password = self.get_param('videopassword')
if video_password:
basic_data_params['password'] = video_password
+356 -140
View File
@@ -67,7 +67,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
_TFA_URL = 'https://accounts.google.com/_/signin/challenge?hl=en&TL={0}'
_RESERVED_NAMES = (
r'channel|c|user|playlist|watch|w|v|embed|e|watch_popup|'
r'channel|c|user|browse|playlist|watch|w|v|embed|e|watch_popup|'
r'movies|results|shared|hashtag|trending|feed|feeds|oembed|'
r'storefront|oops|index|account|reporthistory|t/terms|about|upload|signin|logout')
@@ -85,12 +85,25 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised.
"""
def warn(message):
self.report_warning(message)
# username+password login is broken
if self._LOGIN_REQUIRED and self.get_param('cookiefile') is None:
self.raise_login_required(
'Login details are needed to download this content', method='cookies')
username, password = self._get_login_info()
if username:
warn('Logging in using username and password is broken. %s' % self._LOGIN_HINTS['cookies'])
return
# Everything below this is broken!
# No authentication to be performed
if username is None:
if self._LOGIN_REQUIRED and self._downloader.params.get('cookiefile') is None:
if self._LOGIN_REQUIRED and self.get_param('cookiefile') is None:
raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
# if self._downloader.params.get('cookiefile'): # TODO remove 'and False' later - too many people using outdated cookies and open issues, remind them.
# if self.get_param('cookiefile'): # TODO remove 'and False' later - too many people using outdated cookies and open issues, remind them.
# self.to_screen('[Cookies] Reminder - Make sure to always use up to date cookies!')
return True
@@ -126,9 +139,6 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
'Google-Accounts-XSRF': 1,
})
def warn(message):
self.report_warning(message)
lookup_req = [
username,
None, [], None, 'US', None, None, 2, False, True,
@@ -398,6 +408,10 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
headers['X-Origin'] = 'https://www.youtube.com'
return headers
@staticmethod
def is_music_url(url):
return re.match(r'https?://music\.youtube\.com/', url) is not None
def _extract_video(self, renderer):
video_id = renderer.get('videoId')
title = try_get(
@@ -521,7 +535,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
)? # all until now is optional -> you can pass the naked ID
(?P<id>[0-9A-Za-z_-]{11}) # here is it! the YouTube video ID
(?(1).+)? # if we found the ID, everything can follow
$""" % {
(?:\#|$)""" % {
'invidious': '|'.join(_INVIDIOUS_SITES),
}
_PLAYER_INFO_RE = (
@@ -1303,6 +1317,18 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'params': {
'skip_download': True,
},
}, {
# Has multiple audio streams
'url': 'WaOKSUlf4TM',
'only_matching': True
}, {
# Requires Premium: has format 141 when requested using YTM url
'url': 'https://music.youtube.com/watch?v=XclachpHxis',
'only_matching': True
}, {
# multiple subtitles with same lang_code
'url': 'https://www.youtube.com/watch?v=wsQiKKfKxug',
'only_matching': True,
},
]
@@ -1444,7 +1470,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
)
self._player_cache[player_id] = func
func = self._player_cache[player_id]
if self._downloader.params.get('youtube_print_sig_code'):
if self.get_param('youtube_print_sig_code'):
self._print_sig_code(func, s)
return func(s)
except Exception as e:
@@ -1674,7 +1700,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if not continuation:
break
headers = self._generate_api_headers(ytcfg, identity_token, account_syncid, visitor_data)
retries = self._downloader.params.get('extractor_retries', 3)
retries = self.get_param('extractor_retries', 3)
count = -1
last_error = None
@@ -1843,11 +1869,47 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
def _real_extract(self, url):
url, smuggled_data = unsmuggle_url(url, {})
video_id = self._match_id(url)
is_music_url = smuggled_data.get('is_music_url') or self.is_music_url(url)
base_url = self.http_scheme() + '//www.youtube.com/'
webpage_url = base_url + 'watch?v=' + video_id
webpage = self._download_webpage(
webpage_url + '&bpctr=9999999999&has_verified=1', video_id, fatal=False)
def get_text(x):
if not x:
return
text = x.get('simpleText')
if text and isinstance(text, compat_str):
return text
runs = x.get('runs')
if not isinstance(runs, list):
return
return ''.join([r['text'] for r in runs if isinstance(r.get('text'), compat_str)])
ytm_streaming_data = {}
if is_music_url:
# we are forcing to use parse_json because 141 only appeared in get_video_info.
# el, c, cver, cplayer field required for 141(aac 256kbps) codec
# maybe paramter of youtube music player?
ytm_player_response = self._parse_json(try_get(compat_parse_qs(
self._download_webpage(
base_url + 'get_video_info', video_id,
'Fetching youtube music info webpage',
'unable to download youtube music info webpage', query={
'video_id': video_id,
'eurl': 'https://youtube.googleapis.com/v/' + video_id,
'el': 'detailpage',
'c': 'WEB_REMIX',
'cver': '0.1',
'cplayer': 'UNIPLAYER',
'html5': '1',
}, fatal=False)),
lambda x: x['player_response'][0],
compat_str) or '{}', video_id)
ytm_streaming_data = ytm_player_response.get('streamingData') or {}
player_response = None
if webpage:
player_response = self._extract_yt_initial_variable(
@@ -1868,6 +1930,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'unable to download video info webpage', query={
'video_id': video_id,
'eurl': 'https://youtube.googleapis.com/v/' + video_id,
'html5': '1',
}, fatal=False)),
lambda x: x['player_response'][0],
compat_str) or '{}', video_id)
@@ -1882,17 +1945,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
return self.url_result(
trailer_video_id, self.ie_key(), trailer_video_id)
def get_text(x):
if not x:
return
text = x.get('simpleText')
if text and isinstance(text, compat_str):
return text
runs = x.get('runs')
if not isinstance(runs, list):
return
return ''.join([r['text'] for r in runs if isinstance(r.get('text'), compat_str)])
search_meta = (
lambda x: self._html_search_meta(x, webpage, default=None)) \
if webpage else lambda x: None
@@ -1908,7 +1960,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
video_description = video_details.get('shortDescription')
if not smuggled_data.get('force_singlefeed', False):
if not self._downloader.params.get('noplaylist'):
if not self.get_param('noplaylist'):
multifeed_metadata_list = try_get(
player_response,
lambda x: x['multicamera']['playerLegacyMulticameraRenderer']['metadataList'],
@@ -1951,20 +2003,33 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
else:
self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
formats = []
itags = []
formats, itags, stream_ids = [], [], []
itag_qualities = {}
player_url = None
q = qualities(['tiny', 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'])
q = qualities([
'tiny', 'audio_quality_low', 'audio_quality_medium', 'audio_quality_high', # Audio only formats
'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'
])
streaming_data = player_response.get('streamingData') or {}
streaming_formats = streaming_data.get('formats') or []
streaming_formats.extend(streaming_data.get('adaptiveFormats') or [])
streaming_formats.extend(ytm_streaming_data.get('formats') or [])
streaming_formats.extend(ytm_streaming_data.get('adaptiveFormats') or [])
for fmt in streaming_formats:
if fmt.get('targetDurationSec') or fmt.get('drmFamilies'):
continue
itag = str_or_none(fmt.get('itag'))
audio_track = fmt.get('audioTrack') or {}
stream_id = '%s.%s' % (itag or '', audio_track.get('id', ''))
if stream_id in stream_ids:
continue
quality = fmt.get('quality')
if quality == 'tiny' or not quality:
quality = fmt.get('audioQuality', '').lower() or quality
if itag and quality:
itag_qualities[itag] = quality
# FORMAT_STREAM_TYPE_OTF(otf=1) requires downloading the init fragment
@@ -1994,19 +2059,22 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if itag:
itags.append(itag)
stream_ids.append(stream_id)
tbr = float_or_none(
fmt.get('averageBitrate') or fmt.get('bitrate'), 1000)
dct = {
'asr': int_or_none(fmt.get('audioSampleRate')),
'filesize': int_or_none(fmt.get('contentLength')),
'format_id': itag,
'format_note': fmt.get('qualityLabel') or quality,
'format_note': audio_track.get('displayName') or fmt.get('qualityLabel') or quality,
'fps': int_or_none(fmt.get('fps')),
'height': int_or_none(fmt.get('height')),
'quality': q(quality),
'tbr': tbr,
'url': fmt_url,
'width': fmt.get('width'),
'language': audio_track.get('id', '').split('.')[0],
}
mimetype = fmt.get('mimeType')
if mimetype:
@@ -2030,38 +2098,37 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
dct['container'] = dct['ext'] + '_dash'
formats.append(dct)
hls_manifest_url = streaming_data.get('hlsManifestUrl')
if hls_manifest_url:
for f in self._extract_m3u8_formats(
hls_manifest_url, video_id, 'mp4', fatal=False):
itag = self._search_regex(
r'/itag/(\d+)', f['url'], 'itag', default=None)
if itag:
f['format_id'] = itag
for sd in (streaming_data, ytm_streaming_data):
hls_manifest_url = sd.get('hlsManifestUrl')
if hls_manifest_url:
for f in self._extract_m3u8_formats(
hls_manifest_url, video_id, 'mp4', fatal=False):
itag = self._search_regex(
r'/itag/(\d+)', f['url'], 'itag', default=None)
if itag:
f['format_id'] = itag
formats.append(f)
if self._downloader.params.get('youtube_include_dash_manifest', True):
dash_manifest_url = streaming_data.get('dashManifestUrl')
if dash_manifest_url:
for f in self._extract_mpd_formats(
dash_manifest_url, video_id, fatal=False):
itag = f['format_id']
if itag in itags:
continue
if itag in itag_qualities:
# Not actually usefull since the sorting is already done with "quality,res,fps,codec"
# but kept to maintain feature parity (and code similarity) with youtube-dl
# Remove if this causes any issues with sorting in future
f['quality'] = q(itag_qualities[itag])
filesize = int_or_none(self._search_regex(
r'/clen/(\d+)', f.get('fragment_base_url')
or f['url'], 'file size', default=None))
if filesize:
f['filesize'] = filesize
formats.append(f)
if self.get_param('youtube_include_dash_manifest', True):
for sd in (streaming_data, ytm_streaming_data):
dash_manifest_url = sd.get('dashManifestUrl')
if dash_manifest_url:
for f in self._extract_mpd_formats(
dash_manifest_url, video_id, fatal=False):
itag = f['format_id']
if itag in itags:
continue
if itag in itag_qualities:
f['quality'] = q(itag_qualities[itag])
filesize = int_or_none(self._search_regex(
r'/clen/(\d+)', f.get('fragment_base_url')
or f['url'], 'file size', default=None))
if filesize:
f['filesize'] = filesize
formats.append(f)
if not formats:
if not self._downloader.params.get('allow_unplayable_formats') and streaming_data.get('licenseInfos'):
if not self.get_param('allow_unplayable_formats') and streaming_data.get('licenseInfos'):
self.raise_no_formats(
'This video is DRM protected.', expected=True)
pemr = try_get(
@@ -2116,16 +2183,24 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if 'maxresdefault' in thumbnail_url:
thumbnail_url = thumbnail_url.split('?')[0]
thumbnails.append({
'height': int_or_none(thumbnail.get('height')),
'url': thumbnail_url,
'height': int_or_none(thumbnail.get('height')),
'width': int_or_none(thumbnail.get('width')),
'preference': 1 if 'maxresdefault' in thumbnail_url else -1
})
if thumbnails:
break
else:
thumbnail = search_meta(['og:image', 'twitter:image'])
if thumbnail:
thumbnails = [{'url': thumbnail}]
thumbnail_url = search_meta(['og:image', 'twitter:image'])
if thumbnail_url:
thumbnails.append({
'url': thumbnail_url,
'preference': 1 if 'maxresdefault' in thumbnail_url else -1
})
# All videos have a maxresdefault thumbnail, but sometimes it does not appear in the webpage
# See: https://github.com/ytdl-org/youtube-dl/issues/29049
thumbnails.append({
'url': 'https://i.ytimg.com/vi/%s/maxresdefault.jpg' % video_id,
'preference': 1,
})
self._remove_duplicate_formats(thumbnails)
category = microformat.get('category') or search_meta('genre')
channel_id = video_details.get('channelId') \
@@ -2175,8 +2250,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
lambda x: x['captions']['playerCaptionsTracklistRenderer'], dict)
subtitles = {}
if pctr:
def process_language(container, base_url, lang_code, query):
lang_subs = []
def process_language(container, base_url, lang_code, sub_name, query):
lang_subs = container.setdefault(lang_code, [])
for fmt in self._SUBTITLE_FORMATS:
query.update({
'fmt': fmt,
@@ -2184,19 +2259,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
lang_subs.append({
'ext': fmt,
'url': update_url_query(base_url, query),
'name': sub_name,
})
container[lang_code] = lang_subs
for caption_track in (pctr.get('captionTracks') or []):
base_url = caption_track.get('baseUrl')
if not base_url:
continue
if caption_track.get('kind') != 'asr':
lang_code = caption_track.get('languageCode')
lang_code = (
remove_start(caption_track.get('vssId') or '', '.').replace('.', '-')
or caption_track.get('languageCode'))
if not lang_code:
continue
process_language(
subtitles, base_url, lang_code, {})
subtitles, base_url, lang_code,
try_get(caption_track, lambda x: x.get('name').get('simpleText')),
{})
continue
automatic_captions = {}
for translation_language in (pctr.get('translationLanguages') or []):
@@ -2205,6 +2284,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
continue
process_language(
automatic_captions, base_url, translation_language_code,
try_get(translation_language, lambda x: x['languageName']['simpleText']),
{'tlang': translation_language_code})
info['automatic_captions'] = automatic_captions
info['subtitles'] = subtitles
@@ -2415,8 +2495,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
is_unlisted=None if is_private is None else is_unlisted)
# get xsrf for annotations or comments
get_annotations = self._downloader.params.get('writeannotations', False)
get_comments = self._downloader.params.get('getcomments', False)
get_annotations = self.get_param('writeannotations', False)
get_comments = self.get_param('getcomments', False)
if get_annotations or get_comments:
xsrf_token = None
ytcfg = self._extract_ytcfg(video_id, webpage)
@@ -2464,7 +2544,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
invidio\.us
)/
(?:
(?:channel|c|user)/|
(?P<channel_type>channel|c|user|browse)/|
(?P<not_channel>
feed/|hashtag/|
(?:playlist|watch)\?.*?\blist=
@@ -2476,7 +2556,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
IE_NAME = 'youtube:tab'
_TESTS = [{
# playlists, multipage
'note': 'playlists, multipage',
'url': 'https://www.youtube.com/c/ИгорьКлейнер/playlists?view=1&flow=grid',
'playlist_mincount': 94,
'info_dict': {
@@ -2487,7 +2567,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'uploader_id': 'UCqj7Cz7revf5maW9g5pgNcg',
},
}, {
# playlists, multipage, different order
'note': 'playlists, multipage, different order',
'url': 'https://www.youtube.com/user/igorkle1/playlists?view=1&sort=dd',
'playlist_mincount': 94,
'info_dict': {
@@ -2498,16 +2578,18 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'uploader': 'Игорь Клейнер',
},
}, {
# playlists, series
'note': 'playlists, series',
'url': 'https://www.youtube.com/c/3blue1brown/playlists?view=50&sort=dd&shelf_id=3',
'playlist_mincount': 5,
'info_dict': {
'id': 'UCYO_jab_esuFRV4b17AJtAw',
'title': '3Blue1Brown - Playlists',
'description': 'md5:e1384e8a133307dd10edee76e875d62f',
'uploader_id': 'UCYO_jab_esuFRV4b17AJtAw',
'uploader': '3Blue1Brown',
},
}, {
# playlists, singlepage
'note': 'playlists, singlepage',
'url': 'https://www.youtube.com/user/ThirstForScience/playlists',
'playlist_mincount': 4,
'info_dict': {
@@ -2521,7 +2603,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'url': 'https://www.youtube.com/c/ChristophLaimer/playlists',
'only_matching': True,
}, {
# basic, single video playlist
'note': 'basic, single video playlist',
'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
'info_dict': {
'uploader_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
@@ -2531,7 +2613,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_count': 1,
}, {
# empty playlist
'note': 'empty playlist',
'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFodcLWhDpGCYnngnHtQ-Xf',
'info_dict': {
'uploader_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
@@ -2541,7 +2623,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_count': 0,
}, {
# Home tab
'note': 'Home tab',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/featured',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -2552,7 +2634,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_mincount': 2,
}, {
# Videos tab
'note': 'Videos tab',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/videos',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -2563,7 +2645,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_mincount': 975,
}, {
# Videos tab, sorted by popular
'note': 'Videos tab, sorted by popular',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/videos?view=0&sort=p&flow=grid',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -2574,7 +2656,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_mincount': 199,
}, {
# Playlists tab
'note': 'Playlists tab',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/playlists',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -2585,7 +2667,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_mincount': 17,
}, {
# Community tab
'note': 'Community tab',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/community',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -2596,7 +2678,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_mincount': 18,
}, {
# Channels tab
'note': 'Channels tab',
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/channels',
'info_dict': {
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -2606,6 +2688,17 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'uploader_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
},
'playlist_mincount': 12,
}, {
'note': 'Search tab',
'url': 'https://www.youtube.com/c/3blue1brown/search?query=linear%20algebra',
'playlist_mincount': 40,
'info_dict': {
'id': 'UCYO_jab_esuFRV4b17AJtAw',
'title': '3Blue1Brown - Search - linear algebra',
'description': 'md5:e1384e8a133307dd10edee76e875d62f',
'uploader': '3Blue1Brown',
'uploader_id': 'UCYO_jab_esuFRV4b17AJtAw',
},
}, {
'url': 'https://invidio.us/channel/UCmlqkdCBesrv2Lak1mF_MxA',
'only_matching': True,
@@ -2637,7 +2730,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
},
'playlist_mincount': 1123,
}, {
# even larger playlist, 8832 videos
'note': 'even larger playlist, 8832 videos',
'url': 'http://www.youtube.com/user/NASAgovVideo/videos',
'only_matching': True,
}, {
@@ -2659,12 +2752,9 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'uploader': 'Phim Siêu Nhân Nhật Bản',
'uploader_id': 'UCTYLiWFZy8xtPwxFwX9rV7Q',
},
'playlist_mincount': 1400,
'expected_warnings': [
'YouTube said: INFO - Unavailable videos are hidden',
]
'playlist_mincount': 200,
}, {
'note': 'Playlist with unavailable videos in a later page',
'note': 'Playlist with unavailable videos in page 7',
'url': 'https://www.youtube.com/playlist?list=UU8l9frL61Yl5KFOl87nIm2w',
'info_dict': {
'title': 'Uploads from BlankTV',
@@ -2672,9 +2762,9 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'uploader': 'BlankTV',
'uploader_id': 'UC8l9frL61Yl5KFOl87nIm2w',
},
'playlist_mincount': 20000,
'playlist_mincount': 1000,
}, {
# https://github.com/ytdl-org/youtube-dl/issues/21844
'note': 'https://github.com/ytdl-org/youtube-dl/issues/21844',
'url': 'https://www.youtube.com/playlist?list=PLzH6n4zXuckpfMu_4Ff8E7Z1behQks5ba',
'info_dict': {
'title': 'Data Analysis with Dr Mike Pound',
@@ -2688,7 +2778,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'url': 'https://invidio.us/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
'only_matching': True,
}, {
# Playlist URL that does not actually serve a playlist
'note': 'Playlist URL that does not actually serve a playlist',
'url': 'https://www.youtube.com/watch?v=FqZTN594JQw&list=PLMYEtVRpaqY00V9W81Cwmzp6N6vZqfUKD4',
'info_dict': {
'id': 'FqZTN594JQw',
@@ -2720,14 +2810,14 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
}, {
'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live',
'info_dict': {
'id': '9Auq9mYxFEE',
'id': 'X1whbWASnNQ', # This will keep changing
'ext': 'mp4',
'title': compat_str,
'uploader': 'Sky News',
'uploader_id': 'skynews',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/skynews',
'upload_date': '20191102',
'description': 'md5:85ddd75d888674631aaf9599a9a0b0ae',
'upload_date': r're:\d{8}',
'description': compat_str,
'categories': ['News & Politics'],
'tags': list,
'like_count': int,
@@ -2736,6 +2826,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'params': {
'skip_download': True,
},
'expected_warnings': ['Downloading just video ', 'Ignoring subtitle tracks found in '],
}, {
'url': 'https://www.youtube.com/user/TheYoungTurks/live',
'info_dict': {
@@ -2767,27 +2858,23 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'url': 'https://www.youtube.com/feed/trending',
'only_matching': True,
}, {
# needs auth
'url': 'https://www.youtube.com/feed/library',
'only_matching': True,
}, {
# needs auth
'url': 'https://www.youtube.com/feed/history',
'only_matching': True,
}, {
# needs auth
'url': 'https://www.youtube.com/feed/subscriptions',
'only_matching': True,
}, {
# needs auth
'url': 'https://www.youtube.com/feed/watch_later',
'only_matching': True,
}, {
# no longer available?
'note': 'Recommended - redirects to home page',
'url': 'https://www.youtube.com/feed/recommended',
'only_matching': True,
}, {
# inline playlist with not always working continuations
'note': 'inline playlist with not always working continuations',
'url': 'https://www.youtube.com/watch?v=UC6u0Tct-Fo&list=PL36D642111D65BE7C',
'only_matching': True,
}, {
@@ -2815,6 +2902,60 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
}, {
'url': 'https://www.youtube.com/watch?list=PLW4dVinRY435CBE_JD3t-0SRXKfnZHS1P&feature=youtu.be&v=M9cJMXmQ_ZU',
'only_matching': True,
}, {
'note': 'Requires Premium: should request additional YTM-info webpage (and have format 141) for videos in playlist',
'url': 'https://music.youtube.com/playlist?list=PLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
'only_matching': True
}, {
'note': '/browse/ should redirect to /channel/',
'url': 'https://music.youtube.com/browse/UC1a8OFewdjuLq6KlF8M_8Ng',
'only_matching': True
}, {
'note': 'VLPL, should redirect to playlist?list=PL...',
'url': 'https://music.youtube.com/browse/VLPLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
'info_dict': {
'id': 'PLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
'uploader': 'NoCopyrightSounds',
'description': 'Providing you with copyright free / safe music for gaming, live streaming, studying and more!',
'uploader_id': 'UC_aEa8K-EOJ3D6gOs7HcyNg',
'title': 'NCS Releases',
},
'playlist_mincount': 166,
}, {
'note': 'Topic, should redirect to playlist?list=UU...',
'url': 'https://music.youtube.com/browse/UC9ALqqC4aIeG5iDs7i90Bfw',
'info_dict': {
'id': 'UU9ALqqC4aIeG5iDs7i90Bfw',
'uploader_id': 'UC9ALqqC4aIeG5iDs7i90Bfw',
'title': 'Uploads from Royalty Free Music - Topic',
'uploader': 'Royalty Free Music - Topic',
},
'expected_warnings': [
'A channel/user page was given',
'The URL does not have a videos tab',
],
'playlist_mincount': 101,
}, {
'note': 'Topic without a UU playlist',
'url': 'https://www.youtube.com/channel/UCtFRv9O2AHqOZjjynzrv-xg',
'info_dict': {
'id': 'UCtFRv9O2AHqOZjjynzrv-xg',
'title': 'UCtFRv9O2AHqOZjjynzrv-xg',
},
'expected_warnings': [
'A channel/user page was given',
'The URL does not have a videos tab',
'Falling back to channel URL',
],
'playlist_mincount': 9,
}, {
'note': 'Youtube music Album',
'url': 'https://music.youtube.com/browse/MPREb_gTAcphH99wE',
'info_dict': {
'id': 'OLAK5uy_l1m0thk3g31NmIIz_vMIbWtyv7eZixlH0',
'title': 'Album - Royalty Free Music Library V2 (50 Songs)',
},
'playlist_count': 50,
}]
@classmethod
@@ -3176,8 +3317,9 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
@staticmethod
def _extract_selected_tab(tabs):
for tab in tabs:
if try_get(tab, lambda x: x['tabRenderer']['selected'], bool):
return tab['tabRenderer']
renderer = dict_get(tab, ('tabRenderer', 'expandableTabRenderer')) or {}
if renderer.get('selected') is True:
return renderer
else:
raise ExtractorError('Unable to find selected tab')
@@ -3251,6 +3393,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
try_get(data, lambda x: x['header']['hashtagHeaderRenderer']['hashtag']['simpleText'])
or playlist_id)
title += format_field(selected_tab, 'title', ' - %s')
title += format_field(selected_tab, 'expandedText', ' - %s')
metadata = {
'playlist_id': playlist_id,
@@ -3333,27 +3476,27 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
self._extract_mix_playlist(playlist, playlist_id, data, webpage),
playlist_id=playlist_id, playlist_title=title)
def _extract_alerts(self, data, expected=False):
def _real_extract_alerts():
for alert_dict in try_get(data, lambda x: x['alerts'], list) or []:
if not isinstance(alert_dict, dict):
@staticmethod
def _extract_alerts(data):
for alert_dict in try_get(data, lambda x: x['alerts'], list) or []:
if not isinstance(alert_dict, dict):
continue
for alert in alert_dict.values():
alert_type = alert.get('type')
if not alert_type:
continue
for alert in alert_dict.values():
alert_type = alert.get('type')
if not alert_type:
continue
message = try_get(alert, lambda x: x['text']['simpleText'], compat_str) or ''
if message:
yield alert_type, message
for run in try_get(alert, lambda x: x['text']['runs'], list) or []:
message += try_get(run, lambda x: x['text'], compat_str)
if message:
yield alert_type, message
message = try_get(alert, lambda x: x['text']['simpleText'], compat_str) or ''
if message:
yield alert_type, message
for run in try_get(alert, lambda x: x['text']['runs'], list) or []:
message += try_get(run, lambda x: x['text'], compat_str)
if message:
yield alert_type, message
def _report_alerts(self, alerts, expected=True):
errors = []
warnings = []
for alert_type, alert_message in _real_extract_alerts():
for alert_type, alert_message in alerts:
if alert_type.lower() == 'error':
errors.append([alert_type, alert_message])
else:
@@ -3364,6 +3507,9 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
if errors:
raise ExtractorError('YouTube said: %s' % errors[-1][1], expected=expected)
def _extract_and_report_alerts(self, data, *args, **kwargs):
return self._report_alerts(self._extract_alerts(data), *args, **kwargs)
def _reload_with_unavailable_videos(self, item_id, data, webpage):
"""
Get playlist with unavailable videos if the 'show unavailable videos' button exists.
@@ -3413,7 +3559,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
response = None
last_error = None
count = -1
retries = self._downloader.params.get('extractor_retries', 3)
retries = self.get_param('extractor_retries', 3)
if check_get_keys is None:
check_get_keys = []
while count < retries:
@@ -3442,7 +3588,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
else:
# Youtube may send alerts if there was an issue with the continuation page
self._extract_alerts(response, expected=False)
self._extract_and_report_alerts(response, expected=False)
if not check_get_keys or dict_get(response, check_get_keys):
break
# Youtube sometimes sends incomplete data
@@ -3457,7 +3603,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
return response
def _extract_webpage(self, url, item_id):
retries = self._downloader.params.get('extractor_retries', 3)
retries = self.get_param('extractor_retries', 3)
count = -1
last_error = 'Incomplete yt initial data recieved'
while count < retries:
@@ -3470,52 +3616,124 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
url, item_id,
'Downloading webpage%s' % (' (retry #%d)' % count if count else ''))
data = self._extract_yt_initial_data(item_id, webpage)
self._extract_alerts(data, expected=True)
if data.get('contents') or data.get('currentVideoEndpoint'):
break
# Extract alerts here only when there is error
self._extract_and_report_alerts(data)
if count >= retries:
raise ExtractorError(last_error)
return webpage, data
@staticmethod
def _smuggle_data(entries, data):
for entry in entries:
if data:
entry['url'] = smuggle_url(entry['url'], data)
yield entry
def _real_extract(self, url):
url, smuggled_data = unsmuggle_url(url, {})
if self.is_music_url(url):
smuggled_data['is_music_url'] = True
info_dict = self.__real_extract(url, smuggled_data)
if info_dict.get('entries'):
info_dict['entries'] = self._smuggle_data(info_dict['entries'], smuggled_data)
return info_dict
_url_re = re.compile(r'(?P<pre>%s)(?(channel_type)(?P<tab>/\w+))?(?P<post>.*)$' % _VALID_URL)
def __real_extract(self, url, smuggled_data):
item_id = self._match_id(url)
url = compat_urlparse.urlunparse(
compat_urlparse.urlparse(url)._replace(netloc='www.youtube.com'))
compat_opts = self._downloader.params.get('compat_opts', [])
compat_opts = self.get_param('compat_opts', [])
# This is not matched in a channel page with a tab selected
mobj = re.match(r'(?P<pre>%s)(?P<post>/?(?![^#?]).*$)' % self._VALID_URL, url)
mobj = mobj.groupdict() if mobj else {}
if mobj and not mobj.get('not_channel') and 'no-youtube-channel-redirect' not in compat_opts:
def get_mobj(url):
mobj = self._url_re.match(url).groupdict()
mobj.update((k, '') for k, v in mobj.items() if v is None)
return mobj
mobj = get_mobj(url)
# Youtube returns incomplete data if tabname is not lower case
pre, tab, post, is_channel = mobj['pre'], mobj['tab'].lower(), mobj['post'], not mobj['not_channel']
if is_channel:
if smuggled_data.get('is_music_url'):
if item_id[:2] == 'VL':
# Youtube music VL channels have an equivalent playlist
item_id = item_id[2:]
pre, tab, post, is_channel = 'https://www.youtube.com/playlist?list=%s' % item_id, '', '', False
elif item_id[:2] == 'MP':
# Youtube music albums (/channel/MP...) have a OLAK playlist that can be extracted from the webpage
item_id = self._search_regex(
r'\\x22audioPlaylistId\\x22:\\x22([0-9A-Za-z_-]+)\\x22',
self._download_webpage('https://music.youtube.com/channel/%s' % item_id, item_id),
'playlist id')
pre, tab, post, is_channel = 'https://www.youtube.com/playlist?list=%s' % item_id, '', '', False
elif mobj['channel_type'] == 'browse':
# Youtube music /browse/ should be changed to /channel/
pre = 'https://www.youtube.com/channel/%s' % item_id
if is_channel and not tab and 'no-youtube-channel-redirect' not in compat_opts:
# Home URLs should redirect to /videos/
self.report_warning(
'A channel/user page was given. All the channel\'s videos will be downloaded. '
'To download only the videos in the home page, add a "/featured" to the URL')
url = '%s/videos%s' % (mobj.get('pre'), mobj.get('post') or '')
tab = '/videos'
url = ''.join((pre, tab, post))
mobj = get_mobj(url)
# Handle both video/playlist URLs
qs = parse_qs(url)
video_id = qs.get('v', [None])[0]
playlist_id = qs.get('list', [None])[0]
if not video_id and (mobj.get('not_channel') or '').startswith('watch'):
if not video_id and mobj['not_channel'].startswith('watch'):
if not playlist_id:
# If there is neither video or playlist ids,
# youtube redirects to home page, which is undesirable
# If there is neither video or playlist ids, youtube redirects to home page, which is undesirable
raise ExtractorError('Unable to recognize tab page')
# Common mistake: https://www.youtube.com/watch?list=playlist_id
self.report_warning('A video URL was given without video ID. Trying to download playlist %s' % playlist_id)
url = 'https://www.youtube.com/playlist?list=%s' % playlist_id
mobj = get_mobj(url)
if video_id and playlist_id:
if self._downloader.params.get('noplaylist'):
if self.get_param('noplaylist'):
self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
return self.url_result(video_id, ie=YoutubeIE.ie_key(), video_id=video_id)
self.to_screen('Downloading playlist %s; add --no-playlist to just download video %s' % (playlist_id, video_id))
webpage, data = self._extract_webpage(url, item_id)
tabs = try_get(
data, lambda x: x['contents']['twoColumnBrowseResultsRenderer']['tabs'], list)
if tabs:
selected_tab = self._extract_selected_tab(tabs)
tab_name = selected_tab.get('title', '')
if (mobj['tab'] == '/videos' and tab_name.lower() != mobj['tab'][1:]
and 'no-youtube-channel-redirect' not in compat_opts):
if not mobj['not_channel'] and item_id[:2] == 'UC':
# Topic channels don't have /videos. Use the equivalent playlist instead
self.report_warning('The URL does not have a %s tab. Trying to redirect to playlist UU%s instead' % (mobj['tab'][1:], item_id[2:]))
pl_id = 'UU%s' % item_id[2:]
pl_url = 'https://www.youtube.com/playlist?list=%s%s' % (pl_id, mobj['post'])
try:
pl_webpage, pl_data = self._extract_webpage(pl_url, pl_id)
for alert_type, alert_message in self._extract_alerts(pl_data):
if alert_type == 'error':
raise ExtractorError('Youtube said: %s' % alert_message)
item_id, url, webpage, data = pl_id, pl_url, pl_webpage, pl_data
except ExtractorError:
self.report_warning('The playlist gave error. Falling back to channel URL')
else:
self.report_warning('The URL does not have a %s tab. %s is being downloaded instead' % (mobj['tab'][1:], tab_name))
self.write_debug('Final URL: %s' % url)
# YouTube sometimes provides a button to reload playlist with unavailable videos.
if 'no-youtube-unavailable-videos' not in compat_opts:
data = self._reload_with_unavailable_videos(item_id, data, webpage) or data
self._extract_and_report_alerts(data)
tabs = try_get(
data, lambda x: x['contents']['twoColumnBrowseResultsRenderer']['tabs'], list)
@@ -3612,12 +3830,13 @@ class YoutubePlaylistIE(InfoExtractor):
def _real_extract(self, url):
playlist_id = self._match_id(url)
qs = parse_qs(url)
if not qs:
qs = {'list': playlist_id}
return self.url_result(
update_url_query('https://www.youtube.com/playlist', qs),
ie=YoutubeTabIE.ie_key(), video_id=playlist_id)
is_music_url = self.is_music_url(url)
url = update_url_query(
'https://www.youtube.com/playlist',
parse_qs(url) or {'list': playlist_id})
if is_music_url:
url = smuggle_url(url, {'is_music_url': True})
return self.url_result(url, ie=YoutubeTabIE.ie_key(), video_id=playlist_id)
class YoutubeYtBeIE(InfoExtractor):
@@ -3811,9 +4030,6 @@ class YoutubeFeedsInfoExtractor(YoutubeTabIE):
def IE_NAME(self):
return 'youtube:%s' % self._FEED_NAME
def _real_initialize(self):
self._login()
def _real_extract(self, url):
return self.url_result(
'https://www.youtube.com/feed/%s' % self._FEED_NAME,
+1 -1
View File
@@ -35,7 +35,7 @@ class ZoomIE(InfoExtractor):
except ExtractorError:
form = None
if form:
password = self._downloader.params.get('videopassword')
password = self.get_param('videopassword')
if not password:
raise ExtractorError(
'This video is protected by a passcode, use the --video-password option', expected=True)
+38 -25
View File
@@ -107,13 +107,15 @@ def parseOpts(overrideArguments=None):
return ''.join(opts)
def _comma_separated_values_options_callback(option, opt_str, value, parser, prepend=True):
def _list_from_options_callback(option, opt_str, value, parser, append=True, delim=','):
# append can be True, False or -1 (prepend)
current = getattr(parser.values, option.dest) if append else []
value = [value] if delim is None else value.split(delim)
setattr(
parser.values, option.dest,
value.split(',') if not prepend
else value.split(',') + getattr(parser.values, option.dest))
current + value if append is True else value + current)
def _dict_from_multiple_values_options_callback(
def _dict_from_options_callback(
option, opt_str, value, parser,
allowed_keys=r'[\w-]+', delimiter=':', default_key=None, process=None, multiple_keys=True):
@@ -232,7 +234,7 @@ def parseOpts(overrideArguments=None):
general.add_option(
'--compat-options',
metavar='OPTS', dest='compat_opts', default=[],
action='callback', callback=_comma_separated_values_options_callback, type='str',
action='callback', callback=_list_from_options_callback, type='str',
help=(
'Options that can help keep compatibility with youtube-dl and youtube-dlc '
'configurations by reverting some of the changes made in yt-dlp. '
@@ -465,8 +467,8 @@ def parseOpts(overrideArguments=None):
help='Video format code, see "FORMAT SELECTION" for more details')
video_format.add_option(
'-S', '--format-sort', metavar='SORTORDER',
dest='format_sort', default=[],
action='callback', callback=_comma_separated_values_options_callback, type='str',
dest='format_sort', default=[], type='str', action='callback',
callback=_list_from_options_callback, callback_kwargs={'append': -1},
help='Sort the formats by the fields given, see "Sorting Formats" for more details')
video_format.add_option(
'--format-sort-force', '--S-force',
@@ -576,7 +578,7 @@ def parseOpts(overrideArguments=None):
subtitles.add_option(
'--sub-langs', '--srt-langs',
action='callback', dest='subtitleslangs', metavar='LANGS', type='str',
default=[], callback=_comma_separated_values_options_callback,
default=[], callback=_list_from_options_callback,
help=(
'Languages of the subtitles to download (can be regex) or "all" separated by commas. (Eg: --sub-langs en.*,ja) '
'You can prefix the language code with a "-" to exempt it from the requested languages. (Eg: --sub-langs all,-live_chat) '
@@ -678,7 +680,7 @@ def parseOpts(overrideArguments=None):
downloader.add_option(
'--downloader', '--external-downloader',
dest='external_downloader', metavar='[PROTO:]NAME', default={}, type='str',
action='callback', callback=_dict_from_multiple_values_options_callback,
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': 'http|ftp|m3u8|dash|rtsp|rtmp|mms',
'default_key': 'default',
@@ -695,7 +697,7 @@ def parseOpts(overrideArguments=None):
downloader.add_option(
'--downloader-args', '--external-downloader-args',
metavar='NAME:ARGS', dest='external_downloader_args', default={}, type='str',
action='callback', callback=_dict_from_multiple_values_options_callback,
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': '|'.join(list_external_downloaders()),
'default_key': 'default',
@@ -731,7 +733,7 @@ def parseOpts(overrideArguments=None):
workarounds.add_option(
'--add-header',
metavar='FIELD:VALUE', dest='headers', default={}, type='str',
action='callback', callback=_dict_from_multiple_values_options_callback,
action='callback', callback=_dict_from_options_callback,
callback_kwargs={'multiple_keys': False},
help='Specify a custom HTTP header and its value, separated by a colon ":". You can use this option multiple times',
)
@@ -786,38 +788,45 @@ def parseOpts(overrideArguments=None):
'--skip-download', '--no-download',
action='store_true', dest='skip_download', default=False,
help='Do not download the video but write all related files (Alias: --no-download)')
verbosity.add_option(
'-O', '--print', metavar='TEMPLATE',
action='callback', dest='forceprint', type='str', default=[],
callback=_list_from_options_callback, callback_kwargs={'delim': None},
help=(
'Simulate, quiet but print the given fields. Either a field name '
'or similar formatting as the output template can be used'))
verbosity.add_option(
'-g', '--get-url',
action='store_true', dest='geturl', default=False,
help='Simulate, quiet but print URL')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'-e', '--get-title',
action='store_true', dest='gettitle', default=False,
help='Simulate, quiet but print title')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--get-id',
action='store_true', dest='getid', default=False,
help='Simulate, quiet but print id')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--get-thumbnail',
action='store_true', dest='getthumbnail', default=False,
help='Simulate, quiet but print thumbnail URL')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--get-description',
action='store_true', dest='getdescription', default=False,
help='Simulate, quiet but print video description')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--get-duration',
action='store_true', dest='getduration', default=False,
help='Simulate, quiet but print video length')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--get-filename',
action='store_true', dest='getfilename', default=False,
help='Simulate, quiet but print output filename')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'--get-format',
action='store_true', dest='getformat', default=False,
help='Simulate, quiet but print output format')
help=optparse.SUPPRESS_HELP)
verbosity.add_option(
'-j', '--dump-json',
action='store_true', dest='dumpjson', default=False,
@@ -893,7 +902,7 @@ def parseOpts(overrideArguments=None):
filesystem.add_option(
'-P', '--paths',
metavar='TYPES:PATH', dest='paths', default={}, type='str',
action='callback', callback=_dict_from_multiple_values_options_callback,
action='callback', callback=_dict_from_options_callback,
callback_kwargs={'allowed_keys': 'home|temp|%s' % '|'.join(OUTTMPL_TYPES.keys())},
help=(
'The paths where the files should be downloaded. '
@@ -906,7 +915,7 @@ def parseOpts(overrideArguments=None):
filesystem.add_option(
'-o', '--output',
metavar='[TYPES:]TEMPLATE', dest='outtmpl', default={}, type='str',
action='callback', callback=_dict_from_multiple_values_options_callback,
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': '|'.join(OUTTMPL_TYPES.keys()),
'default_key': 'default'
@@ -1039,11 +1048,15 @@ def parseOpts(overrideArguments=None):
action='store_false', dest='clean_infojson',
help='Write all fields to the infojson')
filesystem.add_option(
'--get-comments',
'--write-comments', '--get-comments',
action='store_true', dest='getcomments', default=False,
help=(
'Retrieve video comments to be placed in the .info.json file. '
'The comments are fetched even without this option if the extraction is known to be quick'))
'Retrieve video comments to be placed in the infojson. '
'The comments are fetched even without this option if the extraction is known to be quick (Alias: --get-comments)'))
filesystem.add_option(
'--no-write-comments', '--no-get-comments',
action='store_true', dest='getcomments', default=False,
help='Do not retrieve video comments unless the extraction is known to be quick (Alias: --no-get-comments)')
filesystem.add_option(
'--load-info-json', '--load-info',
dest='load_info_filename', metavar='FILE',
@@ -1132,7 +1145,7 @@ def parseOpts(overrideArguments=None):
postproc.add_option(
'--postprocessor-args', '--ppa',
metavar='NAME:ARGS', dest='postprocessor_args', default={}, type='str',
action='callback', callback=_dict_from_multiple_values_options_callback,
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': r'\w+(?:\+\w+)?', 'default_key': 'default-compat',
'process': compat_shlex_split,
+3 -4
View File
@@ -54,10 +54,9 @@ class PostProcessor(object):
if self._downloader:
return self._downloader.report_error(text, *args, **kwargs)
def write_debug(self, text, prefix=True, *args, **kwargs):
tag = '[debug] ' if prefix else ''
if self.get_param('verbose', False) and self._downloader:
return self._downloader.to_screen('%s%s' % (tag, text), *args, **kwargs)
def write_debug(self, text, *args, **kwargs):
if self._downloader:
return self._downloader.write_debug(text, *args, **kwargs)
def get_param(self, name, default=None, *args, **kwargs):
if self._downloader:
+41 -24
View File
@@ -1,14 +1,16 @@
# coding: utf-8
from __future__ import unicode_literals
import base64
import imghdr
import os
import subprocess
import struct
import re
import base64
try:
import mutagen
from mutagen.oggvorbis import OggVorbis
from mutagen.oggopus import OggOpus
from mutagen.flac import Picture, FLAC
has_mutagen = True
except ImportError:
has_mutagen = False
@@ -39,6 +41,23 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
FFmpegPostProcessor.__init__(self, downloader)
self._already_have_thumbnail = already_have_thumbnail
def _get_thumbnail_resolution(self, filename, thumbnail_dict):
def guess():
width, height = thumbnail_dict.get('width'), thumbnail_dict.get('height')
if width and height:
return width, height
try:
size_regex = r',\s*(?P<w>\d+)x(?P<h>\d+)\s*[,\[]'
size_result = self.run_ffmpeg(filename, filename, ['-hide_banner'])
mobj = re.search(size_regex, size_result)
if mobj is None:
return guess()
except PostProcessingError as err:
self.report_warning('unable to find the thumbnail resolution; %s' % error_to_compat_str(err))
return guess()
return int(mobj.group('w')), int(mobj.group('h'))
def run(self, info):
filename = info['filepath']
temp_filename = prepend_extension(filename, 'temp')
@@ -135,34 +154,32 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
self.report_warning('The file format doesn\'t support embedding a thumbnail')
success = False
elif info['ext'] in ['ogg', 'opus']:
elif info['ext'] in ['ogg', 'opus', 'flac']:
if not has_mutagen:
raise EmbedThumbnailPPError('module mutagen was not found. Please install using `python -m pip install mutagen`')
self.to_screen('Adding thumbnail to "%s"' % filename)
size_regex = r',\s*(?P<w>\d+)x(?P<h>\d+)\s*[,\[]'
size_result = self.run_ffmpeg(thumbnail_filename, thumbnail_filename, ['-hide_banner'])
mobj = re.search(size_regex, size_result)
width, height = int(mobj.group('w')), int(mobj.group('h'))
mimetype = ('image/%s' % ('png' if thumbnail_ext == 'png' else 'jpeg')).encode('ascii')
# https://xiph.org/flac/format.html#metadata_block_picture
data = bytearray()
data += struct.pack('>II', 3, len(mimetype))
data += mimetype
data += struct.pack('>IIIIII', 0, width, height, 8, 0, os.stat(thumbnail_filename).st_size) # 32 if png else 24
fin = open(thumbnail_filename, "rb")
data += fin.read()
fin.close()
temp_filename = filename
f = mutagen.File(temp_filename)
f.tags['METADATA_BLOCK_PICTURE'] = base64.b64encode(data).decode('ascii')
f = {'opus': OggOpus, 'flac': FLAC, 'ogg': OggVorbis}[info['ext']](filename)
pic = Picture()
pic.mime = 'image/%s' % imghdr.what(thumbnail_filename)
with open(thumbnail_filename, 'rb') as thumbfile:
pic.data = thumbfile.read()
pic.type = 3 # front cover
res = self._get_thumbnail_resolution(thumbnail_filename, info['thumbnails'][-1])
if res is not None:
pic.width, pic.height = res
if info['ext'] == 'flac':
f.add_picture(pic)
else:
# https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
f['METADATA_BLOCK_PICTURE'] = base64.b64encode(pic.write()).decode('ascii')
f.save()
else:
raise EmbedThumbnailPPError('Supported filetypes for thumbnail embedding are: mp3, mkv/mka, ogg/opus, m4a/mp4/mov')
raise EmbedThumbnailPPError('Supported filetypes for thumbnail embedding are: mp3, mkv/mka, ogg/opus/flac, m4a/mp4/mov')
if success and temp_filename != filename:
os.remove(encodeFilename(filename))
+8 -4
View File
@@ -474,8 +474,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
filename = information['filepath']
ext = information['ext']
sub_langs = []
sub_filenames = []
sub_langs, sub_names, sub_filenames = [], [], []
webm_vtt_warn = False
mp4_ass_warn = False
@@ -485,6 +484,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
self.report_warning('JSON subtitles cannot be embedded')
elif ext != 'webm' or ext == 'webm' and sub_ext == 'vtt':
sub_langs.append(lang)
sub_names.append(sub_info.get('name'))
sub_filenames.append(sub_info['filepath'])
else:
if not webm_vtt_warn and ext == 'webm' and sub_ext != 'vtt':
@@ -510,10 +510,13 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
]
if information['ext'] == 'mp4':
opts += ['-c:s', 'mov_text']
for (i, lang) in enumerate(sub_langs):
for i, (lang, name) in enumerate(zip(sub_langs, sub_names)):
opts.extend(['-map', '%d:0' % (i + 1)])
lang_code = ISO639Utils.short2long(lang) or lang
opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code])
if name:
opts.extend(['-metadata:s:s:%d' % i, 'handler_name=%s' % name,
'-metadata:s:s:%d' % i, 'title=%s' % name])
temp_filename = prepend_extension(filename, 'temp')
self.to_screen('Embedding subtitles in "%s"' % filename)
@@ -605,7 +608,8 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
in_filenames.append(metadata_filename)
options.extend(['-map_metadata', '1'])
if '__infojson_filename' in info and info['ext'] in ('mkv', 'mka'):
if ('no-attach-info-json' not in self.get_param('compat_opts', [])
and '__infojson_filename' in info and info['ext'] in ('mkv', 'mka')):
old_stream, new_stream = self.get_stream_number(
filename, ('tags', 'mimetype'), 'application/json')
if old_stream is not None:
+2
View File
@@ -1748,6 +1748,7 @@ DATE_FORMATS = (
'%Y-%m-%d %H:%M',
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M:%S.%f',
'%Y-%m-%d %H:%M:%S:%f',
'%d.%m.%Y %H:%M',
'%d.%m.%Y %H.%M',
'%Y-%m-%dT%H:%M:%SZ',
@@ -4311,6 +4312,7 @@ OUTTMPL_TYPES = {
'description': 'description',
'annotation': 'annotations.xml',
'infojson': 'info.json',
'pl_thumbnail': None,
'pl_description': 'description',
'pl_infojson': 'info.json',
}
+1 -1
View File
@@ -1,3 +1,3 @@
from __future__ import unicode_literals
__version__ = '2021.04.22'
__version__ = '2021.05.11'
+1
View File
@@ -1,2 +1,3 @@
# flake8: noqa
from .sample import SamplePluginIE
+5 -3
View File
@@ -1,11 +1,13 @@
# coding: utf-8
from __future__ import unicode_literals
# Don't use relative imports
# Don't use relative imports
from yt_dlp.extractor.common import InfoExtractor
# See https://github.com/ytdl-org/youtube-dl#adding-support-for-a-new-site
# for instuctions on making extractors
# ️ Instructions on making extractors can be found at:
# 🔗 https://github.com/ytdl-org/youtube-dl#adding-support-for-a-new-site
class SamplePluginIE(InfoExtractor):
_WORKING = False