mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-12-08 15:12:47 +01:00
Compare commits
109 Commits
2022.06.22
...
2022.07.18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b5583b112 | ||
|
|
135f05ef66 | ||
|
|
c6e07cf1e1 | ||
|
|
ce7f6aa660 | ||
|
|
1765c6039e | ||
|
|
fbb888a3d5 | ||
|
|
2aab569f1c | ||
|
|
2e2c60c4ba | ||
|
|
306770819e | ||
|
|
dfa6661e0f | ||
|
|
24093d52a7 | ||
|
|
f5e438a976 | ||
|
|
d08e1e6875 | ||
|
|
956f1cf805 | ||
|
|
129dfa5f45 | ||
|
|
3df6a603e4 | ||
|
|
a7dc6a89f6 | ||
|
|
5200976949 | ||
|
|
e3e606de12 | ||
|
|
88f60feb32 | ||
|
|
a904a7f8c6 | ||
|
|
49afc1d84a | ||
|
|
6edf28081f | ||
|
|
5f2da312fa | ||
|
|
eb2333bce1 | ||
|
|
660c0c4efd | ||
|
|
fe588ce8ef | ||
|
|
26b92a919d | ||
|
|
8f47b39b27 | ||
|
|
2f1b7afe32 | ||
|
|
dd634acd71 | ||
|
|
ebf99aaf70 | ||
|
|
cbd4f237b4 | ||
|
|
418bbfd722 | ||
|
|
45e8a04e48 | ||
|
|
0f44636597 | ||
|
|
7a7eeb1005 | ||
|
|
4e7f375c94 | ||
|
|
f5ea47488a | ||
|
|
134c913cca | ||
|
|
56b5b832bf | ||
|
|
cb794ee010 | ||
|
|
6d645b5577 | ||
|
|
563e0bf82a | ||
|
|
d816f61fbf | ||
|
|
4019bf0525 | ||
|
|
65ea4cba29 | ||
|
|
17a23f0930 | ||
|
|
258d88f301 | ||
|
|
a3fb1ca5ab | ||
|
|
1275aeb955 | ||
|
|
170a031386 | ||
|
|
65493f64e1 | ||
|
|
63e66cd0ad | ||
|
|
f2df407165 | ||
|
|
ca9def714a | ||
|
|
47cdc68e03 | ||
|
|
7b84d6f9b3 | ||
|
|
12a1b2254d | ||
|
|
6154438178 | ||
|
|
168bbc4f38 | ||
|
|
a3976e0760 | ||
|
|
385f7f3895 | ||
|
|
5c0dc6e603 | ||
|
|
284a60c516 | ||
|
|
44f14eb43e | ||
|
|
ca9f1df253 | ||
|
|
a63b35a60c | ||
|
|
28cdb605aa | ||
|
|
5b836d4739 | ||
|
|
84a251e1f5 | ||
|
|
9d339c41e2 | ||
|
|
ae61d108dd | ||
|
|
47046464fa | ||
|
|
b1f94422cc | ||
|
|
c2c8921b41 | ||
|
|
844086505f | ||
|
|
63da2d0911 | ||
|
|
1db1461272 | ||
|
|
5fb450a64c | ||
|
|
6d916fe709 | ||
|
|
2c60eae899 | ||
|
|
962ffcf89c | ||
|
|
8a40bffaf9 | ||
|
|
e08f72e675 | ||
|
|
1685d46007 | ||
|
|
8d214c484c | ||
|
|
9eef7c4e55 | ||
|
|
bbae437723 | ||
|
|
30d22d775b | ||
|
|
c043c24625 | ||
|
|
74900105be | ||
|
|
d1bf2e199c | ||
|
|
c800598cd1 | ||
|
|
14f25df2b6 | ||
|
|
54007a45f1 | ||
|
|
ac66811112 | ||
|
|
3c5386cd71 | ||
|
|
bc40160883 | ||
|
|
379a4f161d | ||
|
|
06cc8f103b | ||
|
|
34baaced11 | ||
|
|
9809740ba5 | ||
|
|
f67baae17e | ||
|
|
37e40d693b | ||
|
|
0c36dc00d7 | ||
|
|
28163422a6 | ||
|
|
1ac4fd80c8 | ||
|
|
885fe351fb |
52
.github/ISSUE_TEMPLATE/1_broken_site.yml
vendored
52
.github/ISSUE_TEMPLATE/1_broken_site.yml
vendored
@@ -11,13 +11,13 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I'm reporting a broken site
|
- label: I'm reporting a broken site
|
||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **2022.06.22.1** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
@@ -26,37 +26,45 @@ body:
|
|||||||
id: region
|
id: region
|
||||||
attributes:
|
attributes:
|
||||||
label: Region
|
label: Region
|
||||||
description: "Enter the region the site is accessible from"
|
description: Enter the country/region that the site is accessible from
|
||||||
placeholder: "India"
|
placeholder: India
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your issue in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
required: true
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: log
|
id: log
|
||||||
attributes:
|
attributes:
|
||||||
label: Verbose log
|
label: Complete Verbose Output
|
||||||
description: |
|
description: |
|
||||||
Provide the complete verbose output of yt-dlp **that clearly demonstrates the problem**.
|
It should start like this:
|
||||||
Add the `-vU` flag to your command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
[debug] Portable config file: yt-dlp.conf
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
[debug] Portable config: ['-i']
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
[debug] yt-dlp version 2022.07.18 [9d339c4] (win32_exe)
|
||||||
[debug] yt-dlp version 2022.06.22.1 (exe)
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
yt-dlp is up to date (2022.06.22.1)
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: 2022.07.18, Current version: 2022.07.18
|
||||||
|
yt-dlp is up to date (2022.07.18)
|
||||||
<more lines>
|
<more lines>
|
||||||
render: shell
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I'm reporting a new site support request
|
- label: I'm reporting a new site support request
|
||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **2022.06.22.1** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
|
- label: I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
@@ -26,8 +26,8 @@ body:
|
|||||||
id: region
|
id: region
|
||||||
attributes:
|
attributes:
|
||||||
label: Region
|
label: Region
|
||||||
description: "Enter the region the site is accessible from"
|
description: Enter the country/region that the site is accessible from
|
||||||
placeholder: "India"
|
placeholder: India
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: example-urls
|
id: example-urls
|
||||||
attributes:
|
attributes:
|
||||||
@@ -43,31 +43,40 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide any additional information
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
required: true
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: log
|
id: log
|
||||||
attributes:
|
attributes:
|
||||||
label: Verbose log
|
label: Complete Verbose Output
|
||||||
description: |
|
description: |
|
||||||
Provide the complete verbose output **using one of the example URLs provided above**.
|
It should start like this:
|
||||||
Add the `-vU` flag to your command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
[debug] Portable config file: yt-dlp.conf
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
[debug] Portable config: ['-i']
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
[debug] yt-dlp version 2022.07.18 [9d339c4] (win32_exe)
|
||||||
[debug] yt-dlp version 2022.06.22.1 (exe)
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
yt-dlp is up to date (2022.06.22.1)
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: 2022.07.18, Current version: 2022.07.18
|
||||||
|
yt-dlp is up to date (2022.07.18)
|
||||||
<more lines>
|
<more lines>
|
||||||
render: shell
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I'm requesting a site-specific feature
|
- label: I'm requesting a site-specific feature
|
||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **2022.06.22.1** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
@@ -24,8 +24,8 @@ body:
|
|||||||
id: region
|
id: region
|
||||||
attributes:
|
attributes:
|
||||||
label: Region
|
label: Region
|
||||||
description: "Enter the region the site is accessible from"
|
description: Enter the country/region that the site is accessible from
|
||||||
placeholder: "India"
|
placeholder: India
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: example-urls
|
id: example-urls
|
||||||
attributes:
|
attributes:
|
||||||
@@ -39,33 +39,40 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your site feature request in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Please make sure the description is worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
required: true
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: log
|
id: log
|
||||||
attributes:
|
attributes:
|
||||||
label: Verbose log
|
label: Complete Verbose Output
|
||||||
description: |
|
description: |
|
||||||
Provide the complete verbose output of yt-dlp that demonstrates the need for the enhancement.
|
It should start like this:
|
||||||
Add the `-vU` flag to your command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
[debug] Portable config file: yt-dlp.conf
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
[debug] Portable config: ['-i']
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
[debug] yt-dlp version 2022.07.18 [9d339c4] (win32_exe)
|
||||||
[debug] yt-dlp version 2022.06.22.1 (exe)
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
yt-dlp is up to date (2022.06.22.1)
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: 2022.07.18, Current version: 2022.07.18
|
||||||
|
yt-dlp is up to date (2022.07.18)
|
||||||
<more lines>
|
<more lines>
|
||||||
render: shell
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
49
.github/ISSUE_TEMPLATE/4_bug_report.yml
vendored
49
.github/ISSUE_TEMPLATE/4_bug_report.yml
vendored
@@ -11,46 +11,53 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I'm reporting a bug unrelated to a specific site
|
- label: I'm reporting a bug unrelated to a specific site
|
||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **2022.06.22.1** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
||||||
required: true
|
required: true
|
||||||
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your issue in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Please make sure the description is worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
required: true
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: log
|
id: log
|
||||||
attributes:
|
attributes:
|
||||||
label: Verbose log
|
label: Complete Verbose Output
|
||||||
description: |
|
description: |
|
||||||
Provide the complete verbose output of yt-dlp **that clearly demonstrates the problem**.
|
It should start like this:
|
||||||
Add the `-vU` flag to **your** command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
[debug] Portable config file: yt-dlp.conf
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
[debug] Portable config: ['-i']
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
[debug] yt-dlp version 2022.07.18 [9d339c4] (win32_exe)
|
||||||
[debug] yt-dlp version 2022.06.22.1 (exe)
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
yt-dlp is up to date (2022.06.22.1)
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: 2022.07.18, Current version: 2022.07.18
|
||||||
|
yt-dlp is up to date (2022.07.18)
|
||||||
<more lines>
|
<more lines>
|
||||||
render: shell
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
47
.github/ISSUE_TEMPLATE/5_feature_request.yml
vendored
47
.github/ISSUE_TEMPLATE/5_feature_request.yml
vendored
@@ -13,41 +13,46 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've looked through the [README](https://github.com/yt-dlp/yt-dlp#readme)
|
- label: I've looked through the [README](https://github.com/yt-dlp/yt-dlp#readme)
|
||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **2022.06.22.1** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your site feature request in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Please make sure the description is worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: log
|
id: log
|
||||||
attributes:
|
attributes:
|
||||||
label: Verbose log
|
label: Complete Verbose Output
|
||||||
description: |
|
description: |
|
||||||
If your feature request involves an existing yt-dlp command, provide the complete verbose output of that command.
|
It should start like this:
|
||||||
Add the `-vU` flag to **your** command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
[debug] Portable config file: yt-dlp.conf
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
[debug] Portable config: ['-i']
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
[debug] yt-dlp version 2022.07.18 [9d339c4] (win32_exe)
|
||||||
[debug] yt-dlp version 2021.12.01 (exe)
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
yt-dlp is up to date (2021.12.01)
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: 2022.07.18, Current version: 2022.07.18
|
||||||
|
yt-dlp is up to date (2022.07.18)
|
||||||
<more lines>
|
<more lines>
|
||||||
render: shell
|
render: shell
|
||||||
|
|||||||
55
.github/ISSUE_TEMPLATE/6_question.yml
vendored
55
.github/ISSUE_TEMPLATE/6_question.yml
vendored
@@ -2,6 +2,12 @@ name: Ask question
|
|||||||
description: Ask yt-dlp related question
|
description: Ask yt-dlp related question
|
||||||
labels: [question]
|
labels: [question]
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### Make sure you are **only** asking a question and not reporting a bug or requesting a feature.
|
||||||
|
If your question contains "isn't working" or "can you add", this is most likely the wrong template.
|
||||||
|
If you are in doubt whether this is the right template, **use another template**!
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: checklist
|
id: checklist
|
||||||
attributes:
|
attributes:
|
||||||
@@ -13,43 +19,46 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've looked through the [README](https://github.com/yt-dlp/yt-dlp#readme)
|
- label: I've looked through the [README](https://github.com/yt-dlp/yt-dlp#readme)
|
||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **2022.06.22.1** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar questions including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar questions **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: question
|
id: question
|
||||||
attributes:
|
attributes:
|
||||||
label: Question
|
label: Please make sure the question is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Ask your question in an arbitrary form.
|
placeholder: Provide any additional information and as much context and examples as possible
|
||||||
Please make sure it's worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information and as much context and examples as possible.
|
|
||||||
If your question contains "isn't working" or "can you add", this is most likely the wrong template.
|
|
||||||
If you are in doubt if this is the right template, use another template!
|
|
||||||
placeholder: WRITE QUESTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: log
|
id: log
|
||||||
attributes:
|
attributes:
|
||||||
label: Verbose log
|
label: Complete Verbose Output
|
||||||
description: |
|
description: |
|
||||||
If your question involves a yt-dlp command, provide the complete verbose output of that command.
|
It should start like this:
|
||||||
Add the `-vU` flag to **your** command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
[debug] Portable config file: yt-dlp.conf
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
[debug] Portable config: ['-i']
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
[debug] yt-dlp version 2022.07.18 [9d339c4] (win32_exe)
|
||||||
[debug] yt-dlp version 2021.12.01 (exe)
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
yt-dlp is up to date (2021.12.01)
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: 2022.07.18, Current version: 2022.07.18
|
||||||
|
yt-dlp is up to date (2022.07.18)
|
||||||
<more lines>
|
<more lines>
|
||||||
render: shell
|
render: shell
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml
vendored
38
.github/ISSUE_TEMPLATE_tmpl/1_broken_site.yml
vendored
@@ -17,7 +17,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
@@ -26,38 +26,14 @@ body:
|
|||||||
id: region
|
id: region
|
||||||
attributes:
|
attributes:
|
||||||
label: Region
|
label: Region
|
||||||
description: "Enter the region the site is accessible from"
|
description: Enter the country/region that the site is accessible from
|
||||||
placeholder: "India"
|
placeholder: India
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your issue in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Verbose log
|
|
||||||
description: |
|
|
||||||
Provide the complete verbose output of yt-dlp **that clearly demonstrates the problem**.
|
|
||||||
Add the `-vU` flag to your command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
|
||||||
[debug] Portable config file: yt-dlp.conf
|
|
||||||
[debug] Portable config: ['-i']
|
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
|
||||||
[debug] yt-dlp version %(version)s (exe)
|
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
|
||||||
[debug] Proxy map: {}
|
|
||||||
yt-dlp is up to date (%(version)s)
|
|
||||||
<more lines>
|
|
||||||
render: shell
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
%(verbose)s
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
|
- label: I've checked that none of provided URLs [violate any copyrights](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
@@ -26,8 +26,8 @@ body:
|
|||||||
id: region
|
id: region
|
||||||
attributes:
|
attributes:
|
||||||
label: Region
|
label: Region
|
||||||
description: "Enter the region the site is accessible from"
|
description: Enter the country/region that the site is accessible from
|
||||||
placeholder: "India"
|
placeholder: India
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: example-urls
|
id: example-urls
|
||||||
attributes:
|
attributes:
|
||||||
@@ -43,32 +43,9 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide any additional information
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Verbose log
|
|
||||||
description: |
|
|
||||||
Provide the complete verbose output **using one of the example URLs provided above**.
|
|
||||||
Add the `-vU` flag to your command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
|
||||||
[debug] Portable config file: yt-dlp.conf
|
|
||||||
[debug] Portable config: ['-i']
|
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
|
||||||
[debug] yt-dlp version %(version)s (exe)
|
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
|
||||||
[debug] Proxy map: {}
|
|
||||||
yt-dlp is up to date (%(version)s)
|
|
||||||
<more lines>
|
|
||||||
render: shell
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
%(verbose)s
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
- label: I've checked that all provided URLs are playable in a browser with the same IP and same login details
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
@@ -24,8 +24,8 @@ body:
|
|||||||
id: region
|
id: region
|
||||||
attributes:
|
attributes:
|
||||||
label: Region
|
label: Region
|
||||||
description: "Enter the region the site is accessible from"
|
description: Enter the country/region that the site is accessible from
|
||||||
placeholder: "India"
|
placeholder: India
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: example-urls
|
id: example-urls
|
||||||
attributes:
|
attributes:
|
||||||
@@ -39,34 +39,9 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your site feature request in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Please make sure the description is worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Verbose log
|
|
||||||
description: |
|
|
||||||
Provide the complete verbose output of yt-dlp that demonstrates the need for the enhancement.
|
|
||||||
Add the `-vU` flag to your command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
|
||||||
[debug] Portable config file: yt-dlp.conf
|
|
||||||
[debug] Portable config: ['-i']
|
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
|
||||||
[debug] yt-dlp version %(version)s (exe)
|
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
|
||||||
[debug] Proxy map: {}
|
|
||||||
yt-dlp is up to date (%(version)s)
|
|
||||||
<more lines>
|
|
||||||
render: shell
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
%(verbose)s
|
||||||
|
|||||||
35
.github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml
vendored
35
.github/ISSUE_TEMPLATE_tmpl/4_bug_report.yml
vendored
@@ -17,41 +17,16 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
- label: I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your issue in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Please make sure the description is worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Verbose log
|
|
||||||
description: |
|
|
||||||
Provide the complete verbose output of yt-dlp **that clearly demonstrates the problem**.
|
|
||||||
Add the `-vU` flag to **your** command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
|
||||||
[debug] Portable config file: yt-dlp.conf
|
|
||||||
[debug] Portable config: ['-i']
|
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
|
||||||
[debug] yt-dlp version %(version)s (exe)
|
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
|
||||||
[debug] Proxy map: {}
|
|
||||||
yt-dlp is up to date (%(version)s)
|
|
||||||
<more lines>
|
|
||||||
render: shell
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
%(verbose)s
|
||||||
|
|||||||
@@ -15,39 +15,16 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **%(version)s** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **%(version)s** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Provide a description that is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Provide an explanation of your site feature request in an arbitrary form.
|
placeholder: Provide any additional information, any suggested solutions, and as much context and examples as possible
|
||||||
Please make sure the description is worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information, any suggested solutions, and as much context and examples as possible
|
|
||||||
placeholder: WRITE DESCRIPTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
%(verbose_optional)s
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Verbose log
|
|
||||||
description: |
|
|
||||||
If your feature request involves an existing yt-dlp command, provide the complete verbose output of that command.
|
|
||||||
Add the `-vU` flag to **your** command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
|
||||||
[debug] Portable config file: yt-dlp.conf
|
|
||||||
[debug] Portable config: ['-i']
|
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
|
||||||
[debug] yt-dlp version 2021.12.01 (exe)
|
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
|
||||||
[debug] Proxy map: {}
|
|
||||||
yt-dlp is up to date (2021.12.01)
|
|
||||||
<more lines>
|
|
||||||
render: shell
|
|
||||||
|
|||||||
41
.github/ISSUE_TEMPLATE_tmpl/6_question.yml
vendored
41
.github/ISSUE_TEMPLATE_tmpl/6_question.yml
vendored
@@ -2,6 +2,12 @@ name: Ask question
|
|||||||
description: Ask yt-dlp related question
|
description: Ask yt-dlp related question
|
||||||
labels: [question]
|
labels: [question]
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### Make sure you are **only** asking a question and not reporting a bug or requesting a feature.
|
||||||
|
If your question contains "isn't working" or "can you add", this is most likely the wrong template.
|
||||||
|
If you are in doubt whether this is the right template, **use another template**!
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: checklist
|
id: checklist
|
||||||
attributes:
|
attributes:
|
||||||
@@ -15,41 +21,16 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I've verified that I'm running yt-dlp version **%(version)s** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
- label: I've verified that I'm running yt-dlp version **%(version)s** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
|
||||||
required: true
|
required: true
|
||||||
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar questions including closed ones. DO NOT post duplicates
|
- label: I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar questions **including closed ones**. DO NOT post duplicates
|
||||||
required: true
|
required: true
|
||||||
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
- label: I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: question
|
id: question
|
||||||
attributes:
|
attributes:
|
||||||
label: Question
|
label: Please make sure the question is worded well enough to be understood
|
||||||
description: |
|
description: See [is-the-description-of-the-issue-itself-sufficient](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#is-the-description-of-the-issue-itself-sufficient)
|
||||||
Ask your question in an arbitrary form.
|
placeholder: Provide any additional information and as much context and examples as possible
|
||||||
Please make sure it's worded well enough to be understood, see [is-the-description-of-the-issue-itself-sufficient](https://github.com/ytdl-org/youtube-dl#is-the-description-of-the-issue-itself-sufficient).
|
|
||||||
Provide any additional information and as much context and examples as possible.
|
|
||||||
If your question contains "isn't working" or "can you add", this is most likely the wrong template.
|
|
||||||
If you are in doubt if this is the right template, use another template!
|
|
||||||
placeholder: WRITE QUESTION HERE
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
%(verbose_optional)s
|
||||||
id: log
|
|
||||||
attributes:
|
|
||||||
label: Verbose log
|
|
||||||
description: |
|
|
||||||
If your question involves a yt-dlp command, provide the complete verbose output of that command.
|
|
||||||
Add the `-vU` flag to **your** command line you run yt-dlp with (`yt-dlp -vU <your command line>`), copy the WHOLE output and insert it below.
|
|
||||||
It should look similar to this:
|
|
||||||
placeholder: |
|
|
||||||
[debug] Command-line config: ['-vU', 'http://www.youtube.com/watch?v=BaW_jenozKc']
|
|
||||||
[debug] Portable config file: yt-dlp.conf
|
|
||||||
[debug] Portable config: ['-i']
|
|
||||||
[debug] Encodings: locale cp1252, fs utf-8, stdout utf-8, stderr utf-8, pref cp1252
|
|
||||||
[debug] yt-dlp version 2021.12.01 (exe)
|
|
||||||
[debug] Python version 3.8.8 (CPython 64bit) - Windows-10-10.0.19041-SP0
|
|
||||||
[debug] exe versions: ffmpeg 3.0.1, ffprobe 3.0.1
|
|
||||||
[debug] Optional libraries: Cryptodome, keyring, mutagen, sqlite, websockets
|
|
||||||
[debug] Proxy map: {}
|
|
||||||
yt-dlp is up to date (2021.12.01)
|
|
||||||
<more lines>
|
|
||||||
render: shell
|
|
||||||
|
|||||||
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,8 @@
|
|||||||
|
<details open><summary>Template</summary> <!-- OPEN is intentional -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
# Please follow the guide below
|
|
||||||
|
# PLEASE FOLLOW THE GUIDE BELOW
|
||||||
|
|
||||||
- You will be asked some questions, please read them **carefully** and answer honestly
|
- You will be asked some questions, please read them **carefully** and answer honestly
|
||||||
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
|
- Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
|
||||||
@@ -22,8 +25,16 @@
|
|||||||
- [ ] Core bug fix/improvement
|
- [ ] Core bug fix/improvement
|
||||||
- [ ] New feature (It is strongly [recommended to open an issue first](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-new-feature-or-making-overarching-changes))
|
- [ ] New feature (It is strongly [recommended to open an issue first](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#adding-new-feature-or-making-overarching-changes))
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Description of your *pull request* and other information
|
### Description of your *pull request* and other information
|
||||||
|
|
||||||
Explanation of your *pull request* in arbitrary form goes here. Please **make sure the description explains the purpose and effect** of your *pull request* and is worded well enough to be understood. Provide as much **context and examples** as possible.
|
</details>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Explanation of your *pull request* in arbitrary form goes here. Please **make sure the description explains the purpose and effect** of your *pull request* and is worded well enough to be understood. Provide as much **context and examples** as possible
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
|
||||||
|
Fixes #
|
||||||
|
|||||||
88
.github/workflows/build.yml
vendored
88
.github/workflows/build.yml
vendored
@@ -8,6 +8,7 @@ jobs:
|
|||||||
version_suffix: ${{ steps.version_suffix.outputs.version_suffix }}
|
version_suffix: ${{ steps.version_suffix.outputs.version_suffix }}
|
||||||
ytdlp_version: ${{ steps.bump_version.outputs.ytdlp_version }}
|
ytdlp_version: ${{ steps.bump_version.outputs.ytdlp_version }}
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
release_id: ${{ steps.create_release.outputs.id }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@@ -29,6 +30,7 @@ jobs:
|
|||||||
make issuetemplates
|
make issuetemplates
|
||||||
|
|
||||||
- name: Push to release
|
- name: Push to release
|
||||||
|
id: push_release
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name github-actions
|
git config --global user.name github-actions
|
||||||
git config --global user.email github-actions@example.com
|
git config --global user.email github-actions@example.com
|
||||||
@@ -57,15 +59,19 @@ jobs:
|
|||||||
tag_name: ${{ steps.bump_version.outputs.ytdlp_version }}
|
tag_name: ${{ steps.bump_version.outputs.ytdlp_version }}
|
||||||
release_name: yt-dlp ${{ steps.bump_version.outputs.ytdlp_version }}
|
release_name: yt-dlp ${{ steps.bump_version.outputs.ytdlp_version }}
|
||||||
commitish: ${{ steps.push_release.outputs.head_sha }}
|
commitish: ${{ steps.push_release.outputs.head_sha }}
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
body: |
|
body: |
|
||||||
#### [A description of the various files]((https://github.com/yt-dlp/yt-dlp#release-files)) are in the README
|
#### [A description of the various files]((https://github.com/yt-dlp/yt-dlp#release-files)) are in the README
|
||||||
|
|
||||||
---
|
---
|
||||||
|
<details open><summary><h3>Changelog</summary>
|
||||||
|
<p>
|
||||||
|
|
||||||
### Changelog:
|
|
||||||
${{ env.changelog }}
|
${{ env.changelog }}
|
||||||
draft: false
|
|
||||||
prerelease: false
|
</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
build_unix:
|
build_unix:
|
||||||
@@ -235,6 +241,56 @@ jobs:
|
|||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
|
||||||
|
build_macos_legacy:
|
||||||
|
runs-on: macos-latest
|
||||||
|
needs: create_release
|
||||||
|
outputs:
|
||||||
|
sha256_macos_legacy: ${{ steps.get_sha.outputs.sha256_macos_legacy }}
|
||||||
|
sha512_macos_legacy: ${{ steps.get_sha.outputs.sha512_macos_legacy }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install Python
|
||||||
|
# We need the official Python, because the GA ones only support newer macOS versions
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: 3.10.5
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: 10.9 # Used up by the Python build tools
|
||||||
|
run: |
|
||||||
|
# Hack to get the latest patch version. Uncomment if needed
|
||||||
|
#brew install python@3.10
|
||||||
|
#export PYTHON_VERSION=$( $(brew --prefix)/opt/python@3.10/bin/python3 --version | cut -d ' ' -f 2 )
|
||||||
|
curl https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macos11.pkg -o "python.pkg"
|
||||||
|
sudo installer -pkg python.pkg -target /
|
||||||
|
python3 --version
|
||||||
|
- name: Install Requirements
|
||||||
|
run: |
|
||||||
|
brew install coreutils
|
||||||
|
python3 -m pip install -U --user pip Pyinstaller -r requirements.txt
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
python3 devscripts/update-version.py ${{ needs.create_release.outputs.version_suffix }}
|
||||||
|
python3 devscripts/make_lazy_extractors.py
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
python3 pyinst.py
|
||||||
|
- name: Get SHA2-SUMS
|
||||||
|
id: get_sha
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=sha256_macos_legacy::$(sha256sum dist/yt-dlp_macos | awk '{print $1}')"
|
||||||
|
echo "::set-output name=sha512_macos_legacy::$(sha512sum dist/yt-dlp_macos | awk '{print $1}')"
|
||||||
|
|
||||||
|
- name: Upload standalone binary
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./dist/yt-dlp_macos
|
||||||
|
asset_name: yt-dlp_macos_legacy
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
|
||||||
build_windows:
|
build_windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: create_release
|
needs: create_release
|
||||||
@@ -350,7 +406,7 @@ jobs:
|
|||||||
|
|
||||||
finish:
|
finish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [create_release, build_unix, build_windows, build_windows32, build_macos]
|
needs: [create_release, build_unix, build_windows, build_windows32, build_macos, build_macos_legacy]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Make SHA2-SUMS files
|
- name: Make SHA2-SUMS files
|
||||||
@@ -365,6 +421,7 @@ jobs:
|
|||||||
echo "${{ needs.build_windows.outputs.sha256_win_zip }} yt-dlp_win.zip" >> SHA2-256SUMS
|
echo "${{ needs.build_windows.outputs.sha256_win_zip }} yt-dlp_win.zip" >> SHA2-256SUMS
|
||||||
echo "${{ needs.build_macos.outputs.sha256_macos }} yt-dlp_macos" >> SHA2-256SUMS
|
echo "${{ needs.build_macos.outputs.sha256_macos }} yt-dlp_macos" >> SHA2-256SUMS
|
||||||
echo "${{ needs.build_macos.outputs.sha256_macos_zip }} yt-dlp_macos.zip" >> SHA2-256SUMS
|
echo "${{ needs.build_macos.outputs.sha256_macos_zip }} yt-dlp_macos.zip" >> SHA2-256SUMS
|
||||||
|
echo "${{ needs.build_macos_legacy.outputs.sha256_macos_legacy }} yt-dlp_macos_legacy" >> SHA2-256SUMS
|
||||||
echo "${{ needs.build_unix.outputs.sha512_bin }} yt-dlp" >> SHA2-512SUMS
|
echo "${{ needs.build_unix.outputs.sha512_bin }} yt-dlp" >> SHA2-512SUMS
|
||||||
echo "${{ needs.build_unix.outputs.sha512_tar }} yt-dlp.tar.gz" >> SHA2-512SUMS
|
echo "${{ needs.build_unix.outputs.sha512_tar }} yt-dlp.tar.gz" >> SHA2-512SUMS
|
||||||
echo "${{ needs.build_unix.outputs.sha512_linux }} yt-dlp_linux" >> SHA2-512SUMS
|
echo "${{ needs.build_unix.outputs.sha512_linux }} yt-dlp_linux" >> SHA2-512SUMS
|
||||||
@@ -375,6 +432,7 @@ jobs:
|
|||||||
echo "${{ needs.build_windows.outputs.sha512_win_zip }} yt-dlp_win.zip" >> SHA2-512SUMS
|
echo "${{ needs.build_windows.outputs.sha512_win_zip }} yt-dlp_win.zip" >> SHA2-512SUMS
|
||||||
echo "${{ needs.build_macos.outputs.sha512_macos }} yt-dlp_macos" >> SHA2-512SUMS
|
echo "${{ needs.build_macos.outputs.sha512_macos }} yt-dlp_macos" >> SHA2-512SUMS
|
||||||
echo "${{ needs.build_macos.outputs.sha512_macos_zip }} yt-dlp_macos.zip" >> SHA2-512SUMS
|
echo "${{ needs.build_macos.outputs.sha512_macos_zip }} yt-dlp_macos.zip" >> SHA2-512SUMS
|
||||||
|
echo "${{ needs.build_macos_legacy.outputs.sha512_macos_legacy }} yt-dlp_macos_legacy" >> SHA2-512SUMS
|
||||||
|
|
||||||
- name: Upload SHA2-256SUMS file
|
- name: Upload SHA2-256SUMS file
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
@@ -394,3 +452,25 @@ jobs:
|
|||||||
asset_path: ./SHA2-512SUMS
|
asset_path: ./SHA2-512SUMS
|
||||||
asset_name: SHA2-512SUMS
|
asset_name: SHA2-512SUMS
|
||||||
asset_content_type: text/plain
|
asset_content_type: text/plain
|
||||||
|
|
||||||
|
- name: Make Update spec
|
||||||
|
run: |
|
||||||
|
echo "# This file is used for regulating self-update" >> _update_spec
|
||||||
|
echo "lock 2022.07.18 .+ Python 3.6" >> _update_spec
|
||||||
|
- name: Upload update spec
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./_update_spec
|
||||||
|
asset_name: _update_spec
|
||||||
|
asset_content_type: text/plain
|
||||||
|
|
||||||
|
- name: Finalize release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh api -X PATCH -H "Accept: application/vnd.github.v3+json" \
|
||||||
|
/repos/${{ github.repository }}/releases/${{ needs.create_release.outputs.release_id }} \
|
||||||
|
-F draft=false
|
||||||
|
|||||||
22
.github/workflows/download.yml
vendored
22
.github/workflows/download.yml
vendored
@@ -1,15 +1,31 @@
|
|||||||
name: Download Tests
|
name: Download Tests
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
quick:
|
||||||
name: Download Tests
|
name: Quick Download Tests
|
||||||
if: "contains(github.event.head_commit.message, 'ci run dl')"
|
if: "contains(github.event.head_commit.message, 'ci run dl')"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
- name: Install test requirements
|
||||||
|
run: pip install pytest
|
||||||
|
- name: Run tests
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./devscripts/run_tests.sh download
|
||||||
|
|
||||||
|
full:
|
||||||
|
name: Full Download Tests
|
||||||
|
if: "contains(github.event.head_commit.message, 'ci run dl all')"
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
python-version: ['3.6', '3.7', '3.9', '3.10', 3.11-dev, pypy-3.6, pypy-3.7, pypy-3.8]
|
python-version: ['3.6', '3.7', '3.10', 3.11-dev, pypy-3.6, pypy-3.7, pypy-3.8]
|
||||||
run-tests-ext: [sh]
|
run-tests-ext: [sh]
|
||||||
include:
|
include:
|
||||||
# atleast one of each CPython/PyPy tests must be in windows
|
# atleast one of each CPython/PyPy tests must be in windows
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -27,11 +27,13 @@ cookies
|
|||||||
*.ass
|
*.ass
|
||||||
*.avi
|
*.avi
|
||||||
*.desktop
|
*.desktop
|
||||||
|
*.f4v
|
||||||
*.flac
|
*.flac
|
||||||
*.flv
|
*.flv
|
||||||
*.jpeg
|
*.jpeg
|
||||||
*.jpg
|
*.jpg
|
||||||
*.m4a
|
*.m4a
|
||||||
|
*.mpga
|
||||||
*.m4v
|
*.m4v
|
||||||
*.mhtml
|
*.mhtml
|
||||||
*.mkv
|
*.mkv
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ title = self._search_regex( # incorrect
|
|||||||
webpage, 'title', group='title')
|
webpage, 'title', group='title')
|
||||||
```
|
```
|
||||||
|
|
||||||
Here the presence or absence of other attributes including `style` is irrelevent for the data we need, and so the regex must not depend on it
|
Here the presence or absence of other attributes including `style` is irrelevant for the data we need, and so the regex must not depend on it
|
||||||
|
|
||||||
|
|
||||||
#### Keep the regular expressions as simple as possible, but no simpler
|
#### Keep the regular expressions as simple as possible, but no simpler
|
||||||
@@ -501,7 +501,7 @@ There is a soft limit to keep lines of code under 100 characters long. This mean
|
|||||||
|
|
||||||
For example, you should **never** split long string literals like URLs or some other often copied entities over multiple lines to fit this limit:
|
For example, you should **never** split long string literals like URLs or some other often copied entities over multiple lines to fit this limit:
|
||||||
|
|
||||||
Conversely, don't unecessarily split small lines further. As a rule of thumb, if removing the line split keeps the code under 80 characters, it should be a single line.
|
Conversely, don't unnecessarily split small lines further. As a rule of thumb, if removing the line split keeps the code under 80 characters, it should be a single line.
|
||||||
|
|
||||||
##### Examples
|
##### Examples
|
||||||
|
|
||||||
|
|||||||
18
CONTRIBUTORS
18
CONTRIBUTORS
@@ -267,3 +267,21 @@ sqrtNOT
|
|||||||
bubbleguuum
|
bubbleguuum
|
||||||
darkxex
|
darkxex
|
||||||
miseran
|
miseran
|
||||||
|
StefanLobbenmeier
|
||||||
|
crazymoose77756
|
||||||
|
nomevi
|
||||||
|
Brett824
|
||||||
|
pingiun
|
||||||
|
dosy4ev
|
||||||
|
EhtishamSabir
|
||||||
|
Ferdi265
|
||||||
|
FirefoxMetzger
|
||||||
|
ftk
|
||||||
|
lamby
|
||||||
|
llamasblade
|
||||||
|
lockmatrix
|
||||||
|
misaelaguayo
|
||||||
|
odo2063
|
||||||
|
pritam20ps05
|
||||||
|
scy
|
||||||
|
sheerluck
|
||||||
|
|||||||
110
Changelog.md
110
Changelog.md
@@ -11,6 +11,112 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### 2022.07.18
|
||||||
|
|
||||||
|
* Allow users to specify encoding in each config files by [Lesmiscore](https://github.com/Lesmiscore)
|
||||||
|
* Discard infodict from memory if no longer needed
|
||||||
|
* Do not allow extractors to return `None`
|
||||||
|
* Do not load system certificates when `certifi` is used
|
||||||
|
* Fix rounding of integers in format table
|
||||||
|
* Improve chapter sanitization
|
||||||
|
* Skip some fixup if remux/recode is needed by [Lesmiscore](https://github.com/Lesmiscore)
|
||||||
|
* Support `--no-progress` for `--wait-for-video`
|
||||||
|
* Fix bug in [612f2be](https://github.com/yt-dlp/yt-dlp/commit/612f2be5d3924540158dfbe5f25d841f04cff8c6)
|
||||||
|
* [outtmpl] Add alternate form `h` for HTML escaping
|
||||||
|
* [aes] Add multiple padding modes in CBC by [elyse0](https://github.com/elyse0)
|
||||||
|
* [extractor/common] Passthrough `errnote=False` to parsers
|
||||||
|
* [extractor/generic] Remove HEAD request
|
||||||
|
* [http] Ensure the file handle is always closed
|
||||||
|
* [ModifyChapters] Modify duration in infodict
|
||||||
|
* [options] Fix aliases to `--config-location`
|
||||||
|
* [utils] Fix `get_domain`
|
||||||
|
* [build] Consistent order for lazy extractors by [lamby](https://github.com/lamby)
|
||||||
|
* [build] Fix architecture suffix of executables by [odo2063](https://github.com/odo2063)
|
||||||
|
* [build] Improve `setup.py`
|
||||||
|
* [update] Do not check `_update_spec` when up to date
|
||||||
|
* [update] Prepare to remove Python 3.6 support
|
||||||
|
* [compat] Let PyInstaller detect _legacy module
|
||||||
|
* [devscripts/update-formulae] Do not change dependency section
|
||||||
|
* [test] Split download tests so they can be more easily run in CI
|
||||||
|
* [docs] Improve docstring of `download_ranges` by [FirefoxMetzger](https://github.com/FirefoxMetzger)
|
||||||
|
* [docs] Improve issue templates
|
||||||
|
* [build] Fix bug in [6d916fe](https://github.com/yt-dlp/yt-dlp/commit/6d916fe709a38e8c4c69b73843acf170b5165931)
|
||||||
|
* [cleanup, utils] Refactor parse_codecs
|
||||||
|
* [cleanup] Misc fixes and cleanup
|
||||||
|
* [extractor/acfun] Add extractors by [lockmatrix](https://github.com/lockmatrix)
|
||||||
|
* [extractor/Audiodraft] Add extractors by [Ashish0804](https://github.com/Ashish0804), [fstirlitz](https://github.com/fstirlitz)
|
||||||
|
* [extractor/cellebrite] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/detik] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/hytale] Add extractor by [llamasblade](https://github.com/llamasblade), [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [extractor/liputan6] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/mocha] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/rtl.lu] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/rtvsl] Add extractor by [iw0nderhow](https://github.com/iw0nderhow), [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [extractor/StarTrek] Add extractor by [scy](https://github.com/scy)
|
||||||
|
* [extractor/syvdk] Add extractor by [misaelaguayo](https://github.com/misaelaguayo)
|
||||||
|
* [extractor/theholetv] Add extractor by [dosy4ev](https://github.com/dosy4ev)
|
||||||
|
* [extractor/TubeTuGraz] Add extractor by [Ferdi265](https://github.com/Ferdi265), [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [extractor/tviplayer] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/wetv] Add extractors by [elyse0](https://github.com/elyse0)
|
||||||
|
* [extractor/wikimedia] Add extractor by [EhtishamSabir](https://github.com/EhtishamSabir), [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [extractor/youtube] Fix duration check for post-live manifestless mode
|
||||||
|
* [extractor/youtube] More metadata for storyboards by [ftk](https://github.com/ftk)
|
||||||
|
* [extractor/bigo] Fix extractor by [Lesmiscore](https://github.com/Lesmiscore)
|
||||||
|
* [extractor/BiliIntl] Fix subtitle extraction by [MinePlayersPE](https://github.com/MinePlayersPE)
|
||||||
|
* [extractor/crunchyroll] Improve `_VALID_URL`
|
||||||
|
* [extractor/fifa] Fix extractor by [ischmidt20](https://github.com/ischmidt20)
|
||||||
|
* [extractor/instagram] Fix post/story extractors by [pritam20ps05](https://github.com/pritam20ps05), [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [extractor/iq] Set language correctly for Korean subtitles
|
||||||
|
* [extractor/MangoTV] Fix subtitle languages
|
||||||
|
* [extractor/Netverse] Improve playlist extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/philharmoniedeparis] Fix extractor by [sqrtNOT](https://github.com/sqrtNOT)
|
||||||
|
* [extractor/Trovo] Fix extractor by [u-spec-png](https://github.com/u-spec-png)
|
||||||
|
* [extractor/twitch] Support storyboards for VODs by [ftk](https://github.com/ftk)
|
||||||
|
* [extractor/WatchESPN] Improve `_VALID_URL` by [IONECarter](https://github.com/IONECarter), [dirkf](https://github.com/dirkf)
|
||||||
|
* [extractor/WSJArticle] Fix video id extraction by [sqrtNOT](https://github.com/sqrtNOT)
|
||||||
|
* [extractor/Ximalaya] Fix extractors by [lockmatrix](https://github.com/lockmatrix)
|
||||||
|
* [cleanup, extractor/youtube] Fix tests by [sheerluck](https://github.com/sheerluck)
|
||||||
|
|
||||||
|
|
||||||
|
### 2022.06.29
|
||||||
|
|
||||||
|
* Fix `--downloader native`
|
||||||
|
* Fix `section_end` of clips
|
||||||
|
* Fix playlist error handling
|
||||||
|
* Sanitize `chapters`
|
||||||
|
* [extractor] Fix `_create_request` when headers is None
|
||||||
|
* [extractor] Fix empty `BaseURL` in MPD
|
||||||
|
* [ffmpeg] Write full output to debug on error
|
||||||
|
* [hls] Warn user when trying to download live HLS
|
||||||
|
* [options] Fix `parse_known_args` for `--`
|
||||||
|
* [utils] Fix inconsistent default handling between HTTP and HTTPS requests by [coletdjnz](https://github.com/coletdjnz)
|
||||||
|
* [build] Draft release until complete
|
||||||
|
* [build] Fix release tag commit
|
||||||
|
* [build] Standalone x64 builds for MacOS 10.9 by [StefanLobbenmeier](https://github.com/StefanLobbenmeier)
|
||||||
|
* [update] Ability to set a maximum version for specific variants
|
||||||
|
* [compat] Fix `compat.WINDOWS_VT_MODE`
|
||||||
|
* [compat] Remove deprecated functions from core code
|
||||||
|
* [compat] Remove more functions
|
||||||
|
* [cleanup, extractor] Reduce direct use of `_downloader`
|
||||||
|
* [cleanup] Consistent style for file heads
|
||||||
|
* [cleanup] Fix some typos by [crazymoose77756](https://github.com/crazymoose77756)
|
||||||
|
* [cleanup] Misc fixes and cleanup
|
||||||
|
* [extractor/Scrolller] Add extractor by [LunarFang416](https://github.com/LunarFang416)
|
||||||
|
* [extractor/ViMP] Add playlist extractor by [FestplattenSchnitzel](https://github.com/FestplattenSchnitzel)
|
||||||
|
* [extractor/fuyin] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/livestreamfails] Add extractor by [nomevi](https://github.com/nomevi)
|
||||||
|
* [extractor/premiershiprugby] Add extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/steam] Add broadcast extractor by [HobbyistDev](https://github.com/HobbyistDev)
|
||||||
|
* [extractor/youtube] Mark videos as fully watched by [Brett824](https://github.com/Brett824)
|
||||||
|
* [extractor/CWTV] Extract thumbnail by [ischmidt20](https://github.com/ischmidt20)
|
||||||
|
* [extractor/ViMP] Add thumbnail and support more sites by [FestplattenSchnitzel](https://github.com/FestplattenSchnitzel)
|
||||||
|
* [extractor/dropout] Support cookies and login only as needed by [pingiun](https://github.com/pingiun), [pukkandan](https://github.com/pukkandan)
|
||||||
|
* [extractor/ertflix] Improve `_VALID_URL`
|
||||||
|
* [extractor/lbry] Use HEAD request for redirect URL by [flashdagger](https://github.com/flashdagger)
|
||||||
|
* [extractor/mediaset] Improve `_VALID_URL`
|
||||||
|
* [extractor/npr] Implement [e50c350](https://github.com/yt-dlp/yt-dlp/commit/e50c3500b43d80e4492569c4b4523c4379c6fbb2) differently
|
||||||
|
* [extractor/tennistv] Rewrite extractor by [pukkandan](https://github.com/pukkandan), [zenerdi0de](https://github.com/zenerdi0de)
|
||||||
|
|
||||||
### 2022.06.22.1
|
### 2022.06.22.1
|
||||||
|
|
||||||
* [build] Fix updating homebrew formula
|
* [build] Fix updating homebrew formula
|
||||||
@@ -544,7 +650,7 @@
|
|||||||
* [downloader/ffmpeg] Handle unknown formats better
|
* [downloader/ffmpeg] Handle unknown formats better
|
||||||
* [outtmpl] Handle `-o ""` better
|
* [outtmpl] Handle `-o ""` better
|
||||||
* [outtmpl] Handle hard-coded file extension better
|
* [outtmpl] Handle hard-coded file extension better
|
||||||
* [extractor] Add convinience function `_yes_playlist`
|
* [extractor] Add convenience function `_yes_playlist`
|
||||||
* [extractor] Allow non-fatal `title` extraction
|
* [extractor] Allow non-fatal `title` extraction
|
||||||
* [extractor] Extract video inside `Article` json_ld
|
* [extractor] Extract video inside `Article` json_ld
|
||||||
* [generic] Allow further processing of json_ld URL
|
* [generic] Allow further processing of json_ld URL
|
||||||
@@ -1678,7 +1784,7 @@
|
|||||||
* [utils] Generalize `traverse_dict` to `traverse_obj`
|
* [utils] Generalize `traverse_dict` to `traverse_obj`
|
||||||
* [downloader/ffmpeg] Hide FFmpeg banner unless in verbose mode by [fstirlitz](https://github.com/fstirlitz)
|
* [downloader/ffmpeg] Hide FFmpeg banner unless in verbose mode by [fstirlitz](https://github.com/fstirlitz)
|
||||||
* [build] Release `yt-dlp.tar.gz`
|
* [build] Release `yt-dlp.tar.gz`
|
||||||
* [build,update] Add GNU-style SHA512 and prepare updater for simlar SHA256 by [nihil-admirari](https://github.com/nihil-admirari)
|
* [build,update] Add GNU-style SHA512 and prepare updater for similar SHA256 by [nihil-admirari](https://github.com/nihil-admirari)
|
||||||
* [pyinst] Show Python version in exe metadata by [nihil-admirari](https://github.com/nihil-admirari)
|
* [pyinst] Show Python version in exe metadata by [nihil-admirari](https://github.com/nihil-admirari)
|
||||||
* [docs] Improve documentation of dependencies
|
* [docs] Improve documentation of dependencies
|
||||||
* [cleanup] Mark unused files
|
* [cleanup] Mark unused files
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -17,8 +17,8 @@ pypi-files: AUTHORS Changelog.md LICENSE README.md README.txt supportedsites \
|
|||||||
clean-test:
|
clean-test:
|
||||||
rm -rf test/testdata/sigs/player-*.js tmp/ *.annotations.xml *.aria2 *.description *.dump *.frag \
|
rm -rf test/testdata/sigs/player-*.js tmp/ *.annotations.xml *.aria2 *.description *.dump *.frag \
|
||||||
*.frag.aria2 *.frag.urls *.info.json *.live_chat.json *.meta *.part* *.tmp *.temp *.unknown_video *.ytdl \
|
*.frag.aria2 *.frag.urls *.info.json *.live_chat.json *.meta *.part* *.tmp *.temp *.unknown_video *.ytdl \
|
||||||
*.3gp *.ape *.ass *.avi *.desktop *.flac *.flv *.jpeg *.jpg *.m4a *.m4v *.mhtml *.mkv *.mov *.mp3 \
|
*.3gp *.ape *.ass *.avi *.desktop *.f4v *.flac *.flv *.jpeg *.jpg *.m4a *.mpga *.m4v *.mhtml *.mkv *.mov \
|
||||||
*.mp4 *.ogg *.opus *.png *.sbv *.srt *.swf *.swp *.ttml *.url *.vtt *.wav *.webloc *.webm *.webp
|
*.mp3 *.mp4 *.ogg *.opus *.png *.sbv *.srt *.swf *.swp *.ttml *.url *.vtt *.wav *.webloc *.webm *.webp
|
||||||
clean-dist:
|
clean-dist:
|
||||||
rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ \
|
rm -rf yt-dlp.1.temp.md yt-dlp.1 README.txt MANIFEST build/ dist/ .coverage cover/ yt-dlp.tar.gz completions/ \
|
||||||
yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS .mailmap
|
yt_dlp/extractor/lazy_extractors.py *.spec CONTRIBUTING.md.tmp yt-dlp yt-dlp.exe yt_dlp.egg-info/ AUTHORS .mailmap
|
||||||
|
|||||||
51
README.md
51
README.md
@@ -71,7 +71,7 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t
|
|||||||
|
|
||||||
# NEW FEATURES
|
# NEW FEATURES
|
||||||
|
|
||||||
* Based on **youtube-dl 2021.12.17 [commit/8a158a9](https://github.com/ytdl-org/youtube-dl/commit/8a158a936c8b002ef536e9e2b778ded02c09c0fa)**<!--([exceptions](https://github.com/yt-dlp/yt-dlp/issues/21))--> and **youtube-dlc 2020.11.11-3 [commit/f9401f2](https://github.com/blackjack4494/yt-dlc/commit/f9401f2a91987068139c5f757b12fc711d4c0cee)**: You get all the features and patches of [youtube-dlc](https://github.com/blackjack4494/yt-dlc) in addition to the latest [youtube-dl](https://github.com/ytdl-org/youtube-dl)
|
* Merged with **youtube-dl v2021.12.17+ [commit/a03b977](https://github.com/ytdl-org/youtube-dl/commit/a03b9775d544b06a5b4f2aa630214c7c22fc2229)**<!--([exceptions](https://github.com/yt-dlp/yt-dlp/issues/21))--> and **youtube-dlc v2020.11.11-3+ [commit/f9401f2](https://github.com/blackjack4494/yt-dlc/commit/f9401f2a91987068139c5f757b12fc711d4c0cee)**: You get all the features and patches of [youtube-dlc](https://github.com/blackjack4494/yt-dlc) in addition to the latest [youtube-dl](https://github.com/ytdl-org/youtube-dl)
|
||||||
|
|
||||||
* **[SponsorBlock Integration](#sponsorblock-options)**: You can mark/remove sponsor sections in youtube videos by utilizing the [SponsorBlock](https://sponsor.ajay.app) API
|
* **[SponsorBlock Integration](#sponsorblock-options)**: You can mark/remove sponsor sections in youtube videos by utilizing the [SponsorBlock](https://sponsor.ajay.app) API
|
||||||
|
|
||||||
@@ -79,18 +79,13 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t
|
|||||||
|
|
||||||
* **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 livestreams 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 livestreams are not available. See [#31](https://github.com/yt-dlp/yt-dlp/pull/31) for details.
|
||||||
|
|
||||||
* **Youtube improvements**:
|
* **YouTube improvements**:
|
||||||
* All Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`, `:ytnotif`) and private playlists supports downloading multiple pages of content
|
* Supports Clips, Stories (`ytstories:<channel UCID>`), Search (including filters)**\***, YouTube Music Search, Channel-specific search, Search prefixes (`ytsearch:`, `ytsearchdate:`)**\***, Mixes, YouTube Music Albums/Channels ([except self-uploaded music](https://github.com/yt-dlp/yt-dlp/issues/723)), and Feeds (`:ytfav`, `:ytwatchlater`, `:ytsubs`, `:ythistory`, `:ytrec`, `:ytnotif`)
|
||||||
* Search (`ytsearch:`, `ytsearchdate:`), search URLs and in-channel search works
|
* Fix for [n-sig based throttling](https://github.com/ytdl-org/youtube-dl/issues/29326) **\***
|
||||||
* Mixes supports downloading multiple pages of content
|
* Supports some (but not all) age-gated content without cookies
|
||||||
* Some (but not all) age-gated content can be downloaded without cookies
|
* Download livestreams from the start using `--live-from-start` (*experimental*)
|
||||||
* Fix for [n-sig based throttling](https://github.com/ytdl-org/youtube-dl/issues/29326)
|
* `255kbps` audio is extracted (if available) from YouTube Music when premium cookies are given
|
||||||
* Redirect channel's home URL automatically to `/video` to preserve the old behaviour
|
* Redirect channel's home URL automatically to `/video` to preserve the old behaviour
|
||||||
* `255kbps` audio is extracted (if available) from youtube music when premium cookies are given
|
|
||||||
* Youtube music Albums, channels etc can be downloaded ([except self-uploaded music](https://github.com/yt-dlp/yt-dlp/issues/723))
|
|
||||||
* Download livestreams from the start using `--live-from-start` (experimental)
|
|
||||||
* Support for downloading stories (`ytstories:<channel UCID>`)
|
|
||||||
* Support for downloading clips
|
|
||||||
|
|
||||||
* **Cookies from browser**: Cookies can be automatically extracted from all major web browsers using `--cookies-from-browser BROWSER[+KEYRING][:PROFILE]`
|
* **Cookies from browser**: Cookies can be automatically extracted from all major web browsers using `--cookies-from-browser BROWSER[+KEYRING][:PROFILE]`
|
||||||
|
|
||||||
@@ -124,6 +119,8 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t
|
|||||||
|
|
||||||
See [changelog](Changelog.md) or [commits](https://github.com/yt-dlp/yt-dlp/commits) for the full list of changes
|
See [changelog](Changelog.md) or [commits](https://github.com/yt-dlp/yt-dlp/commits) for the full list of changes
|
||||||
|
|
||||||
|
Features marked with a **\*** have been back-ported to youtube-dl
|
||||||
|
|
||||||
### Differences in default behavior
|
### Differences in default behavior
|
||||||
|
|
||||||
Some of yt-dlp's default options are different from that of youtube-dl and youtube-dlc:
|
Some of yt-dlp's default options are different from that of youtube-dl and youtube-dlc:
|
||||||
@@ -149,8 +146,8 @@ Some of yt-dlp's default options are different from that of youtube-dl and youtu
|
|||||||
* 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 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
|
* 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
|
||||||
* `certifi` will be used for SSL root certificates, if installed. If you want to use only system certificates, use `--compat-options no-certifi`
|
* `certifi` will be used for SSL root certificates, if installed. If you want to use system certificates (e.g. self-signed), use `--compat-options no-certifi`
|
||||||
* youtube-dl tries to remove some superfluous punctuations from filenames. While this can sometimes be helpfull, it is often undesirable. So yt-dlp tries to keep the fields in the filenames as close to their original values as possible. You can use `--compat-options filename-sanitization` to revert to youtube-dl's behavior
|
* youtube-dl tries to remove some superfluous punctuations from filenames. While this can sometimes be helpful, it is often undesirable. So yt-dlp tries to keep the fields in the filenames as close to their original values as possible. You can use `--compat-options filename-sanitization` to revert to youtube-dl's behavior
|
||||||
|
|
||||||
For ease of use, a few more compat options are available:
|
For ease of use, a few more compat options are available:
|
||||||
|
|
||||||
@@ -239,9 +236,9 @@ If you [installed using Homebrew](#with-homebrew), run `brew upgrade yt-dlp/taps
|
|||||||
|
|
||||||
File|Description
|
File|Description
|
||||||
:---|:---
|
:---|:---
|
||||||
[yt-dlp](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp)|Platform-independant [zipimport](https://docs.python.org/3/library/zipimport.html) binary. Needs Python (recommended for **Linux/BSD**)
|
[yt-dlp](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp)|Platform-independent [zipimport](https://docs.python.org/3/library/zipimport.html) binary. Needs Python (recommended for **Linux/BSD**)
|
||||||
[yt-dlp.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe)|Windows (Win7 SP1+) standalone x64 binary (recommended for **Windows**)
|
[yt-dlp.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe)|Windows (Win7 SP1+) standalone x64 binary (recommended for **Windows**)
|
||||||
[yt-dlp_macos](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos)|MacOS (10.15+) standalone executable (recommended for **MacOS**)
|
[yt-dlp_macos](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos)|Universal MacOS (10.15+) standalone executable (recommended for **MacOS**)
|
||||||
|
|
||||||
#### Alternatives
|
#### Alternatives
|
||||||
|
|
||||||
@@ -249,10 +246,11 @@ File|Description
|
|||||||
:---|:---
|
:---|:---
|
||||||
[yt-dlp_x86.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_x86.exe)|Windows (Vista SP2+) standalone x86 (32-bit) binary
|
[yt-dlp_x86.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_x86.exe)|Windows (Vista SP2+) standalone x86 (32-bit) binary
|
||||||
[yt-dlp_min.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_min.exe)|Windows (Win7 SP1+) standalone x64 binary built with `py2exe`<br/> ([Not recommended](#standalone-py2exe-builds-windows))
|
[yt-dlp_min.exe](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_min.exe)|Windows (Win7 SP1+) standalone x64 binary built with `py2exe`<br/> ([Not recommended](#standalone-py2exe-builds-windows))
|
||||||
[yt-dlp_linux](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux)|UNIX standalone x64 binary
|
[yt-dlp_linux](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux)|Linux standalone x64 binary
|
||||||
[yt-dlp_linux.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux.zip)|Unpackaged Unix executable (no auto-update)
|
[yt-dlp_linux.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux.zip)|Unpackaged Linux executable (no auto-update)
|
||||||
[yt-dlp_win.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win.zip)|Unpackaged Windows executable (no auto-update)
|
[yt-dlp_win.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_win.zip)|Unpackaged Windows executable (no auto-update)
|
||||||
[yt-dlp_macos.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos.zip)|Unpackaged MacOS (10.15+) executable (no auto-update)
|
[yt-dlp_macos.zip](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos.zip)|Unpackaged MacOS (10.15+) executable (no auto-update)
|
||||||
|
[yt-dlp_macos_legacy](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos_legacy)|MacOS (10.9+) standalone x64 executable
|
||||||
|
|
||||||
#### Misc
|
#### Misc
|
||||||
|
|
||||||
@@ -307,7 +305,7 @@ While all the other dependencies are optional, `ffmpeg` and `ffprobe` are highly
|
|||||||
|
|
||||||
To use or redistribute the dependencies, you must agree to their respective licensing terms.
|
To use or redistribute the dependencies, you must agree to their respective licensing terms.
|
||||||
|
|
||||||
The Windows and MacOS standalone release binaries are built with the Python interpreter and the packages marked with **\*** included.
|
The standalone release binaries are built with the Python interpreter and the packages marked with **\*** included.
|
||||||
|
|
||||||
If you do not have the necessary dependencies for a task you are attempting, yt-dlp will warn you. All the currently available dependencies are visible at the top of the `--verbose` output
|
If you do not have the necessary dependencies for a task you are attempting, yt-dlp will warn you. All the currently available dependencies are visible at the top of the `--verbose` output
|
||||||
|
|
||||||
@@ -416,7 +414,8 @@ You can also fork the project on github and run your fork's [build workflow](.gi
|
|||||||
--no-wait-for-video Do not wait for scheduled streams (default)
|
--no-wait-for-video Do not wait for scheduled streams (default)
|
||||||
--mark-watched Mark videos watched (even with --simulate)
|
--mark-watched Mark videos watched (even with --simulate)
|
||||||
--no-mark-watched Do not mark videos watched (default)
|
--no-mark-watched Do not mark videos watched (default)
|
||||||
--no-colors Do not emit color codes in output
|
--no-colors Do not emit color codes in output (Alias:
|
||||||
|
--no-colours)
|
||||||
--compat-options OPTS Options that can help keep compatibility
|
--compat-options OPTS Options that can help keep compatibility
|
||||||
with youtube-dl or youtube-dlc
|
with youtube-dl or youtube-dlc
|
||||||
configurations by reverting some of the
|
configurations by reverting some of the
|
||||||
@@ -433,7 +432,7 @@ You can also fork the project on github and run your fork's [build workflow](.gi
|
|||||||
"-S=aext:ARG0,abr -x --audio-format ARG0".
|
"-S=aext:ARG0,abr -x --audio-format ARG0".
|
||||||
All defined aliases are listed in the --help
|
All defined aliases are listed in the --help
|
||||||
output. Alias options can trigger more
|
output. Alias options can trigger more
|
||||||
aliases; so be carefull to avoid defining
|
aliases; so be careful to avoid defining
|
||||||
recursive options. As a safety measure, each
|
recursive options. As a safety measure, each
|
||||||
alias may be triggered a maximum of 100
|
alias may be triggered a maximum of 100
|
||||||
times. This option can be used multiple times
|
times. This option can be used multiple times
|
||||||
@@ -466,7 +465,7 @@ You can also fork the project on github and run your fork's [build workflow](.gi
|
|||||||
explicitly provided IP block in CIDR notation
|
explicitly provided IP block in CIDR notation
|
||||||
|
|
||||||
## Video Selection:
|
## Video Selection:
|
||||||
-I, --playlist-items ITEM_SPEC Comma seperated playlist_index of the videos
|
-I, --playlist-items ITEM_SPEC Comma separated playlist_index of the videos
|
||||||
to download. You can specify a range using
|
to download. You can specify a range using
|
||||||
"[START]:[STOP][:STEP]". For backward
|
"[START]:[STOP][:STEP]". For backward
|
||||||
compatibility, START-STOP is also supported.
|
compatibility, START-STOP is also supported.
|
||||||
@@ -1162,6 +1161,12 @@ Note that options in configuration file are just the same options aka switches u
|
|||||||
|
|
||||||
You can use `--ignore-config` if you want to disable all configuration files for a particular yt-dlp run. If `--ignore-config` is found inside any configuration file, no further configuration will be loaded. For example, having the option in the portable configuration file prevents loading of home, user, and system configurations. Additionally, (for backward compatibility) if `--ignore-config` is found inside the system configuration file, the user configuration is not loaded.
|
You can use `--ignore-config` if you want to disable all configuration files for a particular yt-dlp run. If `--ignore-config` is found inside any configuration file, no further configuration will be loaded. For example, having the option in the portable configuration file prevents loading of home, user, and system configurations. Additionally, (for backward compatibility) if `--ignore-config` is found inside the system configuration file, the user configuration is not loaded.
|
||||||
|
|
||||||
|
### Config file encoding
|
||||||
|
|
||||||
|
The config files are decoded according to the UTF BOM if present, and in the encoding from system locale otherwise.
|
||||||
|
|
||||||
|
If you want your file to be decoded differently, add `# coding: ENCODING` to the beginning of the file (e.g. `# coding: shift-jis`). There must be no characters before that, even spaces or BOM.
|
||||||
|
|
||||||
### Authentication with `.netrc` file
|
### Authentication with `.netrc` file
|
||||||
|
|
||||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every yt-dlp execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](https://stackoverflow.com/tags/.netrc/info) on a per extractor basis. For that you will need to create a `.netrc` file in `--netrc-location` and restrict permissions to read/write by only you:
|
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every yt-dlp execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](https://stackoverflow.com/tags/.netrc/info) on a per extractor basis. For that you will need to create a `.netrc` file in `--netrc-location` and restrict permissions to read/write by only you:
|
||||||
@@ -1208,7 +1213,7 @@ The field names themselves (the part inside the parenthesis) can also have some
|
|||||||
|
|
||||||
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. **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`, `D`, `S` can be used for converting to **B**ytes, **j**son (flag `#` for pretty-printing), a comma separated **l**ist (flag `#` for `\n` newline-separated), a string **q**uoted for the terminal (flag `#` to split a list into different arguments), to add **D**ecimal suffixes (Eg: 10M) (flag `#` to use 1024 as factor), and to **S**anitize as filename (flag `#` for restricted), respectively
|
1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, yt-dlp additionally supports converting to `B` = **B**ytes, `j` = **j**son (flag `#` for pretty-printing), `h` = HTML escaping, `l` = a comma separated **l**ist (flag `#` for `\n` newline-separated), `q` = a string **q**uoted for the terminal (flag `#` to split a list into different arguments), `D` = add **D**ecimal suffixes (Eg: 10M) (flag `#` to use 1024 as factor), and `S` = **S**anitize as filename (flag `#` for restricted)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
BASH_COMPLETION_FILE = "completions/bash/yt-dlp"
|
BASH_COMPLETION_FILE = "completions/bash/yt-dlp"
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import sys
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import gettestcases
|
|
||||||
|
|
||||||
from yt_dlp.utils import compat_urllib_parse_urlparse, compat_urllib_request
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
from test.helper import gettestcases
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
METHOD = 'LIST'
|
METHOD = 'LIST'
|
||||||
@@ -26,7 +28,7 @@ else:
|
|||||||
for test in gettestcases():
|
for test in gettestcases():
|
||||||
if METHOD == 'EURISTIC':
|
if METHOD == 'EURISTIC':
|
||||||
try:
|
try:
|
||||||
webpage = compat_urllib_request.urlopen(test['url'], timeout=10).read()
|
webpage = urllib.request.urlopen(test['url'], timeout=10).read()
|
||||||
except Exception:
|
except Exception:
|
||||||
print('\nFail: {}'.format(test['name']))
|
print('\nFail: {}'.format(test['name']))
|
||||||
continue
|
continue
|
||||||
@@ -36,7 +38,7 @@ for test in gettestcases():
|
|||||||
RESULT = 'porn' in webpage.lower()
|
RESULT = 'porn' in webpage.lower()
|
||||||
|
|
||||||
elif METHOD == 'LIST':
|
elif METHOD == 'LIST':
|
||||||
domain = compat_urllib_parse_urlparse(test['url']).netloc
|
domain = urllib.parse.urlparse(test['url']).netloc
|
||||||
if not domain:
|
if not domain:
|
||||||
print('\nFail: {}'.format(test['name']))
|
print('\nFail: {}'.format(test['name']))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import optparse
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
from yt_dlp.utils import shell_quote
|
from yt_dlp.utils import shell_quote
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import codecs
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import codecs
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from yt_dlp.aes import aes_encrypt, key_expansion
|
from yt_dlp.aes import aes_encrypt, key_expansion
|
||||||
from yt_dlp.utils import intlist_to_bytes
|
from yt_dlp.utils import intlist_to_bytes
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import optparse
|
import optparse
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import optparse
|
import optparse
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
def read(fname):
|
def read(fname):
|
||||||
@@ -7,22 +16,62 @@ def read(fname):
|
|||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
# Get the version from yt_dlp/version.py without importing the package
|
# Get the version without importing the package
|
||||||
def read_version(fname):
|
def read_version(fname):
|
||||||
exec(compile(read(fname), fname, 'exec'))
|
exec(compile(read(fname), fname, 'exec'))
|
||||||
return locals()['__version__']
|
return locals()['__version__']
|
||||||
|
|
||||||
|
|
||||||
|
VERBOSE_TMPL = '''
|
||||||
|
- type: checkboxes
|
||||||
|
id: verbose
|
||||||
|
attributes:
|
||||||
|
label: Provide verbose output that clearly demonstrates the problem
|
||||||
|
options:
|
||||||
|
- label: Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
|
||||||
|
required: true
|
||||||
|
- label: Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: Complete Verbose Output
|
||||||
|
description: |
|
||||||
|
It should start like this:
|
||||||
|
placeholder: |
|
||||||
|
[debug] Command-line config: ['-vU', 'test:youtube']
|
||||||
|
[debug] Portable config "yt-dlp.conf": ['-i']
|
||||||
|
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||||
|
[debug] yt-dlp version %(version)s [9d339c4] (win32_exe)
|
||||||
|
[debug] Python 3.8.10 (CPython 64bit) - Windows-10-10.0.22000-SP0
|
||||||
|
[debug] Checking exe version: ffmpeg -bsfs
|
||||||
|
[debug] Checking exe version: ffprobe -bsfs
|
||||||
|
[debug] exe versions: ffmpeg N-106550-g072101bd52-20220410 (fdk,setts), ffprobe N-106624-g391ce570c8-20220415, phantomjs 2.1.1
|
||||||
|
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
|
||||||
|
[debug] Proxy map: {}
|
||||||
|
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||||
|
Latest version: %(version)s, Current version: %(version)s
|
||||||
|
yt-dlp is up to date (%(version)s)
|
||||||
|
<more lines>
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
'''.strip()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = optparse.OptionParser(usage='%prog INFILE OUTFILE')
|
parser = optparse.OptionParser(usage='%prog INFILE OUTFILE')
|
||||||
options, args = parser.parse_args()
|
_, args = parser.parse_args()
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
parser.error('Expected an input and an output filename')
|
parser.error('Expected an input and an output filename')
|
||||||
|
|
||||||
|
fields = {'version': read_version('yt_dlp/version.py')}
|
||||||
|
fields['verbose'] = VERBOSE_TMPL % fields
|
||||||
|
fields['verbose_optional'] = re.sub(r'(\n\s+validations:)?\n\s+required: true', '', fields['verbose'])
|
||||||
|
|
||||||
infile, outfile = args
|
infile, outfile = args
|
||||||
with open(outfile, 'w', encoding='utf-8') as outf:
|
with open(outfile, 'w', encoding='utf-8') as outf:
|
||||||
outf.write(
|
outf.write(read(infile) % fields)
|
||||||
read(infile) % {'version': read_version('yt_dlp/version.py')})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import optparse
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from inspect import getsource
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
from inspect import getsource
|
||||||
|
|
||||||
NO_ATTR = object()
|
NO_ATTR = object()
|
||||||
STATIC_CLASS_PROPERTIES = ['IE_NAME', 'IE_DESC', 'SEARCH_KEY', '_WORKING', '_NETRC_MACHINE', 'age_limit']
|
STATIC_CLASS_PROPERTIES = ['IE_NAME', 'IE_DESC', 'SEARCH_KEY', '_WORKING', '_NETRC_MACHINE', 'age_limit']
|
||||||
CLASS_METHODS = [
|
CLASS_METHODS = [
|
||||||
@@ -91,7 +94,7 @@ def sort_ies(ies, ignored_bases):
|
|||||||
for c in classes[:]:
|
for c in classes[:]:
|
||||||
bases = set(c.__bases__) - {object, *ignored_bases}
|
bases = set(c.__bases__) - {object, *ignored_bases}
|
||||||
restart = False
|
restart = False
|
||||||
for b in bases:
|
for b in sorted(bases, key=lambda x: x.__name__):
|
||||||
if b not in classes and b not in returned_classes:
|
if b not in classes and b not in returned_classes:
|
||||||
assert b.__name__ != 'GenericIE', 'Cannot inherit from GenericIE'
|
assert b.__name__ != 'GenericIE', 'Cannot inherit from GenericIE'
|
||||||
classes.insert(0, b)
|
classes.insert(0, b)
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# yt-dlp --help | make_readme.py
|
"""
|
||||||
# This must be run in a console of correct width
|
yt-dlp --help | make_readme.py
|
||||||
|
This must be run in a console of correct width
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import optparse
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import optparse
|
||||||
|
|
||||||
from yt_dlp.extractor import list_extractor_classes
|
from yt_dlp.extractor import list_extractor_classes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import optparse
|
import optparse
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
@@ -23,7 +24,7 @@ yt\-dlp \- A youtube-dl fork with additional features and patches
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = optparse.OptionParser(usage='%prog OUTFILE.md')
|
parser = optparse.OptionParser(usage='%prog OUTFILE.md')
|
||||||
options, args = parser.parse_args()
|
_, args = parser.parse_args()
|
||||||
if len(args) != 1:
|
if len(args) != 1:
|
||||||
parser.error('Expected an output filename')
|
parser.error('Expected an output filename')
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import json
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from yt_dlp.compat import compat_urllib_request
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
# usage: python3 ./devscripts/update-formulae.py <path-to-formulae-rb> <version>
|
# usage: python3 ./devscripts/update-formulae.py <path-to-formulae-rb> <version>
|
||||||
# version can be either 0-aligned (yt-dlp version) or normalized (PyPl version)
|
# version can be either 0-aligned (yt-dlp version) or normalized (PyPl version)
|
||||||
@@ -15,7 +18,7 @@ filename, version = sys.argv[1:]
|
|||||||
|
|
||||||
normalized_version = '.'.join(str(int(x)) for x in version.split('.'))
|
normalized_version = '.'.join(str(int(x)) for x in version.split('.'))
|
||||||
|
|
||||||
pypi_release = json.loads(compat_urllib_request.urlopen(
|
pypi_release = json.loads(urllib.request.urlopen(
|
||||||
'https://pypi.org/pypi/yt-dlp/%s/json' % normalized_version
|
'https://pypi.org/pypi/yt-dlp/%s/json' % normalized_version
|
||||||
).read().decode())
|
).read().decode())
|
||||||
|
|
||||||
@@ -27,8 +30,8 @@ url = tarball_file['url']
|
|||||||
with open(filename) as r:
|
with open(filename) as r:
|
||||||
formulae_text = r.read()
|
formulae_text = r.read()
|
||||||
|
|
||||||
formulae_text = re.sub(r'sha256 "[0-9a-f]*?"', 'sha256 "%s"' % sha256sum, formulae_text)
|
formulae_text = re.sub(r'sha256 "[0-9a-f]*?"', 'sha256 "%s"' % sha256sum, formulae_text, count=1)
|
||||||
formulae_text = re.sub(r'url "[^"]*?"', 'url "%s"' % url, formulae_text)
|
formulae_text = re.sub(r'url "[^"]*?"', 'url "%s"' % url, formulae_text, count=1)
|
||||||
|
|
||||||
with open(filename, 'w') as w:
|
with open(filename, 'w') as w:
|
||||||
w.write(formulae_text)
|
w.write(formulae_text)
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
ZSH_COMPLETION_FILE = "completions/zsh/_yt-dlp"
|
ZSH_COMPLETION_FILE = "completions/zsh/_yt-dlp"
|
||||||
|
|||||||
21
pyinst.py
21
pyinst.py
@@ -1,11 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PyInstaller.__main__ import run as run_pyinstaller
|
from PyInstaller.__main__ import run as run_pyinstaller
|
||||||
|
|
||||||
OS_NAME, ARCH = sys.platform, platform.architecture()[0][:2]
|
OS_NAME, MACHINE, ARCH = sys.platform, platform.machine(), platform.architecture()[0][:2]
|
||||||
|
if MACHINE in ('x86_64', 'AMD64') or ('i' in MACHINE and '86' in MACHINE):
|
||||||
|
# NB: Windows x86 has MACHINE = AMD64 irrespective of bitness
|
||||||
|
MACHINE = 'x86' if ARCH == '32' else ''
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -17,7 +21,7 @@ def main():
|
|||||||
opts.append('--onefile')
|
opts.append('--onefile')
|
||||||
|
|
||||||
name, final_file = exe(onedir)
|
name, final_file = exe(onedir)
|
||||||
print(f'Building yt-dlp v{version} {ARCH}bit for {OS_NAME} with options {opts}')
|
print(f'Building yt-dlp v{version} for {OS_NAME} {platform.machine()} with options {opts}')
|
||||||
print('Remember to update the version using "devscripts/update-version.py"')
|
print('Remember to update the version using "devscripts/update-version.py"')
|
||||||
if not os.path.isfile('yt_dlp/extractor/lazy_extractors.py'):
|
if not os.path.isfile('yt_dlp/extractor/lazy_extractors.py'):
|
||||||
print('WARNING: Building without lazy_extractors. Run '
|
print('WARNING: Building without lazy_extractors. Run '
|
||||||
@@ -29,9 +33,6 @@ def main():
|
|||||||
'--icon=devscripts/logo.ico',
|
'--icon=devscripts/logo.ico',
|
||||||
'--upx-exclude=vcruntime140.dll',
|
'--upx-exclude=vcruntime140.dll',
|
||||||
'--noconfirm',
|
'--noconfirm',
|
||||||
# NB: Modules that are only imported dynamically must be added here.
|
|
||||||
# --collect-submodules may not work correctly if user has a yt-dlp installed via PIP
|
|
||||||
'--hidden-import=yt_dlp.compat._legacy',
|
|
||||||
*dependency_options(),
|
*dependency_options(),
|
||||||
*opts,
|
*opts,
|
||||||
'yt_dlp/__main__.py',
|
'yt_dlp/__main__.py',
|
||||||
@@ -43,7 +44,7 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
def parse_options():
|
def parse_options():
|
||||||
# Compatability with older arguments
|
# Compatibility with older arguments
|
||||||
opts = sys.argv[1:]
|
opts = sys.argv[1:]
|
||||||
if opts[0:1] in (['32'], ['64']):
|
if opts[0:1] in (['32'], ['64']):
|
||||||
if ARCH != opts[0]:
|
if ARCH != opts[0]:
|
||||||
@@ -64,7 +65,7 @@ def exe(onedir):
|
|||||||
name = '_'.join(filter(None, (
|
name = '_'.join(filter(None, (
|
||||||
'yt-dlp',
|
'yt-dlp',
|
||||||
{'win32': '', 'darwin': 'macos'}.get(OS_NAME, OS_NAME),
|
{'win32': '', 'darwin': 'macos'}.get(OS_NAME, OS_NAME),
|
||||||
ARCH == '32' and 'x86'
|
MACHINE
|
||||||
)))
|
)))
|
||||||
return name, ''.join(filter(None, (
|
return name, ''.join(filter(None, (
|
||||||
'dist/',
|
'dist/',
|
||||||
@@ -121,7 +122,7 @@ def windows_set_version(exe, version):
|
|||||||
)
|
)
|
||||||
|
|
||||||
version_list = version_to_list(version)
|
version_list = version_to_list(version)
|
||||||
suffix = '_x86' if ARCH == '32' else ''
|
suffix = MACHINE and f'_{MACHINE}'
|
||||||
SetVersion(exe, VSVersionInfo(
|
SetVersion(exe, VSVersionInfo(
|
||||||
ffi=FixedFileInfo(
|
ffi=FixedFileInfo(
|
||||||
filevers=version_list,
|
filevers=version_list,
|
||||||
@@ -135,9 +136,9 @@ def windows_set_version(exe, version):
|
|||||||
),
|
),
|
||||||
kids=[
|
kids=[
|
||||||
StringFileInfo([StringTable('040904B0', [
|
StringFileInfo([StringTable('040904B0', [
|
||||||
StringStruct('Comments', 'yt-dlp%s Command Line Interface.' % suffix),
|
StringStruct('Comments', 'yt-dlp%s Command Line Interface' % suffix),
|
||||||
StringStruct('CompanyName', 'https://github.com/yt-dlp'),
|
StringStruct('CompanyName', 'https://github.com/yt-dlp'),
|
||||||
StringStruct('FileDescription', 'yt-dlp%s' % (' (32 Bit)' if ARCH == '32' else '')),
|
StringStruct('FileDescription', 'yt-dlp%s' % (MACHINE and f' ({MACHINE})')),
|
||||||
StringStruct('FileVersion', version),
|
StringStruct('FileVersion', version),
|
||||||
StringStruct('InternalName', f'yt-dlp{suffix}'),
|
StringStruct('InternalName', f'yt-dlp{suffix}'),
|
||||||
StringStruct('LegalCopyright', 'pukkandan.ytdlp@gmail.com | UNLICENSE'),
|
StringStruct('LegalCopyright', 'pukkandan.ytdlp@gmail.com | UNLICENSE'),
|
||||||
|
|||||||
@@ -37,3 +37,5 @@ line_length = 80
|
|||||||
reverse_relative = true
|
reverse_relative = true
|
||||||
ensure_newline_before_comments = true
|
ensure_newline_before_comments = true
|
||||||
include_trailing_comma = true
|
include_trailing_comma = true
|
||||||
|
known_first_party =
|
||||||
|
test
|
||||||
|
|||||||
49
setup.py
49
setup.py
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@@ -9,7 +11,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from distutils.core import Command, setup
|
from distutils.core import Command, setup
|
||||||
setuptools_available = False
|
setuptools_available = False
|
||||||
from distutils.spawn import spawn
|
|
||||||
|
|
||||||
|
|
||||||
def read(fname):
|
def read(fname):
|
||||||
@@ -35,12 +36,24 @@ LONG_DESCRIPTION = '\n\n'.join((
|
|||||||
REQUIREMENTS = read('requirements.txt').splitlines()
|
REQUIREMENTS = read('requirements.txt').splitlines()
|
||||||
|
|
||||||
|
|
||||||
if sys.argv[1:2] == ['py2exe']:
|
def packages():
|
||||||
|
if setuptools_available:
|
||||||
|
return find_packages(exclude=('youtube_dl', 'youtube_dlc', 'test', 'ytdlp_plugins'))
|
||||||
|
|
||||||
|
return [
|
||||||
|
'yt_dlp', 'yt_dlp.extractor', 'yt_dlp.downloader', 'yt_dlp.postprocessor', 'yt_dlp.compat',
|
||||||
|
'yt_dlp.extractor.anvato_token_generator',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def py2exe_params():
|
||||||
import py2exe # noqa: F401
|
import py2exe # noqa: F401
|
||||||
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'py2exe builds do not support pycryptodomex and needs VC++14 to run. '
|
'py2exe builds do not support pycryptodomex and needs VC++14 to run. '
|
||||||
'The recommended way is to use "pyinst.py" to build using pyinstaller')
|
'The recommended way is to use "pyinst.py" to build using pyinstaller')
|
||||||
params = {
|
|
||||||
|
return {
|
||||||
'console': [{
|
'console': [{
|
||||||
'script': './yt_dlp/__main__.py',
|
'script': './yt_dlp/__main__.py',
|
||||||
'dest_base': 'yt-dlp',
|
'dest_base': 'yt-dlp',
|
||||||
@@ -49,6 +62,7 @@ if sys.argv[1:2] == ['py2exe']:
|
|||||||
'comments': LONG_DESCRIPTION.split('\n')[0],
|
'comments': LONG_DESCRIPTION.split('\n')[0],
|
||||||
'product_name': 'yt-dlp',
|
'product_name': 'yt-dlp',
|
||||||
'product_version': VERSION,
|
'product_version': VERSION,
|
||||||
|
'icon_resources': [(1, 'devscripts/logo.ico')],
|
||||||
}],
|
}],
|
||||||
'options': {
|
'options': {
|
||||||
'py2exe': {
|
'py2exe': {
|
||||||
@@ -65,7 +79,8 @@ if sys.argv[1:2] == ['py2exe']:
|
|||||||
'zipfile': None
|
'zipfile': None
|
||||||
}
|
}
|
||||||
|
|
||||||
else:
|
|
||||||
|
def build_params():
|
||||||
files_spec = [
|
files_spec = [
|
||||||
('share/bash-completion/completions', ['completions/bash/yt-dlp']),
|
('share/bash-completion/completions', ['completions/bash/yt-dlp']),
|
||||||
('share/zsh/site-functions', ['completions/zsh/_yt-dlp']),
|
('share/zsh/site-functions', ['completions/zsh/_yt-dlp']),
|
||||||
@@ -73,25 +88,23 @@ else:
|
|||||||
('share/doc/yt_dlp', ['README.txt']),
|
('share/doc/yt_dlp', ['README.txt']),
|
||||||
('share/man/man1', ['yt-dlp.1'])
|
('share/man/man1', ['yt-dlp.1'])
|
||||||
]
|
]
|
||||||
root = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
data_files = []
|
data_files = []
|
||||||
for dirname, files in files_spec:
|
for dirname, files in files_spec:
|
||||||
resfiles = []
|
resfiles = []
|
||||||
for fn in files:
|
for fn in files:
|
||||||
if not os.path.exists(fn):
|
if not os.path.exists(fn):
|
||||||
warnings.warn('Skipping file %s since it is not present. Try running `make pypi-files` first' % fn)
|
warnings.warn(f'Skipping file {fn} since it is not present. Try running " make pypi-files " first')
|
||||||
else:
|
else:
|
||||||
resfiles.append(fn)
|
resfiles.append(fn)
|
||||||
data_files.append((dirname, resfiles))
|
data_files.append((dirname, resfiles))
|
||||||
|
|
||||||
params = {
|
params = {'data_files': data_files}
|
||||||
'data_files': data_files,
|
|
||||||
}
|
|
||||||
|
|
||||||
if setuptools_available:
|
if setuptools_available:
|
||||||
params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']}
|
params['entry_points'] = {'console_scripts': ['yt-dlp = yt_dlp:main']}
|
||||||
else:
|
else:
|
||||||
params['scripts'] = ['yt-dlp']
|
params['scripts'] = ['yt-dlp']
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
class build_lazy_extractors(Command):
|
class build_lazy_extractors(Command):
|
||||||
@@ -105,16 +118,13 @@ class build_lazy_extractors(Command):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
spawn([sys.executable, 'devscripts/make_lazy_extractors.py', 'yt_dlp/extractor/lazy_extractors.py'],
|
if self.dry_run:
|
||||||
dry_run=self.dry_run)
|
print('Skipping build of lazy extractors in dry run mode')
|
||||||
|
return
|
||||||
|
subprocess.run([sys.executable, 'devscripts/make_lazy_extractors.py', 'yt_dlp/extractor/lazy_extractors.py'])
|
||||||
if setuptools_available:
|
|
||||||
packages = find_packages(exclude=('youtube_dl', 'youtube_dlc', 'test', 'ytdlp_plugins'))
|
|
||||||
else:
|
|
||||||
packages = ['yt_dlp', 'yt_dlp.downloader', 'yt_dlp.extractor', 'yt_dlp.postprocessor']
|
|
||||||
|
|
||||||
|
|
||||||
|
params = py2exe_params() if sys.argv[1:2] == ['py2exe'] else build_params()
|
||||||
setup(
|
setup(
|
||||||
name='yt-dlp',
|
name='yt-dlp',
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
@@ -124,8 +134,9 @@ setup(
|
|||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
url='https://github.com/yt-dlp/yt-dlp',
|
url='https://github.com/yt-dlp/yt-dlp',
|
||||||
packages=packages,
|
packages=packages(),
|
||||||
install_requires=REQUIREMENTS,
|
install_requires=REQUIREMENTS,
|
||||||
|
python_requires='>=3.6',
|
||||||
project_urls={
|
project_urls={
|
||||||
'Documentation': 'https://github.com/yt-dlp/yt-dlp#readme',
|
'Documentation': 'https://github.com/yt-dlp/yt-dlp#readme',
|
||||||
'Source': 'https://github.com/yt-dlp/yt-dlp',
|
'Source': 'https://github.com/yt-dlp/yt-dlp',
|
||||||
@@ -149,8 +160,6 @@ setup(
|
|||||||
'License :: Public Domain',
|
'License :: Public Domain',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
],
|
],
|
||||||
python_requires='>=3.6',
|
|
||||||
|
|
||||||
cmdclass={'build_lazy_extractors': build_lazy_extractors},
|
cmdclass={'build_lazy_extractors': build_lazy_extractors},
|
||||||
**params
|
**params
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
- **17live**
|
- **17live**
|
||||||
- **17live:clip**
|
- **17live:clip**
|
||||||
- **1tv**: Первый канал
|
- **1tv**: Первый канал
|
||||||
|
- **20.detik.com**
|
||||||
- **20min**
|
- **20min**
|
||||||
- **23video**
|
- **23video**
|
||||||
- **247sports**
|
- **247sports**
|
||||||
@@ -31,6 +32,8 @@
|
|||||||
- **AcademicEarth:Course**
|
- **AcademicEarth:Course**
|
||||||
- **acast**
|
- **acast**
|
||||||
- **acast:channel**
|
- **acast:channel**
|
||||||
|
- **AcFunBangumi**
|
||||||
|
- **AcFunVideo**
|
||||||
- **ADN**: [<abbr title="netrc machine"><em>animedigitalnetwork</em></abbr>] Anime Digital Network
|
- **ADN**: [<abbr title="netrc machine"><em>animedigitalnetwork</em></abbr>] Anime Digital Network
|
||||||
- **AdobeConnect**
|
- **AdobeConnect**
|
||||||
- **adobetv**
|
- **adobetv**
|
||||||
@@ -94,6 +97,8 @@
|
|||||||
- **ATVAt**
|
- **ATVAt**
|
||||||
- **AudiMedia**
|
- **AudiMedia**
|
||||||
- **AudioBoom**
|
- **AudioBoom**
|
||||||
|
- **Audiodraft:custom**
|
||||||
|
- **Audiodraft:generic**
|
||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **Audius**: Audius.co
|
- **Audius**: Audius.co
|
||||||
@@ -205,6 +210,7 @@
|
|||||||
- **CCMA**
|
- **CCMA**
|
||||||
- **CCTV**: 央视网
|
- **CCTV**: 央视网
|
||||||
- **CDA**
|
- **CDA**
|
||||||
|
- **Cellebrite**
|
||||||
- **CeskaTelevize**
|
- **CeskaTelevize**
|
||||||
- **CGTN**
|
- **CGTN**
|
||||||
- **channel9**: Channel 9
|
- **channel9**: Channel 9
|
||||||
@@ -418,6 +424,7 @@
|
|||||||
- **Funk**
|
- **Funk**
|
||||||
- **Fusion**
|
- **Fusion**
|
||||||
- **Fux**
|
- **Fux**
|
||||||
|
- **FuyinTV**
|
||||||
- **Gab**
|
- **Gab**
|
||||||
- **GabTV**
|
- **GabTV**
|
||||||
- **Gaia**: [<abbr title="netrc machine"><em>gaia</em></abbr>]
|
- **Gaia**: [<abbr title="netrc machine"><em>gaia</em></abbr>]
|
||||||
@@ -502,6 +509,7 @@
|
|||||||
- **HungamaSong**
|
- **HungamaSong**
|
||||||
- **huya:live**: huya.com
|
- **huya:live**: huya.com
|
||||||
- **Hypem**
|
- **Hypem**
|
||||||
|
- **Hytale**
|
||||||
- **Icareus**
|
- **Icareus**
|
||||||
- **ign.com**
|
- **ign.com**
|
||||||
- **IGNArticle**
|
- **IGNArticle**
|
||||||
@@ -614,10 +622,12 @@
|
|||||||
- **linkedin:learning**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
|
- **linkedin:learning**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
|
||||||
- **linkedin:learning:course**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
|
- **linkedin:learning:course**: [<abbr title="netrc machine"><em>linkedin</em></abbr>]
|
||||||
- **LinuxAcademy**: [<abbr title="netrc machine"><em>linuxacademy</em></abbr>]
|
- **LinuxAcademy**: [<abbr title="netrc machine"><em>linuxacademy</em></abbr>]
|
||||||
|
- **Liputan6**
|
||||||
- **LiTV**
|
- **LiTV**
|
||||||
- **LiveJournal**
|
- **LiveJournal**
|
||||||
- **livestream**
|
- **livestream**
|
||||||
- **livestream:original**
|
- **livestream:original**
|
||||||
|
- **Livestreamfails**
|
||||||
- **Lnk**
|
- **Lnk**
|
||||||
- **LnkGo**
|
- **LnkGo**
|
||||||
- **loc**: Library of Congress
|
- **loc**: Library of Congress
|
||||||
@@ -696,6 +706,7 @@
|
|||||||
- **MLSSoccer**
|
- **MLSSoccer**
|
||||||
- **Mnet**
|
- **Mnet**
|
||||||
- **MNetTV**: [<abbr title="netrc machine"><em>mnettv</em></abbr>]
|
- **MNetTV**: [<abbr title="netrc machine"><em>mnettv</em></abbr>]
|
||||||
|
- **MochaVideo**
|
||||||
- **MoeVideo**: LetitBit video services: moevideo.net, playreplay.net and videochart.net
|
- **MoeVideo**: LetitBit video services: moevideo.net, playreplay.net and videochart.net
|
||||||
- **Mofosex**
|
- **Mofosex**
|
||||||
- **MofosexEmbed**
|
- **MofosexEmbed**
|
||||||
@@ -982,6 +993,7 @@
|
|||||||
- **PornoVoisines**
|
- **PornoVoisines**
|
||||||
- **PornoXO**
|
- **PornoXO**
|
||||||
- **PornTube**
|
- **PornTube**
|
||||||
|
- **PremiershipRugby**
|
||||||
- **PressTV**
|
- **PressTV**
|
||||||
- **ProjectVeritas**
|
- **ProjectVeritas**
|
||||||
- **prosiebensat1**: ProSiebenSat.1 Digital
|
- **prosiebensat1**: ProSiebenSat.1 Digital
|
||||||
@@ -1065,10 +1077,14 @@
|
|||||||
- **RTDocumentryPlaylist**
|
- **RTDocumentryPlaylist**
|
||||||
- **rte**: Raidió Teilifís Éireann TV
|
- **rte**: Raidió Teilifís Éireann TV
|
||||||
- **rte:radio**: Raidió Teilifís Éireann radio
|
- **rte:radio**: Raidió Teilifís Éireann radio
|
||||||
|
- **rtl.lu:article**
|
||||||
|
- **rtl.lu:tele-vod**
|
||||||
- **rtl.nl**: rtl.nl and rtlxl.nl
|
- **rtl.nl**: rtl.nl and rtlxl.nl
|
||||||
- **rtl2**
|
- **rtl2**
|
||||||
- **rtl2:you**
|
- **rtl2:you**
|
||||||
- **rtl2:you:series**
|
- **rtl2:you:series**
|
||||||
|
- **RTLLuLive**
|
||||||
|
- **RTLLuRadio**
|
||||||
- **RTNews**
|
- **RTNews**
|
||||||
- **RTP**
|
- **RTP**
|
||||||
- **RTRFM**
|
- **RTRFM**
|
||||||
@@ -1080,6 +1096,7 @@
|
|||||||
- **rtve.es:television**
|
- **rtve.es:television**
|
||||||
- **RTVNH**
|
- **RTVNH**
|
||||||
- **RTVS**
|
- **RTVS**
|
||||||
|
- **rtvslo.si**
|
||||||
- **RUHD**
|
- **RUHD**
|
||||||
- **Rule34Video**
|
- **Rule34Video**
|
||||||
- **RumbleChannel**
|
- **RumbleChannel**
|
||||||
@@ -1113,6 +1130,7 @@
|
|||||||
- **ScreencastOMatic**
|
- **ScreencastOMatic**
|
||||||
- **ScrippsNetworks**
|
- **ScrippsNetworks**
|
||||||
- **scrippsnetworks:watch**
|
- **scrippsnetworks:watch**
|
||||||
|
- **Scrolller**
|
||||||
- **SCTE**: [<abbr title="netrc machine"><em>scte</em></abbr>]
|
- **SCTE**: [<abbr title="netrc machine"><em>scte</em></abbr>]
|
||||||
- **SCTECourse**: [<abbr title="netrc machine"><em>scte</em></abbr>]
|
- **SCTECourse**: [<abbr title="netrc machine"><em>scte</em></abbr>]
|
||||||
- **Seeker**
|
- **Seeker**
|
||||||
@@ -1187,8 +1205,10 @@
|
|||||||
- **SRGSSR**
|
- **SRGSSR**
|
||||||
- **SRGSSRPlay**: srf.ch, rts.ch, rsi.ch, rtr.ch and swissinfo.ch play sites
|
- **SRGSSRPlay**: srf.ch, rts.ch, rsi.ch, rtr.ch and swissinfo.ch play sites
|
||||||
- **stanfordoc**: Stanford Open ClassRoom
|
- **stanfordoc**: Stanford Open ClassRoom
|
||||||
|
- **StarTrek**
|
||||||
- **startv**
|
- **startv**
|
||||||
- **Steam**
|
- **Steam**
|
||||||
|
- **SteamCommunityBroadcast**
|
||||||
- **Stitcher**
|
- **Stitcher**
|
||||||
- **StitcherShow**
|
- **StitcherShow**
|
||||||
- **StoryFire**
|
- **StoryFire**
|
||||||
@@ -1213,6 +1233,7 @@
|
|||||||
- **SVTSeries**
|
- **SVTSeries**
|
||||||
- **SWRMediathek**
|
- **SWRMediathek**
|
||||||
- **Syfy**
|
- **Syfy**
|
||||||
|
- **SYVDK**
|
||||||
- **SztvHu**
|
- **SztvHu**
|
||||||
- **t-online.de**
|
- **t-online.de**
|
||||||
- **Tagesschau**
|
- **Tagesschau**
|
||||||
@@ -1251,6 +1272,7 @@
|
|||||||
- **TenPlay**: [<abbr title="netrc machine"><em>10play</em></abbr>]
|
- **TenPlay**: [<abbr title="netrc machine"><em>10play</em></abbr>]
|
||||||
- **TF1**
|
- **TF1**
|
||||||
- **TFO**
|
- **TFO**
|
||||||
|
- **TheHoleTv**
|
||||||
- **TheIntercept**
|
- **TheIntercept**
|
||||||
- **ThePlatform**
|
- **ThePlatform**
|
||||||
- **ThePlatformFeed**
|
- **ThePlatformFeed**
|
||||||
@@ -1293,6 +1315,8 @@
|
|||||||
- **TruNews**
|
- **TruNews**
|
||||||
- **TruTV**
|
- **TruTV**
|
||||||
- **Tube8**
|
- **Tube8**
|
||||||
|
- **TubeTuGraz**: [<abbr title="netrc machine"><em>tubetugraz</em></abbr>] tube.tugraz.at
|
||||||
|
- **TubeTuGrazSeries**: [<abbr title="netrc machine"><em>tubetugraz</em></abbr>]
|
||||||
- **TubiTv**: [<abbr title="netrc machine"><em>tubitv</em></abbr>]
|
- **TubiTv**: [<abbr title="netrc machine"><em>tubitv</em></abbr>]
|
||||||
- **TubiTvShow**
|
- **TubiTvShow**
|
||||||
- **Tumblr**: [<abbr title="netrc machine"><em>tumblr</em></abbr>]
|
- **Tumblr**: [<abbr title="netrc machine"><em>tumblr</em></abbr>]
|
||||||
@@ -1321,6 +1345,7 @@
|
|||||||
- **TVCArticle**
|
- **TVCArticle**
|
||||||
- **TVer**
|
- **TVer**
|
||||||
- **tvigle**: Интернет-телевидение Tvigle.ru
|
- **tvigle**: Интернет-телевидение Tvigle.ru
|
||||||
|
- **TVIPlayer**
|
||||||
- **tvland.com**
|
- **tvland.com**
|
||||||
- **TVN24**
|
- **TVN24**
|
||||||
- **TVNet**
|
- **TVNet**
|
||||||
@@ -1427,7 +1452,8 @@
|
|||||||
- **vimeo:watchlater**: [<abbr title="netrc machine"><em>vimeo</em></abbr>] Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)
|
- **vimeo:watchlater**: [<abbr title="netrc machine"><em>vimeo</em></abbr>] Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)
|
||||||
- **Vimm:recording**
|
- **Vimm:recording**
|
||||||
- **Vimm:stream**
|
- **Vimm:stream**
|
||||||
- **Vimp**
|
- **ViMP**
|
||||||
|
- **ViMP:Playlist**
|
||||||
- **Vimple**: Vimple - one-click video hosting
|
- **Vimple**: Vimple - one-click video hosting
|
||||||
- **Vine**
|
- **Vine**
|
||||||
- **vine:user**
|
- **vine:user**
|
||||||
@@ -1492,7 +1518,10 @@
|
|||||||
- **Weibo**
|
- **Weibo**
|
||||||
- **WeiboMobile**
|
- **WeiboMobile**
|
||||||
- **WeiqiTV**: WQTV
|
- **WeiqiTV**: WQTV
|
||||||
|
- **wetv:episode**
|
||||||
|
- **WeTvSeries**
|
||||||
- **whowatch**
|
- **whowatch**
|
||||||
|
- **wikimedia.org**
|
||||||
- **Willow**
|
- **Willow**
|
||||||
- **WimTV**
|
- **WimTV**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import types
|
|||||||
|
|
||||||
import yt_dlp.extractor
|
import yt_dlp.extractor
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.compat import compat_os_name, compat_str
|
from yt_dlp.compat import compat_os_name
|
||||||
from yt_dlp.utils import preferredencoding, write_string
|
from yt_dlp.utils import preferredencoding, write_string
|
||||||
|
|
||||||
if 'pytest' in sys.modules:
|
if 'pytest' in sys.modules:
|
||||||
@@ -96,29 +96,29 @@ md5 = lambda s: hashlib.md5(s.encode()).hexdigest()
|
|||||||
|
|
||||||
|
|
||||||
def expect_value(self, got, expected, field):
|
def expect_value(self, got, expected, field):
|
||||||
if isinstance(expected, compat_str) and expected.startswith('re:'):
|
if isinstance(expected, str) and expected.startswith('re:'):
|
||||||
match_str = expected[len('re:'):]
|
match_str = expected[len('re:'):]
|
||||||
match_rex = re.compile(match_str)
|
match_rex = re.compile(match_str)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(got, compat_str),
|
isinstance(got, str),
|
||||||
f'Expected a {compat_str.__name__} object, but got {type(got).__name__} for field {field}')
|
f'Expected a {str.__name__} object, but got {type(got).__name__} for field {field}')
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
match_rex.match(got),
|
match_rex.match(got),
|
||||||
f'field {field} (value: {got!r}) should match {match_str!r}')
|
f'field {field} (value: {got!r}) should match {match_str!r}')
|
||||||
elif isinstance(expected, compat_str) and expected.startswith('startswith:'):
|
elif isinstance(expected, str) and expected.startswith('startswith:'):
|
||||||
start_str = expected[len('startswith:'):]
|
start_str = expected[len('startswith:'):]
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(got, compat_str),
|
isinstance(got, str),
|
||||||
f'Expected a {compat_str.__name__} object, but got {type(got).__name__} for field {field}')
|
f'Expected a {str.__name__} object, but got {type(got).__name__} for field {field}')
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
got.startswith(start_str),
|
got.startswith(start_str),
|
||||||
f'field {field} (value: {got!r}) should start with {start_str!r}')
|
f'field {field} (value: {got!r}) should start with {start_str!r}')
|
||||||
elif isinstance(expected, compat_str) and expected.startswith('contains:'):
|
elif isinstance(expected, str) and expected.startswith('contains:'):
|
||||||
contains_str = expected[len('contains:'):]
|
contains_str = expected[len('contains:'):]
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(got, compat_str),
|
isinstance(got, str),
|
||||||
f'Expected a {compat_str.__name__} object, but got {type(got).__name__} for field {field}')
|
f'Expected a {str.__name__} object, but got {type(got).__name__} for field {field}')
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
contains_str in got,
|
contains_str in got,
|
||||||
f'field {field} (value: {got!r}) should contain {contains_str!r}')
|
f'field {field} (value: {got!r}) should contain {contains_str!r}')
|
||||||
@@ -142,12 +142,12 @@ def expect_value(self, got, expected, field):
|
|||||||
index, field, type_expected, type_got))
|
index, field, type_expected, type_got))
|
||||||
expect_value(self, item_got, item_expected, field)
|
expect_value(self, item_got, item_expected, field)
|
||||||
else:
|
else:
|
||||||
if isinstance(expected, compat_str) and expected.startswith('md5:'):
|
if isinstance(expected, str) and expected.startswith('md5:'):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(got, compat_str),
|
isinstance(got, str),
|
||||||
f'Expected field {field} to be a unicode object, but got value {got!r} of type {type(got)!r}')
|
f'Expected field {field} to be a unicode object, but got value {got!r} of type {type(got)!r}')
|
||||||
got = 'md5:' + md5(got)
|
got = 'md5:' + md5(got)
|
||||||
elif isinstance(expected, compat_str) and re.match(r'^(?:min|max)?count:\d+', expected):
|
elif isinstance(expected, str) and re.match(r'^(?:min|max)?count:\d+', expected):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(got, (list, dict)),
|
isinstance(got, (list, dict)),
|
||||||
f'Expected field {field} to be a list or a dict, but it is of type {type(got).__name__}')
|
f'Expected field {field} to be a list or a dict, but it is of type {type(got).__name__}')
|
||||||
@@ -236,7 +236,7 @@ def expect_info_dict(self, got_dict, expected_dict):
|
|||||||
missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
|
missing_keys = set(test_info_dict.keys()) - set(expected_dict.keys())
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
def _repr(v):
|
def _repr(v):
|
||||||
if isinstance(v, compat_str):
|
if isinstance(v, str):
|
||||||
return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
|
return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
|
||||||
elif isinstance(v, type):
|
elif isinstance(v, type):
|
||||||
return v.__name__
|
return v.__name__
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,10 +7,12 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
import threading
|
|
||||||
from test.helper import FakeYDL, expect_dict, expect_value, http_server_port
|
|
||||||
|
|
||||||
from yt_dlp.compat import compat_etree_fromstring, compat_http_server
|
import http.server
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from test.helper import FakeYDL, expect_dict, expect_value, http_server_port
|
||||||
|
from yt_dlp.compat import compat_etree_fromstring
|
||||||
from yt_dlp.extractor import YoutubeIE, get_info_extractor
|
from yt_dlp.extractor import YoutubeIE, get_info_extractor
|
||||||
from yt_dlp.extractor.common import InfoExtractor
|
from yt_dlp.extractor.common import InfoExtractor
|
||||||
from yt_dlp.utils import (
|
from yt_dlp.utils import (
|
||||||
@@ -23,7 +26,7 @@ TEAPOT_RESPONSE_STATUS = 418
|
|||||||
TEAPOT_RESPONSE_BODY = "<h1>418 I'm a teapot</h1>"
|
TEAPOT_RESPONSE_BODY = "<h1>418 I'm a teapot</h1>"
|
||||||
|
|
||||||
|
|
||||||
class InfoExtractorTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
|
class InfoExtractorTestRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1655,7 +1658,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
# or the underlying `_download_webpage_handle` returning no content
|
# or the underlying `_download_webpage_handle` returning no content
|
||||||
# when a response matches `expected_status`.
|
# when a response matches `expected_status`.
|
||||||
|
|
||||||
httpd = compat_http_server.HTTPServer(
|
httpd = http.server.HTTPServer(
|
||||||
('127.0.0.1', 0), InfoExtractorTestRequestHandler)
|
('127.0.0.1', 0), InfoExtractorTestRequestHandler)
|
||||||
port = http_server_port(httpd)
|
port = http_server_port(httpd)
|
||||||
server_thread = threading.Thread(target=httpd.serve_forever)
|
server_thread = threading.Thread(target=httpd.serve_forever)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,17 +7,14 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
from test.helper import FakeYDL, assertRegexpMatches
|
import urllib.error
|
||||||
|
|
||||||
|
from test.helper import FakeYDL, assertRegexpMatches
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.compat import (
|
from yt_dlp.compat import compat_os_name
|
||||||
compat_os_name,
|
|
||||||
compat_setenv,
|
|
||||||
compat_str,
|
|
||||||
compat_urllib_error,
|
|
||||||
)
|
|
||||||
from yt_dlp.extractor import YoutubeIE
|
from yt_dlp.extractor import YoutubeIE
|
||||||
from yt_dlp.extractor.common import InfoExtractor
|
from yt_dlp.extractor.common import InfoExtractor
|
||||||
from yt_dlp.postprocessor.common import PostProcessor
|
from yt_dlp.postprocessor.common import PostProcessor
|
||||||
@@ -841,14 +839,14 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
# test('%(foo|)s', ('', '_')) # fixme
|
# test('%(foo|)s', ('', '_')) # fixme
|
||||||
|
|
||||||
# Environment variable expansion for prepare_filename
|
# Environment variable expansion for prepare_filename
|
||||||
compat_setenv('__yt_dlp_var', 'expanded')
|
os.environ['__yt_dlp_var'] = 'expanded'
|
||||||
envvar = '%__yt_dlp_var%' if compat_os_name == 'nt' else '$__yt_dlp_var'
|
envvar = '%__yt_dlp_var%' if compat_os_name == 'nt' else '$__yt_dlp_var'
|
||||||
test(envvar, (envvar, 'expanded'))
|
test(envvar, (envvar, 'expanded'))
|
||||||
if compat_os_name == 'nt':
|
if compat_os_name == 'nt':
|
||||||
test('%s%', ('%s%', '%s%'))
|
test('%s%', ('%s%', '%s%'))
|
||||||
compat_setenv('s', 'expanded')
|
os.environ['s'] = 'expanded'
|
||||||
test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
|
test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
|
||||||
compat_setenv('(test)s', 'expanded')
|
os.environ['(test)s'] = 'expanded'
|
||||||
test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
|
test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
|
||||||
|
|
||||||
# Path expansion and escaping
|
# Path expansion and escaping
|
||||||
@@ -1055,6 +1053,7 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
for v in get_downloaded_info_dicts(params, entries)]
|
for v in get_downloaded_info_dicts(params, entries)]
|
||||||
self.assertEqual(results, list(enumerate(zip(expected_ids, expected_ids))), f'Entries of {name} for {params}')
|
self.assertEqual(results, list(enumerate(zip(expected_ids, expected_ids))), f'Entries of {name} for {params}')
|
||||||
self.assertEqual(sorted(evaluated), expected_eval, f'Evaluation of {name} for {params}')
|
self.assertEqual(sorted(evaluated), expected_eval, f'Evaluation of {name} for {params}')
|
||||||
|
|
||||||
test_selection({}, INDICES)
|
test_selection({}, INDICES)
|
||||||
test_selection({'playlistend': 20}, INDICES, True)
|
test_selection({'playlistend': 20}, INDICES, True)
|
||||||
test_selection({'playlistend': 2}, INDICES[:2])
|
test_selection({'playlistend': 2}, INDICES[:2])
|
||||||
@@ -1101,7 +1100,7 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
def test_urlopen_no_file_protocol(self):
|
def test_urlopen_no_file_protocol(self):
|
||||||
# see https://github.com/ytdl-org/youtube-dl/issues/8227
|
# see https://github.com/ytdl-org/youtube-dl/issues/8227
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
self.assertRaises(compat_urllib_error.URLError, ydl.urlopen, 'file:///etc/passwd')
|
self.assertRaises(urllib.error.URLError, ydl.urlopen, 'file:///etc/passwd')
|
||||||
|
|
||||||
def test_do_not_override_ie_key_in_url_transparent(self):
|
def test_do_not_override_ie_key_in_url_transparent(self):
|
||||||
ydl = YDL()
|
ydl = YDL()
|
||||||
@@ -1187,7 +1186,7 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
|
|
||||||
def _entries(self):
|
def _entries(self):
|
||||||
for n in range(3):
|
for n in range(3):
|
||||||
video_id = compat_str(n)
|
video_id = str(n)
|
||||||
yield {
|
yield {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'ie_key': VideoIE.ie_key(),
|
'ie_key': VideoIE.ie_key(),
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
|
||||||
from yt_dlp.utils import YoutubeDLCookieJar
|
from yt_dlp.utils import YoutubeDLCookieJar
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,6 +7,7 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from yt_dlp.aes import (
|
from yt_dlp.aes import (
|
||||||
@@ -22,6 +24,8 @@ from yt_dlp.aes import (
|
|||||||
aes_encrypt,
|
aes_encrypt,
|
||||||
aes_gcm_decrypt_and_verify,
|
aes_gcm_decrypt_and_verify,
|
||||||
aes_gcm_decrypt_and_verify_bytes,
|
aes_gcm_decrypt_and_verify_bytes,
|
||||||
|
key_expansion,
|
||||||
|
pad_block,
|
||||||
)
|
)
|
||||||
from yt_dlp.dependencies import Cryptodome_AES
|
from yt_dlp.dependencies import Cryptodome_AES
|
||||||
from yt_dlp.utils import bytes_to_intlist, intlist_to_bytes
|
from yt_dlp.utils import bytes_to_intlist, intlist_to_bytes
|
||||||
@@ -110,6 +114,41 @@ class TestAES(unittest.TestCase):
|
|||||||
decrypted = intlist_to_bytes(aes_ecb_decrypt(data, self.key, self.iv))
|
decrypted = intlist_to_bytes(aes_ecb_decrypt(data, self.key, self.iv))
|
||||||
self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)
|
self.assertEqual(decrypted.rstrip(b'\x08'), self.secret_msg)
|
||||||
|
|
||||||
|
def test_key_expansion(self):
|
||||||
|
key = '4f6bdaa39e2f8cb07f5e722d9edef314'
|
||||||
|
|
||||||
|
self.assertEqual(key_expansion(bytes_to_intlist(bytearray.fromhex(key))), [
|
||||||
|
0x4F, 0x6B, 0xDA, 0xA3, 0x9E, 0x2F, 0x8C, 0xB0, 0x7F, 0x5E, 0x72, 0x2D, 0x9E, 0xDE, 0xF3, 0x14,
|
||||||
|
0x53, 0x66, 0x20, 0xA8, 0xCD, 0x49, 0xAC, 0x18, 0xB2, 0x17, 0xDE, 0x35, 0x2C, 0xC9, 0x2D, 0x21,
|
||||||
|
0x8C, 0xBE, 0xDD, 0xD9, 0x41, 0xF7, 0x71, 0xC1, 0xF3, 0xE0, 0xAF, 0xF4, 0xDF, 0x29, 0x82, 0xD5,
|
||||||
|
0x2D, 0xAD, 0xDE, 0x47, 0x6C, 0x5A, 0xAF, 0x86, 0x9F, 0xBA, 0x00, 0x72, 0x40, 0x93, 0x82, 0xA7,
|
||||||
|
0xF9, 0xBE, 0x82, 0x4E, 0x95, 0xE4, 0x2D, 0xC8, 0x0A, 0x5E, 0x2D, 0xBA, 0x4A, 0xCD, 0xAF, 0x1D,
|
||||||
|
0x54, 0xC7, 0x26, 0x98, 0xC1, 0x23, 0x0B, 0x50, 0xCB, 0x7D, 0x26, 0xEA, 0x81, 0xB0, 0x89, 0xF7,
|
||||||
|
0x93, 0x60, 0x4E, 0x94, 0x52, 0x43, 0x45, 0xC4, 0x99, 0x3E, 0x63, 0x2E, 0x18, 0x8E, 0xEA, 0xD9,
|
||||||
|
0xCA, 0xE7, 0x7B, 0x39, 0x98, 0xA4, 0x3E, 0xFD, 0x01, 0x9A, 0x5D, 0xD3, 0x19, 0x14, 0xB7, 0x0A,
|
||||||
|
0xB0, 0x4E, 0x1C, 0xED, 0x28, 0xEA, 0x22, 0x10, 0x29, 0x70, 0x7F, 0xC3, 0x30, 0x64, 0xC8, 0xC9,
|
||||||
|
0xE8, 0xA6, 0xC1, 0xE9, 0xC0, 0x4C, 0xE3, 0xF9, 0xE9, 0x3C, 0x9C, 0x3A, 0xD9, 0x58, 0x54, 0xF3,
|
||||||
|
0xB4, 0x86, 0xCC, 0xDC, 0x74, 0xCA, 0x2F, 0x25, 0x9D, 0xF6, 0xB3, 0x1F, 0x44, 0xAE, 0xE7, 0xEC])
|
||||||
|
|
||||||
|
def test_pad_block(self):
|
||||||
|
block = [0x21, 0xA0, 0x43, 0xFF]
|
||||||
|
|
||||||
|
self.assertEqual(pad_block(block, 'pkcs7'),
|
||||||
|
block + [0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C])
|
||||||
|
|
||||||
|
self.assertEqual(pad_block(block, 'iso7816'),
|
||||||
|
block + [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||||
|
|
||||||
|
self.assertEqual(pad_block(block, 'whitespace'),
|
||||||
|
block + [0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20])
|
||||||
|
|
||||||
|
self.assertEqual(pad_block(block, 'zero'),
|
||||||
|
block + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||||
|
|
||||||
|
block = list(range(16))
|
||||||
|
for mode in ('pkcs7', 'iso7816', 'whitespace', 'zero'):
|
||||||
|
self.assertEqual(pad_block(block, mode), block, mode)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,8 +7,8 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import is_download_test, try_rm
|
|
||||||
|
|
||||||
|
from test.helper import is_download_test, try_rm
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import collections
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
@@ -8,8 +8,9 @@ import unittest
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
from test.helper import gettestcases
|
import collections
|
||||||
|
|
||||||
|
from test.helper import gettestcases
|
||||||
from yt_dlp.extractor import FacebookIE, YoutubeIE, gen_extractors
|
from yt_dlp.extractor import FacebookIE, YoutubeIE, gen_extractors
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
from test.helper import FakeYDL
|
import shutil
|
||||||
|
|
||||||
|
from test.helper import FakeYDL
|
||||||
from yt_dlp.cache import Cache
|
from yt_dlp.cache import Cache
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -7,16 +8,14 @@ import unittest
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from yt_dlp import compat
|
from yt_dlp import compat
|
||||||
from yt_dlp.compat import (
|
from yt_dlp.compat import (
|
||||||
compat_etree_fromstring,
|
compat_etree_fromstring,
|
||||||
compat_expanduser,
|
compat_expanduser,
|
||||||
compat_getenv,
|
|
||||||
compat_setenv,
|
|
||||||
compat_str,
|
|
||||||
compat_struct_unpack,
|
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_unquote_plus,
|
|
||||||
compat_urllib_parse_urlencode,
|
compat_urllib_parse_urlencode,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,28 +25,19 @@ class TestCompat(unittest.TestCase):
|
|||||||
with self.assertWarns(DeprecationWarning):
|
with self.assertWarns(DeprecationWarning):
|
||||||
compat.compat_basestring
|
compat.compat_basestring
|
||||||
|
|
||||||
|
with self.assertWarns(DeprecationWarning):
|
||||||
|
compat.WINDOWS_VT_MODE
|
||||||
|
|
||||||
compat.asyncio.events # Must not raise error
|
compat.asyncio.events # Must not raise error
|
||||||
|
|
||||||
def test_compat_getenv(self):
|
|
||||||
test_str = 'тест'
|
|
||||||
compat_setenv('yt_dlp_COMPAT_GETENV', test_str)
|
|
||||||
self.assertEqual(compat_getenv('yt_dlp_COMPAT_GETENV'), test_str)
|
|
||||||
|
|
||||||
def test_compat_setenv(self):
|
|
||||||
test_var = 'yt_dlp_COMPAT_SETENV'
|
|
||||||
test_str = 'тест'
|
|
||||||
compat_setenv(test_var, test_str)
|
|
||||||
compat_getenv(test_var)
|
|
||||||
self.assertEqual(compat_getenv(test_var), test_str)
|
|
||||||
|
|
||||||
def test_compat_expanduser(self):
|
def test_compat_expanduser(self):
|
||||||
old_home = os.environ.get('HOME')
|
old_home = os.environ.get('HOME')
|
||||||
test_str = R'C:\Documents and Settings\тест\Application Data'
|
test_str = R'C:\Documents and Settings\тест\Application Data'
|
||||||
try:
|
try:
|
||||||
compat_setenv('HOME', test_str)
|
os.environ['HOME'] = test_str
|
||||||
self.assertEqual(compat_expanduser('~'), test_str)
|
self.assertEqual(compat_expanduser('~'), test_str)
|
||||||
finally:
|
finally:
|
||||||
compat_setenv('HOME', old_home or '')
|
os.environ['HOME'] = old_home or ''
|
||||||
|
|
||||||
def test_compat_urllib_parse_unquote(self):
|
def test_compat_urllib_parse_unquote(self):
|
||||||
self.assertEqual(compat_urllib_parse_unquote('abc%20def'), 'abc def')
|
self.assertEqual(compat_urllib_parse_unquote('abc%20def'), 'abc def')
|
||||||
@@ -69,8 +59,8 @@ class TestCompat(unittest.TestCase):
|
|||||||
'''(^◣_◢^)っ︻デ═一 ⇀ ⇀ ⇀ ⇀ ⇀ ↶%I%Break%Things%''')
|
'''(^◣_◢^)っ︻デ═一 ⇀ ⇀ ⇀ ⇀ ⇀ ↶%I%Break%Things%''')
|
||||||
|
|
||||||
def test_compat_urllib_parse_unquote_plus(self):
|
def test_compat_urllib_parse_unquote_plus(self):
|
||||||
self.assertEqual(compat_urllib_parse_unquote_plus('abc%20def'), 'abc def')
|
self.assertEqual(urllib.parse.unquote_plus('abc%20def'), 'abc def')
|
||||||
self.assertEqual(compat_urllib_parse_unquote_plus('%7e/abc+def'), '~/abc def')
|
self.assertEqual(urllib.parse.unquote_plus('%7e/abc+def'), '~/abc def')
|
||||||
|
|
||||||
def test_compat_urllib_parse_urlencode(self):
|
def test_compat_urllib_parse_urlencode(self):
|
||||||
self.assertEqual(compat_urllib_parse_urlencode({'abc': 'def'}), 'abc=def')
|
self.assertEqual(compat_urllib_parse_urlencode({'abc': 'def'}), 'abc=def')
|
||||||
@@ -91,11 +81,11 @@ class TestCompat(unittest.TestCase):
|
|||||||
</root>
|
</root>
|
||||||
'''
|
'''
|
||||||
doc = compat_etree_fromstring(xml.encode())
|
doc = compat_etree_fromstring(xml.encode())
|
||||||
self.assertTrue(isinstance(doc.attrib['foo'], compat_str))
|
self.assertTrue(isinstance(doc.attrib['foo'], str))
|
||||||
self.assertTrue(isinstance(doc.attrib['spam'], compat_str))
|
self.assertTrue(isinstance(doc.attrib['spam'], str))
|
||||||
self.assertTrue(isinstance(doc.find('normal').text, compat_str))
|
self.assertTrue(isinstance(doc.find('normal').text, str))
|
||||||
self.assertTrue(isinstance(doc.find('chinese').text, compat_str))
|
self.assertTrue(isinstance(doc.find('chinese').text, str))
|
||||||
self.assertTrue(isinstance(doc.find('foo/bar').text, compat_str))
|
self.assertTrue(isinstance(doc.find('foo/bar').text, str))
|
||||||
|
|
||||||
def test_compat_etree_fromstring_doctype(self):
|
def test_compat_etree_fromstring_doctype(self):
|
||||||
xml = '''<?xml version="1.0"?>
|
xml = '''<?xml version="1.0"?>
|
||||||
@@ -104,7 +94,7 @@ class TestCompat(unittest.TestCase):
|
|||||||
compat_etree_fromstring(xml)
|
compat_etree_fromstring(xml)
|
||||||
|
|
||||||
def test_struct_unpack(self):
|
def test_struct_unpack(self):
|
||||||
self.assertEqual(compat_struct_unpack('!B', b'\x00'), (0,))
|
self.assertEqual(struct.unpack('!B', b'\x00'), (0,))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import http.client
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from test.helper import (
|
from test.helper import (
|
||||||
assertGreaterEqual,
|
assertGreaterEqual,
|
||||||
expect_info_dict,
|
expect_info_dict,
|
||||||
@@ -20,12 +25,7 @@ from test.helper import (
|
|||||||
try_rm,
|
try_rm,
|
||||||
)
|
)
|
||||||
|
|
||||||
import yt_dlp.YoutubeDL
|
import yt_dlp.YoutubeDL # isort: split
|
||||||
from yt_dlp.compat import (
|
|
||||||
compat_http_client,
|
|
||||||
compat_HTTPError,
|
|
||||||
compat_urllib_error,
|
|
||||||
)
|
|
||||||
from yt_dlp.extractor import get_info_extractor
|
from yt_dlp.extractor import get_info_extractor
|
||||||
from yt_dlp.utils import (
|
from yt_dlp.utils import (
|
||||||
DownloadError,
|
DownloadError,
|
||||||
@@ -167,7 +167,7 @@ def generator(test_case, tname):
|
|||||||
force_generic_extractor=params.get('force_generic_extractor', False))
|
force_generic_extractor=params.get('force_generic_extractor', False))
|
||||||
except (DownloadError, ExtractorError) as err:
|
except (DownloadError, ExtractorError) as err:
|
||||||
# Check if the exception is not a network related one
|
# Check if the exception is not a network related one
|
||||||
if not err.exc_info[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError, compat_http_client.BadStatusLine) or (err.exc_info[0] == compat_HTTPError and err.exc_info[1].code == 503):
|
if not err.exc_info[0] in (urllib.error.URLError, socket.timeout, UnavailableVideoError, http.client.BadStatusLine) or (err.exc_info[0] == urllib.error.HTTPError and err.exc_info[1].code == 503):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if try_num == RETRIES:
|
if try_num == RETRIES:
|
||||||
@@ -273,7 +273,11 @@ def batch_generator(name, num_tests):
|
|||||||
|
|
||||||
def test_template(self):
|
def test_template(self):
|
||||||
for i in range(num_tests):
|
for i in range(num_tests):
|
||||||
getattr(self, f'test_{name}_{i}' if i else f'test_{name}')()
|
test_name = f'test_{name}_{i}' if i else f'test_{name}'
|
||||||
|
try:
|
||||||
|
getattr(self, test_name)()
|
||||||
|
except unittest.SkipTest:
|
||||||
|
print(f'Skipped {test_name}')
|
||||||
|
|
||||||
return test_template
|
return test_template
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
import threading
|
|
||||||
from test.helper import http_server_port, try_rm
|
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from test.helper import http_server_port, try_rm
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.compat import compat_http_server
|
|
||||||
from yt_dlp.downloader.http import HttpFD
|
from yt_dlp.downloader.http import HttpFD
|
||||||
from yt_dlp.utils import encodeFilename
|
from yt_dlp.utils import encodeFilename
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
|||||||
TEST_SIZE = 10 * 1024
|
TEST_SIZE = 10 * 1024
|
||||||
|
|
||||||
|
|
||||||
class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
|
class HTTPTestRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ class FakeLogger:
|
|||||||
|
|
||||||
class TestHttpFD(unittest.TestCase):
|
class TestHttpFD(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.httpd = compat_http_server.HTTPServer(
|
self.httpd = http.server.HTTPServer(
|
||||||
('127.0.0.1', 0), HTTPTestRequestHandler)
|
('127.0.0.1', 0), HTTPTestRequestHandler)
|
||||||
self.port = http_server_port(self.httpd)
|
self.port = http_server_port(self.httpd)
|
||||||
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import contextlib
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from yt_dlp.utils import encodeArgument
|
from yt_dlp.utils import encodeArgument
|
||||||
|
|
||||||
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,17 +7,19 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import http.server
|
||||||
import ssl
|
import ssl
|
||||||
import threading
|
import threading
|
||||||
from test.helper import http_server_port
|
import urllib.request
|
||||||
|
|
||||||
|
from test.helper import http_server_port
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.compat import compat_http_server, compat_urllib_request
|
|
||||||
|
|
||||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
|
class HTTPTestRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -53,7 +56,7 @@ class FakeLogger:
|
|||||||
|
|
||||||
class TestHTTP(unittest.TestCase):
|
class TestHTTP(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.httpd = compat_http_server.HTTPServer(
|
self.httpd = http.server.HTTPServer(
|
||||||
('127.0.0.1', 0), HTTPTestRequestHandler)
|
('127.0.0.1', 0), HTTPTestRequestHandler)
|
||||||
self.port = http_server_port(self.httpd)
|
self.port = http_server_port(self.httpd)
|
||||||
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
||||||
@@ -64,7 +67,7 @@ class TestHTTP(unittest.TestCase):
|
|||||||
class TestHTTPS(unittest.TestCase):
|
class TestHTTPS(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
||||||
self.httpd = compat_http_server.HTTPServer(
|
self.httpd = http.server.HTTPServer(
|
||||||
('127.0.0.1', 0), HTTPTestRequestHandler)
|
('127.0.0.1', 0), HTTPTestRequestHandler)
|
||||||
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
sslctx.load_cert_chain(certfn, None)
|
sslctx.load_cert_chain(certfn, None)
|
||||||
@@ -90,7 +93,7 @@ class TestClientCert(unittest.TestCase):
|
|||||||
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
certfn = os.path.join(TEST_DIR, 'testcert.pem')
|
||||||
self.certdir = os.path.join(TEST_DIR, 'testdata', 'certificate')
|
self.certdir = os.path.join(TEST_DIR, 'testdata', 'certificate')
|
||||||
cacertfn = os.path.join(self.certdir, 'ca.crt')
|
cacertfn = os.path.join(self.certdir, 'ca.crt')
|
||||||
self.httpd = compat_http_server.HTTPServer(('127.0.0.1', 0), HTTPTestRequestHandler)
|
self.httpd = http.server.HTTPServer(('127.0.0.1', 0), HTTPTestRequestHandler)
|
||||||
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
sslctx.verify_mode = ssl.CERT_REQUIRED
|
sslctx.verify_mode = ssl.CERT_REQUIRED
|
||||||
sslctx.load_verify_locations(cafile=cacertfn)
|
sslctx.load_verify_locations(cafile=cacertfn)
|
||||||
@@ -130,7 +133,7 @@ class TestClientCert(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def _build_proxy_handler(name):
|
def _build_proxy_handler(name):
|
||||||
class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
|
class HTTPTestRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
proxy_name = name
|
proxy_name = name
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
@@ -146,14 +149,14 @@ def _build_proxy_handler(name):
|
|||||||
|
|
||||||
class TestProxy(unittest.TestCase):
|
class TestProxy(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.proxy = compat_http_server.HTTPServer(
|
self.proxy = http.server.HTTPServer(
|
||||||
('127.0.0.1', 0), _build_proxy_handler('normal'))
|
('127.0.0.1', 0), _build_proxy_handler('normal'))
|
||||||
self.port = http_server_port(self.proxy)
|
self.port = http_server_port(self.proxy)
|
||||||
self.proxy_thread = threading.Thread(target=self.proxy.serve_forever)
|
self.proxy_thread = threading.Thread(target=self.proxy.serve_forever)
|
||||||
self.proxy_thread.daemon = True
|
self.proxy_thread.daemon = True
|
||||||
self.proxy_thread.start()
|
self.proxy_thread.start()
|
||||||
|
|
||||||
self.geo_proxy = compat_http_server.HTTPServer(
|
self.geo_proxy = http.server.HTTPServer(
|
||||||
('127.0.0.1', 0), _build_proxy_handler('geo'))
|
('127.0.0.1', 0), _build_proxy_handler('geo'))
|
||||||
self.geo_port = http_server_port(self.geo_proxy)
|
self.geo_port = http_server_port(self.geo_proxy)
|
||||||
self.geo_proxy_thread = threading.Thread(target=self.geo_proxy.serve_forever)
|
self.geo_proxy_thread = threading.Thread(target=self.geo_proxy.serve_forever)
|
||||||
@@ -170,7 +173,7 @@ class TestProxy(unittest.TestCase):
|
|||||||
response = ydl.urlopen(url).read().decode()
|
response = ydl.urlopen(url).read().decode()
|
||||||
self.assertEqual(response, f'normal: {url}')
|
self.assertEqual(response, f'normal: {url}')
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = urllib.request.Request(url)
|
||||||
req.add_header('Ytdl-request-proxy', geo_proxy)
|
req.add_header('Ytdl-request-proxy', geo_proxy)
|
||||||
response = ydl.urlopen(req).read().decode()
|
response = ydl.urlopen(req).read().decode()
|
||||||
self.assertEqual(response, f'geo: {url}')
|
self.assertEqual(response, f'geo: {url}')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,8 +7,8 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import FakeYDL, is_download_test
|
|
||||||
|
|
||||||
|
from test.helper import FakeYDL, is_download_test
|
||||||
from yt_dlp.extractor import IqiyiIE
|
from yt_dlp.extractor import IqiyiIE
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,6 +7,7 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
from yt_dlp.jsinterp import JSInterpreter
|
from yt_dlp.jsinterp import JSInterpreter
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from test.helper import is_download_test, try_rm
|
from test.helper import is_download_test, try_rm
|
||||||
|
|
||||||
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import get_params, is_download_test, try_rm
|
|
||||||
|
|
||||||
import yt_dlp.YoutubeDL
|
from test.helper import get_params, is_download_test, try_rm
|
||||||
|
import yt_dlp.YoutubeDL # isort: split
|
||||||
from yt_dlp.utils import DownloadError
|
from yt_dlp.utils import DownloadError
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,6 +7,7 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
from yt_dlp import YoutubeDL
|
from yt_dlp import YoutubeDL
|
||||||
from yt_dlp.compat import compat_shlex_quote
|
from yt_dlp.compat import compat_shlex_quote
|
||||||
from yt_dlp.postprocessor import (
|
from yt_dlp.postprocessor import (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,11 +7,12 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import subprocess
|
import subprocess
|
||||||
from test.helper import FakeYDL, get_params, is_download_test
|
import urllib.request
|
||||||
|
|
||||||
from yt_dlp.compat import compat_str, compat_urllib_request
|
from test.helper import FakeYDL, get_params, is_download_test
|
||||||
|
|
||||||
|
|
||||||
@is_download_test
|
@is_download_test
|
||||||
@@ -51,7 +53,7 @@ class TestMultipleSocks(unittest.TestCase):
|
|||||||
if params is None:
|
if params is None:
|
||||||
return
|
return
|
||||||
ydl = FakeYDL()
|
ydl = FakeYDL()
|
||||||
req = compat_urllib_request.Request('http://yt-dl.org/ip')
|
req = urllib.request.Request('http://yt-dl.org/ip')
|
||||||
req.add_header('Ytdl-request-proxy', params['secondary_proxy'])
|
req.add_header('Ytdl-request-proxy', params['secondary_proxy'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ydl.urlopen(req).read().decode(),
|
ydl.urlopen(req).read().decode(),
|
||||||
@@ -62,7 +64,7 @@ class TestMultipleSocks(unittest.TestCase):
|
|||||||
if params is None:
|
if params is None:
|
||||||
return
|
return
|
||||||
ydl = FakeYDL()
|
ydl = FakeYDL()
|
||||||
req = compat_urllib_request.Request('https://yt-dl.org/ip')
|
req = urllib.request.Request('https://yt-dl.org/ip')
|
||||||
req.add_header('Ytdl-request-proxy', params['secondary_proxy'])
|
req.add_header('Ytdl-request-proxy', params['secondary_proxy'])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
ydl.urlopen(req).read().decode(),
|
ydl.urlopen(req).read().decode(),
|
||||||
@@ -99,13 +101,13 @@ class TestSocks(unittest.TestCase):
|
|||||||
return ydl.urlopen('http://yt-dl.org/ip').read().decode()
|
return ydl.urlopen('http://yt-dl.org/ip').read().decode()
|
||||||
|
|
||||||
def test_socks4(self):
|
def test_socks4(self):
|
||||||
self.assertTrue(isinstance(self._get_ip('socks4'), compat_str))
|
self.assertTrue(isinstance(self._get_ip('socks4'), str))
|
||||||
|
|
||||||
def test_socks4a(self):
|
def test_socks4a(self):
|
||||||
self.assertTrue(isinstance(self._get_ip('socks4a'), compat_str))
|
self.assertTrue(isinstance(self._get_ip('socks4a'), str))
|
||||||
|
|
||||||
def test_socks5(self):
|
def test_socks5(self):
|
||||||
self.assertTrue(isinstance(self._get_ip('socks5'), compat_str))
|
self.assertTrue(isinstance(self._get_ip('socks5'), str))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,8 +7,8 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import FakeYDL, is_download_test, md5
|
|
||||||
|
|
||||||
|
from test.helper import FakeYDL, is_download_test, md5
|
||||||
from yt_dlp.extractor import (
|
from yt_dlp.extractor import (
|
||||||
NPOIE,
|
NPOIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
@@ -8,19 +8,16 @@ import unittest
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
# Various small unit tests
|
import contextlib
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from yt_dlp.compat import (
|
from yt_dlp.compat import (
|
||||||
compat_chr,
|
|
||||||
compat_etree_fromstring,
|
compat_etree_fromstring,
|
||||||
compat_getenv,
|
|
||||||
compat_HTMLParseError,
|
compat_HTMLParseError,
|
||||||
compat_os_name,
|
compat_os_name,
|
||||||
compat_setenv,
|
|
||||||
)
|
)
|
||||||
from yt_dlp.utils import (
|
from yt_dlp.utils import (
|
||||||
Config,
|
Config,
|
||||||
@@ -42,6 +39,7 @@ from yt_dlp.utils import (
|
|||||||
datetime_from_str,
|
datetime_from_str,
|
||||||
detect_exe_version,
|
detect_exe_version,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
determine_file_encoding,
|
||||||
dfxp2srt,
|
dfxp2srt,
|
||||||
dict_get,
|
dict_get,
|
||||||
encode_base_n,
|
encode_base_n,
|
||||||
@@ -266,20 +264,20 @@ class TestUtil(unittest.TestCase):
|
|||||||
def env(var):
|
def env(var):
|
||||||
return f'%{var}%' if sys.platform == 'win32' else f'${var}'
|
return f'%{var}%' if sys.platform == 'win32' else f'${var}'
|
||||||
|
|
||||||
compat_setenv('yt_dlp_EXPATH_PATH', 'expanded')
|
os.environ['yt_dlp_EXPATH_PATH'] = 'expanded'
|
||||||
self.assertEqual(expand_path(env('yt_dlp_EXPATH_PATH')), 'expanded')
|
self.assertEqual(expand_path(env('yt_dlp_EXPATH_PATH')), 'expanded')
|
||||||
|
|
||||||
old_home = os.environ.get('HOME')
|
old_home = os.environ.get('HOME')
|
||||||
test_str = R'C:\Documents and Settings\тест\Application Data'
|
test_str = R'C:\Documents and Settings\тест\Application Data'
|
||||||
try:
|
try:
|
||||||
compat_setenv('HOME', test_str)
|
os.environ['HOME'] = test_str
|
||||||
self.assertEqual(expand_path(env('HOME')), compat_getenv('HOME'))
|
self.assertEqual(expand_path(env('HOME')), os.getenv('HOME'))
|
||||||
self.assertEqual(expand_path('~'), compat_getenv('HOME'))
|
self.assertEqual(expand_path('~'), os.getenv('HOME'))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
expand_path('~/%s' % env('yt_dlp_EXPATH_PATH')),
|
expand_path('~/%s' % env('yt_dlp_EXPATH_PATH')),
|
||||||
'%s/expanded' % compat_getenv('HOME'))
|
'%s/expanded' % os.getenv('HOME'))
|
||||||
finally:
|
finally:
|
||||||
compat_setenv('HOME', old_home or '')
|
os.environ['HOME'] = old_home or ''
|
||||||
|
|
||||||
def test_prepend_extension(self):
|
def test_prepend_extension(self):
|
||||||
self.assertEqual(prepend_extension('abc.ext', 'temp'), 'abc.temp.ext')
|
self.assertEqual(prepend_extension('abc.ext', 'temp'), 'abc.temp.ext')
|
||||||
@@ -898,7 +896,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
'dynamic_range': 'HDR10',
|
'dynamic_range': 'HDR10',
|
||||||
})
|
})
|
||||||
self.assertEqual(parse_codecs('av01.0.12M.10.0.110.09.16.09.0'), {
|
self.assertEqual(parse_codecs('av01.0.12M.10.0.110.09.16.09.0'), {
|
||||||
'vcodec': 'av01.0.12M.10',
|
'vcodec': 'av01.0.12M.10.0.110.09.16.09.0',
|
||||||
'acodec': 'none',
|
'acodec': 'none',
|
||||||
'dynamic_range': 'HDR10',
|
'dynamic_range': 'HDR10',
|
||||||
})
|
})
|
||||||
@@ -1128,7 +1126,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(extract_attributes('<e x="décomposé">'), {'x': 'décompose\u0301'})
|
self.assertEqual(extract_attributes('<e x="décomposé">'), {'x': 'décompose\u0301'})
|
||||||
# "Narrow" Python builds don't support unicode code points outside BMP.
|
# "Narrow" Python builds don't support unicode code points outside BMP.
|
||||||
try:
|
try:
|
||||||
compat_chr(0x10000)
|
chr(0x10000)
|
||||||
supports_outside_bmp = True
|
supports_outside_bmp = True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
supports_outside_bmp = False
|
supports_outside_bmp = False
|
||||||
@@ -1825,6 +1823,25 @@ Line 1
|
|||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
os.remove(FILE)
|
os.remove(FILE)
|
||||||
|
|
||||||
|
def test_determine_file_encoding(self):
|
||||||
|
self.assertEqual(determine_file_encoding(b''), (None, 0))
|
||||||
|
self.assertEqual(determine_file_encoding(b'--verbose -x --audio-format mkv\n'), (None, 0))
|
||||||
|
|
||||||
|
self.assertEqual(determine_file_encoding(b'\xef\xbb\xbf'), ('utf-8', 3))
|
||||||
|
self.assertEqual(determine_file_encoding(b'\x00\x00\xfe\xff'), ('utf-32-be', 4))
|
||||||
|
self.assertEqual(determine_file_encoding(b'\xff\xfe'), ('utf-16-le', 2))
|
||||||
|
|
||||||
|
self.assertEqual(determine_file_encoding(b'\xff\xfe# coding: utf-8\n--verbose'), ('utf-16-le', 2))
|
||||||
|
|
||||||
|
self.assertEqual(determine_file_encoding(b'# coding: utf-8\n--verbose'), ('utf-8', 0))
|
||||||
|
self.assertEqual(determine_file_encoding(b'# coding: someencodinghere-12345\n--verbose'), ('someencodinghere-12345', 0))
|
||||||
|
|
||||||
|
self.assertEqual(determine_file_encoding(b'#coding:utf-8\n--verbose'), ('utf-8', 0))
|
||||||
|
self.assertEqual(determine_file_encoding(b'# coding: utf-8 \r\n--verbose'), ('utf-8', 0))
|
||||||
|
|
||||||
|
self.assertEqual(determine_file_encoding('# coding: utf-32-be'.encode('utf-32-be')), ('utf-32-be', 0))
|
||||||
|
self.assertEqual(determine_file_encoding('# coding: utf-16-le'.encode('utf-16-le')), ('utf-16-le', 0))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,11 +7,12 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
from test.helper import get_params, is_download_test, try_rm
|
|
||||||
|
|
||||||
import yt_dlp.extractor
|
import yt_dlp.extractor
|
||||||
import yt_dlp.YoutubeDL
|
import yt_dlp.YoutubeDL
|
||||||
|
from test.helper import get_params, is_download_test, try_rm
|
||||||
|
|
||||||
|
|
||||||
class YoutubeDL(yt_dlp.YoutubeDL):
|
class YoutubeDL(yt_dlp.YoutubeDL):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -6,8 +7,8 @@ import unittest
|
|||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from test.helper import FakeYDL, is_download_test
|
|
||||||
|
|
||||||
|
from test.helper import FakeYDL, is_download_test
|
||||||
from yt_dlp.extractor import YoutubeIE, YoutubeTabIE
|
from yt_dlp.extractor import YoutubeIE, YoutubeTabIE
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Allow direct execution
|
# Allow direct execution
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from test.helper import FakeYDL, is_download_test
|
|
||||||
|
|
||||||
from yt_dlp.compat import compat_str
|
from test.helper import FakeYDL, is_download_test
|
||||||
from yt_dlp.extractor import YoutubeIE
|
from yt_dlp.extractor import YoutubeIE
|
||||||
from yt_dlp.jsinterp import JSInterpreter
|
from yt_dlp.jsinterp import JSInterpreter
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ def t_factory(name, sig_func, url_pattern):
|
|||||||
def signature(jscode, sig_input):
|
def signature(jscode, sig_input):
|
||||||
func = YoutubeIE(FakeYDL())._parse_sig_js(jscode)
|
func = YoutubeIE(FakeYDL())._parse_sig_js(jscode)
|
||||||
src_sig = (
|
src_sig = (
|
||||||
compat_str(string.printable[:sig_input])
|
str(string.printable[:sig_input])
|
||||||
if isinstance(sig_input, int) else sig_input)
|
if isinstance(sig_input, int) else sig_input)
|
||||||
return func(src_sig)
|
return func(src_sig)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
@@ -11,7 +10,6 @@ import json
|
|||||||
import locale
|
import locale
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -26,15 +24,7 @@ import urllib.request
|
|||||||
from string import ascii_letters
|
from string import ascii_letters
|
||||||
|
|
||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .compat import (
|
from .compat import compat_os_name, compat_shlex_quote
|
||||||
HAS_LEGACY as compat_has_legacy,
|
|
||||||
compat_get_terminal_size,
|
|
||||||
compat_os_name,
|
|
||||||
compat_shlex_quote,
|
|
||||||
compat_str,
|
|
||||||
compat_urllib_error,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from .cookies import load_cookies
|
from .cookies import load_cookies
|
||||||
from .downloader import FFmpegFD, get_suitable_downloader, shorten_protocol_name
|
from .downloader import FFmpegFD, get_suitable_downloader, shorten_protocol_name
|
||||||
from .downloader.rtmp import rtmpdump_version
|
from .downloader.rtmp import rtmpdump_version
|
||||||
@@ -52,9 +42,11 @@ from .postprocessor import (
|
|||||||
FFmpegFixupTimestampPP,
|
FFmpegFixupTimestampPP,
|
||||||
FFmpegMergerPP,
|
FFmpegMergerPP,
|
||||||
FFmpegPostProcessor,
|
FFmpegPostProcessor,
|
||||||
|
FFmpegVideoConvertorPP,
|
||||||
MoveFilesAfterDownloadPP,
|
MoveFilesAfterDownloadPP,
|
||||||
get_postprocessor,
|
get_postprocessor,
|
||||||
)
|
)
|
||||||
|
from .postprocessor.ffmpeg import resolve_mapping as resolve_recode_mapping
|
||||||
from .update import detect_variant
|
from .update import detect_variant
|
||||||
from .utils import (
|
from .utils import (
|
||||||
DEFAULT_OUTTMPL,
|
DEFAULT_OUTTMPL,
|
||||||
@@ -93,12 +85,14 @@ from .utils import (
|
|||||||
YoutubeDLRedirectHandler,
|
YoutubeDLRedirectHandler,
|
||||||
age_restricted,
|
age_restricted,
|
||||||
args_to_str,
|
args_to_str,
|
||||||
|
bug_reports_message,
|
||||||
date_from_str,
|
date_from_str,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
determine_protocol,
|
determine_protocol,
|
||||||
encode_compat_str,
|
encode_compat_str,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
error_to_compat_str,
|
error_to_compat_str,
|
||||||
|
escapeHTML,
|
||||||
expand_path,
|
expand_path,
|
||||||
filter_dict,
|
filter_dict,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
@@ -118,7 +112,6 @@ from .utils import (
|
|||||||
number_of_digits,
|
number_of_digits,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
parse_filesize,
|
parse_filesize,
|
||||||
platform_name,
|
|
||||||
preferredencoding,
|
preferredencoding,
|
||||||
prepend_extension,
|
prepend_extension,
|
||||||
register_socks_protocols,
|
register_socks_protocols,
|
||||||
@@ -134,6 +127,7 @@ from .utils import (
|
|||||||
strftime_or_none,
|
strftime_or_none,
|
||||||
subtitles_filename,
|
subtitles_filename,
|
||||||
supports_terminal_sequences,
|
supports_terminal_sequences,
|
||||||
|
system_identifier,
|
||||||
timetuple_from_msec,
|
timetuple_from_msec,
|
||||||
to_high_limit_path,
|
to_high_limit_path,
|
||||||
traverse_obj,
|
traverse_obj,
|
||||||
@@ -312,7 +306,7 @@ class YoutubeDL:
|
|||||||
client_certificate_password: Password for client certificate private key, if encrypted.
|
client_certificate_password: Password for client certificate private key, if encrypted.
|
||||||
If not provided and the key is encrypted, yt-dlp will ask interactively
|
If not provided and the key is encrypted, yt-dlp will ask interactively
|
||||||
prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
|
prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
|
||||||
At the moment, this is only supported by YouTube.
|
(Only supported by some extractors)
|
||||||
http_headers: A dictionary of custom headers to be used for all requests
|
http_headers: A dictionary of custom headers to be used for all requests
|
||||||
proxy: URL of the proxy server to use
|
proxy: URL of the proxy server to use
|
||||||
geo_verification_proxy: URL of the proxy to use for IP address verification
|
geo_verification_proxy: URL of the proxy to use for IP address verification
|
||||||
@@ -324,9 +318,14 @@ class YoutubeDL:
|
|||||||
default_search: Prepend this string if an input url is not valid.
|
default_search: Prepend this string if an input url is not valid.
|
||||||
'auto' for elaborate guessing
|
'auto' for elaborate guessing
|
||||||
encoding: Use this encoding instead of the system-specified.
|
encoding: Use this encoding instead of the system-specified.
|
||||||
extract_flat: Do not resolve URLs, return the immediate result.
|
extract_flat: Whether to resolve and process url_results further
|
||||||
Pass in 'in_playlist' to only show this behavior for
|
* False: Always process (default)
|
||||||
playlist items.
|
* True: Never process
|
||||||
|
* 'in_playlist': Do not process inside playlist/multi_video
|
||||||
|
* 'discard': Always process, but don't return the result
|
||||||
|
from inside playlist/multi_video
|
||||||
|
* 'discard_in_playlist': Same as "discard", but only for
|
||||||
|
playlists (not multi_video)
|
||||||
wait_for_video: If given, wait for scheduled streams to become available.
|
wait_for_video: If given, wait for scheduled streams to become available.
|
||||||
The value should be a tuple containing the range
|
The value should be a tuple containing the range
|
||||||
(min_secs, max_secs) to wait between retries
|
(min_secs, max_secs) to wait between retries
|
||||||
@@ -430,19 +429,22 @@ class YoutubeDL:
|
|||||||
retry_sleep_functions: Dictionary of functions that takes the number of attempts
|
retry_sleep_functions: Dictionary of functions that takes the number of attempts
|
||||||
as argument and returns the time to sleep in seconds.
|
as argument and returns the time to sleep in seconds.
|
||||||
Allowed keys are 'http', 'fragment', 'file_access'
|
Allowed keys are 'http', 'fragment', 'file_access'
|
||||||
download_ranges: A function that gets called for every video with the signature
|
download_ranges: A callback function that gets called for every video with
|
||||||
(info_dict, *, ydl) -> Iterable[Section].
|
the signature (info_dict, ydl) -> Iterable[Section].
|
||||||
Only the returned sections will be downloaded. Each Section contains:
|
Only the returned sections will be downloaded.
|
||||||
|
Each Section is a dict with the following keys:
|
||||||
* start_time: Start time of the section in seconds
|
* start_time: Start time of the section in seconds
|
||||||
* end_time: End time of the section in seconds
|
* end_time: End time of the section in seconds
|
||||||
* title: Section title (Optional)
|
* title: Section title (Optional)
|
||||||
* index: Section number (Optional)
|
* index: Section number (Optional)
|
||||||
|
force_keyframes_at_cuts: Re-encode the video when downloading ranges to get precise cuts
|
||||||
|
noprogress: Do not print the progress bar
|
||||||
|
|
||||||
The following parameters are not used by YoutubeDL itself, they are used by
|
The following parameters are not used by YoutubeDL itself, they are used by
|
||||||
the downloader (see yt_dlp/downloader/common.py):
|
the downloader (see yt_dlp/downloader/common.py):
|
||||||
nopart, updatetime, buffersize, ratelimit, throttledratelimit, min_filesize,
|
nopart, updatetime, buffersize, ratelimit, throttledratelimit, min_filesize,
|
||||||
max_filesize, test, noresizebuffer, retries, file_access_retries, fragment_retries,
|
max_filesize, test, noresizebuffer, retries, file_access_retries, fragment_retries,
|
||||||
continuedl, noprogress, xattr_set_filesize, hls_use_mpegts, http_chunk_size,
|
continuedl, xattr_set_filesize, hls_use_mpegts, http_chunk_size,
|
||||||
external_downloader_args, concurrent_fragment_downloads.
|
external_downloader_args, concurrent_fragment_downloads.
|
||||||
|
|
||||||
The following options are used by the post processors:
|
The following options are used by the post processors:
|
||||||
@@ -585,7 +587,9 @@ class YoutubeDL:
|
|||||||
MIN_SUPPORTED, MIN_RECOMMENDED = (3, 6), (3, 7)
|
MIN_SUPPORTED, MIN_RECOMMENDED = (3, 6), (3, 7)
|
||||||
current_version = sys.version_info[:2]
|
current_version = sys.version_info[:2]
|
||||||
if current_version < MIN_RECOMMENDED:
|
if current_version < MIN_RECOMMENDED:
|
||||||
msg = 'Support for Python version %d.%d has been deprecated and will break in future versions of yt-dlp'
|
msg = ('Support for Python version %d.%d has been deprecated. '
|
||||||
|
'See https://github.com/yt-dlp/yt-dlp/issues/3764 for more details.'
|
||||||
|
'\n You will no longer receive updates on this version')
|
||||||
if current_version < MIN_SUPPORTED:
|
if current_version < MIN_SUPPORTED:
|
||||||
msg = 'Python version %d.%d is no longer supported'
|
msg = 'Python version %d.%d is no longer supported'
|
||||||
self.deprecation_warning(
|
self.deprecation_warning(
|
||||||
@@ -618,8 +622,6 @@ class YoutubeDL:
|
|||||||
self.deprecation_warning(msg)
|
self.deprecation_warning(msg)
|
||||||
|
|
||||||
self.params['compat_opts'] = set(self.params.get('compat_opts', ()))
|
self.params['compat_opts'] = set(self.params.get('compat_opts', ()))
|
||||||
if not compat_has_legacy:
|
|
||||||
self.params['compat_opts'].add('no-compat-legacy')
|
|
||||||
if 'list-formats' in self.params['compat_opts']:
|
if 'list-formats' in self.params['compat_opts']:
|
||||||
self.params['listformats_table'] = False
|
self.params['listformats_table'] = False
|
||||||
|
|
||||||
@@ -644,7 +646,7 @@ class YoutubeDL:
|
|||||||
try:
|
try:
|
||||||
import pty
|
import pty
|
||||||
master, slave = pty.openpty()
|
master, slave = pty.openpty()
|
||||||
width = compat_get_terminal_size().columns
|
width = shutil.get_terminal_size().columns
|
||||||
width_args = [] if width is None else ['-w', str(width)]
|
width_args = [] if width is None else ['-w', str(width)]
|
||||||
sp_kwargs = {'stdin': subprocess.PIPE, 'stdout': slave, 'stderr': self._out_files.error}
|
sp_kwargs = {'stdin': subprocess.PIPE, 'stdout': slave, 'stderr': self._out_files.error}
|
||||||
try:
|
try:
|
||||||
@@ -799,7 +801,7 @@ class YoutubeDL:
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
assert hasattr(self, '_output_process')
|
assert hasattr(self, '_output_process')
|
||||||
assert isinstance(message, compat_str)
|
assert isinstance(message, str)
|
||||||
line_count = message.count('\n') + 1
|
line_count = message.count('\n') + 1
|
||||||
self._output_process.stdin.write((message + '\n').encode())
|
self._output_process.stdin.write((message + '\n').encode())
|
||||||
self._output_process.stdin.flush()
|
self._output_process.stdin.flush()
|
||||||
@@ -835,7 +837,7 @@ class YoutubeDL:
|
|||||||
|
|
||||||
def to_stderr(self, message, only_once=False):
|
def to_stderr(self, message, only_once=False):
|
||||||
"""Print message to stderr"""
|
"""Print message to stderr"""
|
||||||
assert isinstance(message, compat_str)
|
assert isinstance(message, str)
|
||||||
if self.params.get('logger'):
|
if self.params.get('logger'):
|
||||||
self.params['logger'].error(message)
|
self.params['logger'].error(message)
|
||||||
else:
|
else:
|
||||||
@@ -1053,7 +1055,7 @@ class YoutubeDL:
|
|||||||
def validate_outtmpl(cls, outtmpl):
|
def validate_outtmpl(cls, outtmpl):
|
||||||
''' @return None or Exception object '''
|
''' @return None or Exception object '''
|
||||||
outtmpl = re.sub(
|
outtmpl = re.sub(
|
||||||
STR_FORMAT_RE_TMPL.format('[^)]*', '[ljqBUDS]'),
|
STR_FORMAT_RE_TMPL.format('[^)]*', '[ljhqBUDS]'),
|
||||||
lambda mobj: f'{mobj.group(0)[:-1]}s',
|
lambda mobj: f'{mobj.group(0)[:-1]}s',
|
||||||
cls._outtmpl_expandpath(outtmpl))
|
cls._outtmpl_expandpath(outtmpl))
|
||||||
try:
|
try:
|
||||||
@@ -1096,7 +1098,7 @@ class YoutubeDL:
|
|||||||
}
|
}
|
||||||
|
|
||||||
TMPL_DICT = {}
|
TMPL_DICT = {}
|
||||||
EXTERNAL_FORMAT_RE = re.compile(STR_FORMAT_RE_TMPL.format('[^)]*', f'[{STR_FORMAT_TYPES}ljqBUDS]'))
|
EXTERNAL_FORMAT_RE = re.compile(STR_FORMAT_RE_TMPL.format('[^)]*', f'[{STR_FORMAT_TYPES}ljhqBUDS]'))
|
||||||
MATH_FUNCTIONS = {
|
MATH_FUNCTIONS = {
|
||||||
'+': float.__add__,
|
'+': float.__add__,
|
||||||
'-': float.__sub__,
|
'-': float.__sub__,
|
||||||
@@ -1205,6 +1207,8 @@ class YoutubeDL:
|
|||||||
value, fmt = delim.join(map(str, variadic(value, allowed_types=(str, bytes)))), str_fmt
|
value, fmt = delim.join(map(str, variadic(value, allowed_types=(str, bytes)))), str_fmt
|
||||||
elif fmt[-1] == 'j': # json
|
elif fmt[-1] == 'j': # json
|
||||||
value, fmt = json.dumps(value, default=_dumpjson_default, indent=4 if '#' in flags else None), str_fmt
|
value, fmt = json.dumps(value, default=_dumpjson_default, indent=4 if '#' in flags else None), str_fmt
|
||||||
|
elif fmt[-1] == 'h': # html
|
||||||
|
value, fmt = escapeHTML(value), str_fmt
|
||||||
elif fmt[-1] == 'q': # quoted
|
elif fmt[-1] == 'q': # quoted
|
||||||
value = map(str, variadic(value) if '#' in flags else [value])
|
value = map(str, variadic(value) if '#' in flags else [value])
|
||||||
value, fmt = ' '.join(map(compat_shlex_quote, value)), str_fmt
|
value, fmt = ' '.join(map(compat_shlex_quote, value)), str_fmt
|
||||||
@@ -1462,7 +1466,12 @@ class YoutubeDL:
|
|||||||
|
|
||||||
def progress(msg):
|
def progress(msg):
|
||||||
nonlocal last_msg
|
nonlocal last_msg
|
||||||
self.to_screen(msg + ' ' * (len(last_msg) - len(msg)) + '\r', skip_eol=True)
|
full_msg = f'{msg}\n'
|
||||||
|
if not self.params.get('noprogress'):
|
||||||
|
full_msg = msg + ' ' * (len(last_msg) - len(msg)) + '\r'
|
||||||
|
elif last_msg:
|
||||||
|
return
|
||||||
|
self.to_screen(full_msg, skip_eol=True)
|
||||||
last_msg = msg
|
last_msg = msg
|
||||||
|
|
||||||
min_wait, max_wait = self.params.get('wait_for_video')
|
min_wait, max_wait = self.params.get('wait_for_video')
|
||||||
@@ -1496,6 +1505,7 @@ class YoutubeDL:
|
|||||||
def __extract_info(self, url, ie, download, extra_info, process):
|
def __extract_info(self, url, ie, download, extra_info, process):
|
||||||
ie_result = ie.extract(url)
|
ie_result = ie.extract(url)
|
||||||
if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
|
if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
|
||||||
|
self.report_warning(f'Extractor {ie.IE_NAME} returned nothing{bug_reports_message()}')
|
||||||
return
|
return
|
||||||
if isinstance(ie_result, list):
|
if isinstance(ie_result, list):
|
||||||
# Backwards compatibility: old IE result format
|
# Backwards compatibility: old IE result format
|
||||||
@@ -1570,7 +1580,7 @@ class YoutubeDL:
|
|||||||
additional_urls = (ie_result or {}).get('additional_urls')
|
additional_urls = (ie_result or {}).get('additional_urls')
|
||||||
if additional_urls:
|
if additional_urls:
|
||||||
# TODO: Improve MetadataParserPP to allow setting a list
|
# TODO: Improve MetadataParserPP to allow setting a list
|
||||||
if isinstance(additional_urls, compat_str):
|
if isinstance(additional_urls, str):
|
||||||
additional_urls = [additional_urls]
|
additional_urls = [additional_urls]
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
'[info] %s: %d additional URL(s) requested' % (ie_result['id'], len(additional_urls)))
|
'[info] %s: %d additional URL(s) requested' % (ie_result['id'], len(additional_urls)))
|
||||||
@@ -1680,8 +1690,10 @@ class YoutubeDL:
|
|||||||
|
|
||||||
def __process_playlist(self, ie_result, download):
|
def __process_playlist(self, ie_result, download):
|
||||||
"""Process each entry in the playlist"""
|
"""Process each entry in the playlist"""
|
||||||
|
assert ie_result['_type'] in ('playlist', 'multi_video')
|
||||||
|
|
||||||
title = ie_result.get('title') or ie_result.get('id') or '<Untitled>'
|
title = ie_result.get('title') or ie_result.get('id') or '<Untitled>'
|
||||||
self.to_screen(f'[download] Downloading playlist: {title}')
|
self.to_screen(f'[download] Downloading {ie_result["_type"]}: {title}')
|
||||||
|
|
||||||
all_entries = PlaylistEntries(self, ie_result)
|
all_entries = PlaylistEntries(self, ie_result)
|
||||||
entries = orderedSet(all_entries.get_requested_items(), lazy=True)
|
entries = orderedSet(all_entries.get_requested_items(), lazy=True)
|
||||||
@@ -1725,6 +1737,12 @@ class YoutubeDL:
|
|||||||
self.to_screen(f'[{ie_result["extractor"]}] Playlist {title}: Downloading {n_entries} videos'
|
self.to_screen(f'[{ie_result["extractor"]}] Playlist {title}: Downloading {n_entries} videos'
|
||||||
f'{format_field(ie_result, "playlist_count", " of %s")}')
|
f'{format_field(ie_result, "playlist_count", " of %s")}')
|
||||||
|
|
||||||
|
keep_resolved_entries = self.params.get('extract_flat') != 'discard'
|
||||||
|
if self.params.get('extract_flat') == 'discard_in_playlist':
|
||||||
|
keep_resolved_entries = ie_result['_type'] != 'playlist'
|
||||||
|
if keep_resolved_entries:
|
||||||
|
self.write_debug('The information of all playlist entries will be held in memory')
|
||||||
|
|
||||||
failures = 0
|
failures = 0
|
||||||
max_failures = self.params.get('skip_playlist_after_errors') or float('inf')
|
max_failures = self.params.get('skip_playlist_after_errors') or float('inf')
|
||||||
for i, (playlist_index, entry) in enumerate(entries):
|
for i, (playlist_index, entry) in enumerate(entries):
|
||||||
@@ -1732,7 +1750,7 @@ class YoutubeDL:
|
|||||||
resolved_entries.append((playlist_index, entry))
|
resolved_entries.append((playlist_index, entry))
|
||||||
|
|
||||||
# TODO: Add auto-generated fields
|
# TODO: Add auto-generated fields
|
||||||
if self._match_entry(entry, incomplete=True) is not None:
|
if not entry or self._match_entry(entry, incomplete=True) is not None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.to_screen('[download] Downloading video %s of %s' % (
|
self.to_screen('[download] Downloading video %s of %s' % (
|
||||||
@@ -1765,7 +1783,8 @@ class YoutubeDL:
|
|||||||
self.report_error(
|
self.report_error(
|
||||||
f'Skipping the remaining entries in playlist "{title}" since {failures} items failed extraction')
|
f'Skipping the remaining entries in playlist "{title}" since {failures} items failed extraction')
|
||||||
break
|
break
|
||||||
resolved_entries[i] = (playlist_index, entry_result)
|
if keep_resolved_entries:
|
||||||
|
resolved_entries[i] = (playlist_index, entry_result)
|
||||||
|
|
||||||
# Update with processed data
|
# Update with processed data
|
||||||
ie_result['requested_entries'], ie_result['entries'] = tuple(zip(*resolved_entries)) or ([], [])
|
ie_result['requested_entries'], ie_result['entries'] = tuple(zip(*resolved_entries)) or ([], [])
|
||||||
@@ -2363,10 +2382,10 @@ class YoutubeDL:
|
|||||||
|
|
||||||
def sanitize_string_field(info, string_field):
|
def sanitize_string_field(info, string_field):
|
||||||
field = info.get(string_field)
|
field = info.get(string_field)
|
||||||
if field is None or isinstance(field, compat_str):
|
if field is None or isinstance(field, str):
|
||||||
return
|
return
|
||||||
report_force_conversion(string_field, 'a string', 'string')
|
report_force_conversion(string_field, 'a string', 'string')
|
||||||
info[string_field] = compat_str(field)
|
info[string_field] = str(field)
|
||||||
|
|
||||||
def sanitize_numeric_fields(info):
|
def sanitize_numeric_fields(info):
|
||||||
for numeric_field in self._NUMERIC_FIELDS:
|
for numeric_field in self._NUMERIC_FIELDS:
|
||||||
@@ -2383,6 +2402,20 @@ class YoutubeDL:
|
|||||||
if (info_dict.get('duration') or 0) <= 0 and info_dict.pop('duration', None):
|
if (info_dict.get('duration') or 0) <= 0 and info_dict.pop('duration', None):
|
||||||
self.report_warning('"duration" field is negative, there is an error in extractor')
|
self.report_warning('"duration" field is negative, there is an error in extractor')
|
||||||
|
|
||||||
|
chapters = info_dict.get('chapters') or []
|
||||||
|
if chapters and chapters[0].get('start_time'):
|
||||||
|
chapters.insert(0, {'start_time': 0})
|
||||||
|
|
||||||
|
dummy_chapter = {'end_time': 0, 'start_time': info_dict.get('duration')}
|
||||||
|
for idx, (prev, current, next_) in enumerate(zip(
|
||||||
|
(dummy_chapter, *chapters), chapters, (*chapters[1:], dummy_chapter)), 1):
|
||||||
|
if current.get('start_time') is None:
|
||||||
|
current['start_time'] = prev.get('end_time')
|
||||||
|
if not current.get('end_time'):
|
||||||
|
current['end_time'] = next_.get('start_time')
|
||||||
|
if not current.get('title'):
|
||||||
|
current['title'] = f'<Untitled Chapter {idx}>'
|
||||||
|
|
||||||
if 'playlist' not in info_dict:
|
if 'playlist' not in info_dict:
|
||||||
# It isn't part of a playlist
|
# It isn't part of a playlist
|
||||||
info_dict['playlist'] = None
|
info_dict['playlist'] = None
|
||||||
@@ -2469,7 +2502,7 @@ class YoutubeDL:
|
|||||||
sanitize_numeric_fields(format)
|
sanitize_numeric_fields(format)
|
||||||
format['url'] = sanitize_url(format['url'])
|
format['url'] = sanitize_url(format['url'])
|
||||||
if not format.get('format_id'):
|
if not format.get('format_id'):
|
||||||
format['format_id'] = compat_str(i)
|
format['format_id'] = str(i)
|
||||||
else:
|
else:
|
||||||
# Sanitize format_id from characters used in format selector expression
|
# Sanitize format_id from characters used in format selector expression
|
||||||
format['format_id'] = re.sub(r'[\s,/+\[\]()]', '_', format['format_id'])
|
format['format_id'] = re.sub(r'[\s,/+\[\]()]', '_', format['format_id'])
|
||||||
@@ -2619,7 +2652,7 @@ class YoutubeDL:
|
|||||||
if chapter or offset:
|
if chapter or offset:
|
||||||
new_info.update({
|
new_info.update({
|
||||||
'section_start': offset + chapter.get('start_time', 0),
|
'section_start': offset + chapter.get('start_time', 0),
|
||||||
'section_end': offset + min(chapter.get('end_time', 0), duration),
|
'section_end': offset + min(chapter.get('end_time', duration), duration),
|
||||||
'section_title': chapter.get('title'),
|
'section_title': chapter.get('title'),
|
||||||
'section_number': chapter.get('index'),
|
'section_number': chapter.get('index'),
|
||||||
})
|
})
|
||||||
@@ -3171,22 +3204,23 @@ class YoutubeDL:
|
|||||||
self.report_warning(f'{vid}: {msg}. Install ffmpeg to fix this automatically')
|
self.report_warning(f'{vid}: {msg}. Install ffmpeg to fix this automatically')
|
||||||
|
|
||||||
stretched_ratio = info_dict.get('stretched_ratio')
|
stretched_ratio = info_dict.get('stretched_ratio')
|
||||||
ffmpeg_fixup(
|
ffmpeg_fixup(stretched_ratio not in (1, None),
|
||||||
stretched_ratio not in (1, None),
|
f'Non-uniform pixel ratio {stretched_ratio}',
|
||||||
f'Non-uniform pixel ratio {stretched_ratio}',
|
FFmpegFixupStretchedPP)
|
||||||
FFmpegFixupStretchedPP)
|
|
||||||
|
|
||||||
ffmpeg_fixup(
|
|
||||||
(info_dict.get('requested_formats') is None
|
|
||||||
and info_dict.get('container') == 'm4a_dash'
|
|
||||||
and info_dict.get('ext') == 'm4a'),
|
|
||||||
'writing DASH m4a. Only some players support this container',
|
|
||||||
FFmpegFixupM4aPP)
|
|
||||||
|
|
||||||
downloader = get_suitable_downloader(info_dict, self.params) if 'protocol' in info_dict else None
|
downloader = get_suitable_downloader(info_dict, self.params) if 'protocol' in info_dict else None
|
||||||
downloader = downloader.FD_NAME if downloader else None
|
downloader = downloader.FD_NAME if downloader else None
|
||||||
|
|
||||||
if info_dict.get('requested_formats') is None: # Not necessary if doing merger
|
ext = info_dict.get('ext')
|
||||||
|
postprocessed_by_ffmpeg = info_dict.get('requested_formats') or any((
|
||||||
|
isinstance(pp, FFmpegVideoConvertorPP)
|
||||||
|
and resolve_recode_mapping(ext, pp.mapping)[0] not in (ext, None)
|
||||||
|
) for pp in self._pps['post_process'])
|
||||||
|
|
||||||
|
if not postprocessed_by_ffmpeg:
|
||||||
|
ffmpeg_fixup(ext == 'm4a' and info_dict.get('container') == 'm4a_dash',
|
||||||
|
'writing DASH m4a. Only some players support this container',
|
||||||
|
FFmpegFixupM4aPP)
|
||||||
ffmpeg_fixup(downloader == 'hlsnative' and not self.params.get('hls_use_mpegts')
|
ffmpeg_fixup(downloader == 'hlsnative' and not self.params.get('hls_use_mpegts')
|
||||||
or info_dict.get('is_live') and self.params.get('hls_use_mpegts') is None,
|
or info_dict.get('is_live') and self.params.get('hls_use_mpegts') is None,
|
||||||
'Possible MPEG-TS in MP4 container or malformed AAC timestamps',
|
'Possible MPEG-TS in MP4 container or malformed AAC timestamps',
|
||||||
@@ -3510,28 +3544,39 @@ class YoutubeDL:
|
|||||||
] for f in formats if f.get('preference') is None or f['preference'] >= -1000]
|
] for f in formats if f.get('preference') is None or f['preference'] >= -1000]
|
||||||
return render_table(['format code', 'extension', 'resolution', 'note'], table, extra_gap=1)
|
return render_table(['format code', 'extension', 'resolution', 'note'], table, extra_gap=1)
|
||||||
|
|
||||||
|
def simplified_codec(f, field):
|
||||||
|
assert field in ('acodec', 'vcodec')
|
||||||
|
codec = f.get(field, 'unknown')
|
||||||
|
if not codec:
|
||||||
|
return 'unknown'
|
||||||
|
elif codec != 'none':
|
||||||
|
return '.'.join(codec.split('.')[:4])
|
||||||
|
|
||||||
|
if field == 'vcodec' and f.get('acodec') == 'none':
|
||||||
|
return 'images'
|
||||||
|
elif field == 'acodec' and f.get('vcodec') == 'none':
|
||||||
|
return ''
|
||||||
|
return self._format_out('audio only' if field == 'vcodec' else 'video only',
|
||||||
|
self.Styles.SUPPRESS)
|
||||||
|
|
||||||
delim = self._format_out('\u2502', self.Styles.DELIM, '|', test_encoding=True)
|
delim = self._format_out('\u2502', self.Styles.DELIM, '|', test_encoding=True)
|
||||||
table = [
|
table = [
|
||||||
[
|
[
|
||||||
self._format_out(format_field(f, 'format_id'), self.Styles.ID),
|
self._format_out(format_field(f, 'format_id'), self.Styles.ID),
|
||||||
format_field(f, 'ext'),
|
format_field(f, 'ext'),
|
||||||
format_field(f, func=self.format_resolution, ignore=('audio only', 'images')),
|
format_field(f, func=self.format_resolution, ignore=('audio only', 'images')),
|
||||||
format_field(f, 'fps', '\t%d'),
|
format_field(f, 'fps', '\t%d', func=round),
|
||||||
format_field(f, 'dynamic_range', '%s', ignore=(None, 'SDR')).replace('HDR', ''),
|
format_field(f, 'dynamic_range', '%s', ignore=(None, 'SDR')).replace('HDR', ''),
|
||||||
delim,
|
delim,
|
||||||
format_field(f, 'filesize', ' \t%s', func=format_bytes) + format_field(f, 'filesize_approx', '~\t%s', func=format_bytes),
|
format_field(f, 'filesize', ' \t%s', func=format_bytes) + format_field(f, 'filesize_approx', '~\t%s', func=format_bytes),
|
||||||
format_field(f, 'tbr', '\t%dk'),
|
format_field(f, 'tbr', '\t%dk', func=round),
|
||||||
shorten_protocol_name(f.get('protocol', '')),
|
shorten_protocol_name(f.get('protocol', '')),
|
||||||
delim,
|
delim,
|
||||||
format_field(f, 'vcodec', default='unknown').replace(
|
simplified_codec(f, 'vcodec'),
|
||||||
'none', 'images' if f.get('acodec') == 'none'
|
format_field(f, 'vbr', '\t%dk', func=round),
|
||||||
else self._format_out('audio only', self.Styles.SUPPRESS)),
|
simplified_codec(f, 'acodec'),
|
||||||
format_field(f, 'vbr', '\t%dk'),
|
format_field(f, 'abr', '\t%dk', func=round),
|
||||||
format_field(f, 'acodec', default='unknown').replace(
|
format_field(f, 'asr', '\t%s', func=format_decimal_suffix),
|
||||||
'none', '' if f.get('vcodec') == 'none'
|
|
||||||
else self._format_out('video only', self.Styles.SUPPRESS)),
|
|
||||||
format_field(f, 'abr', '\t%dk'),
|
|
||||||
format_field(f, 'asr', '\t%dHz'),
|
|
||||||
join_nonempty(
|
join_nonempty(
|
||||||
self._format_out('UNSUPPORTED', 'light red') if f.get('ext') in ('f4f', 'f4m') else None,
|
self._format_out('UNSUPPORTED', 'light red') if f.get('ext') in ('f4f', 'f4m') else None,
|
||||||
format_field(f, 'language', '[%s]'),
|
format_field(f, 'language', '[%s]'),
|
||||||
@@ -3655,17 +3700,7 @@ class YoutubeDL:
|
|||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
sys.exc_clear()
|
sys.exc_clear()
|
||||||
|
|
||||||
def python_implementation():
|
write_debug(system_identifier())
|
||||||
impl_name = platform.python_implementation()
|
|
||||||
if impl_name == 'PyPy' and hasattr(sys, 'pypy_version_info'):
|
|
||||||
return impl_name + ' version %d.%d.%d' % sys.pypy_version_info[:3]
|
|
||||||
return impl_name
|
|
||||||
|
|
||||||
write_debug('Python version %s (%s %s) - %s' % (
|
|
||||||
platform.python_version(),
|
|
||||||
python_implementation(),
|
|
||||||
platform.architecture()[0],
|
|
||||||
platform_name()))
|
|
||||||
|
|
||||||
exe_versions, ffmpeg_features = FFmpegPostProcessor.get_versions_and_features(self)
|
exe_versions, ffmpeg_features = FFmpegPostProcessor.get_versions_and_features(self)
|
||||||
ffmpeg_features = {key for key, val in ffmpeg_features.items() if val}
|
ffmpeg_features = {key for key, val in ffmpeg_features.items() if val}
|
||||||
@@ -3724,7 +3759,7 @@ class YoutubeDL:
|
|||||||
else:
|
else:
|
||||||
proxies = {'http': opts_proxy, 'https': opts_proxy}
|
proxies = {'http': opts_proxy, 'https': opts_proxy}
|
||||||
else:
|
else:
|
||||||
proxies = compat_urllib_request.getproxies()
|
proxies = urllib.request.getproxies()
|
||||||
# Set HTTPS proxy to HTTP one if given (https://github.com/ytdl-org/youtube-dl/issues/805)
|
# Set HTTPS proxy to HTTP one if given (https://github.com/ytdl-org/youtube-dl/issues/805)
|
||||||
if 'http' in proxies and 'https' not in proxies:
|
if 'http' in proxies and 'https' not in proxies:
|
||||||
proxies['https'] = proxies['http']
|
proxies['https'] = proxies['http']
|
||||||
@@ -3740,13 +3775,13 @@ class YoutubeDL:
|
|||||||
# default FileHandler and allows us to disable the file protocol, which
|
# default FileHandler and allows us to disable the file protocol, which
|
||||||
# can be used for malicious purposes (see
|
# can be used for malicious purposes (see
|
||||||
# https://github.com/ytdl-org/youtube-dl/issues/8227)
|
# https://github.com/ytdl-org/youtube-dl/issues/8227)
|
||||||
file_handler = compat_urllib_request.FileHandler()
|
file_handler = urllib.request.FileHandler()
|
||||||
|
|
||||||
def file_open(*args, **kwargs):
|
def file_open(*args, **kwargs):
|
||||||
raise compat_urllib_error.URLError('file:// scheme is explicitly disabled in yt-dlp for security reasons')
|
raise urllib.error.URLError('file:// scheme is explicitly disabled in yt-dlp for security reasons')
|
||||||
file_handler.file_open = file_open
|
file_handler.file_open = file_open
|
||||||
|
|
||||||
opener = compat_urllib_request.build_opener(
|
opener = urllib.request.build_opener(
|
||||||
proxy_handler, https_handler, cookie_processor, ydlh, redirect_handler, data_handler, file_handler)
|
proxy_handler, https_handler, cookie_processor, ydlh, redirect_handler, data_handler, file_handler)
|
||||||
|
|
||||||
# Delete the default user-agent header, which would otherwise apply in
|
# Delete the default user-agent header, which would otherwise apply in
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
f'You are using an unsupported version of Python. Only Python versions 3.6 and above are supported by yt-dlp' # noqa: F541
|
f'You are using an unsupported version of Python. Only Python versions 3.6 and above are supported by yt-dlp' # noqa: F541
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import getpass
|
||||||
import itertools
|
import itertools
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .compat import compat_getpass, compat_shlex_quote
|
from .compat import compat_shlex_quote
|
||||||
from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS
|
from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS
|
||||||
from .downloader import FileDownloader
|
from .downloader import FileDownloader
|
||||||
from .downloader.external import get_external_downloader
|
from .downloader.external import get_external_downloader
|
||||||
@@ -403,6 +404,8 @@ def validate_options(opts):
|
|||||||
|
|
||||||
default_downloader = None
|
default_downloader = None
|
||||||
for proto, path in opts.external_downloader.items():
|
for proto, path in opts.external_downloader.items():
|
||||||
|
if path == 'native':
|
||||||
|
continue
|
||||||
ed = get_external_downloader(path)
|
ed = get_external_downloader(path)
|
||||||
if ed is None:
|
if ed is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -514,7 +517,7 @@ def validate_options(opts):
|
|||||||
# Do not unnecessarily download audio
|
# Do not unnecessarily download audio
|
||||||
opts.format = 'bestaudio/best'
|
opts.format = 'bestaudio/best'
|
||||||
|
|
||||||
if opts.getcomments and opts.writeinfojson is None:
|
if opts.getcomments and opts.writeinfojson is None and not opts.embed_infojson:
|
||||||
# If JSON is not printed anywhere, but comments are requested, save it to file
|
# If JSON is not printed anywhere, but comments are requested, save it to file
|
||||||
if not opts.dumpjson or opts.print_json or opts.dump_single_json:
|
if not opts.dumpjson or opts.print_json or opts.dump_single_json:
|
||||||
opts.writeinfojson = True
|
opts.writeinfojson = True
|
||||||
@@ -529,9 +532,9 @@ def validate_options(opts):
|
|||||||
|
|
||||||
# Ask for passwords
|
# Ask for passwords
|
||||||
if opts.username is not None and opts.password is None:
|
if opts.username is not None and opts.password is None:
|
||||||
opts.password = compat_getpass('Type account password and press [Return]: ')
|
opts.password = getpass.getpass('Type account password and press [Return]: ')
|
||||||
if opts.ap_username is not None and opts.ap_password is None:
|
if opts.ap_username is not None and opts.ap_password is None:
|
||||||
opts.ap_password = compat_getpass('Type TV provider account password and press [Return]: ')
|
opts.ap_password = getpass.getpass('Type TV provider account password and press [Return]: ')
|
||||||
|
|
||||||
return warnings, deprecation_warnings
|
return warnings, deprecation_warnings
|
||||||
|
|
||||||
@@ -663,8 +666,11 @@ def get_postprocessors(opts):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ParsedOptions = collections.namedtuple('ParsedOptions', ('parser', 'options', 'urls', 'ydl_opts'))
|
||||||
|
|
||||||
|
|
||||||
def parse_options(argv=None):
|
def parse_options(argv=None):
|
||||||
""" @returns (parser, opts, urls, ydl_opts) """
|
"""@returns ParsedOptions(parser, opts, urls, ydl_opts)"""
|
||||||
parser, opts, urls = parseOpts(argv)
|
parser, opts, urls = parseOpts(argv)
|
||||||
urls = get_urls(urls, opts.batchfile, opts.verbose)
|
urls = get_urls(urls, opts.batchfile, opts.verbose)
|
||||||
|
|
||||||
@@ -682,13 +688,28 @@ def parse_options(argv=None):
|
|||||||
'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl'
|
'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl'
|
||||||
))
|
))
|
||||||
|
|
||||||
|
playlist_pps = [pp for pp in postprocessors if pp.get('when') == 'playlist']
|
||||||
|
write_playlist_infojson = (opts.writeinfojson and not opts.clean_infojson
|
||||||
|
and opts.allow_playlist_files and opts.outtmpl.get('pl_infojson') != '')
|
||||||
|
if not any((
|
||||||
|
opts.extract_flat,
|
||||||
|
opts.dump_single_json,
|
||||||
|
opts.forceprint.get('playlist'),
|
||||||
|
opts.print_to_file.get('playlist'),
|
||||||
|
write_playlist_infojson,
|
||||||
|
)):
|
||||||
|
if not playlist_pps:
|
||||||
|
opts.extract_flat = 'discard'
|
||||||
|
elif playlist_pps == [{'key': 'FFmpegConcat', 'only_multi_video': True, 'when': 'playlist'}]:
|
||||||
|
opts.extract_flat = 'discard_in_playlist'
|
||||||
|
|
||||||
final_ext = (
|
final_ext = (
|
||||||
opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
|
opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
|
||||||
else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS
|
else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS
|
||||||
else opts.audioformat if (opts.extractaudio and opts.audioformat in FFmpegExtractAudioPP.SUPPORTED_EXTS)
|
else opts.audioformat if (opts.extractaudio and opts.audioformat in FFmpegExtractAudioPP.SUPPORTED_EXTS)
|
||||||
else None)
|
else None)
|
||||||
|
|
||||||
return parser, opts, urls, {
|
return ParsedOptions(parser, opts, urls, {
|
||||||
'usenetrc': opts.usenetrc,
|
'usenetrc': opts.usenetrc,
|
||||||
'netrc_location': opts.netrc_location,
|
'netrc_location': opts.netrc_location,
|
||||||
'username': opts.username,
|
'username': opts.username,
|
||||||
@@ -861,7 +882,7 @@ def parse_options(argv=None):
|
|||||||
'_warnings': warnings,
|
'_warnings': warnings,
|
||||||
'_deprecation_warnings': deprecation_warnings,
|
'_deprecation_warnings': deprecation_warnings,
|
||||||
'compat_opts': opts.compat_opts,
|
'compat_opts': opts.compat_opts,
|
||||||
}
|
})
|
||||||
|
|
||||||
|
|
||||||
def _real_main(argv=None):
|
def _real_main(argv=None):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Execute with
|
# Execute with
|
||||||
# $ python -m yt_dlp
|
# $ python -m yt_dlp
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import base64
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
from .compat import compat_b64decode, compat_ord
|
from .compat import compat_ord
|
||||||
from .dependencies import Cryptodome_AES
|
from .dependencies import Cryptodome_AES
|
||||||
from .utils import bytes_to_intlist, intlist_to_bytes
|
from .utils import bytes_to_intlist, intlist_to_bytes
|
||||||
|
|
||||||
@@ -23,6 +24,10 @@ else:
|
|||||||
return intlist_to_bytes(aes_gcm_decrypt_and_verify(*map(bytes_to_intlist, (data, key, tag, nonce))))
|
return intlist_to_bytes(aes_gcm_decrypt_and_verify(*map(bytes_to_intlist, (data, key, tag, nonce))))
|
||||||
|
|
||||||
|
|
||||||
|
def aes_cbc_encrypt_bytes(data, key, iv, **kwargs):
|
||||||
|
return intlist_to_bytes(aes_cbc_encrypt(*map(bytes_to_intlist, (data, key, iv)), **kwargs))
|
||||||
|
|
||||||
|
|
||||||
def unpad_pkcs7(data):
|
def unpad_pkcs7(data):
|
||||||
return data[:-compat_ord(data[-1])]
|
return data[:-compat_ord(data[-1])]
|
||||||
|
|
||||||
@@ -30,6 +35,33 @@ def unpad_pkcs7(data):
|
|||||||
BLOCK_SIZE_BYTES = 16
|
BLOCK_SIZE_BYTES = 16
|
||||||
|
|
||||||
|
|
||||||
|
def pad_block(block, padding_mode):
|
||||||
|
"""
|
||||||
|
Pad a block with the given padding mode
|
||||||
|
@param {int[]} block block to pad
|
||||||
|
@param padding_mode padding mode
|
||||||
|
"""
|
||||||
|
padding_size = BLOCK_SIZE_BYTES - len(block)
|
||||||
|
|
||||||
|
PADDING_BYTE = {
|
||||||
|
'pkcs7': padding_size,
|
||||||
|
'iso7816': 0x0,
|
||||||
|
'whitespace': 0x20,
|
||||||
|
'zero': 0x0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if padding_size < 0:
|
||||||
|
raise ValueError('Block size exceeded')
|
||||||
|
elif padding_mode not in PADDING_BYTE:
|
||||||
|
raise NotImplementedError(f'Padding mode {padding_mode} is not implemented')
|
||||||
|
|
||||||
|
if padding_mode == 'iso7816' and padding_size:
|
||||||
|
block = block + [0x80] # NB: += mutates list
|
||||||
|
padding_size -= 1
|
||||||
|
|
||||||
|
return block + [PADDING_BYTE[padding_mode]] * padding_size
|
||||||
|
|
||||||
|
|
||||||
def aes_ecb_encrypt(data, key, iv=None):
|
def aes_ecb_encrypt(data, key, iv=None):
|
||||||
"""
|
"""
|
||||||
Encrypt with aes in ECB mode
|
Encrypt with aes in ECB mode
|
||||||
@@ -136,13 +168,14 @@ def aes_cbc_decrypt(data, key, iv):
|
|||||||
return decrypted_data
|
return decrypted_data
|
||||||
|
|
||||||
|
|
||||||
def aes_cbc_encrypt(data, key, iv):
|
def aes_cbc_encrypt(data, key, iv, *, padding_mode='pkcs7'):
|
||||||
"""
|
"""
|
||||||
Encrypt with aes in CBC mode. Using PKCS#7 padding
|
Encrypt with aes in CBC mode
|
||||||
|
|
||||||
@param {int[]} data cleartext
|
@param {int[]} data cleartext
|
||||||
@param {int[]} key 16/24/32-Byte cipher key
|
@param {int[]} key 16/24/32-Byte cipher key
|
||||||
@param {int[]} iv 16-Byte IV
|
@param {int[]} iv 16-Byte IV
|
||||||
|
@param padding_mode Padding mode to use
|
||||||
@returns {int[]} encrypted data
|
@returns {int[]} encrypted data
|
||||||
"""
|
"""
|
||||||
expanded_key = key_expansion(key)
|
expanded_key = key_expansion(key)
|
||||||
@@ -152,8 +185,8 @@ def aes_cbc_encrypt(data, key, iv):
|
|||||||
previous_cipher_block = iv
|
previous_cipher_block = iv
|
||||||
for i in range(block_count):
|
for i in range(block_count):
|
||||||
block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
|
block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
|
||||||
remaining_length = BLOCK_SIZE_BYTES - len(block)
|
block = pad_block(block, padding_mode)
|
||||||
block += [remaining_length] * remaining_length
|
|
||||||
mixed_block = xor(block, previous_cipher_block)
|
mixed_block = xor(block, previous_cipher_block)
|
||||||
|
|
||||||
encrypted_block = aes_encrypt(mixed_block, expanded_key)
|
encrypted_block = aes_encrypt(mixed_block, expanded_key)
|
||||||
@@ -264,7 +297,7 @@ def aes_decrypt_text(data, password, key_size_bytes):
|
|||||||
"""
|
"""
|
||||||
NONCE_LENGTH_BYTES = 8
|
NONCE_LENGTH_BYTES = 8
|
||||||
|
|
||||||
data = bytes_to_intlist(compat_b64decode(data))
|
data = bytes_to_intlist(base64.b64decode(data))
|
||||||
password = bytes_to_intlist(password.encode())
|
password = bytes_to_intlist(password.encode())
|
||||||
|
|
||||||
key = password[:key_size_bytes] + [0] * (key_size_bytes - len(password))
|
key = password[:key_size_bytes] + [0] * (key_size_bytes - len(password))
|
||||||
@@ -501,13 +534,22 @@ def ghash(subkey, data):
|
|||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'aes_ctr_decrypt',
|
|
||||||
'aes_cbc_decrypt',
|
'aes_cbc_decrypt',
|
||||||
'aes_cbc_decrypt_bytes',
|
'aes_cbc_decrypt_bytes',
|
||||||
|
'aes_ctr_decrypt',
|
||||||
'aes_decrypt_text',
|
'aes_decrypt_text',
|
||||||
'aes_encrypt',
|
'aes_decrypt',
|
||||||
|
'aes_ecb_decrypt',
|
||||||
'aes_gcm_decrypt_and_verify',
|
'aes_gcm_decrypt_and_verify',
|
||||||
'aes_gcm_decrypt_and_verify_bytes',
|
'aes_gcm_decrypt_and_verify_bytes',
|
||||||
|
|
||||||
|
'aes_cbc_encrypt',
|
||||||
|
'aes_cbc_encrypt_bytes',
|
||||||
|
'aes_ctr_encrypt',
|
||||||
|
'aes_ecb_encrypt',
|
||||||
|
'aes_encrypt',
|
||||||
|
|
||||||
'key_expansion',
|
'key_expansion',
|
||||||
|
'pad_block',
|
||||||
'unpad_pkcs7',
|
'unpad_pkcs7',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from .compat import compat_getenv
|
|
||||||
from .utils import expand_path, write_json_file
|
from .utils import expand_path, write_json_file
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@ class Cache:
|
|||||||
def _get_root_dir(self):
|
def _get_root_dir(self):
|
||||||
res = self._ydl.params.get('cachedir')
|
res = self._ydl.params.get('cachedir')
|
||||||
if res is None:
|
if res is None:
|
||||||
cache_root = compat_getenv('XDG_CACHE_HOME', '~/.cache')
|
cache_root = os.getenv('XDG_CACHE_HOME', '~/.cache')
|
||||||
res = os.path.join(cache_root, 'yt-dlp')
|
res = os.path.join(cache_root, 'yt-dlp')
|
||||||
return expand_path(res)
|
return expand_path(res)
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,9 @@ from . import re
|
|||||||
from ._deprecated import * # noqa: F401, F403
|
from ._deprecated import * # noqa: F401, F403
|
||||||
from .compat_utils import passthrough_module
|
from .compat_utils import passthrough_module
|
||||||
|
|
||||||
|
|
||||||
# XXX: Implement this the same way as other DeprecationWarnings without circular import
|
# XXX: Implement this the same way as other DeprecationWarnings without circular import
|
||||||
try:
|
passthrough_module(__name__, '._legacy', callback=lambda attr: warnings.warn(
|
||||||
passthrough_module(__name__, '._legacy', callback=lambda attr: warnings.warn(
|
DeprecationWarning(f'{__name__}.{attr} is deprecated'), stacklevel=2))
|
||||||
DeprecationWarning(f'{__name__}.{attr} is deprecated'), stacklevel=2))
|
|
||||||
HAS_LEGACY = True
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
# Keep working even without _legacy module
|
|
||||||
HAS_LEGACY = False
|
|
||||||
del passthrough_module
|
|
||||||
|
|
||||||
|
|
||||||
# HTMLParseError has been deprecated in Python 3.3 and removed in
|
# HTMLParseError has been deprecated in Python 3.3 and removed in
|
||||||
@@ -77,3 +70,9 @@ if compat_os_name in ('nt', 'ce'):
|
|||||||
return userhome + path[i:]
|
return userhome + path[i:]
|
||||||
else:
|
else:
|
||||||
compat_expanduser = os.path.expanduser
|
compat_expanduser = os.path.expanduser
|
||||||
|
|
||||||
|
|
||||||
|
# NB: Add modules that are imported dynamically here so that PyInstaller can find them
|
||||||
|
# See https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/438
|
||||||
|
if False:
|
||||||
|
from . import _legacy # noqa: F401
|
||||||
|
|||||||
@@ -1,52 +1,16 @@
|
|||||||
"""Deprecated - New code should avoid these"""
|
"""Deprecated - New code should avoid these"""
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import getpass
|
import urllib.error
|
||||||
import html
|
import urllib.parse
|
||||||
import html.parser
|
|
||||||
import http
|
compat_str = str
|
||||||
import http.client
|
|
||||||
import http.cookiejar
|
|
||||||
import http.cookies
|
|
||||||
import http.server
|
|
||||||
import itertools
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import struct
|
|
||||||
import tokenize
|
|
||||||
import urllib
|
|
||||||
|
|
||||||
compat_b64decode = base64.b64decode
|
compat_b64decode = base64.b64decode
|
||||||
compat_chr = chr
|
|
||||||
compat_cookiejar = http.cookiejar
|
|
||||||
compat_cookiejar_Cookie = http.cookiejar.Cookie
|
|
||||||
compat_cookies_SimpleCookie = http.cookies.SimpleCookie
|
|
||||||
compat_get_terminal_size = shutil.get_terminal_size
|
|
||||||
compat_getenv = os.getenv
|
|
||||||
compat_getpass = getpass.getpass
|
|
||||||
compat_html_entities = html.entities
|
|
||||||
compat_html_entities_html5 = html.entities.html5
|
|
||||||
compat_HTMLParser = html.parser.HTMLParser
|
|
||||||
compat_http_client = http.client
|
|
||||||
compat_http_server = http.server
|
|
||||||
compat_HTTPError = urllib.error.HTTPError
|
compat_HTTPError = urllib.error.HTTPError
|
||||||
compat_itertools_count = itertools.count
|
compat_urlparse = urllib.parse
|
||||||
compat_parse_qs = urllib.parse.parse_qs
|
compat_parse_qs = urllib.parse.parse_qs
|
||||||
compat_str = str
|
|
||||||
compat_struct_pack = struct.pack
|
|
||||||
compat_struct_unpack = struct.unpack
|
|
||||||
compat_tokenize_tokenize = tokenize.tokenize
|
|
||||||
compat_urllib_error = urllib.error
|
|
||||||
compat_urllib_parse_unquote = urllib.parse.unquote
|
compat_urllib_parse_unquote = urllib.parse.unquote
|
||||||
compat_urllib_parse_unquote_plus = urllib.parse.unquote_plus
|
|
||||||
compat_urllib_parse_urlencode = urllib.parse.urlencode
|
compat_urllib_parse_urlencode = urllib.parse.urlencode
|
||||||
compat_urllib_parse_urlparse = urllib.parse.urlparse
|
compat_urllib_parse_urlparse = urllib.parse.urlparse
|
||||||
compat_urllib_request = urllib.request
|
|
||||||
compat_urlparse = compat_urllib_parse = urllib.parse
|
|
||||||
|
|
||||||
|
|
||||||
def compat_setenv(key, value, env=os.environ):
|
|
||||||
env[key] = value
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [x for x in globals() if x.startswith('compat_')]
|
|
||||||
|
|||||||
@@ -2,18 +2,27 @@
|
|||||||
|
|
||||||
import collections
|
import collections
|
||||||
import ctypes
|
import ctypes
|
||||||
import http
|
import getpass
|
||||||
|
import html.entities
|
||||||
|
import html.parser
|
||||||
import http.client
|
import http.client
|
||||||
import http.cookiejar
|
import http.cookiejar
|
||||||
import http.cookies
|
import http.cookies
|
||||||
import http.server
|
import http.server
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import urllib
|
import tokenize
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
from subprocess import DEVNULL
|
from subprocess import DEVNULL
|
||||||
|
|
||||||
|
from .compat_utils import passthrough_module # isort: split
|
||||||
from .asyncio import run as compat_asyncio_run # noqa: F401
|
from .asyncio import run as compat_asyncio_run # noqa: F401
|
||||||
from .re import Pattern as compat_Pattern # noqa: F401
|
from .re import Pattern as compat_Pattern # noqa: F401
|
||||||
from .re import match as compat_Match # noqa: F401
|
from .re import match as compat_Match # noqa: F401
|
||||||
@@ -21,6 +30,8 @@ from ..dependencies import Cryptodome_AES as compat_pycrypto_AES # noqa: F401
|
|||||||
from ..dependencies import brotli as compat_brotli # noqa: F401
|
from ..dependencies import brotli as compat_brotli # noqa: F401
|
||||||
from ..dependencies import websockets as compat_websockets # noqa: F401
|
from ..dependencies import websockets as compat_websockets # noqa: F401
|
||||||
|
|
||||||
|
passthrough_module(__name__, '...utils', ('WINDOWS_VT_MODE', 'windows_enable_vt_mode'))
|
||||||
|
|
||||||
|
|
||||||
# compat_ctypes_WINFUNCTYPE = ctypes.WINFUNCTYPE
|
# compat_ctypes_WINFUNCTYPE = ctypes.WINFUNCTYPE
|
||||||
# will not work since ctypes.WINFUNCTYPE does not exist in UNIX machines
|
# will not work since ctypes.WINFUNCTYPE does not exist in UNIX machines
|
||||||
@@ -28,14 +39,31 @@ def compat_ctypes_WINFUNCTYPE(*args, **kwargs):
|
|||||||
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def compat_setenv(key, value, env=os.environ):
|
||||||
|
env[key] = value
|
||||||
|
|
||||||
|
|
||||||
compat_basestring = str
|
compat_basestring = str
|
||||||
|
compat_chr = chr
|
||||||
compat_collections_abc = collections.abc
|
compat_collections_abc = collections.abc
|
||||||
|
compat_cookiejar = http.cookiejar
|
||||||
|
compat_cookiejar_Cookie = http.cookiejar.Cookie
|
||||||
compat_cookies = http.cookies
|
compat_cookies = http.cookies
|
||||||
|
compat_cookies_SimpleCookie = http.cookies.SimpleCookie
|
||||||
compat_etree_Element = etree.Element
|
compat_etree_Element = etree.Element
|
||||||
compat_etree_register_namespace = etree.register_namespace
|
compat_etree_register_namespace = etree.register_namespace
|
||||||
compat_filter = filter
|
compat_filter = filter
|
||||||
|
compat_get_terminal_size = shutil.get_terminal_size
|
||||||
|
compat_getenv = os.getenv
|
||||||
|
compat_getpass = getpass.getpass
|
||||||
|
compat_html_entities = html.entities
|
||||||
|
compat_html_entities_html5 = html.entities.html5
|
||||||
|
compat_HTMLParser = html.parser.HTMLParser
|
||||||
|
compat_http_client = http.client
|
||||||
|
compat_http_server = http.server
|
||||||
compat_input = input
|
compat_input = input
|
||||||
compat_integer_types = (int, )
|
compat_integer_types = (int, )
|
||||||
|
compat_itertools_count = itertools.count
|
||||||
compat_kwargs = lambda kwargs: kwargs
|
compat_kwargs = lambda kwargs: kwargs
|
||||||
compat_map = map
|
compat_map = map
|
||||||
compat_numeric_types = (int, float, complex)
|
compat_numeric_types = (int, float, complex)
|
||||||
@@ -43,11 +71,18 @@ compat_print = print
|
|||||||
compat_shlex_split = shlex.split
|
compat_shlex_split = shlex.split
|
||||||
compat_socket_create_connection = socket.create_connection
|
compat_socket_create_connection = socket.create_connection
|
||||||
compat_Struct = struct.Struct
|
compat_Struct = struct.Struct
|
||||||
|
compat_struct_pack = struct.pack
|
||||||
|
compat_struct_unpack = struct.unpack
|
||||||
compat_subprocess_get_DEVNULL = lambda: DEVNULL
|
compat_subprocess_get_DEVNULL = lambda: DEVNULL
|
||||||
|
compat_tokenize_tokenize = tokenize.tokenize
|
||||||
|
compat_urllib_error = urllib.error
|
||||||
|
compat_urllib_parse = urllib.parse
|
||||||
compat_urllib_parse_quote = urllib.parse.quote
|
compat_urllib_parse_quote = urllib.parse.quote
|
||||||
compat_urllib_parse_quote_plus = urllib.parse.quote_plus
|
compat_urllib_parse_quote_plus = urllib.parse.quote_plus
|
||||||
|
compat_urllib_parse_unquote_plus = urllib.parse.unquote_plus
|
||||||
compat_urllib_parse_unquote_to_bytes = urllib.parse.unquote_to_bytes
|
compat_urllib_parse_unquote_to_bytes = urllib.parse.unquote_to_bytes
|
||||||
compat_urllib_parse_urlunparse = urllib.parse.urlunparse
|
compat_urllib_parse_urlunparse = urllib.parse.urlunparse
|
||||||
|
compat_urllib_request = urllib.request
|
||||||
compat_urllib_request_DataHandler = urllib.request.DataHandler
|
compat_urllib_request_DataHandler = urllib.request.DataHandler
|
||||||
compat_urllib_response = urllib.response
|
compat_urllib_response = urllib.response
|
||||||
compat_urlretrieve = urllib.request.urlretrieve
|
compat_urlretrieve = urllib.request.urlretrieve
|
||||||
@@ -55,10 +90,3 @@ compat_xml_parse_error = etree.ParseError
|
|||||||
compat_xpath = lambda xpath: xpath
|
compat_xpath = lambda xpath: xpath
|
||||||
compat_zip = zip
|
compat_zip = zip
|
||||||
workaround_optparse_bug9161 = lambda: None
|
workaround_optparse_bug9161 = lambda: None
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
|
||||||
if name in ('WINDOWS_VT_MODE', 'windows_enable_vt_mode'):
|
|
||||||
from .. import utils
|
|
||||||
return getattr(utils, name)
|
|
||||||
raise AttributeError(name)
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import importlib
|
|||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
|
||||||
_NO_ATTRIBUTE = object()
|
_NO_ATTRIBUTE = object()
|
||||||
|
|
||||||
_Package = collections.namedtuple('Package', ('name', 'version'))
|
_Package = collections.namedtuple('Package', ('name', 'version'))
|
||||||
@@ -31,7 +30,7 @@ def _is_package(module):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def passthrough_module(parent, child, *, callback=lambda _: None):
|
def passthrough_module(parent, child, allowed_attributes=None, *, callback=lambda _: None):
|
||||||
parent_module = importlib.import_module(parent)
|
parent_module = importlib.import_module(parent)
|
||||||
child_module = None # Import child module only as needed
|
child_module = None # Import child module only as needed
|
||||||
|
|
||||||
@@ -41,22 +40,30 @@ def passthrough_module(parent, child, *, callback=lambda _: None):
|
|||||||
with contextlib.suppress(ImportError):
|
with contextlib.suppress(ImportError):
|
||||||
return importlib.import_module(f'.{attr}', parent)
|
return importlib.import_module(f'.{attr}', parent)
|
||||||
|
|
||||||
|
ret = self.__from_child(attr)
|
||||||
|
if ret is _NO_ATTRIBUTE:
|
||||||
|
raise AttributeError(f'module {parent} has no attribute {attr}')
|
||||||
|
callback(attr)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __from_child(self, attr):
|
||||||
|
if allowed_attributes is None:
|
||||||
|
if attr.startswith('__') and attr.endswith('__'):
|
||||||
|
return _NO_ATTRIBUTE
|
||||||
|
elif attr not in allowed_attributes:
|
||||||
|
return _NO_ATTRIBUTE
|
||||||
|
|
||||||
nonlocal child_module
|
nonlocal child_module
|
||||||
child_module = child_module or importlib.import_module(child, parent)
|
child_module = child_module or importlib.import_module(child, parent)
|
||||||
|
|
||||||
ret = _NO_ATTRIBUTE
|
|
||||||
with contextlib.suppress(AttributeError):
|
with contextlib.suppress(AttributeError):
|
||||||
ret = getattr(child_module, attr)
|
return getattr(child_module, attr)
|
||||||
|
|
||||||
if _is_package(child_module):
|
if _is_package(child_module):
|
||||||
with contextlib.suppress(ImportError):
|
with contextlib.suppress(ImportError):
|
||||||
ret = importlib.import_module(f'.{attr}', child)
|
return importlib.import_module(f'.{attr}', child)
|
||||||
|
|
||||||
if ret is _NO_ATTRIBUTE:
|
return _NO_ATTRIBUTE
|
||||||
raise AttributeError(f'module {parent} has no attribute {attr}')
|
|
||||||
|
|
||||||
callback(attr)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# Python 3.6 does not have module level __getattr__
|
# Python 3.6 does not have module level __getattr__
|
||||||
# https://peps.python.org/pep-0562/
|
# https://peps.python.org/pep-0562/
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import base64
|
||||||
import contextlib
|
import contextlib
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import http.cookiejar
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -17,7 +19,6 @@ from .aes import (
|
|||||||
aes_gcm_decrypt_and_verify_bytes,
|
aes_gcm_decrypt_and_verify_bytes,
|
||||||
unpad_pkcs7,
|
unpad_pkcs7,
|
||||||
)
|
)
|
||||||
from .compat import compat_b64decode, compat_cookiejar_Cookie
|
|
||||||
from .dependencies import (
|
from .dependencies import (
|
||||||
_SECRETSTORAGE_UNAVAILABLE_REASON,
|
_SECRETSTORAGE_UNAVAILABLE_REASON,
|
||||||
secretstorage,
|
secretstorage,
|
||||||
@@ -142,7 +143,7 @@ def _extract_firefox_cookies(profile, logger):
|
|||||||
total_cookie_count = len(table)
|
total_cookie_count = len(table)
|
||||||
for i, (host, name, value, path, expiry, is_secure) in enumerate(table):
|
for i, (host, name, value, path, expiry, is_secure) in enumerate(table):
|
||||||
progress_bar.print(f'Loading cookie {i: 6d}/{total_cookie_count: 6d}')
|
progress_bar.print(f'Loading cookie {i: 6d}/{total_cookie_count: 6d}')
|
||||||
cookie = compat_cookiejar_Cookie(
|
cookie = http.cookiejar.Cookie(
|
||||||
version=0, name=name, value=value, port=None, port_specified=False,
|
version=0, name=name, value=value, port=None, port_specified=False,
|
||||||
domain=host, domain_specified=bool(host), domain_initial_dot=host.startswith('.'),
|
domain=host, domain_specified=bool(host), domain_initial_dot=host.startswith('.'),
|
||||||
path=path, path_specified=bool(path), secure=is_secure, expires=expiry, discard=False,
|
path=path, path_specified=bool(path), secure=is_secure, expires=expiry, discard=False,
|
||||||
@@ -297,7 +298,7 @@ def _process_chrome_cookie(decryptor, host_key, name, value, encrypted_value, pa
|
|||||||
if value is None:
|
if value is None:
|
||||||
return is_encrypted, None
|
return is_encrypted, None
|
||||||
|
|
||||||
return is_encrypted, compat_cookiejar_Cookie(
|
return is_encrypted, http.cookiejar.Cookie(
|
||||||
version=0, name=name, value=value, port=None, port_specified=False,
|
version=0, name=name, value=value, port=None, port_specified=False,
|
||||||
domain=host_key, domain_specified=bool(host_key), domain_initial_dot=host_key.startswith('.'),
|
domain=host_key, domain_specified=bool(host_key), domain_initial_dot=host_key.startswith('.'),
|
||||||
path=path, path_specified=bool(path), secure=is_secure, expires=expires_utc, discard=False,
|
path=path, path_specified=bool(path), secure=is_secure, expires=expires_utc, discard=False,
|
||||||
@@ -589,7 +590,7 @@ def _parse_safari_cookies_record(data, jar, logger):
|
|||||||
|
|
||||||
p.skip_to(record_size, 'space at the end of the record')
|
p.skip_to(record_size, 'space at the end of the record')
|
||||||
|
|
||||||
cookie = compat_cookiejar_Cookie(
|
cookie = http.cookiejar.Cookie(
|
||||||
version=0, name=name, value=value, port=None, port_specified=False,
|
version=0, name=name, value=value, port=None, port_specified=False,
|
||||||
domain=domain, domain_specified=bool(domain), domain_initial_dot=domain.startswith('.'),
|
domain=domain, domain_specified=bool(domain), domain_initial_dot=domain.startswith('.'),
|
||||||
path=path, path_specified=bool(path), secure=is_secure, expires=expiration_date, discard=False,
|
path=path, path_specified=bool(path), secure=is_secure, expires=expiration_date, discard=False,
|
||||||
@@ -835,7 +836,7 @@ def _get_windows_v10_key(browser_root, logger):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
logger.error('no encrypted key in Local State')
|
logger.error('no encrypted key in Local State')
|
||||||
return None
|
return None
|
||||||
encrypted_key = compat_b64decode(base64_key)
|
encrypted_key = base64.b64decode(base64_key)
|
||||||
prefix = b'DPAPI'
|
prefix = b'DPAPI'
|
||||||
if not encrypted_key.startswith(prefix):
|
if not encrypted_key.startswith(prefix):
|
||||||
logger.error('invalid key')
|
logger.error('invalid key')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
"""Imports all optional dependencies for the project.
|
"""Imports all optional dependencies for the project.
|
||||||
An attribute "_yt_dlp__identifier" may be inserted into the module if it uses an ambigious namespace"""
|
An attribute "_yt_dlp__identifier" may be inserted into the module if it uses an ambiguous namespace"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import brotlicffi as brotli
|
import brotlicffi as brotli
|
||||||
|
|||||||
@@ -59,10 +59,11 @@ PROTOCOL_MAP = {
|
|||||||
|
|
||||||
def shorten_protocol_name(proto, simplify=False):
|
def shorten_protocol_name(proto, simplify=False):
|
||||||
short_protocol_names = {
|
short_protocol_names = {
|
||||||
'm3u8_native': 'm3u8_n',
|
'm3u8_native': 'm3u8',
|
||||||
'rtmp_ffmpeg': 'rtmp_f',
|
'm3u8': 'm3u8F',
|
||||||
|
'rtmp_ffmpeg': 'rtmpF',
|
||||||
'http_dash_segments': 'dash',
|
'http_dash_segments': 'dash',
|
||||||
'http_dash_segments_generator': 'dash_g',
|
'http_dash_segments_generator': 'dashG',
|
||||||
'niconico_dmc': 'dmc',
|
'niconico_dmc': 'dmc',
|
||||||
'websocket_frag': 'WSfrag',
|
'websocket_frag': 'WSfrag',
|
||||||
}
|
}
|
||||||
@@ -70,6 +71,7 @@ def shorten_protocol_name(proto, simplify=False):
|
|||||||
short_protocol_names.update({
|
short_protocol_names.update({
|
||||||
'https': 'http',
|
'https': 'http',
|
||||||
'ftps': 'ftp',
|
'ftps': 'ftp',
|
||||||
|
'm3u8': 'm3u8', # Reverse above m3u8 mapping
|
||||||
'm3u8_native': 'm3u8',
|
'm3u8_native': 'm3u8',
|
||||||
'http_dash_segments_generator': 'dash',
|
'http_dash_segments_generator': 'dash',
|
||||||
'rtmp_ffmpeg': 'rtmp',
|
'rtmp_ffmpeg': 'rtmp',
|
||||||
|
|||||||
@@ -450,8 +450,7 @@ class FileDownloader:
|
|||||||
raise NotImplementedError('This method must be implemented by subclasses')
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
def _hook_progress(self, status, info_dict):
|
def _hook_progress(self, status, info_dict):
|
||||||
if not self._progress_hooks:
|
# Ideally we want to make a copy of the dict, but that is too slow
|
||||||
return
|
|
||||||
status['info_dict'] = info_dict
|
status['info_dict'] = info_dict
|
||||||
# youtube-dl passes the same status object to all the hooks.
|
# youtube-dl passes the same status object to all the hooks.
|
||||||
# Some third party scripts seems to be relying on this.
|
# Some third party scripts seems to be relying on this.
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import functools # isort: split
|
from ..compat import functools
|
||||||
from ..compat import compat_setenv
|
|
||||||
from ..postprocessor.ffmpeg import EXT_TO_OUT_FORMATS, FFmpegPostProcessor
|
from ..postprocessor.ffmpeg import EXT_TO_OUT_FORMATS, FFmpegPostProcessor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
Popen,
|
Popen,
|
||||||
@@ -403,8 +402,8 @@ class FFmpegFD(ExternalFD):
|
|||||||
# We could switch to the following code if we are able to detect version properly
|
# We could switch to the following code if we are able to detect version properly
|
||||||
# args += ['-http_proxy', proxy]
|
# args += ['-http_proxy', proxy]
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
compat_setenv('HTTP_PROXY', proxy, env=env)
|
env['HTTP_PROXY'] = proxy
|
||||||
compat_setenv('http_proxy', proxy, env=env)
|
env['http_proxy'] = proxy
|
||||||
|
|
||||||
protocol = info_dict.get('protocol')
|
protocol = info_dict.get('protocol')
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
|
import base64
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import (
|
from ..compat import compat_etree_fromstring
|
||||||
compat_b64decode,
|
|
||||||
compat_etree_fromstring,
|
|
||||||
compat_struct_pack,
|
|
||||||
compat_struct_unpack,
|
|
||||||
compat_urllib_error,
|
|
||||||
compat_urllib_parse_urlparse,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import fix_xml_ampersands, xpath_text
|
from ..utils import fix_xml_ampersands, xpath_text
|
||||||
|
|
||||||
|
|
||||||
@@ -35,13 +31,13 @@ class FlvReader(io.BytesIO):
|
|||||||
|
|
||||||
# Utility functions for reading numbers and strings
|
# Utility functions for reading numbers and strings
|
||||||
def read_unsigned_long_long(self):
|
def read_unsigned_long_long(self):
|
||||||
return compat_struct_unpack('!Q', self.read_bytes(8))[0]
|
return struct.unpack('!Q', self.read_bytes(8))[0]
|
||||||
|
|
||||||
def read_unsigned_int(self):
|
def read_unsigned_int(self):
|
||||||
return compat_struct_unpack('!I', self.read_bytes(4))[0]
|
return struct.unpack('!I', self.read_bytes(4))[0]
|
||||||
|
|
||||||
def read_unsigned_char(self):
|
def read_unsigned_char(self):
|
||||||
return compat_struct_unpack('!B', self.read_bytes(1))[0]
|
return struct.unpack('!B', self.read_bytes(1))[0]
|
||||||
|
|
||||||
def read_string(self):
|
def read_string(self):
|
||||||
res = b''
|
res = b''
|
||||||
@@ -203,11 +199,11 @@ def build_fragments_list(boot_info):
|
|||||||
|
|
||||||
|
|
||||||
def write_unsigned_int(stream, val):
|
def write_unsigned_int(stream, val):
|
||||||
stream.write(compat_struct_pack('!I', val))
|
stream.write(struct.pack('!I', val))
|
||||||
|
|
||||||
|
|
||||||
def write_unsigned_int_24(stream, val):
|
def write_unsigned_int_24(stream, val):
|
||||||
stream.write(compat_struct_pack('!I', val)[1:])
|
stream.write(struct.pack('!I', val)[1:])
|
||||||
|
|
||||||
|
|
||||||
def write_flv_header(stream):
|
def write_flv_header(stream):
|
||||||
@@ -301,12 +297,12 @@ class F4mFD(FragmentFD):
|
|||||||
# 1. http://live-1-1.rutube.ru/stream/1024/HDS/SD/C2NKsS85HQNckgn5HdEmOQ/1454167650/S-s604419906/move/four/dirs/upper/1024-576p.f4m
|
# 1. http://live-1-1.rutube.ru/stream/1024/HDS/SD/C2NKsS85HQNckgn5HdEmOQ/1454167650/S-s604419906/move/four/dirs/upper/1024-576p.f4m
|
||||||
bootstrap_url = node.get('url')
|
bootstrap_url = node.get('url')
|
||||||
if bootstrap_url:
|
if bootstrap_url:
|
||||||
bootstrap_url = compat_urlparse.urljoin(
|
bootstrap_url = urllib.parse.urljoin(
|
||||||
base_url, bootstrap_url)
|
base_url, bootstrap_url)
|
||||||
boot_info = self._get_bootstrap_from_url(bootstrap_url)
|
boot_info = self._get_bootstrap_from_url(bootstrap_url)
|
||||||
else:
|
else:
|
||||||
bootstrap_url = None
|
bootstrap_url = None
|
||||||
bootstrap = compat_b64decode(node.text)
|
bootstrap = base64.b64decode(node.text)
|
||||||
boot_info = read_bootstrap_info(bootstrap)
|
boot_info = read_bootstrap_info(bootstrap)
|
||||||
return boot_info, bootstrap_url
|
return boot_info, bootstrap_url
|
||||||
|
|
||||||
@@ -336,14 +332,14 @@ class F4mFD(FragmentFD):
|
|||||||
# Prefer baseURL for relative URLs as per 11.2 of F4M 3.0 spec.
|
# Prefer baseURL for relative URLs as per 11.2 of F4M 3.0 spec.
|
||||||
man_base_url = get_base_url(doc) or man_url
|
man_base_url = get_base_url(doc) or man_url
|
||||||
|
|
||||||
base_url = compat_urlparse.urljoin(man_base_url, media.attrib['url'])
|
base_url = urllib.parse.urljoin(man_base_url, media.attrib['url'])
|
||||||
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
||||||
boot_info, bootstrap_url = self._parse_bootstrap_node(
|
boot_info, bootstrap_url = self._parse_bootstrap_node(
|
||||||
bootstrap_node, man_base_url)
|
bootstrap_node, man_base_url)
|
||||||
live = boot_info['live']
|
live = boot_info['live']
|
||||||
metadata_node = media.find(_add_ns('metadata'))
|
metadata_node = media.find(_add_ns('metadata'))
|
||||||
if metadata_node is not None:
|
if metadata_node is not None:
|
||||||
metadata = compat_b64decode(metadata_node.text)
|
metadata = base64.b64decode(metadata_node.text)
|
||||||
else:
|
else:
|
||||||
metadata = None
|
metadata = None
|
||||||
|
|
||||||
@@ -371,7 +367,7 @@ class F4mFD(FragmentFD):
|
|||||||
if not live:
|
if not live:
|
||||||
write_metadata_tag(dest_stream, metadata)
|
write_metadata_tag(dest_stream, metadata)
|
||||||
|
|
||||||
base_url_parsed = compat_urllib_parse_urlparse(base_url)
|
base_url_parsed = urllib.parse.urlparse(base_url)
|
||||||
|
|
||||||
self._start_frag_download(ctx, info_dict)
|
self._start_frag_download(ctx, info_dict)
|
||||||
|
|
||||||
@@ -411,7 +407,7 @@ class F4mFD(FragmentFD):
|
|||||||
if box_type == b'mdat':
|
if box_type == b'mdat':
|
||||||
self._append_fragment(ctx, box_data)
|
self._append_fragment(ctx, box_data)
|
||||||
break
|
break
|
||||||
except compat_urllib_error.HTTPError as err:
|
except urllib.error.HTTPError as err:
|
||||||
if live and (err.code == 404 or err.code == 410):
|
if live and (err.code == 404 or err.code == 410):
|
||||||
# We didn't keep up with the live window. Continue
|
# We didn't keep up with the live window. Continue
|
||||||
# with the next available fragment.
|
# with the next available fragment.
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import http.client
|
|||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from .http import HttpFD
|
from .http import HttpFD
|
||||||
from ..aes import aes_cbc_decrypt_bytes, unpad_pkcs7
|
from ..aes import aes_cbc_decrypt_bytes, unpad_pkcs7
|
||||||
from ..compat import compat_os_name, compat_struct_pack, compat_urllib_error
|
from ..compat import compat_os_name
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
DownloadError,
|
DownloadError,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
@@ -348,7 +350,7 @@ class FragmentFD(FileDownloader):
|
|||||||
decrypt_info = fragment.get('decrypt_info')
|
decrypt_info = fragment.get('decrypt_info')
|
||||||
if not decrypt_info or decrypt_info['METHOD'] != 'AES-128':
|
if not decrypt_info or decrypt_info['METHOD'] != 'AES-128':
|
||||||
return frag_content
|
return frag_content
|
||||||
iv = decrypt_info.get('IV') or compat_struct_pack('>8xq', fragment['media_sequence'])
|
iv = decrypt_info.get('IV') or struct.pack('>8xq', fragment['media_sequence'])
|
||||||
decrypt_info['KEY'] = decrypt_info.get('KEY') or _get_key(info_dict.get('_decryption_key_url') or decrypt_info['URI'])
|
decrypt_info['KEY'] = decrypt_info.get('KEY') or _get_key(info_dict.get('_decryption_key_url') or decrypt_info['URI'])
|
||||||
# Don't decrypt the content in tests since the data is explicitly truncated and it's not to a valid block
|
# Don't decrypt the content in tests since the data is explicitly truncated and it's not to a valid block
|
||||||
# size (see https://github.com/ytdl-org/youtube-dl/pull/27660). Tests only care that the correct data downloaded,
|
# size (see https://github.com/ytdl-org/youtube-dl/pull/27660). Tests only care that the correct data downloaded,
|
||||||
@@ -457,7 +459,7 @@ class FragmentFD(FileDownloader):
|
|||||||
if self._download_fragment(ctx, fragment['url'], info_dict, headers):
|
if self._download_fragment(ctx, fragment['url'], info_dict, headers):
|
||||||
break
|
break
|
||||||
return
|
return
|
||||||
except (compat_urllib_error.HTTPError, http.client.IncompleteRead) as err:
|
except (urllib.error.HTTPError, http.client.IncompleteRead) as err:
|
||||||
# Unavailable (possibly temporary) fragments may be served.
|
# Unavailable (possibly temporary) fragments may be served.
|
||||||
# First we try to retry then either skip or abort.
|
# First we try to retry then either skip or abort.
|
||||||
# See https://github.com/ytdl-org/youtube-dl/issues/10165,
|
# See https://github.com/ytdl-org/youtube-dl/issues/10165,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from . import get_suitable_downloader
|
from . import get_suitable_downloader
|
||||||
from .external import FFmpegFD
|
from .external import FFmpegFD
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from .. import webvtt
|
from .. import webvtt
|
||||||
from ..compat import compat_urlparse
|
|
||||||
from ..dependencies import Cryptodome_AES
|
from ..dependencies import Cryptodome_AES
|
||||||
from ..utils import bug_reports_message, parse_m3u8_attributes, update_url_query
|
from ..utils import bug_reports_message, parse_m3u8_attributes, update_url_query
|
||||||
|
|
||||||
@@ -61,12 +61,18 @@ class HlsFD(FragmentFD):
|
|||||||
s = urlh.read().decode('utf-8', 'ignore')
|
s = urlh.read().decode('utf-8', 'ignore')
|
||||||
|
|
||||||
can_download, message = self.can_download(s, info_dict, self.params.get('allow_unplayable_formats')), None
|
can_download, message = self.can_download(s, info_dict, self.params.get('allow_unplayable_formats')), None
|
||||||
if can_download and not Cryptodome_AES and '#EXT-X-KEY:METHOD=AES-128' in s:
|
if can_download:
|
||||||
if FFmpegFD.available():
|
has_ffmpeg = FFmpegFD.available()
|
||||||
|
no_crypto = not Cryptodome_AES and '#EXT-X-KEY:METHOD=AES-128' in s
|
||||||
|
if no_crypto and has_ffmpeg:
|
||||||
can_download, message = False, 'The stream has AES-128 encryption and pycryptodomex is not available'
|
can_download, message = False, 'The stream has AES-128 encryption and pycryptodomex is not available'
|
||||||
else:
|
elif no_crypto:
|
||||||
message = ('The stream has AES-128 encryption and neither ffmpeg nor pycryptodomex are available; '
|
message = ('The stream has AES-128 encryption and neither ffmpeg nor pycryptodomex are available; '
|
||||||
'Decryption will be performed natively, but will be extremely slow')
|
'Decryption will be performed natively, but will be extremely slow')
|
||||||
|
elif info_dict.get('extractor_key') == 'Generic' and re.search(r'(?m)#EXT-X-MEDIA-SEQUENCE:(?!0$)', s):
|
||||||
|
install_ffmpeg = '' if has_ffmpeg else 'install ffmpeg and '
|
||||||
|
message = ('Live HLS streams are not supported by the native downloader. If this is a livestream, '
|
||||||
|
f'please {install_ffmpeg}add "--downloader ffmpeg --hls-use-mpegts" to your command')
|
||||||
if not can_download:
|
if not can_download:
|
||||||
has_drm = re.search('|'.join([
|
has_drm = re.search('|'.join([
|
||||||
r'#EXT-X-FAXS-CM:', # Adobe Flash Access
|
r'#EXT-X-FAXS-CM:', # Adobe Flash Access
|
||||||
@@ -140,7 +146,7 @@ class HlsFD(FragmentFD):
|
|||||||
extra_query = None
|
extra_query = None
|
||||||
extra_param_to_segment_url = info_dict.get('extra_param_to_segment_url')
|
extra_param_to_segment_url = info_dict.get('extra_param_to_segment_url')
|
||||||
if extra_param_to_segment_url:
|
if extra_param_to_segment_url:
|
||||||
extra_query = compat_urlparse.parse_qs(extra_param_to_segment_url)
|
extra_query = urllib.parse.parse_qs(extra_param_to_segment_url)
|
||||||
i = 0
|
i = 0
|
||||||
media_sequence = 0
|
media_sequence = 0
|
||||||
decrypt_info = {'METHOD': 'NONE'}
|
decrypt_info = {'METHOD': 'NONE'}
|
||||||
@@ -162,7 +168,7 @@ class HlsFD(FragmentFD):
|
|||||||
frag_url = (
|
frag_url = (
|
||||||
line
|
line
|
||||||
if re.match(r'^https?://', line)
|
if re.match(r'^https?://', line)
|
||||||
else compat_urlparse.urljoin(man_url, line))
|
else urllib.parse.urljoin(man_url, line))
|
||||||
if extra_query:
|
if extra_query:
|
||||||
frag_url = update_url_query(frag_url, extra_query)
|
frag_url = update_url_query(frag_url, extra_query)
|
||||||
|
|
||||||
@@ -187,7 +193,7 @@ class HlsFD(FragmentFD):
|
|||||||
frag_url = (
|
frag_url = (
|
||||||
map_info.get('URI')
|
map_info.get('URI')
|
||||||
if re.match(r'^https?://', map_info.get('URI'))
|
if re.match(r'^https?://', map_info.get('URI'))
|
||||||
else compat_urlparse.urljoin(man_url, map_info.get('URI')))
|
else urllib.parse.urljoin(man_url, map_info.get('URI')))
|
||||||
if extra_query:
|
if extra_query:
|
||||||
frag_url = update_url_query(frag_url, extra_query)
|
frag_url = update_url_query(frag_url, extra_query)
|
||||||
|
|
||||||
@@ -215,7 +221,7 @@ class HlsFD(FragmentFD):
|
|||||||
if 'IV' in decrypt_info:
|
if 'IV' in decrypt_info:
|
||||||
decrypt_info['IV'] = binascii.unhexlify(decrypt_info['IV'][2:].zfill(32))
|
decrypt_info['IV'] = binascii.unhexlify(decrypt_info['IV'][2:].zfill(32))
|
||||||
if not re.match(r'^https?://', decrypt_info['URI']):
|
if not re.match(r'^https?://', decrypt_info['URI']):
|
||||||
decrypt_info['URI'] = compat_urlparse.urljoin(
|
decrypt_info['URI'] = urllib.parse.urljoin(
|
||||||
man_url, decrypt_info['URI'])
|
man_url, decrypt_info['URI'])
|
||||||
if extra_query:
|
if extra_query:
|
||||||
decrypt_info['URI'] = update_url_query(decrypt_info['URI'], extra_query)
|
decrypt_info['URI'] = update_url_query(decrypt_info['URI'], extra_query)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import http.client
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
import time
|
import time
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..compat import compat_http_client, compat_urllib_error
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ContentTooShortError,
|
ContentTooShortError,
|
||||||
ThrottledDownload,
|
ThrottledDownload,
|
||||||
@@ -24,7 +25,7 @@ RESPONSE_READ_EXCEPTIONS = (
|
|||||||
socket.timeout, # compat: py < 3.10
|
socket.timeout, # compat: py < 3.10
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
compat_http_client.HTTPException
|
http.client.HTTPException
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ class HttpFD(FileDownloader):
|
|||||||
ctx.resume_len = 0
|
ctx.resume_len = 0
|
||||||
ctx.open_mode = 'wb'
|
ctx.open_mode = 'wb'
|
||||||
ctx.data_len = ctx.content_len = int_or_none(ctx.data.info().get('Content-length', None))
|
ctx.data_len = ctx.content_len = int_or_none(ctx.data.info().get('Content-length', None))
|
||||||
except compat_urllib_error.HTTPError as err:
|
except urllib.error.HTTPError as err:
|
||||||
if err.code == 416:
|
if err.code == 416:
|
||||||
# Unable to resume (requested range not satisfiable)
|
# Unable to resume (requested range not satisfiable)
|
||||||
try:
|
try:
|
||||||
@@ -163,7 +164,7 @@ class HttpFD(FileDownloader):
|
|||||||
ctx.data = self.ydl.urlopen(
|
ctx.data = self.ydl.urlopen(
|
||||||
sanitized_Request(url, request_data, headers))
|
sanitized_Request(url, request_data, headers))
|
||||||
content_length = ctx.data.info()['Content-Length']
|
content_length = ctx.data.info()['Content-Length']
|
||||||
except compat_urllib_error.HTTPError as err:
|
except urllib.error.HTTPError as err:
|
||||||
if err.code < 500 or err.code >= 600:
|
if err.code < 500 or err.code >= 600:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
@@ -196,7 +197,7 @@ class HttpFD(FileDownloader):
|
|||||||
# Unexpected HTTP error
|
# Unexpected HTTP error
|
||||||
raise
|
raise
|
||||||
raise RetryDownload(err)
|
raise RetryDownload(err)
|
||||||
except compat_urllib_error.URLError as err:
|
except urllib.error.URLError as err:
|
||||||
if isinstance(err.reason, ssl.CertificateError):
|
if isinstance(err.reason, ssl.CertificateError):
|
||||||
raise
|
raise
|
||||||
raise RetryDownload(err)
|
raise RetryDownload(err)
|
||||||
@@ -205,6 +206,12 @@ class HttpFD(FileDownloader):
|
|||||||
except RESPONSE_READ_EXCEPTIONS as err:
|
except RESPONSE_READ_EXCEPTIONS as err:
|
||||||
raise RetryDownload(err)
|
raise RetryDownload(err)
|
||||||
|
|
||||||
|
def close_stream():
|
||||||
|
if ctx.stream is not None:
|
||||||
|
if not ctx.tmpfilename == '-':
|
||||||
|
ctx.stream.close()
|
||||||
|
ctx.stream = None
|
||||||
|
|
||||||
def download():
|
def download():
|
||||||
data_len = ctx.data.info().get('Content-length', None)
|
data_len = ctx.data.info().get('Content-length', None)
|
||||||
|
|
||||||
@@ -238,12 +245,9 @@ class HttpFD(FileDownloader):
|
|||||||
before = start # start measuring
|
before = start # start measuring
|
||||||
|
|
||||||
def retry(e):
|
def retry(e):
|
||||||
to_stdout = ctx.tmpfilename == '-'
|
close_stream()
|
||||||
if ctx.stream is not None:
|
ctx.resume_len = (byte_counter if ctx.tmpfilename == '-'
|
||||||
if not to_stdout:
|
else os.path.getsize(encodeFilename(ctx.tmpfilename)))
|
||||||
ctx.stream.close()
|
|
||||||
ctx.stream = None
|
|
||||||
ctx.resume_len = byte_counter if to_stdout else os.path.getsize(encodeFilename(ctx.tmpfilename))
|
|
||||||
raise RetryDownload(e)
|
raise RetryDownload(e)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -381,6 +385,9 @@ class HttpFD(FileDownloader):
|
|||||||
continue
|
continue
|
||||||
except SucceedDownload:
|
except SucceedDownload:
|
||||||
return True
|
return True
|
||||||
|
except: # noqa: E722
|
||||||
|
close_stream()
|
||||||
|
raise
|
||||||
|
|
||||||
self.report_error('giving up after %s retries' % retries)
|
self.report_error('giving up after %s retries' % retries)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import binascii
|
|||||||
import io
|
import io
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import compat_urllib_error
|
|
||||||
|
|
||||||
u8 = struct.Struct('>B')
|
u8 = struct.Struct('>B')
|
||||||
u88 = struct.Struct('>Bx')
|
u88 = struct.Struct('>Bx')
|
||||||
@@ -268,7 +268,7 @@ class IsmFD(FragmentFD):
|
|||||||
extra_state['ism_track_written'] = True
|
extra_state['ism_track_written'] = True
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
break
|
break
|
||||||
except compat_urllib_error.HTTPError as err:
|
except urllib.error.HTTPError as err:
|
||||||
count += 1
|
count += 1
|
||||||
if count <= fragment_retries:
|
if count <= fragment_retries:
|
||||||
self.report_retry_fragment(err, frag_index, count, fragment_retries)
|
self.report_retry_fragment(err, frag_index, count, fragment_retries)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..compat import compat_str
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
Popen,
|
Popen,
|
||||||
check_executable,
|
check_executable,
|
||||||
@@ -143,7 +142,7 @@ class RtmpFD(FileDownloader):
|
|||||||
if isinstance(conn, list):
|
if isinstance(conn, list):
|
||||||
for entry in conn:
|
for entry in conn:
|
||||||
basic_args += ['--conn', entry]
|
basic_args += ['--conn', entry]
|
||||||
elif isinstance(conn, compat_str):
|
elif isinstance(conn, str):
|
||||||
basic_args += ['--conn', conn]
|
basic_args += ['--conn', conn]
|
||||||
if protocol is not None:
|
if protocol is not None:
|
||||||
basic_args += ['--protocol', protocol]
|
basic_args += ['--protocol', protocol]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import urllib.error
|
||||||
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
from ..compat import compat_urllib_error
|
|
||||||
from ..utils import RegexNotFoundError, dict_get, int_or_none, try_get
|
from ..utils import RegexNotFoundError, dict_get, int_or_none, try_get
|
||||||
|
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ class YoutubeLiveChatFD(FragmentFD):
|
|||||||
elif info_dict['protocol'] == 'youtube_live_chat':
|
elif info_dict['protocol'] == 'youtube_live_chat':
|
||||||
continuation_id, offset, click_tracking_params = parse_actions_live(live_chat_continuation)
|
continuation_id, offset, click_tracking_params = parse_actions_live(live_chat_continuation)
|
||||||
return True, continuation_id, offset, click_tracking_params
|
return True, continuation_id, offset, click_tracking_params
|
||||||
except compat_urllib_error.HTTPError as err:
|
except urllib.error.HTTPError as err:
|
||||||
count += 1
|
count += 1
|
||||||
if count <= fragment_retries:
|
if count <= fragment_retries:
|
||||||
self.report_retry_fragment(err, frag_index, count, fragment_retries)
|
self.report_retry_fragment(err, frag_index, count, fragment_retries)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from .acast import (
|
|||||||
ACastIE,
|
ACastIE,
|
||||||
ACastChannelIE,
|
ACastChannelIE,
|
||||||
)
|
)
|
||||||
|
from .acfun import AcFunVideoIE, AcFunBangumiIE
|
||||||
from .adn import ADNIE
|
from .adn import ADNIE
|
||||||
from .adobeconnect import AdobeConnectIE
|
from .adobeconnect import AdobeConnectIE
|
||||||
from .adobetv import (
|
from .adobetv import (
|
||||||
@@ -104,6 +105,10 @@ from .atttechchannel import ATTTechChannelIE
|
|||||||
from .atvat import ATVAtIE
|
from .atvat import ATVAtIE
|
||||||
from .audimedia import AudiMediaIE
|
from .audimedia import AudiMediaIE
|
||||||
from .audioboom import AudioBoomIE
|
from .audioboom import AudioBoomIE
|
||||||
|
from .audiodraft import (
|
||||||
|
AudiodraftCustomIE,
|
||||||
|
AudiodraftGenericIE,
|
||||||
|
)
|
||||||
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
||||||
from .audius import (
|
from .audius import (
|
||||||
AudiusIE,
|
AudiusIE,
|
||||||
@@ -258,6 +263,7 @@ from .ccc import (
|
|||||||
from .ccma import CCMAIE
|
from .ccma import CCMAIE
|
||||||
from .cctv import CCTVIE
|
from .cctv import CCTVIE
|
||||||
from .cda import CDAIE
|
from .cda import CDAIE
|
||||||
|
from .cellebrite import CellebriteIE
|
||||||
from .ceskatelevize import CeskaTelevizeIE
|
from .ceskatelevize import CeskaTelevizeIE
|
||||||
from .cgtn import CGTNIE
|
from .cgtn import CGTNIE
|
||||||
from .channel9 import Channel9IE
|
from .channel9 import Channel9IE
|
||||||
@@ -376,6 +382,7 @@ from .deezer import (
|
|||||||
DeezerAlbumIE,
|
DeezerAlbumIE,
|
||||||
)
|
)
|
||||||
from .democracynow import DemocracynowIE
|
from .democracynow import DemocracynowIE
|
||||||
|
from .detik import Detik20IE
|
||||||
from .dfb import DFBIE
|
from .dfb import DFBIE
|
||||||
from .dhm import DHMIE
|
from .dhm import DHMIE
|
||||||
from .digg import DiggIE
|
from .digg import DiggIE
|
||||||
@@ -563,6 +570,7 @@ from .funimation import (
|
|||||||
)
|
)
|
||||||
from .funk import FunkIE
|
from .funk import FunkIE
|
||||||
from .fusion import FusionIE
|
from .fusion import FusionIE
|
||||||
|
from .fuyintv import FuyinTVIE
|
||||||
from .gab import (
|
from .gab import (
|
||||||
GabTVIE,
|
GabTVIE,
|
||||||
GabIE,
|
GabIE,
|
||||||
@@ -659,6 +667,7 @@ from .hungama import (
|
|||||||
HungamaAlbumPlaylistIE,
|
HungamaAlbumPlaylistIE,
|
||||||
)
|
)
|
||||||
from .hypem import HypemIE
|
from .hypem import HypemIE
|
||||||
|
from .hytale import HytaleIE
|
||||||
from .icareus import IcareusIE
|
from .icareus import IcareusIE
|
||||||
from .ichinanalive import (
|
from .ichinanalive import (
|
||||||
IchinanaLiveIE,
|
IchinanaLiveIE,
|
||||||
@@ -829,6 +838,7 @@ from .linkedin import (
|
|||||||
LinkedInLearningCourseIE,
|
LinkedInLearningCourseIE,
|
||||||
)
|
)
|
||||||
from .linuxacademy import LinuxAcademyIE
|
from .linuxacademy import LinuxAcademyIE
|
||||||
|
from .liputan6 import Liputan6IE
|
||||||
from .litv import LiTVIE
|
from .litv import LiTVIE
|
||||||
from .livejournal import LiveJournalIE
|
from .livejournal import LiveJournalIE
|
||||||
from .livestream import (
|
from .livestream import (
|
||||||
@@ -836,6 +846,7 @@ from .livestream import (
|
|||||||
LivestreamOriginalIE,
|
LivestreamOriginalIE,
|
||||||
LivestreamShortenerIE,
|
LivestreamShortenerIE,
|
||||||
)
|
)
|
||||||
|
from .livestreamfails import LivestreamfailsIE
|
||||||
from .lnkgo import (
|
from .lnkgo import (
|
||||||
LnkGoIE,
|
LnkGoIE,
|
||||||
LnkIE,
|
LnkIE,
|
||||||
@@ -941,6 +952,7 @@ from .mlb import (
|
|||||||
)
|
)
|
||||||
from .mlssoccer import MLSSoccerIE
|
from .mlssoccer import MLSSoccerIE
|
||||||
from .mnet import MnetIE
|
from .mnet import MnetIE
|
||||||
|
from .mocha import MochaVideoIE
|
||||||
from .moevideo import MoeVideoIE
|
from .moevideo import MoeVideoIE
|
||||||
from .mofosex import (
|
from .mofosex import (
|
||||||
MofosexIE,
|
MofosexIE,
|
||||||
@@ -1329,6 +1341,7 @@ from .puhutv import (
|
|||||||
PuhuTVIE,
|
PuhuTVIE,
|
||||||
PuhuTVSerieIE,
|
PuhuTVSerieIE,
|
||||||
)
|
)
|
||||||
|
from .premiershiprugby import PremiershipRugbyIE
|
||||||
from .presstv import PressTVIE
|
from .presstv import PressTVIE
|
||||||
from .projectveritas import ProjectVeritasIE
|
from .projectveritas import ProjectVeritasIE
|
||||||
from .prosiebensat1 import ProSiebenSat1IE
|
from .prosiebensat1 import ProSiebenSat1IE
|
||||||
@@ -1431,7 +1444,13 @@ from .rottentomatoes import RottenTomatoesIE
|
|||||||
from .rozhlas import RozhlasIE
|
from .rozhlas import RozhlasIE
|
||||||
from .rtbf import RTBFIE
|
from .rtbf import RTBFIE
|
||||||
from .rte import RteIE, RteRadioIE
|
from .rte import RteIE, RteRadioIE
|
||||||
from .rtlnl import RtlNlIE
|
from .rtlnl import (
|
||||||
|
RtlNlIE,
|
||||||
|
RTLLuTeleVODIE,
|
||||||
|
RTLLuArticleIE,
|
||||||
|
RTLLuLiveIE,
|
||||||
|
RTLLuRadioIE,
|
||||||
|
)
|
||||||
from .rtl2 import (
|
from .rtl2 import (
|
||||||
RTL2IE,
|
RTL2IE,
|
||||||
RTL2YouIE,
|
RTL2YouIE,
|
||||||
@@ -1455,6 +1474,7 @@ from .rtve import (
|
|||||||
)
|
)
|
||||||
from .rtvnh import RTVNHIE
|
from .rtvnh import RTVNHIE
|
||||||
from .rtvs import RTVSIE
|
from .rtvs import RTVSIE
|
||||||
|
from .rtvslo import RTVSLOIE
|
||||||
from .ruhd import RUHDIE
|
from .ruhd import RUHDIE
|
||||||
from .rule34video import Rule34VideoIE
|
from .rule34video import Rule34VideoIE
|
||||||
from .rumble import (
|
from .rumble import (
|
||||||
@@ -1509,6 +1529,7 @@ from .scte import (
|
|||||||
SCTEIE,
|
SCTEIE,
|
||||||
SCTECourseIE,
|
SCTECourseIE,
|
||||||
)
|
)
|
||||||
|
from .scrolller import ScrolllerIE
|
||||||
from .seeker import SeekerIE
|
from .seeker import SeekerIE
|
||||||
from .senategov import SenateISVPIE, SenateGovIE
|
from .senategov import SenateISVPIE, SenateGovIE
|
||||||
from .sendtonews import SendtoNewsIE
|
from .sendtonews import SendtoNewsIE
|
||||||
@@ -1604,6 +1625,7 @@ from .spike import (
|
|||||||
BellatorIE,
|
BellatorIE,
|
||||||
ParamountNetworkIE,
|
ParamountNetworkIE,
|
||||||
)
|
)
|
||||||
|
from .startrek import StarTrekIE
|
||||||
from .stitcher import (
|
from .stitcher import (
|
||||||
StitcherIE,
|
StitcherIE,
|
||||||
StitcherShowIE,
|
StitcherShowIE,
|
||||||
@@ -1630,7 +1652,10 @@ from .srgssr import (
|
|||||||
from .srmediathek import SRMediathekIE
|
from .srmediathek import SRMediathekIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .startv import StarTVIE
|
from .startv import StarTVIE
|
||||||
from .steam import SteamIE
|
from .steam import (
|
||||||
|
SteamIE,
|
||||||
|
SteamCommunityBroadcastIE,
|
||||||
|
)
|
||||||
from .storyfire import (
|
from .storyfire import (
|
||||||
StoryFireIE,
|
StoryFireIE,
|
||||||
StoryFireUserIE,
|
StoryFireUserIE,
|
||||||
@@ -1658,6 +1683,7 @@ from .svt import (
|
|||||||
SVTSeriesIE,
|
SVTSeriesIE,
|
||||||
)
|
)
|
||||||
from .swrmediathek import SWRMediathekIE
|
from .swrmediathek import SWRMediathekIE
|
||||||
|
from .syvdk import SYVDKIE
|
||||||
from .syfy import SyfyIE
|
from .syfy import SyfyIE
|
||||||
from .sztvhu import SztvHuIE
|
from .sztvhu import SztvHuIE
|
||||||
from .tagesschau import TagesschauIE
|
from .tagesschau import TagesschauIE
|
||||||
@@ -1704,6 +1730,7 @@ from .tenplay import TenPlayIE
|
|||||||
from .testurl import TestURLIE
|
from .testurl import TestURLIE
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .tfo import TFOIE
|
from .tfo import TFOIE
|
||||||
|
from .theholetv import TheHoleTvIE
|
||||||
from .theintercept import TheInterceptIE
|
from .theintercept import TheInterceptIE
|
||||||
from .theplatform import (
|
from .theplatform import (
|
||||||
ThePlatformIE,
|
ThePlatformIE,
|
||||||
@@ -1768,6 +1795,7 @@ from .trueid import TrueIDIE
|
|||||||
from .trunews import TruNewsIE
|
from .trunews import TruNewsIE
|
||||||
from .trutv import TruTVIE
|
from .trutv import TruTVIE
|
||||||
from .tube8 import Tube8IE
|
from .tube8 import Tube8IE
|
||||||
|
from .tubetugraz import TubeTuGrazIE, TubeTuGrazSeriesIE
|
||||||
from .tubitv import (
|
from .tubitv import (
|
||||||
TubiTvIE,
|
TubiTvIE,
|
||||||
TubiTvShowIE,
|
TubiTvShowIE,
|
||||||
@@ -1816,6 +1844,7 @@ from .tvc import (
|
|||||||
)
|
)
|
||||||
from .tver import TVerIE
|
from .tver import TVerIE
|
||||||
from .tvigle import TvigleIE
|
from .tvigle import TvigleIE
|
||||||
|
from .tviplayer import TVIPlayerIE
|
||||||
from .tvland import TVLandIE
|
from .tvland import TVLandIE
|
||||||
from .tvn24 import TVN24IE
|
from .tvn24 import TVN24IE
|
||||||
from .tvnet import TVNetIE
|
from .tvnet import TVNetIE
|
||||||
@@ -1928,7 +1957,10 @@ from .vice import (
|
|||||||
from .vidbit import VidbitIE
|
from .vidbit import VidbitIE
|
||||||
from .viddler import ViddlerIE
|
from .viddler import ViddlerIE
|
||||||
from .videa import VideaIE
|
from .videa import VideaIE
|
||||||
from .videocampus_sachsen import VideocampusSachsenIE
|
from .videocampus_sachsen import (
|
||||||
|
VideocampusSachsenIE,
|
||||||
|
ViMPPlaylistIE,
|
||||||
|
)
|
||||||
from .videodetective import VideoDetectiveIE
|
from .videodetective import VideoDetectiveIE
|
||||||
from .videofyme import VideofyMeIE
|
from .videofyme import VideofyMeIE
|
||||||
from .videomore import (
|
from .videomore import (
|
||||||
@@ -2056,6 +2088,8 @@ from .weibo import (
|
|||||||
WeiboMobileIE
|
WeiboMobileIE
|
||||||
)
|
)
|
||||||
from .weiqitv import WeiqiTVIE
|
from .weiqitv import WeiqiTVIE
|
||||||
|
from .wetv import WeTvEpisodeIE, WeTvSeriesIE
|
||||||
|
from .wikimedia import WikimediaIE
|
||||||
from .willow import WillowIE
|
from .willow import WillowIE
|
||||||
from .wimtv import WimTVIE
|
from .wimtv import WimTVIE
|
||||||
from .whowatch import WhoWatchIE
|
from .whowatch import WhoWatchIE
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import json
|
|||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
import urllib.response
|
import urllib.response
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..aes import aes_ecb_decrypt
|
from ..aes import aes_ecb_decrypt
|
||||||
from ..compat import compat_urllib_parse_urlparse, compat_urllib_request
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
bytes_to_intlist,
|
bytes_to_intlist,
|
||||||
@@ -33,7 +34,7 @@ def add_opener(ydl, handler):
|
|||||||
''' Add a handler for opening URLs, like _download_webpage '''
|
''' Add a handler for opening URLs, like _download_webpage '''
|
||||||
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L426
|
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L426
|
||||||
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L605
|
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L605
|
||||||
assert isinstance(ydl._opener, compat_urllib_request.OpenerDirector)
|
assert isinstance(ydl._opener, urllib.request.OpenerDirector)
|
||||||
ydl._opener.add_handler(handler)
|
ydl._opener.add_handler(handler)
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ def remove_opener(ydl, handler):
|
|||||||
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L426
|
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L426
|
||||||
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L605
|
# https://github.com/python/cpython/blob/main/Lib/urllib/request.py#L605
|
||||||
opener = ydl._opener
|
opener = ydl._opener
|
||||||
assert isinstance(ydl._opener, compat_urllib_request.OpenerDirector)
|
assert isinstance(ydl._opener, urllib.request.OpenerDirector)
|
||||||
if isinstance(handler, (type, tuple)):
|
if isinstance(handler, (type, tuple)):
|
||||||
find_cp = lambda x: isinstance(x, handler)
|
find_cp = lambda x: isinstance(x, handler)
|
||||||
else:
|
else:
|
||||||
@@ -96,20 +97,20 @@ def remove_opener(ydl, handler):
|
|||||||
opener.handlers[:] = [x for x in opener.handlers if not find_cp(x)]
|
opener.handlers[:] = [x for x in opener.handlers if not find_cp(x)]
|
||||||
|
|
||||||
|
|
||||||
class AbemaLicenseHandler(compat_urllib_request.BaseHandler):
|
class AbemaLicenseHandler(urllib.request.BaseHandler):
|
||||||
handler_order = 499
|
handler_order = 499
|
||||||
STRTABLE = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
STRTABLE = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||||
HKEY = b'3AF0298C219469522A313570E8583005A642E73EDD58E3EA2FB7339D3DF1597E'
|
HKEY = b'3AF0298C219469522A313570E8583005A642E73EDD58E3EA2FB7339D3DF1597E'
|
||||||
|
|
||||||
def __init__(self, ie: 'AbemaTVIE'):
|
def __init__(self, ie: 'AbemaTVIE'):
|
||||||
# the protcol that this should really handle is 'abematv-license://'
|
# the protocol that this should really handle is 'abematv-license://'
|
||||||
# abematv_license_open is just a placeholder for development purposes
|
# abematv_license_open is just a placeholder for development purposes
|
||||||
# ref. https://github.com/python/cpython/blob/f4c03484da59049eb62a9bf7777b963e2267d187/Lib/urllib/request.py#L510
|
# ref. https://github.com/python/cpython/blob/f4c03484da59049eb62a9bf7777b963e2267d187/Lib/urllib/request.py#L510
|
||||||
setattr(self, 'abematv-license_open', getattr(self, 'abematv_license_open'))
|
setattr(self, 'abematv-license_open', getattr(self, 'abematv_license_open'))
|
||||||
self.ie = ie
|
self.ie = ie
|
||||||
|
|
||||||
def _get_videokey_from_ticket(self, ticket):
|
def _get_videokey_from_ticket(self, ticket):
|
||||||
to_show = self.ie._downloader.params.get('verbose', False)
|
to_show = self.ie.get_param('verbose', False)
|
||||||
media_token = self.ie._get_media_token(to_show=to_show)
|
media_token = self.ie._get_media_token(to_show=to_show)
|
||||||
|
|
||||||
license_response = self.ie._download_json(
|
license_response = self.ie._download_json(
|
||||||
@@ -136,7 +137,7 @@ class AbemaLicenseHandler(compat_urllib_request.BaseHandler):
|
|||||||
|
|
||||||
def abematv_license_open(self, url):
|
def abematv_license_open(self, url):
|
||||||
url = request_to_url(url)
|
url = request_to_url(url)
|
||||||
ticket = compat_urllib_parse_urlparse(url).netloc
|
ticket = urllib.parse.urlparse(url).netloc
|
||||||
response_data = self._get_videokey_from_ticket(ticket)
|
response_data = self._get_videokey_from_ticket(ticket)
|
||||||
return urllib.response.addinfourl(io.BytesIO(response_data), headers={
|
return urllib.response.addinfourl(io.BytesIO(response_data), headers={
|
||||||
'Content-Length': len(response_data),
|
'Content-Length': len(response_data),
|
||||||
@@ -311,7 +312,7 @@ class AbemaTVIE(AbemaTVBaseIE):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
# starting download using infojson from this extractor is undefined behavior,
|
# starting download using infojson from this extractor is undefined behavior,
|
||||||
# and never be fixed in the future; you must trigger downloads by directly specifing URL.
|
# and never be fixed in the future; you must trigger downloads by directly specifying URL.
|
||||||
# (unless there's a way to hook before downloading by extractor)
|
# (unless there's a way to hook before downloading by extractor)
|
||||||
video_id, video_type = self._match_valid_url(url).group('id', 'type')
|
video_id, video_type = self._match_valid_url(url).group('id', 'type')
|
||||||
headers = {
|
headers = {
|
||||||
|
|||||||
200
yt_dlp/extractor/acfun.py
Normal file
200
yt_dlp/extractor/acfun.py
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
format_field,
|
||||||
|
int_or_none,
|
||||||
|
traverse_obj,
|
||||||
|
parse_codecs,
|
||||||
|
parse_qs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AcFunVideoBaseIE(InfoExtractor):
|
||||||
|
def _extract_metadata(self, video_id, video_info):
|
||||||
|
playjson = self._parse_json(video_info['ksPlayJson'], video_id)
|
||||||
|
|
||||||
|
formats, subtitles = [], {}
|
||||||
|
for video in traverse_obj(playjson, ('adaptationSet', 0, 'representation')):
|
||||||
|
fmts, subs = self._extract_m3u8_formats_and_subtitles(video['url'], video_id, 'mp4', fatal=False)
|
||||||
|
formats.extend(fmts)
|
||||||
|
self._merge_subtitles(subs, target=subtitles)
|
||||||
|
for f in fmts:
|
||||||
|
f.update({
|
||||||
|
'fps': float_or_none(video.get('frameRate')),
|
||||||
|
'width': int_or_none(video.get('width')),
|
||||||
|
'height': int_or_none(video.get('height')),
|
||||||
|
'tbr': float_or_none(video.get('avgBitrate')),
|
||||||
|
**parse_codecs(video.get('codecs', ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'duration': float_or_none(video_info.get('durationMillis'), 1000),
|
||||||
|
'timestamp': int_or_none(video_info.get('uploadTime'), 1000),
|
||||||
|
'http_headers': {'Referer': 'https://www.acfun.cn/'},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AcFunVideoIE(AcFunVideoBaseIE):
|
||||||
|
_VALID_URL = r'https?://www\.acfun\.cn/v/ac(?P<id>[_\d]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.acfun.cn/v/ac35457073',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '35457073',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'duration': 174.208,
|
||||||
|
'timestamp': 1656403967,
|
||||||
|
'title': '1 8 岁 现 状',
|
||||||
|
'description': '“赶紧回去!班主任查班了!”',
|
||||||
|
'uploader': '锤子game',
|
||||||
|
'uploader_id': '51246077',
|
||||||
|
'thumbnail': r're:^https?://.*\.(jpg|jpeg)',
|
||||||
|
'upload_date': '20220628',
|
||||||
|
'like_count': int,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
|
'tags': list,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# example for len(video_list) > 1
|
||||||
|
'url': 'https://www.acfun.cn/v/ac35468952_2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '35468952_2',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '【动画剧集】Rocket & Groot Season 1(2022)/火箭浣熊与格鲁特第1季 P02 S01E02 十拿九穩',
|
||||||
|
'duration': 90.459,
|
||||||
|
'uploader': '比令',
|
||||||
|
'uploader_id': '37259967',
|
||||||
|
'upload_date': '20220629',
|
||||||
|
'timestamp': 1656479962,
|
||||||
|
'tags': list,
|
||||||
|
'like_count': int,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
|
'thumbnail': r're:^https?://.*\.(jpg|jpeg)',
|
||||||
|
'description': 'md5:67583aaf3a0f933bd606bc8a2d3ebb17',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
json_all = self._search_json(r'window.videoInfo\s*=\s*', webpage, 'videoInfo', video_id)
|
||||||
|
|
||||||
|
title = json_all.get('title')
|
||||||
|
video_list = json_all.get('videoList') or []
|
||||||
|
video_internal_id = traverse_obj(json_all, ('currentVideoInfo', 'id'))
|
||||||
|
if video_internal_id and len(video_list) > 1:
|
||||||
|
part_idx, part_video_info = next(
|
||||||
|
(idx + 1, v) for (idx, v) in enumerate(video_list)
|
||||||
|
if v['id'] == video_internal_id)
|
||||||
|
title = f'{title} P{part_idx:02d} {part_video_info["title"]}'
|
||||||
|
|
||||||
|
return {
|
||||||
|
**self._extract_metadata(video_id, json_all['currentVideoInfo']),
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': json_all.get('coverUrl'),
|
||||||
|
'description': json_all.get('description'),
|
||||||
|
'uploader': traverse_obj(json_all, ('user', 'name')),
|
||||||
|
'uploader_id': traverse_obj(json_all, ('user', 'href')),
|
||||||
|
'tags': traverse_obj(json_all, ('tagList', ..., 'name')),
|
||||||
|
'view_count': int_or_none(json_all.get('viewCount')),
|
||||||
|
'like_count': int_or_none(json_all.get('likeCountShow')),
|
||||||
|
'comment_count': int_or_none(json_all.get('commentCountShow')),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AcFunBangumiIE(AcFunVideoBaseIE):
|
||||||
|
_VALID_URL = r'https?://www\.acfun\.cn/bangumi/(?P<id>aa[_\d]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.acfun.cn/bangumi/aa6002917_36188_1745457?ac=2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'aa6002917_36188_1745457__2',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '【7月】租借女友 水原千鹤角色曲『DATE』特别PV',
|
||||||
|
'upload_date': '20200916',
|
||||||
|
'timestamp': 1600243813,
|
||||||
|
'duration': 92.091,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.acfun.cn/bangumi/aa5023171_36188_1750645',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'aa5023171_36188_1750645',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '红孩儿之趴趴蛙寻石记 第5话 ',
|
||||||
|
'duration': 760.0,
|
||||||
|
'season': '红孩儿之趴趴蛙寻石记',
|
||||||
|
'season_id': 5023171,
|
||||||
|
'season_number': 1, # series has only 1 season
|
||||||
|
'episode': 'Episode 5',
|
||||||
|
'episode_number': 5,
|
||||||
|
'upload_date': '20181223',
|
||||||
|
'timestamp': 1545552185,
|
||||||
|
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)',
|
||||||
|
'comment_count': int,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.acfun.cn/bangumi/aa6065485_36188_1885061',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'aa6065485_36188_1885061',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '叽歪老表(第二季) 第5话 坚不可摧',
|
||||||
|
'season': '叽歪老表(第二季)',
|
||||||
|
'season_number': 2,
|
||||||
|
'season_id': 6065485,
|
||||||
|
'episode': '坚不可摧',
|
||||||
|
'episode_number': 5,
|
||||||
|
'upload_date': '20220324',
|
||||||
|
'timestamp': 1648082786,
|
||||||
|
'duration': 105.002,
|
||||||
|
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)',
|
||||||
|
'comment_count': int,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
ac_idx = parse_qs(url).get('ac', [None])[-1]
|
||||||
|
video_id = f'{video_id}{format_field(ac_idx, template="__%s")}'
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
json_bangumi_data = self._search_json(r'window.bangumiData\s*=\s*', webpage, 'bangumiData', video_id)
|
||||||
|
|
||||||
|
if ac_idx:
|
||||||
|
video_info = json_bangumi_data['hlVideoInfo']
|
||||||
|
return {
|
||||||
|
**self._extract_metadata(video_id, video_info),
|
||||||
|
'title': video_info.get('title'),
|
||||||
|
}
|
||||||
|
|
||||||
|
video_info = json_bangumi_data['currentVideoInfo']
|
||||||
|
|
||||||
|
season_id = json_bangumi_data.get('bangumiId')
|
||||||
|
season_number = season_id and next((
|
||||||
|
idx for idx, v in enumerate(json_bangumi_data.get('relatedBangumis') or [], 1)
|
||||||
|
if v.get('id') == season_id), 1)
|
||||||
|
|
||||||
|
json_bangumi_list = self._search_json(
|
||||||
|
r'window\.bangumiList\s*=\s*', webpage, 'bangumiList', video_id, fatal=False)
|
||||||
|
video_internal_id = int_or_none(traverse_obj(json_bangumi_data, ('currentVideoInfo', 'id')))
|
||||||
|
episode_number = video_internal_id and next((
|
||||||
|
idx for idx, v in enumerate(json_bangumi_list.get('items') or [], 1)
|
||||||
|
if v.get('videoId') == video_internal_id), None)
|
||||||
|
|
||||||
|
return {
|
||||||
|
**self._extract_metadata(video_id, video_info),
|
||||||
|
'title': json_bangumi_data.get('showTitle'),
|
||||||
|
'thumbnail': json_bangumi_data.get('image'),
|
||||||
|
'season': json_bangumi_data.get('bangumiTitle'),
|
||||||
|
'season_id': season_id,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode': json_bangumi_data.get('title'),
|
||||||
|
'episode_number': episode_number,
|
||||||
|
'comment_count': int_or_none(json_bangumi_data.get('commentCount')),
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import getpass
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -5,19 +6,15 @@ import urllib.error
|
|||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_urlparse
|
||||||
compat_urlparse,
|
|
||||||
compat_getpass
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
unescapeHTML,
|
|
||||||
urlencode_postdata,
|
|
||||||
unified_timestamp,
|
|
||||||
ExtractorError,
|
|
||||||
NO_DEFAULT,
|
NO_DEFAULT,
|
||||||
|
ExtractorError,
|
||||||
|
unescapeHTML,
|
||||||
|
unified_timestamp,
|
||||||
|
urlencode_postdata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MSO_INFO = {
|
MSO_INFO = {
|
||||||
'DTV': {
|
'DTV': {
|
||||||
'name': 'DIRECTV',
|
'name': 'DIRECTV',
|
||||||
@@ -1431,7 +1428,7 @@ class AdobePassIE(InfoExtractor):
|
|||||||
guid = xml_text(resource, 'guid') if '<' in resource else resource
|
guid = xml_text(resource, 'guid') if '<' in resource else resource
|
||||||
count = 0
|
count = 0
|
||||||
while count < 2:
|
while count < 2:
|
||||||
requestor_info = self._downloader.cache.load(self._MVPD_CACHE, requestor_id) or {}
|
requestor_info = self.cache.load(self._MVPD_CACHE, requestor_id) or {}
|
||||||
authn_token = requestor_info.get('authn_token')
|
authn_token = requestor_info.get('authn_token')
|
||||||
if authn_token and is_expired(authn_token, 'simpleTokenExpires'):
|
if authn_token and is_expired(authn_token, 'simpleTokenExpires'):
|
||||||
authn_token = None
|
authn_token = None
|
||||||
@@ -1506,7 +1503,7 @@ class AdobePassIE(InfoExtractor):
|
|||||||
'send_confirm_link': False,
|
'send_confirm_link': False,
|
||||||
'send_token': True
|
'send_token': True
|
||||||
}))
|
}))
|
||||||
philo_code = compat_getpass('Type auth code you have received [Return]: ')
|
philo_code = getpass.getpass('Type auth code you have received [Return]: ')
|
||||||
self._download_webpage(
|
self._download_webpage(
|
||||||
'https://idp.philo.com/auth/update/login_code', video_id, 'Submitting token', data=urlencode_postdata({
|
'https://idp.philo.com/auth/update/login_code', video_id, 'Submitting token', data=urlencode_postdata({
|
||||||
'token': philo_code
|
'token': philo_code
|
||||||
@@ -1726,12 +1723,12 @@ class AdobePassIE(InfoExtractor):
|
|||||||
raise_mvpd_required()
|
raise_mvpd_required()
|
||||||
raise
|
raise
|
||||||
if '<pendingLogout' in session:
|
if '<pendingLogout' in session:
|
||||||
self._downloader.cache.store(self._MVPD_CACHE, requestor_id, {})
|
self.cache.store(self._MVPD_CACHE, requestor_id, {})
|
||||||
count += 1
|
count += 1
|
||||||
continue
|
continue
|
||||||
authn_token = unescapeHTML(xml_text(session, 'authnToken'))
|
authn_token = unescapeHTML(xml_text(session, 'authnToken'))
|
||||||
requestor_info['authn_token'] = authn_token
|
requestor_info['authn_token'] = authn_token
|
||||||
self._downloader.cache.store(self._MVPD_CACHE, requestor_id, requestor_info)
|
self.cache.store(self._MVPD_CACHE, requestor_id, requestor_info)
|
||||||
|
|
||||||
authz_token = requestor_info.get(guid)
|
authz_token = requestor_info.get(guid)
|
||||||
if authz_token and is_expired(authz_token, 'simpleTokenTTL'):
|
if authz_token and is_expired(authz_token, 'simpleTokenTTL'):
|
||||||
@@ -1747,14 +1744,14 @@ class AdobePassIE(InfoExtractor):
|
|||||||
'userMeta': '1',
|
'userMeta': '1',
|
||||||
}), headers=mvpd_headers)
|
}), headers=mvpd_headers)
|
||||||
if '<pendingLogout' in authorize:
|
if '<pendingLogout' in authorize:
|
||||||
self._downloader.cache.store(self._MVPD_CACHE, requestor_id, {})
|
self.cache.store(self._MVPD_CACHE, requestor_id, {})
|
||||||
count += 1
|
count += 1
|
||||||
continue
|
continue
|
||||||
if '<error' in authorize:
|
if '<error' in authorize:
|
||||||
raise ExtractorError(xml_text(authorize, 'details'), expected=True)
|
raise ExtractorError(xml_text(authorize, 'details'), expected=True)
|
||||||
authz_token = unescapeHTML(xml_text(authorize, 'authzToken'))
|
authz_token = unescapeHTML(xml_text(authorize, 'authzToken'))
|
||||||
requestor_info[guid] = authz_token
|
requestor_info[guid] = authz_token
|
||||||
self._downloader.cache.store(self._MVPD_CACHE, requestor_id, requestor_info)
|
self.cache.store(self._MVPD_CACHE, requestor_id, requestor_info)
|
||||||
|
|
||||||
mvpd_headers.update({
|
mvpd_headers.update({
|
||||||
'ap_19': xml_text(authn_token, 'simpleSamlNameID'),
|
'ap_19': xml_text(authn_token, 'simpleSamlNameID'),
|
||||||
@@ -1770,7 +1767,7 @@ class AdobePassIE(InfoExtractor):
|
|||||||
'hashed_guid': 'false',
|
'hashed_guid': 'false',
|
||||||
}), headers=mvpd_headers)
|
}), headers=mvpd_headers)
|
||||||
if '<pendingLogout' in short_authorize:
|
if '<pendingLogout' in short_authorize:
|
||||||
self._downloader.cache.store(self._MVPD_CACHE, requestor_id, {})
|
self.cache.store(self._MVPD_CACHE, requestor_id, {})
|
||||||
count += 1
|
count += 1
|
||||||
continue
|
continue
|
||||||
return short_authorize
|
return short_authorize
|
||||||
|
|||||||
@@ -1,36 +1,34 @@
|
|||||||
import re
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .youtube import YoutubeIE, YoutubeBaseInfoExtractor
|
from .youtube import YoutubeBaseInfoExtractor, YoutubeIE
|
||||||
from ..compat import (
|
from ..compat import compat_HTTPError, compat_urllib_parse_unquote
|
||||||
compat_urllib_parse_unquote,
|
|
||||||
compat_urllib_parse_unquote_plus,
|
|
||||||
compat_HTTPError
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
KNOWN_EXTENSIONS,
|
||||||
|
ExtractorError,
|
||||||
|
HEADRequest,
|
||||||
bug_reports_message,
|
bug_reports_message,
|
||||||
clean_html,
|
clean_html,
|
||||||
dict_get,
|
dict_get,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
ExtractorError,
|
|
||||||
get_element_by_id,
|
get_element_by_id,
|
||||||
HEADRequest,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
join_nonempty,
|
join_nonempty,
|
||||||
KNOWN_EXTENSIONS,
|
|
||||||
merge_dicts,
|
merge_dicts,
|
||||||
mimetype2ext,
|
mimetype2ext,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_qs,
|
parse_qs,
|
||||||
str_to_int,
|
|
||||||
str_or_none,
|
str_or_none,
|
||||||
|
str_to_int,
|
||||||
traverse_obj,
|
traverse_obj,
|
||||||
try_get,
|
try_get,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
|
url_or_none,
|
||||||
urlhandle_detect_ext,
|
urlhandle_detect_ext,
|
||||||
url_or_none
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -143,7 +141,7 @@ class ArchiveOrgIE(InfoExtractor):
|
|||||||
return json.loads(extract_attributes(element)['value'])
|
return json.loads(extract_attributes(element)['value'])
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = compat_urllib_parse_unquote_plus(self._match_id(url))
|
video_id = urllib.parse.unquote_plus(self._match_id(url))
|
||||||
identifier, entry_id = (video_id.split('/', 1) + [None])[:2]
|
identifier, entry_id = (video_id.split('/', 1) + [None])[:2]
|
||||||
|
|
||||||
# Archive.org metadata API doesn't clearly demarcate playlist entries
|
# Archive.org metadata API doesn't clearly demarcate playlist entries
|
||||||
|
|||||||
93
yt_dlp/extractor/audiodraft.py
Normal file
93
yt_dlp/extractor/audiodraft.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class AudiodraftBaseIE(InfoExtractor):
|
||||||
|
def _audiodraft_extract_from_id(self, player_entry_id):
|
||||||
|
data_json = self._download_json(
|
||||||
|
'https://www.audiodraft.com/scripts/general/player/getPlayerInfoNew.php', player_entry_id,
|
||||||
|
headers={
|
||||||
|
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
}, data=f'id={player_entry_id}'.encode('utf-8'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': str(data_json['entry_id']),
|
||||||
|
'title': data_json.get('entry_title'),
|
||||||
|
'url': data_json['path'],
|
||||||
|
'vcodec': 'none',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'uploader': data_json.get('designer_name'),
|
||||||
|
'uploader_id': data_json.get('designer_id'),
|
||||||
|
'webpage_url': data_json.get('entry_url'),
|
||||||
|
'like_count': int_or_none(data_json.get('entry_likes')),
|
||||||
|
'average_rating': int_or_none(data_json.get('entry_rating')),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AudiodraftCustomIE(AudiodraftBaseIE):
|
||||||
|
IE_NAME = 'Audiodraft:custom'
|
||||||
|
_VALID_URL = r'https?://(?:[-\w]+)\.audiodraft\.com/entry/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://nokiatune.audiodraft.com/entry/5874',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '9485',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Hula Hula Calls',
|
||||||
|
'uploader': 'unclemaki',
|
||||||
|
'uploader_id': '13512',
|
||||||
|
'average_rating': 5,
|
||||||
|
'like_count': int,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://vikinggrace.audiodraft.com/entry/501',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '22241',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'MVG Happy',
|
||||||
|
'uploader': 'frog',
|
||||||
|
'uploader_id': '19142',
|
||||||
|
'average_rating': 5,
|
||||||
|
'like_count': int,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://timferriss.audiodraft.com/entry/765',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '19710',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'ferris03',
|
||||||
|
'uploader': 'malex',
|
||||||
|
'uploader_id': '17335',
|
||||||
|
'average_rating': 5,
|
||||||
|
'like_count': int,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, id)
|
||||||
|
player_entry_id = self._search_regex(r'playAudio\(\'(player_entry_\d+)\'\);', webpage, id, 'play entry id')
|
||||||
|
return self._audiodraft_extract_from_id(player_entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AudiodraftGenericIE(AudiodraftBaseIE):
|
||||||
|
IE_NAME = 'Audiodraft:generic'
|
||||||
|
_VALID_URL = r'https?://www\.audiodraft\.com/contests/[^/#]+#entries&eid=(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.audiodraft.com/contests/570-Score-A-Video-Surprise-Us#entries&eid=30138',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '30138',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'DROP in sound_V2',
|
||||||
|
'uploader': 'TiagoSilva',
|
||||||
|
'uploader_id': '19452',
|
||||||
|
'average_rating': 4,
|
||||||
|
'like_count': int,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
id = self._match_id(url)
|
||||||
|
return self._audiodraft_extract_from_id(f'player_entry_{id}')
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError, try_get, compat_str, str_or_none
|
from ..compat import compat_str, compat_urllib_parse_unquote
|
||||||
from ..compat import compat_urllib_parse_unquote
|
from ..utils import ExtractorError, str_or_none, try_get
|
||||||
|
|
||||||
|
|
||||||
class AudiusBaseIE(InfoExtractor):
|
class AudiusBaseIE(InfoExtractor):
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import xml.etree.ElementTree
|
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import urllib.error
|
||||||
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_HTTPError, compat_str, compat_urlparse
|
||||||
compat_HTTPError,
|
|
||||||
compat_str,
|
|
||||||
compat_urllib_error,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
OnDemandPagedList,
|
OnDemandPagedList,
|
||||||
@@ -391,7 +387,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
href, programme_id, ext='mp4', entry_protocol='m3u8_native',
|
href, programme_id, ext='mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id=format_id, fatal=False)
|
m3u8_id=format_id, fatal=False)
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if not (isinstance(e.exc_info[1], compat_urllib_error.HTTPError)
|
if not (isinstance(e.exc_info[1], urllib.error.HTTPError)
|
||||||
and e.exc_info[1].code in (403, 404)):
|
and e.exc_info[1].code in (403, 404)):
|
||||||
raise
|
raise
|
||||||
fmts = []
|
fmts = []
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class BigoIE(InfoExtractor):
|
|||||||
user_id = self._match_id(url)
|
user_id = self._match_id(url)
|
||||||
|
|
||||||
info_raw = self._download_json(
|
info_raw = self._download_json(
|
||||||
'https://bigo.tv/studio/getInternalStudioInfo',
|
'https://ta.bigo.tv/official_website/studio/getInternalStudioInfo',
|
||||||
user_id, data=urlencode_postdata({'siteId': user_id}))
|
user_id, data=urlencode_postdata({'siteId': user_id}))
|
||||||
|
|
||||||
if not isinstance(info_raw, dict):
|
if not isinstance(info_raw, dict):
|
||||||
@@ -41,14 +41,14 @@ class BigoIE(InfoExtractor):
|
|||||||
if not info.get('alive'):
|
if not info.get('alive'):
|
||||||
raise ExtractorError('This user is offline.', expected=True)
|
raise ExtractorError('This user is offline.', expected=True)
|
||||||
|
|
||||||
|
formats, subs = self._extract_m3u8_formats_and_subtitles(
|
||||||
|
info.get('hls_src'), user_id, 'mp4', 'm3u8')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': info.get('roomId') or user_id,
|
'id': info.get('roomId') or user_id,
|
||||||
'title': info.get('roomTopic') or info.get('nick_name') or user_id,
|
'title': info.get('roomTopic') or info.get('nick_name') or user_id,
|
||||||
'formats': [{
|
'formats': formats,
|
||||||
'url': info.get('hls_src'),
|
'subtitles': subs,
|
||||||
'ext': 'mp4',
|
|
||||||
'protocol': 'm3u8',
|
|
||||||
}],
|
|
||||||
'thumbnail': info.get('snapshot'),
|
'thumbnail': info.get('snapshot'),
|
||||||
'uploader': info.get('nick_name'),
|
'uploader': info.get('nick_name'),
|
||||||
'uploader_id': user_id,
|
'uploader_id': user_id,
|
||||||
|
|||||||
@@ -795,12 +795,14 @@ class BiliIntlBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
def _get_subtitles(self, *, ep_id=None, aid=None):
|
def _get_subtitles(self, *, ep_id=None, aid=None):
|
||||||
sub_json = self._call_api(
|
sub_json = self._call_api(
|
||||||
'/web/v2/subtitle', ep_id or aid, note='Downloading subtitles list',
|
'/web/v2/subtitle', ep_id or aid, fatal=False,
|
||||||
errnote='Unable to download subtitles list', query=filter_dict({
|
note='Downloading subtitles list', errnote='Unable to download subtitles list',
|
||||||
|
query=filter_dict({
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
|
's_locale': 'en_US',
|
||||||
'episode_id': ep_id,
|
'episode_id': ep_id,
|
||||||
'aid': aid,
|
'aid': aid,
|
||||||
}))
|
})) or {}
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for sub in sub_json.get('subtitles') or []:
|
for sub in sub_json.get('subtitles') or []:
|
||||||
sub_url = sub.get('url')
|
sub_url = sub.get('url')
|
||||||
|
|||||||
@@ -600,9 +600,9 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
account_id, player_id, embed, content_type, video_id = self._match_valid_url(url).groups()
|
account_id, player_id, embed, content_type, video_id = self._match_valid_url(url).groups()
|
||||||
|
|
||||||
policy_key_id = '%s_%s' % (account_id, player_id)
|
policy_key_id = '%s_%s' % (account_id, player_id)
|
||||||
policy_key = self._downloader.cache.load('brightcove', policy_key_id)
|
policy_key = self.cache.load('brightcove', policy_key_id)
|
||||||
policy_key_extracted = False
|
policy_key_extracted = False
|
||||||
store_pk = lambda x: self._downloader.cache.store('brightcove', policy_key_id, x)
|
store_pk = lambda x: self.cache.store('brightcove', policy_key_id, x)
|
||||||
|
|
||||||
def extract_policy_key():
|
def extract_policy_key():
|
||||||
base_url = 'http://players.brightcove.net/%s/%s_%s/' % (account_id, player_id, embed)
|
base_url = 'http://players.brightcove.net/%s/%s_%s/' % (account_id, player_id, embed)
|
||||||
|
|||||||
@@ -304,13 +304,13 @@ class CBCGemIE(InfoExtractor):
|
|||||||
def _get_claims_token(self, email, password):
|
def _get_claims_token(self, email, password):
|
||||||
if not self.claims_token_valid():
|
if not self.claims_token_valid():
|
||||||
self._claims_token = self._new_claims_token(email, password)
|
self._claims_token = self._new_claims_token(email, password)
|
||||||
self._downloader.cache.store(self._NETRC_MACHINE, 'claims_token', self._claims_token)
|
self.cache.store(self._NETRC_MACHINE, 'claims_token', self._claims_token)
|
||||||
return self._claims_token
|
return self._claims_token
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
if self.claims_token_valid():
|
if self.claims_token_valid():
|
||||||
return
|
return
|
||||||
self._claims_token = self._downloader.cache.load(self._NETRC_MACHINE, 'claims_token')
|
self._claims_token = self.cache.load(self._NETRC_MACHINE, 'claims_token')
|
||||||
|
|
||||||
def _find_secret_formats(self, formats, video_id):
|
def _find_secret_formats(self, formats, video_id):
|
||||||
""" Find a valid video url and convert it to the secret variant """
|
""" Find a valid video url and convert it to the secret variant """
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import codecs
|
import codecs
|
||||||
import re
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_ord, compat_urllib_parse_unquote
|
||||||
compat_chr,
|
|
||||||
compat_ord,
|
|
||||||
compat_urllib_parse_unquote,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
@@ -16,8 +12,8 @@ from ..utils import (
|
|||||||
multipart_encode,
|
multipart_encode,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
random_birthday,
|
random_birthday,
|
||||||
urljoin,
|
|
||||||
try_get,
|
try_get,
|
||||||
|
urljoin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -144,7 +140,7 @@ class CDAIE(InfoExtractor):
|
|||||||
b = []
|
b = []
|
||||||
for c in a:
|
for c in a:
|
||||||
f = compat_ord(c)
|
f = compat_ord(c)
|
||||||
b.append(compat_chr(33 + (f + 14) % 94) if 33 <= f <= 126 else compat_chr(f))
|
b.append(chr(33 + (f + 14) % 94) if 33 <= f <= 126 else chr(f))
|
||||||
a = ''.join(b)
|
a = ''.join(b)
|
||||||
a = a.replace('.cda.mp4', '')
|
a = a.replace('.cda.mp4', '')
|
||||||
for p in ('.2cda.pl', '.3cda.pl'):
|
for p in ('.2cda.pl', '.3cda.pl'):
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user