mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-21 20:46:36 -05:00
parent
dd0228ce1f
commit
e75bb0d6c3
7 changed files with 58 additions and 58 deletions
|
@ -215,7 +215,7 @@ ## Adding support for a new site
|
||||||
|
|
||||||
$ flake8 yt_dlp/extractor/yourextractor.py
|
$ flake8 yt_dlp/extractor/yourextractor.py
|
||||||
|
|
||||||
1. Make sure your code works under all [Python](https://www.python.org/) versions supported by yt-dlp, namely CPython and PyPy for Python 3.6 and above. Backward compatability is not required for even older versions of Python.
|
1. Make sure your code works under all [Python](https://www.python.org/) versions supported by yt-dlp, namely CPython and PyPy for Python 3.6 and above. Backward compatibility is not required for even older versions of Python.
|
||||||
1. When the tests pass, [add](https://git-scm.com/docs/git-add) the new files, [commit](https://git-scm.com/docs/git-commit) them and [push](https://git-scm.com/docs/git-push) the result, like this:
|
1. When the tests pass, [add](https://git-scm.com/docs/git-add) the new files, [commit](https://git-scm.com/docs/git-commit) them and [push](https://git-scm.com/docs/git-push) the result, like this:
|
||||||
|
|
||||||
$ git add yt_dlp/extractor/extractors.py
|
$ git add yt_dlp/extractor/extractors.py
|
||||||
|
@ -243,7 +243,7 @@ ### Mandatory and optional metafields
|
||||||
- `title` (media title)
|
- `title` (media title)
|
||||||
- `url` (media download URL) or `formats`
|
- `url` (media download URL) or `formats`
|
||||||
|
|
||||||
The aforementioned metafields are the critical data that the extraction does not make any sense without and if any of them fail to be extracted then the extractor is considered completely broken. While, in fact, only `id` is technically mandatory, due to compatability reasons, yt-dlp also treats `title` as mandatory. The extractor is allowed to return the info dict without url or formats in some special cases if it allows the user to extract usefull information with `--ignore-no-formats-error` - Eg: when the video is a live stream that has not started yet.
|
The aforementioned metafields are the critical data that the extraction does not make any sense without and if any of them fail to be extracted then the extractor is considered completely broken. While, in fact, only `id` is technically mandatory, due to compatibility reasons, yt-dlp also treats `title` as mandatory. The extractor is allowed to return the info dict without url or formats in some special cases if it allows the user to extract usefull information with `--ignore-no-formats-error` - Eg: when the video is a live stream that has not started yet.
|
||||||
|
|
||||||
[Any field](yt_dlp/extractor/common.py#219-L426) apart from the aforementioned ones are considered **optional**. That means that extraction should be **tolerant** to situations when sources for these fields can potentially be unavailable (even if they are always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields.
|
[Any field](yt_dlp/extractor/common.py#219-L426) apart from the aforementioned ones are considered **optional**. That means that extraction should be **tolerant** to situations when sources for these fields can potentially be unavailable (even if they are always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields.
|
||||||
|
|
||||||
|
|
32
README.md
32
README.md
|
@ -97,7 +97,7 @@ # NEW FEATURES
|
||||||
|
|
||||||
* **Aria2c with HLS/DASH**: You can use `aria2c` as the external downloader for DASH(mpd) and HLS(m3u8) formats
|
* **Aria2c with HLS/DASH**: You can use `aria2c` as the external downloader for DASH(mpd) and HLS(m3u8) formats
|
||||||
|
|
||||||
* **New and fixed extractors**: Many new extractors have been added and a lot of exisiting ones have been fixed. See the [changelog](Changelog.md) or the [list of supported sites](supportedsites.md)
|
* **New and fixed extractors**: Many new extractors have been added and a lot of existing ones have been fixed. See the [changelog](Changelog.md) or the [list of supported sites](supportedsites.md)
|
||||||
|
|
||||||
* **New MSOs**: Philo, Spectrum, SlingTV, Cablevision, RCN
|
* **New MSOs**: Philo, Spectrum, SlingTV, Cablevision, RCN
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ ### Differences in default behavior
|
||||||
* `avconv` is not supported as as an alternative to `ffmpeg`
|
* `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 [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 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
|
* 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 preferred. 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
|
* 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
|
* `--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
|
* 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
|
||||||
|
@ -142,7 +142,7 @@ ### Differences in default behavior
|
||||||
* If `ffmpeg` is used as the downloader, the downloading and merging of formats happen in a single step when possible. Use `--compat-options no-direct-merge` to revert this
|
* If `ffmpeg` is used as the downloader, the downloading and merging of formats happen in a single step when possible. Use `--compat-options no-direct-merge` to revert this
|
||||||
* Thumbnail embedding in `mp4` is done with mutagen if possible. Use `--compat-options embed-thumbnail-atomicparsley` to force the use of AtomicParsley instead
|
* Thumbnail embedding in `mp4` is done with mutagen if possible. Use `--compat-options embed-thumbnail-atomicparsley` to force the use of AtomicParsley instead
|
||||||
* Some private fields such as filenames are removed by default from the infojson. Use `--no-clean-infojson` or `--compat-options no-clean-infojson` to revert this
|
* Some private fields such as filenames are removed by default from the infojson. Use `--no-clean-infojson` or `--compat-options no-clean-infojson` to revert this
|
||||||
* When `--embed-subs` and `--write-subs` are used together, the subtitles are written to disk and also embedded in the media file. You can use just `--embed-subs` to embed the subs and automatically delete the seperate file. See [#630 (comment)](https://github.com/yt-dlp/yt-dlp/issues/630#issuecomment-893659460) for more info. `--compat-options no-keep-subs` can be used to revert this
|
* When `--embed-subs` and `--write-subs` are used together, the subtitles are written to disk and also embedded in the media file. You can use just `--embed-subs` to embed the subs and automatically delete the separate file. See [#630 (comment)](https://github.com/yt-dlp/yt-dlp/issues/630#issuecomment-893659460) for more info. `--compat-options no-keep-subs` can be used to revert this
|
||||||
|
|
||||||
For ease of use, a few more compat options are available:
|
For ease of use, a few more compat options are available:
|
||||||
* `--compat-options all`: Use all compat options
|
* `--compat-options all`: Use all compat options
|
||||||
|
@ -248,9 +248,9 @@ ## DEPENDENCIES
|
||||||
On windows, [Microsoft Visual C++ 2010 SP1 Redistributable Package (x86)](https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe) 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 manually.
|
On windows, [Microsoft Visual C++ 2010 SP1 Redistributable Package (x86)](https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe) 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 manually.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
While all the other dependancies are optional, `ffmpeg` and `ffprobe` are highly recommended
|
While all the other dependencies are optional, `ffmpeg` and `ffprobe` are highly recommended
|
||||||
|
|
||||||
* [**ffmpeg** and **ffprobe**](https://www.ffmpeg.org) - Required for [merging seperate video and audio files](#format-selection) as well as for various [post-processing](#post-processing-options) tasks. Licence [depends on the build](https://www.ffmpeg.org/legal.html)
|
* [**ffmpeg** and **ffprobe**](https://www.ffmpeg.org) - Required for [merging separate video and audio files](#format-selection) as well as for various [post-processing](#post-processing-options) tasks. Licence [depends on the build](https://www.ffmpeg.org/legal.html)
|
||||||
* [**mutagen**](https://github.com/quodlibet/mutagen) - For embedding thumbnail in certain formats. Licensed under [GPLv2+](https://github.com/quodlibet/mutagen/blob/master/COPYING)
|
* [**mutagen**](https://github.com/quodlibet/mutagen) - For embedding thumbnail in certain formats. Licensed under [GPLv2+](https://github.com/quodlibet/mutagen/blob/master/COPYING)
|
||||||
* [**pycryptodomex**](https://github.com/Legrandin/pycryptodome) - For decrypting AES-128 HLS streams and various other data. Licensed under [BSD2](https://github.com/Legrandin/pycryptodome/blob/master/LICENSE.rst)
|
* [**pycryptodomex**](https://github.com/Legrandin/pycryptodome) - For decrypting AES-128 HLS streams and various other data. Licensed under [BSD2](https://github.com/Legrandin/pycryptodome/blob/master/LICENSE.rst)
|
||||||
* [**websockets**](https://github.com/aaugustin/websockets) - For downloading over websocket. Licensed under [BSD3](https://github.com/aaugustin/websockets/blob/main/LICENSE)
|
* [**websockets**](https://github.com/aaugustin/websockets) - For downloading over websocket. Licensed under [BSD3](https://github.com/aaugustin/websockets/blob/main/LICENSE)
|
||||||
|
@ -266,7 +266,7 @@ ## DEPENDENCIES
|
||||||
|
|
||||||
The Windows and MacOS standalone release binaries are already built with the python interpreter, mutagen, pycryptodomex and websockets included.
|
The Windows and MacOS standalone release binaries are already built with the python interpreter, mutagen, pycryptodomex and websockets included.
|
||||||
|
|
||||||
**Note**: There are some regressions in newer ffmpeg versions that causes various issues when used alongside yt-dlp. Since ffmpeg is such an important dependancy, we provide [custom builds](https://github.com/yt-dlp/FFmpeg-Builds/wiki/Latest#latest-autobuilds) with patches for these issues at [yt-dlp/FFmpeg-Builds](https://github.com/yt-dlp/FFmpeg-Builds). See [the readme](https://github.com/yt-dlp/FFmpeg-Builds#patches-applied) for details on the specifc issues solved by these builds
|
**Note**: There are some regressions in newer ffmpeg versions that causes various issues when used alongside yt-dlp. Since ffmpeg is such an important dependency, we provide [custom builds](https://github.com/yt-dlp/FFmpeg-Builds/wiki/Latest#latest-autobuilds) with patches for these issues at [yt-dlp/FFmpeg-Builds](https://github.com/yt-dlp/FFmpeg-Builds). See [the readme](https://github.com/yt-dlp/FFmpeg-Builds#patches-applied) for details on the specific issues solved by these builds
|
||||||
|
|
||||||
|
|
||||||
## COMPILE
|
## COMPILE
|
||||||
|
@ -924,7 +924,7 @@ ## Post-Processing Options:
|
||||||
(default)
|
(default)
|
||||||
--force-keyframes-at-cuts Force keyframes around the chapters before
|
--force-keyframes-at-cuts Force keyframes around the chapters before
|
||||||
removing/splitting them. Requires a
|
removing/splitting them. Requires a
|
||||||
reencode and thus is very slow, but the
|
re-encode and thus is very slow, but the
|
||||||
resulting video may have fewer artifacts
|
resulting video may have fewer artifacts
|
||||||
around the cuts
|
around the cuts
|
||||||
--no-force-keyframes-at-cuts Do not force keyframes around the chapters
|
--no-force-keyframes-at-cuts Do not force keyframes around the chapters
|
||||||
|
@ -932,7 +932,7 @@ ## Post-Processing Options:
|
||||||
--use-postprocessor NAME[:ARGS] The (case sensitive) name of plugin
|
--use-postprocessor NAME[:ARGS] The (case sensitive) name of plugin
|
||||||
postprocessors to be enabled, and
|
postprocessors to be enabled, and
|
||||||
(optionally) arguments to be passed to it,
|
(optionally) arguments to be passed to it,
|
||||||
seperated by a colon ":". ARGS are a
|
separated by a colon ":". ARGS are a
|
||||||
semicolon ";" delimited list of NAME=VALUE.
|
semicolon ";" delimited list of NAME=VALUE.
|
||||||
The "when" argument determines when the
|
The "when" argument determines when the
|
||||||
postprocessor is invoked. It can be one of
|
postprocessor is invoked. It can be one of
|
||||||
|
@ -1074,13 +1074,13 @@ # OUTPUT TEMPLATE
|
||||||
|
|
||||||
1. **Date/time Formatting**: Date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a `>`. Eg: `%(duration>%H-%M-%S)s`, `%(upload_date>%Y-%m-%d)s`, `%(epoch-3600>%H-%M-%S)s`
|
1. **Date/time Formatting**: Date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a `>`. Eg: `%(duration>%H-%M-%S)s`, `%(upload_date>%Y-%m-%d)s`, `%(epoch-3600>%H-%M-%S)s`
|
||||||
|
|
||||||
1. **Alternatives**: Alternate fields can be specified seperated with a `,`. Eg: `%(release_date>%Y,upload_date>%Y|Unknown)s`
|
1. **Alternatives**: Alternate fields can be specified separated with a `,`. Eg: `%(release_date>%Y,upload_date>%Y|Unknown)s`
|
||||||
|
|
||||||
1. **Replacement**: A replacement value can specified using a `&` separator. If the field is *not* empty, this replacement value will be used instead of the actual field content. This is done after alternate fields are considered; thus the replacement is used if *any* of the alternative fields is *not* empty.
|
1. **Replacement**: A replacement value can specified using a `&` separator. If the field is *not* empty, this replacement value will be used instead of the actual field content. This is done after alternate fields are considered; thus the replacement is used if *any* of the alternative fields is *not* empty.
|
||||||
|
|
||||||
1. **Default**: A literal default value can be specified for when the field is empty using a `|` seperator. This overrides `--output-na-template`. Eg: `%(uploader|Unknown)s`
|
1. **Default**: A literal default value can be specified for when the field is empty using a `|` separator. This overrides `--output-na-template`. Eg: `%(uploader|Unknown)s`
|
||||||
|
|
||||||
1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, `B`, `j`, `l`, `q` can be used for converting to **B**ytes, **j**son (flag `#` for pretty-printing), a comma seperated **l**ist (flag `#` for `\n` newline-seperated) and a string **q**uoted for the terminal (flag `#` to split a list into different arguments), respectively
|
1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, `B`, `j`, `l`, `q` can be used for converting to **B**ytes, **j**son (flag `#` for pretty-printing), a comma separated **l**ist (flag `#` for `\n` newline-separated) and a string **q**uoted for the terminal (flag `#` to split a list into different arguments), respectively
|
||||||
|
|
||||||
1. **Unicode normalization**: The format type `U` can be used for NFC [unicode normalization](https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize). The alternate form flag (`#`) changes the normalization to NFD and the conversion flag `+` can be used for NFKC/NFKD compatibility equivalence normalization. Eg: `%(title)+.100U` is NFKC
|
1. **Unicode normalization**: The format type `U` can be used for NFC [unicode normalization](https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize). The alternate form flag (`#`) changes the normalization to NFD and the conversion flag `+` can be used for NFKC/NFKD compatibility equivalence normalization. Eg: `%(title)+.100U` is NFKC
|
||||||
|
|
||||||
|
@ -1365,7 +1365,7 @@ ## Sorting Formats
|
||||||
- `vcodec`: Video Codec (`av01` > `vp9.2` > `vp9` > `h265` > `h264` > `vp8` > `h263` > `theora` > other > unknown)
|
- `vcodec`: Video Codec (`av01` > `vp9.2` > `vp9` > `h265` > `h264` > `vp8` > `h263` > `theora` > other > unknown)
|
||||||
- `acodec`: Audio Codec (`opus` > `vorbis` > `aac` > `mp4a` > `mp3` > `eac3` > `ac3` > `dts` > other > unknown)
|
- `acodec`: Audio Codec (`opus` > `vorbis` > `aac` > `mp4a` > `mp3` > `eac3` > `ac3` > `dts` > other > unknown)
|
||||||
- `codec`: Equivalent to `vcodec,acodec`
|
- `codec`: Equivalent to `vcodec,acodec`
|
||||||
- `vext`: Video Extension (`mp4` > `webm` > `flv` > other > unknown). If `--prefer-free-formats` is used, `webm` is prefered.
|
- `vext`: Video Extension (`mp4` > `webm` > `flv` > other > unknown). If `--prefer-free-formats` is used, `webm` is preferred.
|
||||||
- `aext`: Audio Extension (`m4a` > `aac` > `mp3` > `ogg` > `opus` > `webm` > other > unknown). If `--prefer-free-formats` is used, the order changes to `opus` > `ogg` > `webm` > `m4a` > `mp3` > `aac`.
|
- `aext`: Audio Extension (`m4a` > `aac` > `mp3` > `ogg` > `opus` > `webm` > other > unknown). If `--prefer-free-formats` is used, the order changes to `opus` > `ogg` > `webm` > `m4a` > `mp3` > `aac`.
|
||||||
- `ext`: Equivalent to `vext,aext`
|
- `ext`: Equivalent to `vext,aext`
|
||||||
- `filesize`: Exact filesize, if known in advance
|
- `filesize`: Exact filesize, if known in advance
|
||||||
|
@ -1388,7 +1388,7 @@ ## Sorting Formats
|
||||||
|
|
||||||
The fields `hasvid` and `ie_pref` are always given highest priority in sorting, irrespective of the user-defined order. This behaviour can be changed by using `--format-sort-force`. Apart from these, the default order used is: `lang,quality,res,fps,hdr:12,codec:vp9.2,size,br,asr,proto,ext,hasaud,source,id`. The extractors may override this default order, but they cannot override the user-provided order.
|
The fields `hasvid` and `ie_pref` are always given highest priority in sorting, irrespective of the user-defined order. This behaviour can be changed by using `--format-sort-force`. Apart from these, the default order used is: `lang,quality,res,fps,hdr:12,codec:vp9.2,size,br,asr,proto,ext,hasaud,source,id`. The extractors may override this default order, but they cannot override the user-provided order.
|
||||||
|
|
||||||
Note that the default has `codec:vp9.2`; i.e. `av1` is not prefered. Similarly, the default for hdr is `hdr:12`; i.e. dolby vision is not prefered. These choices are made since DV and AV1 formats are not yet fully compatible with most devices. This may be changed in the future as more devices become capable of smoothly playing back these formats.
|
Note that the default has `codec:vp9.2`; i.e. `av1` is not preferred. Similarly, the default for hdr is `hdr:12`; i.e. dolby vision is not preferred. These choices are made since DV and AV1 formats are not yet fully compatible with most devices. This may be changed in the future as more devices become capable of smoothly playing back these formats.
|
||||||
|
|
||||||
If your format selector is `worst`, the last item is selected after sorting. This means it will select the format that is worst in all respects. Most of the time, what you actually want is the video with the smallest filesize instead. So it is generally better to use `-f best -S +size,+br,+res,+fps`.
|
If your format selector is `worst`, the last item is selected after sorting. This means it will select the format that is worst in all respects. Most of the time, what you actually want is the video with the smallest filesize instead. So it is generally better to use `-f best -S +size,+br,+res,+fps`.
|
||||||
|
|
||||||
|
@ -1587,7 +1587,7 @@ #### youtube
|
||||||
* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details
|
* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details
|
||||||
* `include_live_dash`: Include live dash formats (These formats don't download properly)
|
* `include_live_dash`: Include live dash formats (These formats don't download properly)
|
||||||
* `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side)
|
* `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side)
|
||||||
* `max_comments`: Limit the amount of comments to gather. Comma-seperated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread`. Default is `all,all,all,all`.
|
* `max_comments`: Limit the amount of comments to gather. Comma-separated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread`. Default is `all,all,all,all`.
|
||||||
* E.g. `all,all,1000,10` will get a maximum of 1000 replies total, with up to 10 replies per thread. `1000,all,100` will get a maximum of 1000 comments, with a maximum of 100 replies total.
|
* E.g. `all,all,1000,10` will get a maximum of 1000 replies total, with up to 10 replies per thread. `1000,all,100` will get a maximum of 1000 comments, with a maximum of 100 replies total.
|
||||||
* `max_comment_depth` Maximum depth for nested comments. YouTube supports depths 1 or 2 (default)
|
* `max_comment_depth` Maximum depth for nested comments. YouTube supports depths 1 or 2 (default)
|
||||||
* **Deprecated**: Set `max-replies` to `0` or `all` in `max_comments` instead (e.g. `max_comments=all,all,0` to get no replies)
|
* **Deprecated**: Set `max-replies` to `0` or `all` in `max_comments` instead (e.g. `max_comments=all,all,0` to get no replies)
|
||||||
|
@ -1655,7 +1655,7 @@ # EMBEDDING YT-DLP
|
||||||
|
|
||||||
class MyLogger:
|
class MyLogger:
|
||||||
def debug(self, msg):
|
def debug(self, msg):
|
||||||
# For compatability with youtube-dl, both debug and info are passed into debug
|
# For compatibility with youtube-dl, both debug and info are passed into debug
|
||||||
# You can distinguish them by the prefix '[debug] '
|
# You can distinguish them by the prefix '[debug] '
|
||||||
if msg.startswith('[debug] '):
|
if msg.startswith('[debug] '):
|
||||||
pass
|
pass
|
||||||
|
@ -1708,7 +1708,7 @@ # ℹ️ See "progress_hooks" in the docstring of yt_dlp.YoutubeDL
|
||||||
'format_id': f'{best_video["format_id"]}+{best_audio["format_id"]}',
|
'format_id': f'{best_video["format_id"]}+{best_audio["format_id"]}',
|
||||||
'ext': best_video['ext'],
|
'ext': best_video['ext'],
|
||||||
'requested_formats': [best_video, best_audio],
|
'requested_formats': [best_video, best_audio],
|
||||||
# Must be + seperated list of protocols
|
# Must be + separated list of protocols
|
||||||
'protocol': f'{best_video["protocol"]}+{best_audio["protocol"]}'
|
'protocol': f'{best_video["protocol"]}+{best_audio["protocol"]}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ def main():
|
||||||
'--icon=devscripts/logo.ico',
|
'--icon=devscripts/logo.ico',
|
||||||
'--upx-exclude=vcruntime140.dll',
|
'--upx-exclude=vcruntime140.dll',
|
||||||
'--noconfirm',
|
'--noconfirm',
|
||||||
*dependancy_options(),
|
*dependency_options(),
|
||||||
*opts,
|
*opts,
|
||||||
'yt_dlp/__main__.py',
|
'yt_dlp/__main__.py',
|
||||||
]
|
]
|
||||||
|
@ -73,11 +73,11 @@ def version_to_list(version):
|
||||||
return list(map(int, version_list)) + [0] * (4 - len(version_list))
|
return list(map(int, version_list)) + [0] * (4 - len(version_list))
|
||||||
|
|
||||||
|
|
||||||
def dependancy_options():
|
def dependency_options():
|
||||||
dependancies = [pycryptodome_module(), 'mutagen'] + collect_submodules('websockets')
|
dependencies = [pycryptodome_module(), 'mutagen'] + collect_submodules('websockets')
|
||||||
excluded_modules = ['test', 'ytdlp_plugins', 'youtube-dl', 'youtube-dlc']
|
excluded_modules = ['test', 'ytdlp_plugins', 'youtube-dl', 'youtube-dlc']
|
||||||
|
|
||||||
yield from (f'--hidden-import={module}' for module in dependancies)
|
yield from (f'--hidden-import={module}' for module in dependencies)
|
||||||
yield from (f'--exclude-module={module}' for module in excluded_modules)
|
yield from (f'--exclude-module={module}' for module in excluded_modules)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2730,7 +2730,7 @@ def process_info(self, info_dict):
|
||||||
_infojson_written = self._write_info_json('video', info_dict, infofn)
|
_infojson_written = self._write_info_json('video', info_dict, infofn)
|
||||||
if _infojson_written:
|
if _infojson_written:
|
||||||
info_dict['infojson_filename'] = infofn
|
info_dict['infojson_filename'] = infofn
|
||||||
# For backward compatability, even though it was a private field
|
# For backward compatibility, even though it was a private field
|
||||||
info_dict['__infojson_filename'] = infofn
|
info_dict['__infojson_filename'] = infofn
|
||||||
elif _infojson_written is None:
|
elif _infojson_written is None:
|
||||||
return
|
return
|
||||||
|
|
|
@ -80,7 +80,7 @@ def _real_extract(self, url):
|
||||||
'format_id': '%s-%d' % (determine_protocol(f), tbr),
|
'format_id': '%s-%d' % (determine_protocol(f), tbr),
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
})
|
})
|
||||||
# 'tbr' was explicitly set to be prefered over 'height' originally,
|
# 'tbr' was explicitly set to be preferred over 'height' originally,
|
||||||
# So this is being kept unless someone can confirm this is unnecessary
|
# So this is being kept unless someone can confirm this is unnecessary
|
||||||
self._sort_formats(info_dict['formats'], ('tbr', 'res'))
|
self._sort_formats(info_dict['formats'], ('tbr', 'res'))
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ def _named_object(self, namespace, obj):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _seperate(expr, delim=',', max_split=None):
|
def _separate(expr, delim=',', max_split=None):
|
||||||
if not expr:
|
if not expr:
|
||||||
return
|
return
|
||||||
counters = {k: 0 for k in _MATCHING_PARENS.values()}
|
counters = {k: 0 for k in _MATCHING_PARENS.values()}
|
||||||
|
@ -111,17 +111,17 @@ def _seperate(expr, delim=',', max_split=None):
|
||||||
yield expr[start:]
|
yield expr[start:]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _seperate_at_paren(expr, delim):
|
def _separate_at_paren(expr, delim):
|
||||||
seperated = list(JSInterpreter._seperate(expr, delim, 1))
|
separated = list(JSInterpreter._separate(expr, delim, 1))
|
||||||
if len(seperated) < 2:
|
if len(separated) < 2:
|
||||||
raise ExtractorError(f'No terminating paren {delim} in {expr}')
|
raise ExtractorError(f'No terminating paren {delim} in {expr}')
|
||||||
return seperated[0][1:].strip(), seperated[1].strip()
|
return separated[0][1:].strip(), separated[1].strip()
|
||||||
|
|
||||||
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
||||||
if allow_recursion < 0:
|
if allow_recursion < 0:
|
||||||
raise ExtractorError('Recursion limit reached')
|
raise ExtractorError('Recursion limit reached')
|
||||||
|
|
||||||
sub_statements = list(self._seperate(stmt, ';'))
|
sub_statements = list(self._separate(stmt, ';'))
|
||||||
stmt = (sub_statements or ['']).pop()
|
stmt = (sub_statements or ['']).pop()
|
||||||
for sub_stmt in sub_statements:
|
for sub_stmt in sub_statements:
|
||||||
ret, should_abort = self.interpret_statement(sub_stmt, local_vars, allow_recursion - 1)
|
ret, should_abort = self.interpret_statement(sub_stmt, local_vars, allow_recursion - 1)
|
||||||
|
@ -151,7 +151,7 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if expr.startswith('{'):
|
if expr.startswith('{'):
|
||||||
inner, outer = self._seperate_at_paren(expr, '}')
|
inner, outer = self._separate_at_paren(expr, '}')
|
||||||
inner, should_abort = self.interpret_statement(inner, local_vars, allow_recursion - 1)
|
inner, should_abort = self.interpret_statement(inner, local_vars, allow_recursion - 1)
|
||||||
if not outer or should_abort:
|
if not outer or should_abort:
|
||||||
return inner
|
return inner
|
||||||
|
@ -159,7 +159,7 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
expr = json.dumps(inner) + outer
|
expr = json.dumps(inner) + outer
|
||||||
|
|
||||||
if expr.startswith('('):
|
if expr.startswith('('):
|
||||||
inner, outer = self._seperate_at_paren(expr, ')')
|
inner, outer = self._separate_at_paren(expr, ')')
|
||||||
inner = self.interpret_expression(inner, local_vars, allow_recursion)
|
inner = self.interpret_expression(inner, local_vars, allow_recursion)
|
||||||
if not outer:
|
if not outer:
|
||||||
return inner
|
return inner
|
||||||
|
@ -167,16 +167,16 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
expr = json.dumps(inner) + outer
|
expr = json.dumps(inner) + outer
|
||||||
|
|
||||||
if expr.startswith('['):
|
if expr.startswith('['):
|
||||||
inner, outer = self._seperate_at_paren(expr, ']')
|
inner, outer = self._separate_at_paren(expr, ']')
|
||||||
name = self._named_object(local_vars, [
|
name = self._named_object(local_vars, [
|
||||||
self.interpret_expression(item, local_vars, allow_recursion)
|
self.interpret_expression(item, local_vars, allow_recursion)
|
||||||
for item in self._seperate(inner)])
|
for item in self._separate(inner)])
|
||||||
expr = name + outer
|
expr = name + outer
|
||||||
|
|
||||||
m = re.match(r'try\s*', expr)
|
m = re.match(r'try\s*', expr)
|
||||||
if m:
|
if m:
|
||||||
if expr[m.end()] == '{':
|
if expr[m.end()] == '{':
|
||||||
try_expr, expr = self._seperate_at_paren(expr[m.end():], '}')
|
try_expr, expr = self._separate_at_paren(expr[m.end():], '}')
|
||||||
else:
|
else:
|
||||||
try_expr, expr = expr[m.end() - 1:], ''
|
try_expr, expr = expr[m.end() - 1:], ''
|
||||||
ret, should_abort = self.interpret_statement(try_expr, local_vars, allow_recursion - 1)
|
ret, should_abort = self.interpret_statement(try_expr, local_vars, allow_recursion - 1)
|
||||||
|
@ -187,23 +187,23 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
m = re.match(r'catch\s*\(', expr)
|
m = re.match(r'catch\s*\(', expr)
|
||||||
if m:
|
if m:
|
||||||
# We ignore the catch block
|
# We ignore the catch block
|
||||||
_, expr = self._seperate_at_paren(expr, '}')
|
_, expr = self._separate_at_paren(expr, '}')
|
||||||
return self.interpret_statement(expr, local_vars, allow_recursion - 1)[0]
|
return self.interpret_statement(expr, local_vars, allow_recursion - 1)[0]
|
||||||
|
|
||||||
m = re.match(r'for\s*\(', expr)
|
m = re.match(r'for\s*\(', expr)
|
||||||
if m:
|
if m:
|
||||||
constructor, remaining = self._seperate_at_paren(expr[m.end() - 1:], ')')
|
constructor, remaining = self._separate_at_paren(expr[m.end() - 1:], ')')
|
||||||
if remaining.startswith('{'):
|
if remaining.startswith('{'):
|
||||||
body, expr = self._seperate_at_paren(remaining, '}')
|
body, expr = self._separate_at_paren(remaining, '}')
|
||||||
else:
|
else:
|
||||||
m = re.match(r'switch\s*\(', remaining) # FIXME
|
m = re.match(r'switch\s*\(', remaining) # FIXME
|
||||||
if m:
|
if m:
|
||||||
switch_val, remaining = self._seperate_at_paren(remaining[m.end() - 1:], ')')
|
switch_val, remaining = self._separate_at_paren(remaining[m.end() - 1:], ')')
|
||||||
body, expr = self._seperate_at_paren(remaining, '}')
|
body, expr = self._separate_at_paren(remaining, '}')
|
||||||
body = 'switch(%s){%s}' % (switch_val, body)
|
body = 'switch(%s){%s}' % (switch_val, body)
|
||||||
else:
|
else:
|
||||||
body, expr = remaining, ''
|
body, expr = remaining, ''
|
||||||
start, cndn, increment = self._seperate(constructor, ';')
|
start, cndn, increment = self._separate(constructor, ';')
|
||||||
if self.interpret_statement(start, local_vars, allow_recursion - 1)[1]:
|
if self.interpret_statement(start, local_vars, allow_recursion - 1)[1]:
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
f'Premature return in the initialization of a for loop in {constructor!r}')
|
f'Premature return in the initialization of a for loop in {constructor!r}')
|
||||||
|
@ -225,14 +225,14 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
|
|
||||||
m = re.match(r'switch\s*\(', expr)
|
m = re.match(r'switch\s*\(', expr)
|
||||||
if m:
|
if m:
|
||||||
switch_val, remaining = self._seperate_at_paren(expr[m.end() - 1:], ')')
|
switch_val, remaining = self._separate_at_paren(expr[m.end() - 1:], ')')
|
||||||
switch_val = self.interpret_expression(switch_val, local_vars, allow_recursion)
|
switch_val = self.interpret_expression(switch_val, local_vars, allow_recursion)
|
||||||
body, expr = self._seperate_at_paren(remaining, '}')
|
body, expr = self._separate_at_paren(remaining, '}')
|
||||||
items = body.replace('default:', 'case default:').split('case ')[1:]
|
items = body.replace('default:', 'case default:').split('case ')[1:]
|
||||||
for default in (False, True):
|
for default in (False, True):
|
||||||
matched = False
|
matched = False
|
||||||
for item in items:
|
for item in items:
|
||||||
case, stmt = [i.strip() for i in self._seperate(item, ':', 1)]
|
case, stmt = [i.strip() for i in self._separate(item, ':', 1)]
|
||||||
if default:
|
if default:
|
||||||
matched = matched or case == 'default'
|
matched = matched or case == 'default'
|
||||||
elif not matched:
|
elif not matched:
|
||||||
|
@ -249,8 +249,8 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
break
|
break
|
||||||
return self.interpret_statement(expr, local_vars, allow_recursion - 1)[0]
|
return self.interpret_statement(expr, local_vars, allow_recursion - 1)[0]
|
||||||
|
|
||||||
# Comma seperated statements
|
# Comma separated statements
|
||||||
sub_expressions = list(self._seperate(expr))
|
sub_expressions = list(self._separate(expr))
|
||||||
expr = sub_expressions.pop().strip() if sub_expressions else ''
|
expr = sub_expressions.pop().strip() if sub_expressions else ''
|
||||||
for sub_expr in sub_expressions:
|
for sub_expr in sub_expressions:
|
||||||
self.interpret_expression(sub_expr, local_vars, allow_recursion)
|
self.interpret_expression(sub_expr, local_vars, allow_recursion)
|
||||||
|
@ -318,11 +318,11 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
return val[idx]
|
return val[idx]
|
||||||
|
|
||||||
for op, opfunc in _OPERATORS:
|
for op, opfunc in _OPERATORS:
|
||||||
seperated = list(self._seperate(expr, op))
|
separated = list(self._separate(expr, op))
|
||||||
if len(seperated) < 2:
|
if len(separated) < 2:
|
||||||
continue
|
continue
|
||||||
right_val = seperated.pop()
|
right_val = separated.pop()
|
||||||
left_val = op.join(seperated)
|
left_val = op.join(separated)
|
||||||
left_val, should_abort = self.interpret_statement(
|
left_val, should_abort = self.interpret_statement(
|
||||||
left_val, local_vars, allow_recursion - 1)
|
left_val, local_vars, allow_recursion - 1)
|
||||||
if should_abort:
|
if should_abort:
|
||||||
|
@ -341,7 +341,7 @@ def interpret_expression(self, expr, local_vars, allow_recursion):
|
||||||
member = remove_quotes(m.group('member') or m.group('member2'))
|
member = remove_quotes(m.group('member') or m.group('member2'))
|
||||||
arg_str = expr[m.end():]
|
arg_str = expr[m.end():]
|
||||||
if arg_str.startswith('('):
|
if arg_str.startswith('('):
|
||||||
arg_str, remaining = self._seperate_at_paren(arg_str, ')')
|
arg_str, remaining = self._separate_at_paren(arg_str, ')')
|
||||||
else:
|
else:
|
||||||
arg_str, remaining = None, arg_str
|
arg_str, remaining = None, arg_str
|
||||||
|
|
||||||
|
@ -370,7 +370,7 @@ def eval_method():
|
||||||
# Function call
|
# Function call
|
||||||
argvals = [
|
argvals = [
|
||||||
self.interpret_expression(v, local_vars, allow_recursion)
|
self.interpret_expression(v, local_vars, allow_recursion)
|
||||||
for v in self._seperate(arg_str)]
|
for v in self._separate(arg_str)]
|
||||||
|
|
||||||
if obj == str:
|
if obj == str:
|
||||||
if member == 'fromCharCode':
|
if member == 'fromCharCode':
|
||||||
|
@ -453,7 +453,7 @@ def eval_method():
|
||||||
fname = m.group('func')
|
fname = m.group('func')
|
||||||
argvals = tuple([
|
argvals = tuple([
|
||||||
int(v) if v.isdigit() else local_vars[v]
|
int(v) if v.isdigit() else local_vars[v]
|
||||||
for v in self._seperate(m.group('args'))])
|
for v in self._separate(m.group('args'))])
|
||||||
if fname in local_vars:
|
if fname in local_vars:
|
||||||
return local_vars[fname](argvals)
|
return local_vars[fname](argvals)
|
||||||
elif fname not in self._functions:
|
elif fname not in self._functions:
|
||||||
|
@ -495,7 +495,7 @@ def extract_function_code(self, funcname):
|
||||||
(?P<code>\{(?:(?!};)[^"]|"([^"]|\\")*")+\})''' % (
|
(?P<code>\{(?:(?!};)[^"]|"([^"]|\\")*")+\})''' % (
|
||||||
re.escape(funcname), re.escape(funcname), re.escape(funcname)),
|
re.escape(funcname), re.escape(funcname), re.escape(funcname)),
|
||||||
self.code)
|
self.code)
|
||||||
code, _ = self._seperate_at_paren(func_m.group('code'), '}') # refine the match
|
code, _ = self._separate_at_paren(func_m.group('code'), '}') # refine the match
|
||||||
if func_m is None:
|
if func_m is None:
|
||||||
raise ExtractorError('Could not find JS function %r' % funcname)
|
raise ExtractorError('Could not find JS function %r' % funcname)
|
||||||
return func_m.group('args').split(','), code
|
return func_m.group('args').split(','), code
|
||||||
|
@ -510,7 +510,7 @@ def extract_function_from_code(self, argnames, code, *global_stack):
|
||||||
if mobj is None:
|
if mobj is None:
|
||||||
break
|
break
|
||||||
start, body_start = mobj.span()
|
start, body_start = mobj.span()
|
||||||
body, remaining = self._seperate_at_paren(code[body_start - 1:], '}')
|
body, remaining = self._separate_at_paren(code[body_start - 1:], '}')
|
||||||
name = self._named_object(
|
name = self._named_object(
|
||||||
local_vars,
|
local_vars,
|
||||||
self.extract_function_from_code(
|
self.extract_function_from_code(
|
||||||
|
@ -532,7 +532,7 @@ def resf(args, **kwargs):
|
||||||
**kwargs
|
**kwargs
|
||||||
})
|
})
|
||||||
var_stack = LocalNameSpace(local_vars, *global_stack)
|
var_stack = LocalNameSpace(local_vars, *global_stack)
|
||||||
for stmt in self._seperate(code.replace('\n', ''), ';'):
|
for stmt in self._separate(code.replace('\n', ''), ';'):
|
||||||
ret, should_abort = self.interpret_statement(stmt, var_stack)
|
ret, should_abort = self.interpret_statement(stmt, var_stack)
|
||||||
if should_abort:
|
if should_abort:
|
||||||
break
|
break
|
||||||
|
|
|
@ -1434,7 +1434,7 @@ def _dict_from_options_callback(
|
||||||
action='store_true', dest='force_keyframes_at_cuts', default=False,
|
action='store_true', dest='force_keyframes_at_cuts', default=False,
|
||||||
help=(
|
help=(
|
||||||
'Force keyframes around the chapters before removing/splitting them. '
|
'Force keyframes around the chapters before removing/splitting them. '
|
||||||
'Requires a reencode and thus is very slow, but the resulting video '
|
'Requires a re-encode and thus is very slow, but the resulting video '
|
||||||
'may have fewer artifacts around the cuts'))
|
'may have fewer artifacts around the cuts'))
|
||||||
postproc.add_option(
|
postproc.add_option(
|
||||||
'--no-force-keyframes-at-cuts',
|
'--no-force-keyframes-at-cuts',
|
||||||
|
@ -1452,7 +1452,7 @@ def _dict_from_options_callback(
|
||||||
'process': lambda val: dict(_postprocessor_opts_parser(*val.split(':', 1)))
|
'process': lambda val: dict(_postprocessor_opts_parser(*val.split(':', 1)))
|
||||||
}, help=(
|
}, help=(
|
||||||
'The (case sensitive) name of plugin postprocessors to be enabled, '
|
'The (case sensitive) name of plugin postprocessors to be enabled, '
|
||||||
'and (optionally) arguments to be passed to it, seperated by a colon ":". '
|
'and (optionally) arguments to be passed to it, separated by a colon ":". '
|
||||||
'ARGS are a semicolon ";" delimited list of NAME=VALUE. '
|
'ARGS are a semicolon ";" delimited list of NAME=VALUE. '
|
||||||
'The "when" argument determines when the postprocessor is invoked. '
|
'The "when" argument determines when the postprocessor is invoked. '
|
||||||
'It can be one of "pre_process" (after extraction), '
|
'It can be one of "pre_process" (after extraction), '
|
||||||
|
|
Loading…
Reference in a new issue