mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-08-31 08:12:17 +03:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e584a65f2a | ||
|
|
c84b2c6736 | ||
|
|
e7b5d68f37 | ||
|
|
b0472c3bce | ||
|
|
500e54cf86 | ||
|
|
5678b282e2 | ||
|
|
55a58debec | ||
|
|
f49b551a0c | ||
|
|
1249676e98 | ||
|
|
7a569456f2 | ||
|
|
3cf981af60 | ||
|
|
c13e2f8a20 | ||
|
|
af8a3f3437 | ||
|
|
785e507ef0 | ||
|
|
6a24c96f7f | ||
|
|
d23e6f5a38 | ||
|
|
4a6296248f | ||
|
|
6b67e1f2b7 | ||
|
|
f69e64a954 | ||
|
|
c102b20965 | ||
|
|
57528faa36 | ||
|
|
25a05fc0e2 | ||
|
|
917dad55e5 | ||
|
|
a5e0f87140 | ||
|
|
3c279b33cb | ||
|
|
7b03011294 | ||
|
|
a75ba96fa4 | ||
|
|
d6c411bcd0 | ||
|
|
98036ccd4f | ||
|
|
da99b21b2d | ||
|
|
8a5f1dd3bd | ||
|
|
9ae7df9a22 | ||
|
|
2b27a203f7 | ||
|
|
4af5541bb5 | ||
|
|
c4f94545c9 | ||
|
|
cfee151fcd | ||
|
|
24aecad5df | ||
|
|
01f4f06fdd | ||
|
|
ad6b5f4b35 | ||
|
|
8902f6ba8c | ||
|
|
b23046bbc8 | ||
|
|
9055188250 | ||
|
|
b05b408d10 | ||
|
|
a2483524fb | ||
|
|
707537a039 | ||
|
|
7937a139cf | ||
|
|
cb309b3293 | ||
|
|
e47691215f | ||
|
|
a541df1ea5 |
@@ -195,7 +195,7 @@ jobs:
|
||||
UPDATE_TO: yt-dlp/yt-dlp@2025.09.05
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # Needed for changelog
|
||||
persist-credentials: false
|
||||
@@ -259,7 +259,7 @@ jobs:
|
||||
SKIP_ONEFILE_BUILD: ${{ (!matrix.onefile && '1') || '' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
UPDATE_TO: yt-dlp/yt-dlp@2025.09.05
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -438,7 +438,7 @@ jobs:
|
||||
UPDATE_TO: yt-dlp/yt-dlp@2025.09.05
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
env:
|
||||
QJS_VERSION: '2025-04-26' # Earliest version with rope strings
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
python-version: pypy-3.11
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python 3.10
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
outputs:
|
||||
commit: ${{ steps.check_for_new_commits.outputs.commit }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
head_sha: ${{ steps.get_target.outputs.head_sha }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true # Needed to git-push the release commit
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # Needed for changelog
|
||||
persist-credentials: false
|
||||
@@ -240,7 +240,7 @@ jobs:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
HEAD_SHA: ${{ needs.prepare.outputs.head_sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true # Needed to git-push the release tag
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
actions: read # Needed by zizmorcore/zizmor-action if repository is private
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
contents: write # Needed to git-push to the wiki
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: yt-dlp/yt-dlp-wiki
|
||||
ref: master
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ For bug reports, this means that your report should contain the **complete** out
|
||||
|
||||
If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--write-pages` and upload the `.dump` files you get [somewhere](https://gist.github.com).
|
||||
|
||||
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like `https://www.youtube.com/watch?v=BaW_jenozKc`. There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. `https://www.youtube.com/`) is *not* an example URL.
|
||||
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like `https://www.youtube.com/watch?v=YE7VzlLtp-4`. There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. `https://www.youtube.com/`) is *not* an example URL.
|
||||
|
||||
### Are you using the latest version?
|
||||
|
||||
|
||||
@@ -1264,7 +1264,7 @@ As an alternative to using the `.netrc` file, which has the disadvantage of keep
|
||||
|
||||
E.g. To use an encrypted `.netrc` file stored as `.authinfo.gpg`
|
||||
```
|
||||
yt-dlp --netrc-cmd 'gpg --decrypt ~/.authinfo.gpg' 'https://www.youtube.com/watch?v=BaW_jenozKc'
|
||||
yt-dlp --netrc-cmd 'gpg --decrypt ~/.authinfo.gpg' 'https://www.youtube.com/watch?v=YE7VzlLtp-4'
|
||||
```
|
||||
|
||||
|
||||
@@ -1454,7 +1454,7 @@ Available only in `--sponsorblock-chapter-title`:
|
||||
- `name` (string): Friendly name of the smallest category
|
||||
- `type` (string): The [SponsorBlock action type](https://wiki.sponsor.ajay.app/w/Types#Action_Type) of the chapter
|
||||
|
||||
Each aforementioned sequence when referenced in an output template will be replaced by the actual value corresponding to the sequence name. E.g. for `-o %(title)s-%(id)s.%(ext)s` and an mp4 video with title `yt-dlp test video` and id `BaW_jenozKc`, this will result in a `yt-dlp test video-BaW_jenozKc.mp4` file created in the current directory.
|
||||
Each aforementioned sequence when referenced in an output template will be replaced by the actual value corresponding to the sequence name. E.g. for `-o %(title)s-%(id)s.%(ext)s` and an mp4 video with title `yt-dlp test video` and id `YE7VzlLtp-4`, this will result in a `yt-dlp test video-YE7VzlLtp-4.mp4` file created in the current directory.
|
||||
|
||||
**Note**: Some of the sequences are not guaranteed to be present, since they depend on the metadata obtained by a particular extractor. Such sequences will be replaced with placeholder value provided with `--output-na-placeholder` (`NA` by default).
|
||||
|
||||
@@ -1473,14 +1473,14 @@ In some cases, you don't want special characters such as 中, spaces, or &, such
|
||||
#### Output template examples
|
||||
|
||||
```bash
|
||||
$ yt-dlp --print filename -o "test video.%(ext)s" BaW_jenozKc
|
||||
$ yt-dlp --print filename -o "test video.%(ext)s" ptd1NN40vMw
|
||||
test video.webm # Literal name with correct extension
|
||||
|
||||
$ yt-dlp --print filename -o "%(title)s.%(ext)s" BaW_jenozKc
|
||||
youtube-dl test video ''_ä↭𝕐.webm # All kinds of weird characters
|
||||
$ yt-dlp --print filename -o "%(title)s.%(ext)s" ptd1NN40vMw
|
||||
To'y!🤯😂🤦🏻♂️.webm # All kinds of weird characters
|
||||
|
||||
$ yt-dlp --print filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
|
||||
youtube-dl_test_video_.webm # Restricted file name
|
||||
$ yt-dlp --print filename -o "%(title)s.%(ext)s" ptd1NN40vMw --restrict-filenames
|
||||
To_y.webm # Restricted file name
|
||||
|
||||
# Download YouTube playlist videos in separate directory indexed by video order in a playlist
|
||||
$ yt-dlp -o "%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s" "https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re"
|
||||
@@ -1489,7 +1489,7 @@ $ yt-dlp -o "%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s" "https://www.y
|
||||
$ yt-dlp -o "%(upload_date>%Y)s/%(title)s.%(ext)s" "https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re"
|
||||
|
||||
# Prefix playlist index with " - " separator, but only if it is available
|
||||
$ yt-dlp -o "%(playlist_index&{} - |)s%(title)s.%(ext)s" BaW_jenozKc "https://www.youtube.com/user/TheLinuxFoundation/playlists"
|
||||
$ yt-dlp -o "%(playlist_index&{} - |)s%(title)s.%(ext)s" YE7VzlLtp-4 "https://www.youtube.com/user/TheLinuxFoundation/playlists"
|
||||
|
||||
# Download all playlists of YouTube channel/user keeping each playlist in separate directory:
|
||||
$ yt-dlp -o "%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s" "https://www.youtube.com/user/TheLinuxFoundation/playlists"
|
||||
@@ -1502,13 +1502,13 @@ $ yt-dlp -P "C:/MyVideos" -o "%(series)s/%(season_number)s - %(season)s/%(episod
|
||||
|
||||
# Download video as "C:\MyVideos\uploader\title.ext", subtitles as "C:\MyVideos\subs\uploader\title.ext"
|
||||
# and put all temporary files in "C:\MyVideos\tmp"
|
||||
$ yt-dlp -P "C:/MyVideos" -P "temp:tmp" -P "subtitle:subs" -o "%(uploader)s/%(title)s.%(ext)s" BaW_jenozKc --write-subs
|
||||
$ yt-dlp -P "C:/MyVideos" -P "temp:tmp" -P "subtitle:subs" -o "%(uploader)s/%(title)s.%(ext)s" YE7VzlLtp-4 --write-subs
|
||||
|
||||
# Download video as "C:\MyVideos\uploader\title.ext" and subtitles as "C:\MyVideos\uploader\subs\title.ext"
|
||||
$ yt-dlp -P "C:/MyVideos" -o "%(uploader)s/%(title)s.%(ext)s" -o "subtitle:%(uploader)s/subs/%(title)s.%(ext)s" BaW_jenozKc --write-subs
|
||||
$ yt-dlp -P "C:/MyVideos" -o "%(uploader)s/%(title)s.%(ext)s" -o "subtitle:%(uploader)s/subs/%(title)s.%(ext)s" YE7VzlLtp-4 --write-subs
|
||||
|
||||
# Stream the video being downloaded to stdout
|
||||
$ yt-dlp -o - BaW_jenozKc
|
||||
$ yt-dlp -o - YE7VzlLtp-4
|
||||
```
|
||||
|
||||
# FORMAT SELECTION
|
||||
@@ -1916,7 +1916,7 @@ The following extractors use this feature:
|
||||
* `dr`: dynamic range to ignore - one or more of `sdr`, `hdr10`, `dv`
|
||||
|
||||
#### instagram
|
||||
* `app_id`: The value of the `X-IG-App-ID` header used for API requests. Default is the web app ID, `936619743392459`
|
||||
* `app_id`: The value of the `X-IG-App-ID` header used for API requests. Can be the actual ID number, `ios`, or `web` (default)
|
||||
|
||||
#### niconicochannelplus
|
||||
* `max_comments`: Maximum number of comments to extract - default is `120`
|
||||
@@ -2062,7 +2062,7 @@ From a Python program, you can embed yt-dlp in a more powerful fashion, like thi
|
||||
```python
|
||||
from yt_dlp import YoutubeDL
|
||||
|
||||
URLS = ['https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
with YoutubeDL() as ydl:
|
||||
ydl.download(URLS)
|
||||
```
|
||||
@@ -2079,7 +2079,7 @@ Most likely, you'll want to use various options. For a list of options available
|
||||
import json
|
||||
import yt_dlp
|
||||
|
||||
URL = 'https://www.youtube.com/watch?v=BaW_jenozKc'
|
||||
URL = 'https://www.youtube.com/watch?v=YE7VzlLtp-4'
|
||||
|
||||
# ℹ️ See help(yt_dlp.YoutubeDL) for a list of available options and public functions
|
||||
ydl_opts = {}
|
||||
@@ -2108,7 +2108,7 @@ print('Some videos failed to download' if error_code
|
||||
```python
|
||||
import yt_dlp
|
||||
|
||||
URLS = ['https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'm4a/bestaudio/best',
|
||||
@@ -2128,7 +2128,7 @@ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
```python
|
||||
import yt_dlp
|
||||
|
||||
URLS = ['https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
|
||||
def longer_than_a_minute(info, *, incomplete):
|
||||
"""Download only videos longer than a minute (or with unknown duration)"""
|
||||
@@ -2149,7 +2149,7 @@ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
```python
|
||||
import yt_dlp
|
||||
|
||||
URLS = ['https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
|
||||
class MyLogger:
|
||||
def debug(self, msg):
|
||||
@@ -2190,7 +2190,7 @@ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
```python
|
||||
import yt_dlp
|
||||
|
||||
URLS = ['https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
|
||||
# ℹ️ See help(yt_dlp.postprocessor.PostProcessor)
|
||||
class MyCustomPP(yt_dlp.postprocessor.PostProcessor):
|
||||
@@ -2211,7 +2211,7 @@ with yt_dlp.YoutubeDL() as ydl:
|
||||
```python
|
||||
import yt_dlp
|
||||
|
||||
URLS = ['https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
|
||||
def format_selector(ctx):
|
||||
""" Select the best video and the best audio that won't result in an mkv.
|
||||
|
||||
@@ -5,9 +5,9 @@ colorama==0.4.6 ; os_name == 'nt' \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via build
|
||||
hatchling==1.29.0 \
|
||||
--hash=sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0 \
|
||||
--hash=sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6
|
||||
hatchling==1.30.1 \
|
||||
--hash=sha256:161eacafb3c6f91526e92116d21426369f2c36e98c36a864f11a96345ad4ee31 \
|
||||
--hash=sha256:eee4fd45357f72ebb3d7a42e5d72cfb5e29ed426d79e8836288926c4258d5f2e
|
||||
importlib-metadata==9.0.0 ; python_full_version < '3.10.2' \
|
||||
--hash=sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7 \
|
||||
--hash=sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc
|
||||
@@ -81,9 +81,9 @@ tomli==2.4.1 ; python_full_version < '3.11' \
|
||||
# via
|
||||
# build
|
||||
# hatchling
|
||||
trove-classifiers==2026.5.22.10 \
|
||||
--hash=sha256:01fe864225726e03efb843827ecabfe319fc4dee8dd66d65b8996cb09be46e2c \
|
||||
--hash=sha256:5477e9974e91904fb2cfa4a7581ab6e2f30c2c38d847fd00ed866080748101d5
|
||||
trove-classifiers==2026.6.1.19 \
|
||||
--hash=sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3 \
|
||||
--hash=sha256:c5132b4b61a829d11cfbd2d72e97f20a45ed6edb95e45c5efdeb5e00836b2745
|
||||
# via hatchling
|
||||
zipp==4.1.0 ; python_full_version < '3.10.2' \
|
||||
--hash=sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f \
|
||||
|
||||
@@ -274,9 +274,9 @@ curl-cffi==0.15.0 ; implementation_name == 'cpython' \
|
||||
--hash=sha256:bda66404010e9ed743b1b83c20c86f24fe21a9a6873e17479d6e67e29d8ded28 \
|
||||
--hash=sha256:ea0c67652bf6893d34ee0f82c944f37e488f6147e9421bef1771cc6545b02ded
|
||||
# via yt-dlp
|
||||
idna==3.17 \
|
||||
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
|
||||
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
|
||||
idna==3.18 \
|
||||
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
||||
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
||||
# via requests
|
||||
markdown-it-py==4.2.0 ; implementation_name == 'cpython' \
|
||||
--hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \
|
||||
|
||||
@@ -174,9 +174,9 @@ charset-normalizer==3.4.7 \
|
||||
--hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \
|
||||
--hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464
|
||||
# via requests
|
||||
idna==3.17 \
|
||||
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
|
||||
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
|
||||
idna==3.18 \
|
||||
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
||||
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
||||
# via requests
|
||||
mutagen==1.47.0 \
|
||||
--hash=sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99 \
|
||||
|
||||
+34
-34
@@ -9,17 +9,17 @@ colorama==0.4.6 ; sys_platform == 'win32' \
|
||||
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
|
||||
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
||||
# via pytest
|
||||
distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
distlib==0.4.3 \
|
||||
--hash=sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b \
|
||||
--hash=sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed
|
||||
# via virtualenv
|
||||
exceptiongroup==1.3.1 ; python_full_version < '3.11' \
|
||||
--hash=sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219 \
|
||||
--hash=sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598
|
||||
# via pytest
|
||||
filelock==3.29.0 \
|
||||
--hash=sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90 \
|
||||
--hash=sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258
|
||||
filelock==3.29.4 \
|
||||
--hash=sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a \
|
||||
--hash=sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767
|
||||
# via
|
||||
# python-discovery
|
||||
# virtualenv
|
||||
@@ -62,16 +62,16 @@ pygments==2.20.0 \
|
||||
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
|
||||
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
|
||||
# via pytest
|
||||
pytest==9.0.3 \
|
||||
--hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \
|
||||
--hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c
|
||||
pytest==9.1.0 \
|
||||
--hash=sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c \
|
||||
--hash=sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32
|
||||
# via pytest-rerunfailures
|
||||
pytest-rerunfailures==16.3 \
|
||||
--hash=sha256:37c9b1231c8083e9f4e724f50f7a21241822f9516c15c700ebbf218d6452355c \
|
||||
--hash=sha256:6bdfb8ffb46c46072e6c16bdedee38b6c13eac620d9415ed5b63152cbf283170
|
||||
python-discovery==1.4.0 \
|
||||
--hash=sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da \
|
||||
--hash=sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3
|
||||
python-discovery==1.4.2 \
|
||||
--hash=sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500 \
|
||||
--hash=sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690
|
||||
# via virtualenv
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
@@ -132,25 +132,25 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via pre-commit
|
||||
ruff==0.15.15 \
|
||||
--hash=sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622 \
|
||||
--hash=sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9 \
|
||||
--hash=sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7 \
|
||||
--hash=sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f \
|
||||
--hash=sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4 \
|
||||
--hash=sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb \
|
||||
--hash=sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4 \
|
||||
--hash=sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530 \
|
||||
--hash=sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627 \
|
||||
--hash=sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4 \
|
||||
--hash=sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c \
|
||||
--hash=sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e \
|
||||
--hash=sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6 \
|
||||
--hash=sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45 \
|
||||
--hash=sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b \
|
||||
--hash=sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f \
|
||||
--hash=sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a \
|
||||
--hash=sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd
|
||||
ruff==0.15.17 \
|
||||
--hash=sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d \
|
||||
--hash=sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7 \
|
||||
--hash=sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219 \
|
||||
--hash=sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f \
|
||||
--hash=sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f \
|
||||
--hash=sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392 \
|
||||
--hash=sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084 \
|
||||
--hash=sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed \
|
||||
--hash=sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7 \
|
||||
--hash=sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e \
|
||||
--hash=sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c \
|
||||
--hash=sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978 \
|
||||
--hash=sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8 \
|
||||
--hash=sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4 \
|
||||
--hash=sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f \
|
||||
--hash=sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719 \
|
||||
--hash=sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456 \
|
||||
--hash=sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d
|
||||
tomli==2.4.1 ; python_full_version < '3.11' \
|
||||
--hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \
|
||||
--hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \
|
||||
@@ -208,7 +208,7 @@ typing-extensions==4.15.0 ; python_full_version < '3.11' \
|
||||
# via
|
||||
# exceptiongroup
|
||||
# virtualenv
|
||||
virtualenv==21.4.2 \
|
||||
--hash=sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c \
|
||||
--hash=sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae
|
||||
virtualenv==21.5.0 \
|
||||
--hash=sha256:8f7c38605023688c89789f566959006af6d61c99eeeb9e58342eb780c5761e5e \
|
||||
--hash=sha256:98847aadf5e2037e0e4d2e19528eb3aca6f23906422e59a510bff231a6d32fce
|
||||
# via pre-commit
|
||||
|
||||
@@ -252,56 +252,53 @@ charset-normalizer==3.4.7 \
|
||||
--hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \
|
||||
--hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464
|
||||
# via requests
|
||||
cryptography==48.0.0 \
|
||||
--hash=sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13 \
|
||||
--hash=sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6 \
|
||||
--hash=sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8 \
|
||||
--hash=sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25 \
|
||||
--hash=sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c \
|
||||
--hash=sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832 \
|
||||
--hash=sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12 \
|
||||
--hash=sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c \
|
||||
--hash=sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7 \
|
||||
--hash=sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c \
|
||||
--hash=sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec \
|
||||
--hash=sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5 \
|
||||
--hash=sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355 \
|
||||
--hash=sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c \
|
||||
--hash=sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741 \
|
||||
--hash=sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86 \
|
||||
--hash=sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321 \
|
||||
--hash=sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a \
|
||||
--hash=sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7 \
|
||||
--hash=sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920 \
|
||||
--hash=sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e \
|
||||
--hash=sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff \
|
||||
--hash=sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd \
|
||||
--hash=sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3 \
|
||||
--hash=sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f \
|
||||
--hash=sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602 \
|
||||
--hash=sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855 \
|
||||
--hash=sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18 \
|
||||
--hash=sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a \
|
||||
--hash=sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336 \
|
||||
--hash=sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239 \
|
||||
--hash=sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74 \
|
||||
--hash=sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a \
|
||||
--hash=sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c \
|
||||
--hash=sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4 \
|
||||
--hash=sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c \
|
||||
--hash=sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f \
|
||||
--hash=sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4 \
|
||||
--hash=sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db \
|
||||
--hash=sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166 \
|
||||
--hash=sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5 \
|
||||
--hash=sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f \
|
||||
--hash=sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae \
|
||||
--hash=sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20 \
|
||||
--hash=sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a \
|
||||
--hash=sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057 \
|
||||
--hash=sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb \
|
||||
--hash=sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c \
|
||||
--hash=sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b
|
||||
cryptography==49.0.0 \
|
||||
--hash=sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001 \
|
||||
--hash=sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122 \
|
||||
--hash=sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6 \
|
||||
--hash=sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c \
|
||||
--hash=sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325 \
|
||||
--hash=sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69 \
|
||||
--hash=sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d \
|
||||
--hash=sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36 \
|
||||
--hash=sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc \
|
||||
--hash=sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6 \
|
||||
--hash=sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b \
|
||||
--hash=sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27 \
|
||||
--hash=sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61 \
|
||||
--hash=sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18 \
|
||||
--hash=sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db \
|
||||
--hash=sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b \
|
||||
--hash=sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb \
|
||||
--hash=sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2 \
|
||||
--hash=sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459 \
|
||||
--hash=sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e \
|
||||
--hash=sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21 \
|
||||
--hash=sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8 \
|
||||
--hash=sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7 \
|
||||
--hash=sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa \
|
||||
--hash=sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9 \
|
||||
--hash=sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db \
|
||||
--hash=sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64 \
|
||||
--hash=sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505 \
|
||||
--hash=sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5 \
|
||||
--hash=sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615 \
|
||||
--hash=sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f \
|
||||
--hash=sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866 \
|
||||
--hash=sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6 \
|
||||
--hash=sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561 \
|
||||
--hash=sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838 \
|
||||
--hash=sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9 \
|
||||
--hash=sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7 \
|
||||
--hash=sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68 \
|
||||
--hash=sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8 \
|
||||
--hash=sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3 \
|
||||
--hash=sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e \
|
||||
--hash=sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a \
|
||||
--hash=sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d \
|
||||
--hash=sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4 \
|
||||
--hash=sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493 \
|
||||
--hash=sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b
|
||||
# via secretstorage
|
||||
curl-cffi==0.15.0 ; implementation_name == 'cpython' \
|
||||
--hash=sha256:08c799b89740b9bc49c09fbc3d5907f13ac1f845ca52620507ef9466d4639dd5 \
|
||||
@@ -326,9 +323,9 @@ curl-cffi==0.15.0 ; implementation_name == 'cpython' \
|
||||
--hash=sha256:bda66404010e9ed743b1b83c20c86f24fe21a9a6873e17479d6e67e29d8ded28 \
|
||||
--hash=sha256:ea0c67652bf6893d34ee0f82c944f37e488f6147e9421bef1771cc6545b02ded
|
||||
# via yt-dlp
|
||||
idna==3.17 \
|
||||
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
|
||||
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
|
||||
idna==3.18 \
|
||||
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
||||
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
||||
# via requests
|
||||
jeepney==0.9.0 \
|
||||
--hash=sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 \
|
||||
|
||||
@@ -184,9 +184,9 @@ charset-normalizer==3.4.7 \
|
||||
delocate==0.13.0 ; sys_platform == 'darwin' \
|
||||
--hash=sha256:11f7596f88984c33f76b27fe2eea7637d1ce369a9e0b6737bbc706b6426e862c \
|
||||
--hash=sha256:a93e67a9f56ee01a3f7096a042231d4ac37fecac873cd5ea34ea2b4f43a8fa13
|
||||
idna==3.17 \
|
||||
--hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \
|
||||
--hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f
|
||||
idna==3.18 \
|
||||
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
||||
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
||||
# via requests
|
||||
macholib==1.16.4 ; sys_platform == 'darwin' \
|
||||
--hash=sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea \
|
||||
@@ -255,22 +255,22 @@ pygments==2.20.0 ; implementation_name == 'cpython' \
|
||||
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
|
||||
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
|
||||
# via rich
|
||||
pyinstaller==6.20.0 \
|
||||
--hash=sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b \
|
||||
--hash=sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9 \
|
||||
--hash=sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941 \
|
||||
--hash=sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7 \
|
||||
--hash=sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712 \
|
||||
--hash=sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e \
|
||||
--hash=sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a \
|
||||
--hash=sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c \
|
||||
--hash=sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59 \
|
||||
--hash=sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2 \
|
||||
--hash=sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e \
|
||||
--hash=sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a
|
||||
pyinstaller-hooks-contrib==2026.5 \
|
||||
--hash=sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9 \
|
||||
--hash=sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08
|
||||
pyinstaller==6.21.0 \
|
||||
--hash=sha256:327d132389f37912609e01be62810cf96b5aa95b613903e4b8692e0d12fb0eda \
|
||||
--hash=sha256:42395ec76df8e8120c36b13339d9db8cab83e316a12839ee303cc00fc941bb74 \
|
||||
--hash=sha256:4e3108b3f02384560da70e39b8bf22b0ad597d02bd68a40d76ea91c1cfa00cad \
|
||||
--hash=sha256:5fa56746c1e76f93634d018502301378a2d0c382553d37d8c3c34ff436c12dd1 \
|
||||
--hash=sha256:605169523a6b5ace39f13dfbff21add9f2bc43df99c7daf9394fefb2c45e8b6f \
|
||||
--hash=sha256:697532279f535ad572bda613db4f821540e235c7854ca6da4d3bf0373f4415ee \
|
||||
--hash=sha256:6b6374d652107dd4a2eeece903ff82bb4045bb5e1006c5a158a6dcdbefe84bf2 \
|
||||
--hash=sha256:7071d4b094d5b40deeef5fa3d3b98a1b846087f7562b49209663d5f9281fe251 \
|
||||
--hash=sha256:7fae06c494ce0ebfe6bd3055c0e409def884f63af2e3705d06bd431ad9237fc7 \
|
||||
--hash=sha256:bb9fab705983e393a2d1cac77d6972513057ad800215fd861dc15ff5272e98fd \
|
||||
--hash=sha256:c6b28d30d8fd99ce162ff3aab5013ed44dbfb747566b1f01b9bed7964d7c14e9 \
|
||||
--hash=sha256:f13c95c9c03fb567217135919f93815c305813126780b0ed6e0123cb8acaf025
|
||||
pyinstaller-hooks-contrib==2026.6 \
|
||||
--hash=sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725 \
|
||||
--hash=sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3
|
||||
# via pyinstaller
|
||||
pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
--hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \
|
||||
@@ -287,9 +287,7 @@ rich==15.0.0 ; implementation_name == 'cpython' \
|
||||
setuptools==82.0.1 \
|
||||
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
|
||||
--hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb
|
||||
# via
|
||||
# pyinstaller
|
||||
# pyinstaller-hooks-contrib
|
||||
# via pyinstaller
|
||||
typing-extensions==4.15.0 ; sys_platform == 'darwin' \
|
||||
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
|
||||
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
||||
|
||||
@@ -18,22 +18,22 @@ pefile==2024.8.26 ; sys_platform == 'win32' \
|
||||
--hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \
|
||||
--hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f
|
||||
# via pyinstaller
|
||||
pyinstaller==6.20.0 \
|
||||
--hash=sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b \
|
||||
--hash=sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9 \
|
||||
--hash=sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941 \
|
||||
--hash=sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7 \
|
||||
--hash=sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712 \
|
||||
--hash=sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e \
|
||||
--hash=sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a \
|
||||
--hash=sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c \
|
||||
--hash=sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59 \
|
||||
--hash=sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2 \
|
||||
--hash=sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e \
|
||||
--hash=sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a
|
||||
pyinstaller-hooks-contrib==2026.5 \
|
||||
--hash=sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9 \
|
||||
--hash=sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08
|
||||
pyinstaller==6.21.0 \
|
||||
--hash=sha256:327d132389f37912609e01be62810cf96b5aa95b613903e4b8692e0d12fb0eda \
|
||||
--hash=sha256:42395ec76df8e8120c36b13339d9db8cab83e316a12839ee303cc00fc941bb74 \
|
||||
--hash=sha256:4e3108b3f02384560da70e39b8bf22b0ad597d02bd68a40d76ea91c1cfa00cad \
|
||||
--hash=sha256:5fa56746c1e76f93634d018502301378a2d0c382553d37d8c3c34ff436c12dd1 \
|
||||
--hash=sha256:605169523a6b5ace39f13dfbff21add9f2bc43df99c7daf9394fefb2c45e8b6f \
|
||||
--hash=sha256:697532279f535ad572bda613db4f821540e235c7854ca6da4d3bf0373f4415ee \
|
||||
--hash=sha256:6b6374d652107dd4a2eeece903ff82bb4045bb5e1006c5a158a6dcdbefe84bf2 \
|
||||
--hash=sha256:7071d4b094d5b40deeef5fa3d3b98a1b846087f7562b49209663d5f9281fe251 \
|
||||
--hash=sha256:7fae06c494ce0ebfe6bd3055c0e409def884f63af2e3705d06bd431ad9237fc7 \
|
||||
--hash=sha256:bb9fab705983e393a2d1cac77d6972513057ad800215fd861dc15ff5272e98fd \
|
||||
--hash=sha256:c6b28d30d8fd99ce162ff3aab5013ed44dbfb747566b1f01b9bed7964d7c14e9 \
|
||||
--hash=sha256:f13c95c9c03fb567217135919f93815c305813126780b0ed6e0123cb8acaf025
|
||||
pyinstaller-hooks-contrib==2026.6 \
|
||||
--hash=sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725 \
|
||||
--hash=sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3
|
||||
# via pyinstaller
|
||||
pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
--hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \
|
||||
@@ -42,6 +42,4 @@ pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
setuptools==82.0.1 \
|
||||
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
|
||||
--hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb
|
||||
# via
|
||||
# pyinstaller
|
||||
# pyinstaller-hooks-contrib
|
||||
# via pyinstaller
|
||||
|
||||
@@ -5,25 +5,25 @@ pycodestyle==2.14.0 \
|
||||
--hash=sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783 \
|
||||
--hash=sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d
|
||||
# via autopep8
|
||||
ruff==0.15.15 \
|
||||
--hash=sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622 \
|
||||
--hash=sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9 \
|
||||
--hash=sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7 \
|
||||
--hash=sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f \
|
||||
--hash=sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4 \
|
||||
--hash=sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb \
|
||||
--hash=sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4 \
|
||||
--hash=sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530 \
|
||||
--hash=sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627 \
|
||||
--hash=sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4 \
|
||||
--hash=sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c \
|
||||
--hash=sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e \
|
||||
--hash=sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6 \
|
||||
--hash=sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45 \
|
||||
--hash=sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b \
|
||||
--hash=sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f \
|
||||
--hash=sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a \
|
||||
--hash=sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd
|
||||
ruff==0.15.17 \
|
||||
--hash=sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d \
|
||||
--hash=sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7 \
|
||||
--hash=sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219 \
|
||||
--hash=sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f \
|
||||
--hash=sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f \
|
||||
--hash=sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392 \
|
||||
--hash=sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084 \
|
||||
--hash=sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed \
|
||||
--hash=sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7 \
|
||||
--hash=sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e \
|
||||
--hash=sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c \
|
||||
--hash=sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978 \
|
||||
--hash=sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8 \
|
||||
--hash=sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4 \
|
||||
--hash=sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f \
|
||||
--hash=sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719 \
|
||||
--hash=sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456 \
|
||||
--hash=sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d
|
||||
tomli==2.4.1 ; python_full_version < '3.11' \
|
||||
--hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \
|
||||
--hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \
|
||||
|
||||
@@ -24,9 +24,9 @@ pygments==2.20.0 \
|
||||
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
|
||||
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
|
||||
# via pytest
|
||||
pytest==9.0.3 \
|
||||
--hash=sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9 \
|
||||
--hash=sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c
|
||||
pytest==9.1.0 \
|
||||
--hash=sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c \
|
||||
--hash=sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32
|
||||
# via pytest-rerunfailures
|
||||
pytest-rerunfailures==16.3 \
|
||||
--hash=sha256:37c9b1231c8083e9f4e724f50f7a21241822f9516c15c700ebbf218d6452355c \
|
||||
|
||||
+20
-20
@@ -1,20 +1,20 @@
|
||||
uv==0.11.17 \
|
||||
--hash=sha256:036d6e2940afe8b79637530b01b9241d8cfd174b07f1179a1ebbd42409c38ca3 \
|
||||
--hash=sha256:12b701fa32c5be3691759a73956e4462f30fa7b0dfa52ec66cb305bbb6ea4129 \
|
||||
--hash=sha256:1a817eeb3026f27a53d3f4b7855a5105f6787dd192140e201eda4d2b9a11b72e \
|
||||
--hash=sha256:1d1be74deec997db1dda05a7e67541c904d65cbfd72e455d3c0a2a1e4bf2cddf \
|
||||
--hash=sha256:283186700c3e65a4644a73a917232da7d3e4a94d25ea0377a44f5b263fa49577 \
|
||||
--hash=sha256:2ccd5487a4a192bc832ea04c867a26883757db8fdfe88bed85d8129c82f9e505 \
|
||||
--hash=sha256:37c915bfcf86f99c1c5be7c9ed21e0d80624067ba47bc8916a3cb0530bc94d27 \
|
||||
--hash=sha256:44ec1fe3af839f87370dcf0400c0cab917cc1ce697d563e860fc7d9ed72655e7 \
|
||||
--hash=sha256:58c07ffc272c847d29cd98ca5082fa4304a645f87c718ec900e3cca9026bd096 \
|
||||
--hash=sha256:659227cac719b618cc91e02be9e274ad5bd72d74fa278123e6373537e9f28216 \
|
||||
--hash=sha256:6d1a033cc68cabb4141d6c1e3b66ffc6e970b98ba42e210f33270251e0bd8697 \
|
||||
--hash=sha256:8426bfe315564d414cbc5ba5467595dc6348965e19acec742914f47da3ff269f \
|
||||
--hash=sha256:9da839e5a491c9a701d7d327a199cafc76ac27a03ac84fd2a8d4bf32c3af2448 \
|
||||
--hash=sha256:bf8f5ad959583dcd2c4ae445c754a97c05700246ff89259f3fd285c9c20f4c00 \
|
||||
--hash=sha256:ce16892a45134d20165c1ceababe06f3e9ce6a58902db1eff812c8c93626823f \
|
||||
--hash=sha256:e301d844eed9401f0f0351de12c55f1306ca05372acb0f28d35717c8ba663a22 \
|
||||
--hash=sha256:ec004b3c9bf9cb7756067ad1bd0bf64eb843e6fa2edbfbb3135ee152c14cea91 \
|
||||
--hash=sha256:f0bf483c0d9fa14283992d56061b498b9d3d4adebd285af8744dc33f64dadfba \
|
||||
--hash=sha256:f2e44dfbfc7778d0d90edc6738f237c91e5e37e4e3cfe94c8a312cec56a41485
|
||||
uv==0.11.21 \
|
||||
--hash=sha256:00193e4e077c27ee3d66da356744dbf0b3aa59356dfbd9a9efb1dc8469af8ad7 \
|
||||
--hash=sha256:05e2f2e0fbf7c423f8287011ba0d2d69464f26a5f13b33df05cd491fbe5a910a \
|
||||
--hash=sha256:083882c73373a16de4c136d54e3386a52388dead5048a07505e25578b157182f \
|
||||
--hash=sha256:0e7916874f125a6f6af4cddd95f892ef19a4bb65c146afea7e544b0f98c63d02 \
|
||||
--hash=sha256:4530761c565f3a519a68f36628ee51f2b467b66573e2023e9073641219b60d23 \
|
||||
--hash=sha256:48c36eb170a5e7a668c1d13d2c8edeb017a3e6484c224f1521b540a6bda9e50b \
|
||||
--hash=sha256:4ed98ff3165bf7b339692d0df918b87e6d36eb0bed5183466330d27d5730d57b \
|
||||
--hash=sha256:525ef0eb56ff982357a321eca953307d824ab6f58473630c69521e8085f12b0a \
|
||||
--hash=sha256:66906cfa7c29c2cf4ea5117cf5614b0b83078ff669e664e2187071fcb24c85c1 \
|
||||
--hash=sha256:870f48082df673016f465b068f40ad5aa7d2d3cfbcfb4e73724630684003a2ab \
|
||||
--hash=sha256:88668a27959df9188ff72b0314f6b14f6acf6090964bb0748974239183ecb51c \
|
||||
--hash=sha256:88d8283f6ea9f0cdbb7717e6e08e916c32a8b8b7e11c72fcc6426a4c4eeb89e0 \
|
||||
--hash=sha256:9c11169a049ec8bf9ddc6a9f55fba9a240942ec8005faaaf4393f00ff7a4c16e \
|
||||
--hash=sha256:a00c78f3eea6db7967d98a505b01b7d80354517c7ff34f51701949f39c7b53e6 \
|
||||
--hash=sha256:af08e0d8f43da43bc68930aee56ca5f38ccfbc79d45b6e8a7d5051f1e975684f \
|
||||
--hash=sha256:b756dd2b368d7cc4aeb48249d06e1250bfcf81f0313ff7d7ec2ccafcd3ee4c93 \
|
||||
--hash=sha256:b9ecdefa81db7e966d1655988cad6f840316228381dd69131ebc4ae9362bbccd \
|
||||
--hash=sha256:d956ba9470d5267cc0ea3d7572cac3bf045bc78adad5b031b5558c6df13d2e19 \
|
||||
--hash=sha256:f64a851e429e6afb96f3a0b688995757ed3697bf1078509e2da8220ffc9805cd
|
||||
|
||||
@@ -18,11 +18,11 @@ pefile==2024.8.26 ; sys_platform == 'win32' \
|
||||
--hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \
|
||||
--hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f
|
||||
# via pyinstaller
|
||||
pyinstaller @ https://github.com/yt-dlp/Pyinstaller-Builds/releases/download/2026.05.02.225304/pyinstaller-6.20.0-py3-none-win_arm64.whl \
|
||||
--hash=sha256:91c40536352aaeace0a4fa7b0094d98ad6c82983eaed5a4786a1a0005904fe4c
|
||||
pyinstaller-hooks-contrib==2026.5 \
|
||||
--hash=sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9 \
|
||||
--hash=sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08
|
||||
pyinstaller @ https://github.com/yt-dlp/Pyinstaller-Builds/releases/download/2026.06.20.202648/pyinstaller-6.21.0-py3-none-win_arm64.whl \
|
||||
--hash=sha256:920d24804314db99c552950806fb355a286eb1e2244fe296efb46319b5a900c2
|
||||
pyinstaller-hooks-contrib==2026.6 \
|
||||
--hash=sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725 \
|
||||
--hash=sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3
|
||||
# via pyinstaller
|
||||
pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
--hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \
|
||||
@@ -31,6 +31,4 @@ pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
setuptools==82.0.1 \
|
||||
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
|
||||
--hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb
|
||||
# via
|
||||
# pyinstaller
|
||||
# pyinstaller-hooks-contrib
|
||||
# via pyinstaller
|
||||
|
||||
@@ -18,11 +18,11 @@ pefile==2024.8.26 ; sys_platform == 'win32' \
|
||||
--hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \
|
||||
--hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f
|
||||
# via pyinstaller
|
||||
pyinstaller @ https://github.com/yt-dlp/Pyinstaller-Builds/releases/download/2026.05.02.225304/pyinstaller-6.20.0-py3-none-win_amd64.whl \
|
||||
--hash=sha256:a05582e749265f9ea1428dad0552bd4577ab8c5acd19ae0f1b1593d476d65748
|
||||
pyinstaller-hooks-contrib==2026.5 \
|
||||
--hash=sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9 \
|
||||
--hash=sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08
|
||||
pyinstaller @ https://github.com/yt-dlp/Pyinstaller-Builds/releases/download/2026.06.20.202648/pyinstaller-6.21.0-py3-none-win_amd64.whl \
|
||||
--hash=sha256:dce4e3c9c8b20a2c5292d8f90c034cd3a854df8fdd054cc05c2a375a2edd4220
|
||||
pyinstaller-hooks-contrib==2026.6 \
|
||||
--hash=sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725 \
|
||||
--hash=sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3
|
||||
# via pyinstaller
|
||||
pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
--hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \
|
||||
@@ -31,6 +31,4 @@ pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
setuptools==82.0.1 \
|
||||
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
|
||||
--hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb
|
||||
# via
|
||||
# pyinstaller
|
||||
# pyinstaller-hooks-contrib
|
||||
# via pyinstaller
|
||||
|
||||
@@ -18,11 +18,11 @@ pefile==2024.8.26 ; sys_platform == 'win32' \
|
||||
--hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \
|
||||
--hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f
|
||||
# via pyinstaller
|
||||
pyinstaller @ https://github.com/yt-dlp/Pyinstaller-Builds/releases/download/2026.05.02.225304/pyinstaller-6.20.0-py3-none-win32.whl \
|
||||
--hash=sha256:6704e50e993c6a2faad3e369ed517bf710b4ae6b6a4def85bb50e4169265d9a4
|
||||
pyinstaller-hooks-contrib==2026.5 \
|
||||
--hash=sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9 \
|
||||
--hash=sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08
|
||||
pyinstaller @ https://github.com/yt-dlp/Pyinstaller-Builds/releases/download/2026.06.20.202648/pyinstaller-6.21.0-py3-none-win32.whl \
|
||||
--hash=sha256:e495b51a5d17b1bc2201783384e20ecfd9f292460460f0dce486ca075bce8f61
|
||||
pyinstaller-hooks-contrib==2026.6 \
|
||||
--hash=sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725 \
|
||||
--hash=sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3
|
||||
# via pyinstaller
|
||||
pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
--hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \
|
||||
@@ -31,6 +31,4 @@ pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \
|
||||
setuptools==82.0.1 \
|
||||
--hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \
|
||||
--hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb
|
||||
# via
|
||||
# pyinstaller
|
||||
# pyinstaller-hooks-contrib
|
||||
# via pyinstaller
|
||||
|
||||
@@ -32,7 +32,7 @@ VERBOSE = '''
|
||||
description: |
|
||||
It should start like this:
|
||||
placeholder: |
|
||||
[debug] Command-line config: ['-vU', 'https://www.youtube.com/watch?v=BaW_jenozKc']
|
||||
[debug] Command-line config: ['-vU', 'https://www.youtube.com/watch?v=YE7VzlLtp-4']
|
||||
[debug] Encodings: locale cp65001, fs utf-8, pref cp65001, out utf-8, error utf-8, screen utf-8
|
||||
[debug] yt-dlp version nightly@... from yt-dlp/yt-dlp-nightly-builds [1a176d874] (win_exe)
|
||||
[debug] Python 3.10.11 (CPython AMD64 64bit) - Windows-10-10.0.20348-SP0 (OpenSSL 1.1.1t 7 Feb 2023)
|
||||
@@ -44,7 +44,7 @@ VERBOSE = '''
|
||||
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
|
||||
Latest version: nightly@... from yt-dlp/yt-dlp-nightly-builds
|
||||
yt-dlp is up to date (nightly@... from yt-dlp/yt-dlp-nightly-builds)
|
||||
[youtube] Extracting URL: https://www.youtube.com/watch?v=BaW_jenozKc
|
||||
[youtube] Extracting URL: https://www.youtube.com/watch?v=YE7VzlLtp-4
|
||||
<more lines>
|
||||
render: shell
|
||||
validations:
|
||||
|
||||
+4
-3
@@ -71,7 +71,7 @@ pin = [
|
||||
"brotlicffi==1.2.0.1 ; implementation_name != 'cpython'",
|
||||
"certifi==2026.5.20",
|
||||
"charset-normalizer==3.4.7",
|
||||
"idna==3.17",
|
||||
"idna==3.18",
|
||||
"mutagen==1.47.0",
|
||||
"pycryptodomex==3.23.0",
|
||||
"requests==2.34.2",
|
||||
@@ -91,14 +91,14 @@ pin-curl-cffi = [
|
||||
]
|
||||
pin-secretstorage = [
|
||||
"cffi==2.0.0 ; platform_python_implementation != 'PyPy'",
|
||||
"cryptography==48.0.0",
|
||||
"cryptography==49.0.0",
|
||||
"jeepney==0.9.0",
|
||||
"pycparser==3.0 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'",
|
||||
"secretstorage==3.5.0",
|
||||
"typing-extensions==4.15.0 ; python_full_version < '3.11'",
|
||||
]
|
||||
pin-deno = [
|
||||
"deno==2.8.1",
|
||||
"deno==2.8.3",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
@@ -395,6 +395,7 @@ banned-from = [
|
||||
"yt_dlp.utils.bytes_to_intlist".msg = "Use `list` instead."
|
||||
"yt_dlp.utils.intlist_to_bytes".msg = "Use `bytes` instead."
|
||||
"yt_dlp.utils.jwt_encode_hs256".msg = "Use `yt_dlp.utils.jwt_encode` instead."
|
||||
"yt_dlp.utils.make_dir".msg = "Use `yt_dlp.utils.make_parent_dirs` instead."
|
||||
"yt_dlp.utils.decodeArgument".msg = "Do not use"
|
||||
"yt_dlp.utils.decodeFilename".msg = "Do not use"
|
||||
"yt_dlp.utils.encodeFilename".msg = "Do not use"
|
||||
|
||||
@@ -327,6 +327,12 @@ class TestUtil(unittest.TestCase):
|
||||
with self.assertRaises(_UnsafeExtensionError):
|
||||
prepend_extension('abc.unexpected_ext', ext, 'ext')
|
||||
|
||||
# Test allow-unsafe-ext compat option
|
||||
_UnsafeExtensionError._enabled = False
|
||||
self.assertEqual(prepend_extension('abc.ext', 'un/safe'), 'abc.un/safe.ext')
|
||||
# Re-enable sanitization for other tests
|
||||
_UnsafeExtensionError._enabled = True
|
||||
|
||||
def test_replace_extension(self):
|
||||
self.assertEqual(replace_extension('abc.ext', 'temp'), 'abc.temp')
|
||||
self.assertEqual(replace_extension('abc.ext', 'temp', 'ext'), 'abc.temp')
|
||||
@@ -345,6 +351,12 @@ class TestUtil(unittest.TestCase):
|
||||
with self.assertRaises(_UnsafeExtensionError):
|
||||
replace_extension('abc.unexpected_ext', ext, 'ext')
|
||||
|
||||
# Test allow-unsafe-ext compat option
|
||||
_UnsafeExtensionError._enabled = False
|
||||
self.assertEqual(replace_extension('abc.ext', 'bin'), 'abc.bin')
|
||||
# Re-enable sanitization for other tests
|
||||
_UnsafeExtensionError._enabled = True
|
||||
|
||||
def test_subtitles_filename(self):
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt'), 'abc.en.vtt')
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt', 'ext'), 'abc.en.vtt')
|
||||
@@ -1393,7 +1405,11 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(parse_resolution('1920×1080 '), {'width': 1920, 'height': 1080})
|
||||
self.assertEqual(parse_resolution('1920 x 1080'), {'width': 1920, 'height': 1080})
|
||||
self.assertEqual(parse_resolution('720p'), {'height': 720})
|
||||
self.assertEqual(parse_resolution('1080p60'), {'height': 1080})
|
||||
self.assertEqual(parse_resolution('1080p120', parse_fps=True), {'height': 1080, 'fps': 120})
|
||||
self.assertEqual(parse_resolution('4k'), {'height': 2160})
|
||||
self.assertEqual(parse_resolution('4K60'), {'height': 2160})
|
||||
self.assertEqual(parse_resolution('4K120', parse_fps=True), {'height': 2160, 'fps': 120})
|
||||
self.assertEqual(parse_resolution('8K'), {'height': 4320})
|
||||
self.assertEqual(parse_resolution('pre_1920x1080_post'), {'width': 1920, 'height': 1080})
|
||||
self.assertEqual(parse_resolution('ep1x2'), {})
|
||||
@@ -2160,6 +2176,10 @@ Line 1
|
||||
headers6 = HTTPHeaderDict(a=1, b=2)
|
||||
self.assertEqual(pickle.loads(pickle.dumps(headers6)), headers6)
|
||||
|
||||
headers7 = HTTPHeaderDict()
|
||||
headers7 |= {'X-dlp': 'data'}
|
||||
self.assertEqual(headers7.sensitive(), {'X-dlp': 'data'})
|
||||
|
||||
def test_extract_basic_auth(self):
|
||||
assert extract_basic_auth('http://:foo.bar') == ('http://:foo.bar', None)
|
||||
assert extract_basic_auth('http://foo.bar') == ('http://foo.bar', None)
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Allow direct execution
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
from test.helper import FakeYDL, is_download_test
|
||||
from yt_dlp.extractor import YoutubeIE, YoutubeTabIE
|
||||
from yt_dlp.utils import ExtractorError
|
||||
|
||||
|
||||
@is_download_test
|
||||
class TestYoutubeLists(unittest.TestCase):
|
||||
def assertIsPlaylist(self, info):
|
||||
"""Make sure the info has '_type' set to 'playlist'"""
|
||||
self.assertEqual(info['_type'], 'playlist')
|
||||
|
||||
def test_youtube_playlist_noplaylist(self):
|
||||
dl = FakeYDL()
|
||||
dl.params['noplaylist'] = True
|
||||
ie = YoutubeTabIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/watch?v=OmJ-4B-mS-Y&list=PLydZ2Hrp_gPRJViZjLFKaBMgCQOYEEkyp&index=2')
|
||||
self.assertEqual(result['_type'], 'url')
|
||||
self.assertEqual(result['ie_key'], YoutubeIE.ie_key())
|
||||
self.assertEqual(YoutubeIE.extract_id(result['url']), 'OmJ-4B-mS-Y')
|
||||
|
||||
def test_youtube_mix(self):
|
||||
dl = FakeYDL()
|
||||
ie = YoutubeTabIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/watch?v=tyITL_exICo&list=RDCLAK5uy_kLWIr9gv1XLlPbaDS965-Db4TrBoUTxQ8')
|
||||
entries = list(result['entries'])
|
||||
self.assertTrue(len(entries) >= 50)
|
||||
original_video = entries[0]
|
||||
self.assertEqual(original_video['id'], 'tyITL_exICo')
|
||||
|
||||
def test_youtube_flat_playlist_extraction(self):
|
||||
dl = FakeYDL()
|
||||
dl.params['extract_flat'] = True
|
||||
ie = YoutubeTabIE(dl)
|
||||
result = ie.extract('https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc')
|
||||
self.assertIsPlaylist(result)
|
||||
entries = list(result['entries'])
|
||||
self.assertTrue(len(entries) == 1)
|
||||
video = entries[0]
|
||||
self.assertEqual(video['_type'], 'url')
|
||||
self.assertEqual(video['ie_key'], 'Youtube')
|
||||
self.assertEqual(video['id'], 'BaW_jenozKc')
|
||||
self.assertEqual(video['url'], 'https://www.youtube.com/watch?v=BaW_jenozKc')
|
||||
self.assertEqual(video['title'], 'youtube-dl test video "\'/\\ä↭𝕐')
|
||||
self.assertEqual(video['duration'], 10)
|
||||
self.assertEqual(video['uploader'], 'Philipp Hagemeister')
|
||||
|
||||
def test_youtube_channel_no_uploads(self):
|
||||
dl = FakeYDL()
|
||||
dl.params['extract_flat'] = True
|
||||
ie = YoutubeTabIE(dl)
|
||||
# no uploads
|
||||
with self.assertRaisesRegex(ExtractorError, r'no uploads'):
|
||||
ie.extract('https://www.youtube.com/channel/UC2yXPzFejc422buOIzn_0CA')
|
||||
|
||||
# no uploads and no UCID given
|
||||
with self.assertRaisesRegex(ExtractorError, r'no uploads'):
|
||||
ie.extract('https://www.youtube.com/news')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -9,6 +9,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
from yt_dlp.extractor import YoutubeIE
|
||||
from yt_dlp.extractor.youtube._base import YoutubeBaseInfoExtractor
|
||||
|
||||
|
||||
class TestYoutubeMisc(unittest.TestCase):
|
||||
@@ -21,6 +22,42 @@ class TestYoutubeMisc(unittest.TestCase):
|
||||
assertExtractId('http://www.youtube.com/watch?v=BaW_jenozKcsharePLED17F32AD9753930', 'BaW_jenozKc')
|
||||
assertExtractId('BaW_jenozKc', 'BaW_jenozKc')
|
||||
|
||||
def test_extract_relative_time(self):
|
||||
ert = YoutubeBaseInfoExtractor.extract_relative_time
|
||||
|
||||
# Abbreviated forms must equal their long-form equivalents.
|
||||
self.assertEqual(ert('5d ago'), ert('5 days ago'))
|
||||
self.assertEqual(ert('1mo ago'), ert('1 month ago'))
|
||||
self.assertEqual(ert('2mo ago'), ert('2 months ago'))
|
||||
self.assertEqual(ert('1y ago'), ert('1 year ago'))
|
||||
self.assertEqual(ert('1yr ago'), ert('1 year ago'))
|
||||
self.assertEqual(ert('3w ago'), ert('3 weeks ago'))
|
||||
self.assertEqual(ert('3wk ago'), ert('3 weeks ago'))
|
||||
|
||||
self.assertIsNotNone(ert('30s ago'))
|
||||
self.assertIsNotNone(ert('30sec ago'))
|
||||
self.assertIsNotNone(ert('10min ago'))
|
||||
self.assertIsNotNone(ert('5h ago'))
|
||||
self.assertIsNotNone(ert('5hr ago'))
|
||||
|
||||
self.assertIsNotNone(ert('today'))
|
||||
self.assertIsNotNone(ert('yesterday'))
|
||||
self.assertIsNotNone(ert('now'))
|
||||
|
||||
self.assertEqual(ert('5 days ago'), ert('5 day ago'))
|
||||
|
||||
self.assertIsNotNone(ert('streamed 6 days ago'))
|
||||
self.assertIsNotNone(ert('5 seconds ago (edited)'))
|
||||
self.assertIsNotNone(ert('updated today'))
|
||||
self.assertIsNotNone(ert('8 yr ago'))
|
||||
|
||||
self.assertIsNone(ert('not a date string'))
|
||||
self.assertIsNone(ert(''))
|
||||
|
||||
# Small safety check to prevent "drift".
|
||||
for unit in YoutubeBaseInfoExtractor._RELATIVE_TIME_UNIT_MAP:
|
||||
self.assertIsNotNone(ert(f'1 {unit} ago'), f'unit {unit!r} did not parse')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -351,62 +351,59 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "48.0.0"
|
||||
version = "49.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/d2/024b5e06be9d44cb021fb0e1a03d34d63989cf56a0fe62f3dfbab695b9b4/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855", size = 3950391, upload-time = "2026-05-04T22:59:17.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -458,24 +455,24 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "deno"
|
||||
version = "2.8.1"
|
||||
version = "2.8.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/eb/b743a520cdd668e070a4535296123f1c62d054b00699f58e49c32ab5925c/deno-2.8.1.tar.gz", hash = "sha256:fb65e568bef30b1a7e63f033713f1a6792a8456e339febdb7d638c6bb2c4c008", size = 8167, upload-time = "2026-05-27T13:01:06.508Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/ec/98f972cca4ca734534947bdf28b86a99e9ed8a5a31a061be2dec9b4105c2/deno-2.8.3.tar.gz", hash = "sha256:5413f4a1814ac3b8e441ca7fe9eb677a4152a42eef05efd56a61d750088a7fc8", size = 8163, upload-time = "2026-06-11T16:13:53.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/71/f9dc8ad874dcc26cf1a154c8f89d77e1155ced1f6a64be9d21127bd555ce/deno-2.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:733e24e4883c9516534cae7a10277048ffea7cc034f9f726e8c145d48ba75d19", size = 42749443, upload-time = "2026-05-27T13:00:49.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/db/1721aca1a9bd132a3f721bd547022534fcdd6221701561c5e33705cdfb6d/deno-2.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b4638504f3730f9b25229db08d1bce87bc64f52498f9f8f5aeba702c7ff2115", size = 39460813, upload-time = "2026-05-27T13:00:52.578Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/20/b50646f865562b8f21532cad6f9804b126efd4030cfd0c5e1d11217b15ca/deno-2.8.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:91f29a2df4cb6135872d68f38005c08fe0c389c84cb7349d7399eedbfbe19829", size = 43893026, upload-time = "2026-05-27T13:00:55.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/1e/75b84e7096b53f077ac01f9b5c979b7b42b0a6497f56d7c4ae072381e059/deno-2.8.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:f0958910ffa88f6b6e142129ab34b7a3aec361fc63904ff803ce1594beca230a", size = 46038250, upload-time = "2026-05-27T13:00:59.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/d6/14f6cf25025644bb8b7d9b4606780b5ec6ee429a55c0d1f05681718c7fc0/deno-2.8.1-py3-none-win_amd64.whl", hash = "sha256:71ec55c0a0944beee376aa824722734cf3e617661bca5e143caa83991921e4f5", size = 41962337, upload-time = "2026-05-27T13:01:03.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/42/71f88d6c04c7f4335f94c1c9eb5aee7e928e1a53056bb9b20c0c4d889f48/deno-2.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:57825615e8d4182160401b56907b2e36712aed55a5dfdef2c0e3089379bb6988", size = 42314540, upload-time = "2026-06-11T16:13:39.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/95/032101e28532fa9ed28ca7dc737934d4631544fa3b11f4efe3573970a05a/deno-2.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:05dbbbd8edf1907abcbcf55395e63f00d74c2672b6039dd1ecd5656baebaa56c", size = 38101511, upload-time = "2026-06-11T16:13:42.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/24/68c1d2d79933738acb8e5ef3a4584f57c6d5deb56c84073a5f4079c24647/deno-2.8.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:9fdb7574a6437fdd59f668e745dc3b3e32725fa360b64204a6b000689fa534e9", size = 42053660, upload-time = "2026-06-11T16:13:45.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/56/0d23c6daa1b139c31591df801feda3ac7d0d6225f1686cbf5caaa5db5c27/deno-2.8.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:9ca5727e5650f8459f39875f0af4b2242ead77da4ce8a8d52e86647036f3d63a", size = 43800396, upload-time = "2026-06-11T16:13:47.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/aa/e35b736205b89c98f31ed4abeebaa7846774e51006b14601e4edb7728e67/deno-2.8.3-py3-none-win_amd64.whl", hash = "sha256:37da75ee91448e4e6f5626f0d5cb18e295dbb6cff5cf780fab1f92ebbbd44071", size = 41552180, upload-time = "2026-06-11T16:13:50.711Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
version = "0.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -492,16 +489,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.29.0"
|
||||
version = "3.29.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hatchling"
|
||||
version = "1.29.0"
|
||||
version = "1.30.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
@@ -510,9 +507,9 @@ dependencies = [
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "trove-classifiers" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/4c/8717ccb844b4fa5a5ba6352e97d743ed24e9a22cf90b7c109c17030a46a1/hatchling-1.30.1.tar.gz", hash = "sha256:eee4fd45357f72ebb3d7a42e5d72cfb5e29ed426d79e8836288926c4258d5f2e", size = 56929, upload-time = "2026-06-02T00:09:41.487Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl", hash = "sha256:161eacafb3c6f91526e92116d21426369f2c36e98c36a864f11a96345ad4ee31", size = 77489, upload-time = "2026-06-02T00:09:40.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -526,11 +523,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.17"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -757,7 +754,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller"
|
||||
version = "6.20.0"
|
||||
version = "6.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "altgraph" },
|
||||
@@ -768,32 +765,31 @@ dependencies = [
|
||||
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/60/d03d52e6690d4e9caf333dcd14550cde634ce6c118b3bc8fa3112c3186fd/pyinstaller-6.20.0.tar.gz", hash = "sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a", size = 4048728, upload-time = "2026-04-22T20:59:36.96Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/4d/ec706c3fcf39e26888c35b39615ff4d5865d184069666c47492cff1fbe50/pyinstaller-6.21.0.tar.gz", hash = "sha256:bb9fab705983e393a2d1cac77d6972513057ad800215fd861dc15ff5272e98fd", size = 4061519, upload-time = "2026-06-13T14:15:06.25Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/e4/e228d6d1bbb7fd62dc660a8fb202a583b023d3a3624ca95d1a9290ee4d6a/pyinstaller-6.20.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e", size = 1047642, upload-time = "2026-04-22T20:58:32.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/bd/afb631bcb3f9040efebd4f6d067f0828b51710818f69fb41a2d4b7787f52/pyinstaller-6.20.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712", size = 742494, upload-time = "2026-04-22T20:58:36.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/08/0729a5bac14754150e5d83b39d87d842eb42b0bffcaa03dbad6252e23a39/pyinstaller-6.20.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941", size = 754191, upload-time = "2026-04-22T20:58:40.603Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/82/bc0ee4c7b97db1958eb651e0da9fb1e672e5ae53ca8867fd97701de52906/pyinstaller-6.20.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e", size = 751902, upload-time = "2026-04-22T20:58:44.7Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e7/770002d6aaa54173881cb2c49bb195ba67b97bf39bac1cdf320f28401629/pyinstaller-6.20.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59", size = 748634, upload-time = "2026-04-22T20:58:48.579Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/db/68ba1fccb71278b2124fb90b37b7c8c0bc4c1173fba45b94466df3d9cb7f/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b", size = 748490, upload-time = "2026-04-22T20:58:52.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/0f/ac77ffa996a56be3d5c8f85734a007f8347240691657f9704e7de2527fa3/pyinstaller-6.20.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7", size = 747650, upload-time = "2026-04-22T20:58:57.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/56/1ee91c3a2bc10ca1f36da10a6fd55ff7efc4dec367171eb25992a827874f/pyinstaller-6.20.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9", size = 747413, upload-time = "2026-04-22T20:59:01.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/55/ae264339996953c4cdf9d89d916a0a8fa26a83cf917a742fff8b9d5f3fe8/pyinstaller-6.20.0-py3-none-win32.whl", hash = "sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c", size = 1331584, upload-time = "2026-04-22T20:59:07.201Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/8c/300f57578882cce259bfb5ae56fda3b69caa3fe9df40a176c719920ea6e2/pyinstaller-6.20.0-py3-none-win_amd64.whl", hash = "sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a", size = 1391851, upload-time = "2026-04-22T20:59:14.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ea/b2f8e1642aecda78c0b75c7321f708e49e10bb3c00dd4f148c40761a1527/pyinstaller-6.20.0-py3-none-win_arm64.whl", hash = "sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2", size = 1332259, upload-time = "2026-04-22T20:59:20.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/4a/53cf98bf66daed012dc9cd78c8203f19a675d696f2fc12afcf8c5049a0e0/pyinstaller-6.21.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:327d132389f37912609e01be62810cf96b5aa95b613903e4b8692e0d12fb0eda", size = 1052350, upload-time = "2026-06-13T14:13:55.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/83/b591295c352ef464c50b4c6ffff1c4f771d875c9e833f578d1b9f564f6b3/pyinstaller-6.21.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7071d4b094d5b40deeef5fa3d3b98a1b846087f7562b49209663d5f9281fe251", size = 748477, upload-time = "2026-06-13T14:14:00.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/8f/88fff4e403873b1e22286911350e75ff00db014aa08e57045da9d4328993/pyinstaller-6.21.0-py3-none-manylinux2014_i686.whl", hash = "sha256:6b6374d652107dd4a2eeece903ff82bb4045bb5e1006c5a158a6dcdbefe84bf2", size = 760877, upload-time = "2026-06-13T14:14:04.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/13/f0e48fbdfd1d05d948157121cea8b1b823dcb89efe6934b71fdd8bdb3f0f/pyinstaller-6.21.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4e3108b3f02384560da70e39b8bf22b0ad597d02bd68a40d76ea91c1cfa00cad", size = 759194, upload-time = "2026-06-13T14:14:10.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/d5/ea7878cf9924ed30d946d8288777424e6d069d94f5bde56b4d0890069664/pyinstaller-6.21.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:697532279f535ad572bda613db4f821540e235c7854ca6da4d3bf0373f4415ee", size = 754979, upload-time = "2026-06-13T14:14:15.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/09/51b8905714b733bac66dbc041a7821372d70d888d273ae474c4037d4202d/pyinstaller-6.21.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:605169523a6b5ace39f13dfbff21add9f2bc43df99c7daf9394fefb2c45e8b6f", size = 754812, upload-time = "2026-06-13T14:14:20.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/43/d77779439d8c6c2e27a77bcfbd1d5cc0f568ebb611bb472b11af81b5f177/pyinstaller-6.21.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5fa56746c1e76f93634d018502301378a2d0c382553d37d8c3c34ff436c12dd1", size = 753887, upload-time = "2026-06-13T14:14:25.268Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/8f/c22df1f6837784ac349057ba693f08e7b1ca7a0e06f9c33c63bc6280007b/pyinstaller-6.21.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:42395ec76df8e8120c36b13339d9db8cab83e316a12839ee303cc00fc941bb74", size = 753779, upload-time = "2026-06-13T14:14:29.445Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/76/1ce8a27ce62ba8cf3a87c9ce6d575610f4e55d7cb0123e7512fc3f4b921a/pyinstaller-6.21.0-py3-none-win32.whl", hash = "sha256:c6b28d30d8fd99ce162ff3aab5013ed44dbfb747566b1f01b9bed7964d7c14e9", size = 1336462, upload-time = "2026-06-13T14:14:35.785Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/fa/ca1d7e5257dd8566a9dfc0dfb02f8a8075eeb53d4b2d3c579f1276759042/pyinstaller-6.21.0-py3-none-win_amd64.whl", hash = "sha256:7fae06c494ce0ebfe6bd3055c0e409def884f63af2e3705d06bd431ad9237fc7", size = 1397487, upload-time = "2026-06-13T14:14:42.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/75/21b51523ce8d96629b71311775a0a65f5f5a872124ab0de33e5c848f8bff/pyinstaller-6.21.0-py3-none-win_arm64.whl", hash = "sha256:f13c95c9c03fb567217135919f93815c305813126780b0ed6e0123cb8acaf025", size = 1346094, upload-time = "2026-06-13T14:14:48.914Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller-hooks-contrib"
|
||||
version = "2026.5"
|
||||
version = "2026.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/67/f4452d68793fb15beba4f19ef39a38a8822f0da7452b503c400d5a21f5c1/pyinstaller_hooks_contrib-2026.5.tar.gz", hash = "sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08", size = 173689, upload-time = "2026-05-04T22:36:55.124Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/5b/c9fe0db5e83ee1c39b2258fa21d23b15e1a60786b6c5990ee5074ead8bb6/pyinstaller_hooks_contrib-2026.6.tar.gz", hash = "sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725", size = 173354, upload-time = "2026-06-08T22:37:16.152Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/5c/fd465d11da4d12b50d7eb5d2ee2ceb780d8d049dbb489f3828d131e387af/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl", hash = "sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9", size = 457314, upload-time = "2026-05-04T22:36:53.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/31/f2d7343d8ed5f7c4678377886f6ce533e6eaaa131b252ce950114c2a7efa/pyinstaller_hooks_contrib-2026.6-py3-none-any.whl", hash = "sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3", size = 457159, upload-time = "2026-06-08T22:37:14.722Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -807,7 +803,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
version = "9.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -818,9 +814,9 @@ dependencies = [
|
||||
{ name = "pygments" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -838,15 +834,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-discovery"
|
||||
version = "1.4.0"
|
||||
version = "1.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -952,27 +948,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.15"
|
||||
version = "0.15.17"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1053,11 +1049,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "trove-classifiers"
|
||||
version = "2026.5.22.10"
|
||||
version = "2026.6.1.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/b6/1c41aa221b157b624ea1a72e975404ef228724d249011ee411ac211a615e/trove_classifiers-2026.5.22.10.tar.gz", hash = "sha256:5477e9974e91904fb2cfa4a7581ab6e2f30c2c38d847fd00ed866080748101d5", size = 17061, upload-time = "2026-05-22T10:17:28.99Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/e3/7ca82ee24c82d344584abd5b8637b3bd056f2900226e8d82fc22f1184b92/trove_classifiers-2026.6.1.19.tar.gz", hash = "sha256:c5132b4b61a829d11cfbd2d72e97f20a45ed6edb95e45c5efdeb5e00836b2745", size = 17059, upload-time = "2026-06-01T19:41:34.649Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/02/9a14d3048ffa4f45b7c60956a9b22688dd925d6de50f6baf7e55f3664942/trove_classifiers-2026.5.22.10-py3-none-any.whl", hash = "sha256:01fe864225726e03efb843827ecabfe319fc4dee8dd66d65b8996cb09be46e2c", size = 14225, upload-time = "2026-05-22T10:17:27.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl", hash = "sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3", size = 14211, upload-time = "2026-06-01T19:41:33.434Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1080,33 +1076,33 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "uv"
|
||||
version = "0.11.17"
|
||||
version = "0.11.21"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2c/8e/ec34c19d0f254fcbcc5c1ce8c7f06e47e0f69a7e1a0269c1d59cb0b0f279/uv-0.11.17.tar.gz", hash = "sha256:1d1be74deec997db1dda05a7e67541c904d65cbfd72e455d3c0a2a1e4bf2cddf", size = 4203607, upload-time = "2026-05-28T20:39:47.707Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/f9/f45bb1c251962ee614afd58ccd3dc06ada7869d04987efc2858a81cc4e0f/uv-0.11.21.tar.gz", hash = "sha256:083882c73373a16de4c136d54e3386a52388dead5048a07505e25578b157182f", size = 4259001, upload-time = "2026-06-11T18:18:26.468Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/2e/e6d42f9d39009eee976f1e5dfd31d3d1943e6e593ad7b191cf11e9744a36/uv-0.11.17-py3-none-linux_armv6l.whl", hash = "sha256:8426bfe315564d414cbc5ba5467595dc6348965e19acec742914f47da3ff269f", size = 23551216, upload-time = "2026-05-28T20:39:05.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/ee/d72bcc60f3585653a4b768425854d737d98d65c1765547d25c2999547ea9/uv-0.11.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6d1a033cc68cabb4141d6c1e3b66ffc6e970b98ba42e210f33270251e0bd8697", size = 22997377, upload-time = "2026-05-28T20:39:25.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/34/1bc69798d9ae998fbc42c61b02883f2ba00d04bdd858e589604d01846287/uv-0.11.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:58c07ffc272c847d29cd98ca5082fa4304a645f87c718ec900e3cca9026bd096", size = 21630197, upload-time = "2026-05-28T20:39:28.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/93/1be48ec6a8933d9a77d0ce5240ed63f68869f68517ccf5d62268ed03f3e8/uv-0.11.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:036d6e2940afe8b79637530b01b9241d8cfd174b07f1179a1ebbd42409c38ca3", size = 23414940, upload-time = "2026-05-28T20:39:55.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/31/b7488ff49d80090ea9d05d67a4d381a1b4479502e9853e654caa1c1c678e/uv-0.11.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:283186700c3e65a4644a73a917232da7d3e4a94d25ea0377a44f5b263fa49577", size = 23096330, upload-time = "2026-05-28T20:39:01.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/95/42b6137c5de06278d229c7eef2f314df2a738cd799795bbb44dace21bd6e/uv-0.11.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2e44dfbfc7778d0d90edc6738f237c91e5e37e4e3cfe94c8a312cec56a41485", size = 23101906, upload-time = "2026-05-28T20:39:17.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/7c/0ca03b2d19965db6d5dfe0c8cf96a3d0b424503c8cbc3cd2ffdc5869a15d/uv-0.11.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a817eeb3026f27a53d3f4b7855a5105f6787dd192140e201eda4d2b9a11b72e", size = 24444409, upload-time = "2026-05-28T20:39:59.218Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/fb/179f55a3b19d47c30ec1f41b9b964da74dfa7053ff310a70a9c4d8cb998d/uv-0.11.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf8f5ad959583dcd2c4ae445c754a97c05700246ff89259f3fd285c9c20f4c00", size = 25540153, upload-time = "2026-05-28T20:39:09.535Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/29/592f42012765c43ae45c112110e214bca7b0cfc08c4c1b52e1dfa47dedd5/uv-0.11.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce16892a45134d20165c1ceababe06f3e9ce6a58902db1eff812c8c93626823f", size = 24665906, upload-time = "2026-05-28T20:39:41.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/51/b75808766f895248553c6370968509cd4f726e6943e310a8f7a171036ad0/uv-0.11.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da839e5a491c9a701d7d327a199cafc76ac27a03ac84fd2a8d4bf32c3af2448", size = 24863325, upload-time = "2026-05-28T20:39:51.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/6a/6f27ee69e97f480104bb8ec335f04c2a12add98edfcc4844a68e9538b6e2/uv-0.11.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ec004b3c9bf9cb7756067ad1bd0bf64eb843e6fa2edbfbb3135ee152c14cea91", size = 23521674, upload-time = "2026-05-28T20:38:55.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/11/1344aca7c710f794750f74de0e552a54ab24193ecc01fa3b3ae22ff822a1/uv-0.11.17-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:659227cac719b618cc91e02be9e274ad5bd72d74fa278123e6373537e9f28216", size = 24224725, upload-time = "2026-05-28T20:39:32.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/44/7b11550c1453ea13b81e549c83523e6ab6ed3231d09b2fd6b9eb19acceaf/uv-0.11.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e301d844eed9401f0f0351de12c55f1306ca05372acb0f28d35717c8ba663a22", size = 24301643, upload-time = "2026-05-28T20:39:45.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/36/8f683bc60547b8f93d0e752a8574d13fad776999cb978482b360c053ca22/uv-0.11.17-py3-none-musllinux_1_1_i686.whl", hash = "sha256:f0bf483c0d9fa14283992d56061b498b9d3d4adebd285af8744dc33f64dadfba", size = 23786049, upload-time = "2026-05-28T20:39:20.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/dc/7a495db39c2970de4fa375c337dbd617b16780911f88f0511f8fe7f6747c/uv-0.11.17-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2ccd5487a4a192bc832ea04c867a26883757db8fdfe88bed85d8129c82f9e505", size = 25049786, upload-time = "2026-05-28T20:40:03.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/dd/74eff72d749eaf7e19f489878e21a368a7fef58d26ea0c63ec044ecd78b1/uv-0.11.17-py3-none-win32.whl", hash = "sha256:12b701fa32c5be3691759a73956e4462f30fa7b0dfa52ec66cb305bbb6ea4129", size = 22479213, upload-time = "2026-05-28T20:39:13.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/99/8af4a92b99a8a4823297c26df727fe957267e03e1196e3caa803c3f6ccb2/uv-0.11.17-py3-none-win_amd64.whl", hash = "sha256:44ec1fe3af839f87370dcf0400c0cab917cc1ce697d563e860fc7d9ed72655e7", size = 25083161, upload-time = "2026-05-28T20:40:07.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/76/a689077832d585d29d87f9cd0d65eca1af58abd29a4eab004d0a8a858b9c/uv-0.11.17-py3-none-win_arm64.whl", hash = "sha256:37c915bfcf86f99c1c5be7c9ed21e0d80624067ba47bc8916a3cb0530bc94d27", size = 23544936, upload-time = "2026-05-28T20:39:37.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/a5/1c863b931f3aba6e07547929b8cb45875038de00678bfd2fbabcd76faeef/uv-0.11.21-py3-none-linux_armv6l.whl", hash = "sha256:48c36eb170a5e7a668c1d13d2c8edeb017a3e6484c224f1521b540a6bda9e50b", size = 23747368, upload-time = "2026-06-11T18:19:21.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/8c/66d22f9152a014fbb17b1308394efe274e860b8beb4933f051396f96dd9f/uv-0.11.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:88d8283f6ea9f0cdbb7717e6e08e916c32a8b8b7e11c72fcc6426a4c4eeb89e0", size = 22992460, upload-time = "2026-06-11T18:18:33.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/f7/31d62c17837c9ae79cc6d5351fc5d54e8926e78b0315b4b6c187e0d1d50d/uv-0.11.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9c11169a049ec8bf9ddc6a9f55fba9a240942ec8005faaaf4393f00ff7a4c16e", size = 21762931, upload-time = "2026-06-11T18:18:41.155Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/04/c5503fc1015095db71c280526f45537f3bb06855ce281ff1761b85d149bf/uv-0.11.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:00193e4e077c27ee3d66da356744dbf0b3aa59356dfbd9a9efb1dc8469af8ad7", size = 23716032, upload-time = "2026-06-11T18:19:17.03Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/ac/46132335772fcdc38e5b5ec76701a8df8e3707605909b5fed46783689501/uv-0.11.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:870f48082df673016f465b068f40ad5aa7d2d3cfbcfb4e73724630684003a2ab", size = 23330010, upload-time = "2026-06-11T18:19:00.825Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/d4/cfa1ea36706c32006dea9bf0a819b56c22af8270ea3a2b57562ce96c2d45/uv-0.11.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af08e0d8f43da43bc68930aee56ca5f38ccfbc79d45b6e8a7d5051f1e975684f", size = 23339731, upload-time = "2026-06-11T18:18:52.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/c5/b34d3cdf05a069c583ef368e6db90242f842d7eb26b246981b3ca8799c27/uv-0.11.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4530761c565f3a519a68f36628ee51f2b467b66573e2023e9073641219b60d23", size = 24657820, upload-time = "2026-06-11T18:19:25.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/b9/89b4e3909111c14311d4a1551afb37f0669587dc1f4ae7e26ec5baea6c09/uv-0.11.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66906cfa7c29c2cf4ea5117cf5614b0b83078ff669e664e2187071fcb24c85c1", size = 25744586, upload-time = "2026-06-11T18:19:09.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/7b/51d53d9fb1aaf38a613c2d20b40583ee2aa47fc000724a00aecbd5e61431/uv-0.11.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:525ef0eb56ff982357a321eca953307d824ab6f58473630c69521e8085f12b0a", size = 24990030, upload-time = "2026-06-11T18:18:29.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/70/3347f736911b73df1f31c0823d6502891f3c49fdeb157fe8060b18c08d1c/uv-0.11.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9ecdefa81db7e966d1655988cad6f840316228381dd69131ebc4ae9362bbccd", size = 25110133, upload-time = "2026-06-11T18:19:13.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/b5/b92538042d78550626ec7ac98b525bcb81ded8605c7ca9d6e35a1454ba71/uv-0.11.21-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4ed98ff3165bf7b339692d0df918b87e6d36eb0bed5183466330d27d5730d57b", size = 23755172, upload-time = "2026-06-11T18:18:19.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/1a/5c8993f95d4384baeaf00b96df0111af3c941a34e4466cde0d52b0b6ad99/uv-0.11.21-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:0e7916874f125a6f6af4cddd95f892ef19a4bb65c146afea7e544b0f98c63d02", size = 24468447, upload-time = "2026-06-11T18:19:04.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/2c/d4db24f9aeab8fce106633cd0388df4c0cf9f0991a2b5d9f58d061a031f7/uv-0.11.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:05e2f2e0fbf7c423f8287011ba0d2d69464f26a5f13b33df05cd491fbe5a910a", size = 24564716, upload-time = "2026-06-11T18:19:29.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/53/c61711e81f9f8d34dd020340ace968499b2539d3bb4ac09d39339df54a9d/uv-0.11.21-py3-none-musllinux_1_1_i686.whl", hash = "sha256:b756dd2b368d7cc4aeb48249d06e1250bfcf81f0313ff7d7ec2ccafcd3ee4c93", size = 23917742, upload-time = "2026-06-11T18:18:57.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/21/210a5562a6a0eddfbe4890eb48e67f167be0307e75f029ca46b8f6386e5d/uv-0.11.21-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:88668a27959df9188ff72b0314f6b14f6acf6090964bb0748974239183ecb51c", size = 25330418, upload-time = "2026-06-11T18:18:37.383Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/3c/81979463de0278facaa59ed3940b9c62f25a68d737d1a6f11cc3f922fba3/uv-0.11.21-py3-none-win32.whl", hash = "sha256:a00c78f3eea6db7967d98a505b01b7d80354517c7ff34f51701949f39c7b53e6", size = 22633520, upload-time = "2026-06-11T18:18:44.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/51/e682e060813424467f14ae964dd7022f8fc537fea5803b5aab0ba1eca9cc/uv-0.11.21-py3-none-win_amd64.whl", hash = "sha256:d956ba9470d5267cc0ea3d7572cac3bf045bc78adad5b031b5558c6df13d2e19", size = 25291878, upload-time = "2026-06-11T18:18:23.832Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/ef/8b1d92f9501963ef8694bb17ad80ba9926d049240d2da0a4f879aa37f3e2/uv-0.11.21-py3-none-win_arm64.whl", hash = "sha256:f64a851e429e6afb96f3a0b688995757ed3697bf1078509e2da8220ffc9805cd", size = 23715885, upload-time = "2026-06-11T18:18:48.596Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "21.4.2"
|
||||
version = "21.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
@@ -1115,9 +1111,9 @@ dependencies = [
|
||||
{ name = "python-discovery" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/0e/933bacb37b57ae7928b0030eef205a3dbb3e37afdbdde5be2e113318958f/virtualenv-21.5.0.tar.gz", hash = "sha256:98847aadf5e2037e0e4d2e19528eb3aca6f23906422e59a510bff231a6d32fce", size = 4577424, upload-time = "2026-06-13T20:36:45.066Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/87/b0667ede418386ab631e48924b845d326f366d61e6bd08fe68a748fae4d4/virtualenv-21.5.0-py3-none-any.whl", hash = "sha256:8f7c38605023688c89789f566959006af6d61c99eeeb9e58342eb780c5761e5e", size = 4557937, upload-time = "2026-06-13T20:36:42.967Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1296,12 +1292,12 @@ requires-dist = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy' and extra == 'pin-secretstorage'", specifier = "==2.0.0" },
|
||||
{ name = "cffi", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==2.0.0" },
|
||||
{ name = "charset-normalizer", marker = "extra == 'pin'", specifier = "==3.4.7" },
|
||||
{ name = "cryptography", marker = "extra == 'pin-secretstorage'", specifier = "==48.0.0" },
|
||||
{ name = "cryptography", marker = "extra == 'pin-secretstorage'", specifier = "==49.0.0" },
|
||||
{ name = "curl-cffi", marker = "implementation_name == 'cpython' and extra == 'curl-cffi'", specifier = ">=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.16" },
|
||||
{ name = "curl-cffi", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==0.15.0" },
|
||||
{ name = "deno", marker = "extra == 'deno'", specifier = ">=2.6.6" },
|
||||
{ name = "deno", marker = "extra == 'pin-deno'", specifier = "==2.8.1" },
|
||||
{ name = "idna", marker = "extra == 'pin'", specifier = "==3.17" },
|
||||
{ name = "deno", marker = "extra == 'pin-deno'", specifier = "==2.8.3" },
|
||||
{ name = "idna", marker = "extra == 'pin'", specifier = "==3.18" },
|
||||
{ name = "jeepney", marker = "extra == 'pin-secretstorage'", specifier = "==0.9.0" },
|
||||
{ name = "markdown-it-py", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==4.2.0" },
|
||||
{ name = "mdurl", marker = "implementation_name == 'cpython' and extra == 'pin-curl-cffi'", specifier = "==0.1.2" },
|
||||
|
||||
+11
-5
@@ -139,7 +139,7 @@ from .utils import (
|
||||
join_nonempty,
|
||||
locked_file,
|
||||
make_archive_id,
|
||||
make_dir,
|
||||
make_parent_dirs,
|
||||
number_of_digits,
|
||||
orderedSet,
|
||||
orderedSet_from_options,
|
||||
@@ -1138,9 +1138,10 @@ class YoutubeDL:
|
||||
"""
|
||||
if self.params.get('logger') is not None:
|
||||
self.params['logger'].warning(message)
|
||||
elif self.params.get('no_warnings'):
|
||||
if self.params.get('verbose'):
|
||||
self.to_stderr(f'[debug:warning] {message}', only_once=only_once)
|
||||
else:
|
||||
if self.params.get('no_warnings'):
|
||||
return
|
||||
self.to_stderr(f'{self._format_err("WARNING:", self.Styles.WARNING)} {message}', only_once)
|
||||
|
||||
def deprecation_warning(self, message, *, stacklevel=0):
|
||||
@@ -2000,7 +2001,7 @@ class YoutubeDL:
|
||||
if webpage_url and webpage_url in self._playlist_urls:
|
||||
self.to_screen(
|
||||
'[download] Skipping already downloaded playlist: {}'.format(
|
||||
ie_result.get('title')) or ie_result.get('id'))
|
||||
ie_result.get('title') or ie_result.get('id')))
|
||||
return
|
||||
|
||||
self._playlist_level += 1
|
||||
@@ -2036,7 +2037,12 @@ class YoutubeDL:
|
||||
raise Exception(f'Invalid result type: {result_type}')
|
||||
|
||||
def _ensure_dir_exists(self, path):
|
||||
return make_dir(path, self.report_error)
|
||||
try:
|
||||
make_parent_dirs(path)
|
||||
return True
|
||||
except OSError as e:
|
||||
self.report_error(f'Unable to create directory: {e}')
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _playlist_infodict(ie_result, strict=False, **kwargs):
|
||||
|
||||
+1
-1
@@ -619,7 +619,7 @@ def validate_options(opts):
|
||||
warnings.append(
|
||||
'Using allow-unsafe-ext opens you up to potential attacks. '
|
||||
'Use with great care!')
|
||||
_UnsafeExtensionError.sanitize_extension = lambda x, prepend=False: x
|
||||
_UnsafeExtensionError._enabled = False
|
||||
|
||||
return warnings, deprecation_warnings
|
||||
|
||||
|
||||
+2
-2
@@ -177,7 +177,7 @@ def _extract_firefox_cookies(profile, container, logger):
|
||||
elif container == 'none':
|
||||
logger.debug('Only loading cookies not belonging to any container')
|
||||
cursor.execute(
|
||||
'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE NOT INSTR(originAttributes,"userContextId=")')
|
||||
"SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE NOT INSTR(originAttributes,'userContextId=')")
|
||||
else:
|
||||
cursor.execute('SELECT host, name, value, path, expiry, isSecure FROM moz_cookies')
|
||||
jar = YoutubeDLCookieJar()
|
||||
@@ -325,7 +325,7 @@ def _extract_chrome_cookies(browser_name, profile, keyring, logger):
|
||||
|
||||
# meta_version is necessary to determine if we need to trim the hash prefix from the cookies
|
||||
# Ref: https://chromium.googlesource.com/chromium/src/+/b02dcebd7cafab92770734dc2bc317bd07f1d891/net/extras/sqlite/sqlite_persistent_cookie_store.cc#223
|
||||
meta_version = int(cursor.execute('SELECT value FROM meta WHERE key = "version"').fetchone()[0])
|
||||
meta_version = int(cursor.execute("SELECT value FROM meta WHERE key = 'version'").fetchone()[0])
|
||||
decryptor = get_cookie_decryptor(
|
||||
config['browser_dir'], config['keyring_name'], logger,
|
||||
keyring=keyring, meta_version=meta_version)
|
||||
|
||||
@@ -209,7 +209,7 @@ class CurlFD(ExternalFD):
|
||||
return False
|
||||
|
||||
cls.exe = path
|
||||
cls._curl_version = version_tuple(parts[1])
|
||||
cls._curl_version = version_tuple(parts[1], lenient=True)
|
||||
return path
|
||||
|
||||
def _make_cmd(self, tmpfilename, info_dict):
|
||||
@@ -310,7 +310,7 @@ class Aria2cFD(ExternalFD):
|
||||
return fn if os.path.isabs(fn) else f'.{os.path.sep}{fn}'
|
||||
|
||||
def _make_cmd(self, tmpfilename, info_dict):
|
||||
cmd = [self.exe, '-c', '--no-conf',
|
||||
cmd = [self.exe, '--no-conf', '--auto-save-interval=10',
|
||||
'--console-log-level=warn', '--summary-interval=0', '--download-result=hide',
|
||||
'--http-accept-gzip=true', '--file-allocation=none', '-x16', '-j16', '-s16',
|
||||
'--min-split-size', '1M']
|
||||
@@ -325,7 +325,13 @@ class Aria2cFD(ExternalFD):
|
||||
cmd += self._bool_option('--check-certificate', 'nocheckcertificate', 'false', 'true', '=')
|
||||
cmd += self._bool_option('--remote-time', 'updatetime', 'true', 'false', '=')
|
||||
cmd += self._bool_option('--show-console-readout', 'noprogress', 'false', 'true', '=')
|
||||
cmd += self._bool_option('--remove-control-file', 'continuedl', 'false', 'true', '=')
|
||||
cmd += self._configuration_args()
|
||||
# do not allow changing these flags
|
||||
cmd += ['--allow-overwrite=true']
|
||||
cmd += ['--always-resume=false']
|
||||
cmd += ['--auto-file-renaming=false']
|
||||
cmd += ['--force-save=false']
|
||||
|
||||
# aria2c strips out spaces from the beginning/end of filenames and paths.
|
||||
# We work around this issue by adding a "./" to the beginning of the
|
||||
@@ -340,7 +346,6 @@ class Aria2cFD(ExternalFD):
|
||||
cmd += [
|
||||
'--out',
|
||||
self._aria2c_filename(os.path.basename(tmpfilename)),
|
||||
'--auto-file-renaming=false',
|
||||
'--',
|
||||
info_dict['url'],
|
||||
]
|
||||
|
||||
@@ -119,7 +119,7 @@ body > figure > img {
|
||||
fragments=fragments,
|
||||
frag_boundary=frag_boundary,
|
||||
title=title,
|
||||
)
|
||||
).encode()
|
||||
|
||||
ctx['dest_stream'].write((
|
||||
'MIME-Version: 1.0\r\n'
|
||||
@@ -135,7 +135,7 @@ body > figure > img {
|
||||
'Content-Type: text/html; charset=utf-8\r\n'
|
||||
f'Content-Length: {len(stub)}\r\n'
|
||||
'\r\n'
|
||||
f'{stub}\r\n').encode())
|
||||
).encode() + stub + b'\r\n')
|
||||
extra_state['header_written'] = True
|
||||
|
||||
for i, fragment in enumerate(fragments):
|
||||
|
||||
@@ -691,7 +691,6 @@ from .gmanetwork import GMANetworkVideoIE
|
||||
from .go import GoIE
|
||||
from .godresource import GodResourceIE
|
||||
from .godtube import GodTubeIE
|
||||
from .gofile import GofileIE
|
||||
from .golem import GolemIE
|
||||
from .goodgame import GoodGameIE
|
||||
from .googledrive import (
|
||||
@@ -1109,6 +1108,8 @@ from .mx3 import (
|
||||
)
|
||||
from .mxplayer import (
|
||||
MxplayerIE,
|
||||
MxplayerRedirectIE,
|
||||
MxplayerSeasonIE,
|
||||
MxplayerShowIE,
|
||||
)
|
||||
from .myspace import (
|
||||
@@ -2118,6 +2119,7 @@ from .unitednations import UnitedNationsWebTvIE
|
||||
from .unity import UnityIE
|
||||
from .unsupported import (
|
||||
KnownDRMIE,
|
||||
KnownLiabilityIE,
|
||||
KnownPiracyIE,
|
||||
)
|
||||
from .uol import UOLIE
|
||||
@@ -2461,5 +2463,8 @@ from .zingmp3 import (
|
||||
ZingMp3UserIE,
|
||||
ZingMp3WeekChartIE,
|
||||
)
|
||||
from .zoom import ZoomIE
|
||||
from .zoom import (
|
||||
ZoomClipsIE,
|
||||
ZoomIE,
|
||||
)
|
||||
from .zype import ZypeIE
|
||||
|
||||
@@ -70,7 +70,7 @@ class AdobeTVVideoIE(InfoExtractor):
|
||||
for fmt in fmts:
|
||||
fmt.update(traverse_obj(source, {
|
||||
'duration': ('duration', {float_or_none(scale=1000)}),
|
||||
'filesize': ('kilobytes', {float_or_none(invscale=1000)}),
|
||||
'filesize': ('kilobytes', {int_or_none(invscale=1000)}),
|
||||
'format_id': (('format', 'label'), {str}, all, {lambda x: join_nonempty(*x)}),
|
||||
'height': ('height', {int_or_none}),
|
||||
'tbr': ('bitrate', {int_or_none}),
|
||||
|
||||
@@ -13,6 +13,7 @@ from ..utils import (
|
||||
int_or_none,
|
||||
orderedSet,
|
||||
parse_iso8601,
|
||||
str_or_none,
|
||||
url_or_none,
|
||||
urlencode_postdata,
|
||||
urljoin,
|
||||
@@ -465,7 +466,7 @@ class AfreecaTVUserIE(AfreecaTVBaseIE):
|
||||
for item in info['data']:
|
||||
yield self.url_result(
|
||||
f'https://vod.sooplive.com/player/{item["title_no"]}/',
|
||||
AfreecaTVIE, item['title_no'], item.get('title_name'))
|
||||
AfreecaTVIE, str_or_none(item['title_no']), item.get('title_name'))
|
||||
|
||||
def _real_extract(self, url):
|
||||
user_id, user_type = self._match_valid_url(url).group('id', 'slug_type')
|
||||
|
||||
@@ -99,7 +99,7 @@ class AllocineIE(InfoExtractor):
|
||||
video_id = display_id
|
||||
media_data = self._download_json(
|
||||
f'http://www.allocine.fr/ws/AcVisiondataV5.ashx?media={video_id}', display_id)
|
||||
title = remove_end(strip_or_none(self._html_extract_title(webpage), ' - AlloCiné'))
|
||||
title = remove_end(strip_or_none(self._html_extract_title(webpage)), ' - AlloCiné')
|
||||
for key, value in media_data['video'].items():
|
||||
if not key.endswith('Path'):
|
||||
continue
|
||||
|
||||
@@ -155,7 +155,7 @@ class AluraCourseIE(AluraIE): # XXX: Do not subclass from concrete IE
|
||||
'_type': 'url_transparent',
|
||||
'id': self._match_id(video_url),
|
||||
'url': video_url,
|
||||
'id_key': self.ie_key(),
|
||||
'ie_key': self.ie_key(),
|
||||
'chapter': chapter,
|
||||
'chapter_number': chapter_number,
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class AMPIE(InfoExtractor): # XXX: Conventionally, base classes should end with
|
||||
'ext': ext,
|
||||
})
|
||||
|
||||
timestamp = unified_timestamp(item.get('pubDate'), ' ') or parse_iso8601(item.get('dc-date'))
|
||||
timestamp = unified_timestamp(item.get('pubDate')) or parse_iso8601(item.get('dc-date'))
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
|
||||
@@ -2,7 +2,7 @@ import random
|
||||
import urllib.parse
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError, str_or_none, try_get
|
||||
from ..utils import ExtractorError, str_or_none, try_get, url_or_none
|
||||
|
||||
|
||||
class AudiusBaseIE(InfoExtractor):
|
||||
@@ -143,6 +143,8 @@ class AudiusIE(AudiusBaseIE):
|
||||
thumbnails = []
|
||||
if isinstance(artworks_data, dict):
|
||||
for quality_key, thumbnail_url in artworks_data.items():
|
||||
if not url_or_none(thumbnail_url):
|
||||
continue
|
||||
thumbnail = {
|
||||
'url': thumbnail_url,
|
||||
}
|
||||
|
||||
@@ -421,6 +421,7 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||
'id': '224',
|
||||
'ext': 'mp3',
|
||||
'title': 'Bandcamp Weekly, 2017-04-04',
|
||||
'episode': 'Magic Moments',
|
||||
'description': 'md5:5d48150916e8e02d030623a48512c874',
|
||||
'thumbnail': 'https://f4.bcbits.com/img/9982549_0.jpg',
|
||||
'series': 'Bandcamp Weekly',
|
||||
@@ -440,22 +441,23 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||
def _real_extract(self, url):
|
||||
show_id = self._match_id(url)
|
||||
show_data = self._download_json(
|
||||
'https://bandcamp.com/api/bcradio_api/1/get_show',
|
||||
'https://bandcamp.com/api/player/2/player_data_web',
|
||||
show_id, 'Downloading radio show JSON',
|
||||
data=json.dumps({'id': show_id}).encode(),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
data=json.dumps({'item_id': int(show_id), 'item_type': 'radio'}).encode(),
|
||||
headers={'Content-Type': 'application/json'})['tracklist']
|
||||
audio_data = show_data['compiledTrack']
|
||||
|
||||
stream_url = audio_data['streamUrl']
|
||||
format_id = traverse_obj(stream_url, ({parse_qs}, 'enc', -1))
|
||||
encoding, _, bitrate_str = (format_id or '').partition('-')
|
||||
|
||||
series_title = show_data.get('title')
|
||||
series_title = show_data.get('subtitle')
|
||||
release_timestamp = unified_timestamp(show_data.get('date'))
|
||||
|
||||
return {
|
||||
'id': show_id,
|
||||
'episode_id': show_id,
|
||||
'episode': show_data.get('title'),
|
||||
'title': join_nonempty(series_title, strftime_or_none(release_timestamp, '%Y-%m-%d'), delim=', '),
|
||||
'series': series_title,
|
||||
'thumbnail': format_field(show_data, 'imageId', 'https://f4.bcbits.com/img/%s_0.jpg', default=None),
|
||||
|
||||
@@ -514,7 +514,10 @@ class BBCCoUkIE(InfoExtractor):
|
||||
value = item.get(p)
|
||||
if value and re.match(r'^[pb][\da-z]{7}$', value):
|
||||
return value
|
||||
get_from_attributes(item)
|
||||
|
||||
if programme_id := get_from_attributes(item):
|
||||
return programme_id
|
||||
|
||||
mediator = item.find(f'./{{{self._EMP_PLAYLIST_NS}}}mediator')
|
||||
if mediator is not None:
|
||||
return get_from_attributes(mediator)
|
||||
|
||||
@@ -1398,7 +1398,9 @@ class BilibiliSpaceAudioIE(BilibiliSpaceBaseIE):
|
||||
def get_entries(page_data):
|
||||
# data is None when the playlist is empty
|
||||
for entry in page_data.get('data') or []:
|
||||
yield self.url_result(f'https://www.bilibili.com/audio/au{entry["id"]}', BilibiliAudioIE, entry['id'])
|
||||
yield self.url_result(
|
||||
f'https://www.bilibili.com/audio/au{entry["id"]}',
|
||||
BilibiliAudioIE, str_or_none(entry['id']))
|
||||
|
||||
_, paged_list = self._extract_playlist(fetch_page, get_metadata, get_entries)
|
||||
return self.playlist_result(paged_list, playlist_id)
|
||||
|
||||
@@ -76,9 +76,9 @@ class BrainPOPBaseIE(InfoExtractor):
|
||||
}, note='Logging in', errnote='Unable to log in', expected_status=400)
|
||||
status_code = int_or_none(login_res['status_code'])
|
||||
if status_code != 1505:
|
||||
message = self._LOGIN_ERRORS.get(status_code) or login_res.get('message')
|
||||
self.report_warning(
|
||||
f'Unable to login: {self._LOGIN_ERRORS.get(status_code) or login_res.get("message")}'
|
||||
or f'Got status code {status_code}')
|
||||
f'Unable to login: {message}' if message else f'Got status code {status_code}')
|
||||
|
||||
|
||||
class BrainPOPIE(BrainPOPBaseIE):
|
||||
|
||||
@@ -88,7 +88,7 @@ class BYUtvIE(InfoExtractor):
|
||||
'url': video_url,
|
||||
'format_id': format_id,
|
||||
})
|
||||
merge_dicts(info, {
|
||||
info = merge_dicts(info, {
|
||||
'title': ep.get('title'),
|
||||
'description': ep.get('description'),
|
||||
'thumbnail': ep.get('imageThumbnail'),
|
||||
|
||||
+12
-15
@@ -375,6 +375,8 @@ class InfoExtractor:
|
||||
favorite by the video uploader
|
||||
* "is_pinned" - Whether the comment is pinned to
|
||||
the top of the comments
|
||||
* "start_time" - Start time (in seconds) for displaying the comment
|
||||
* "end_time" - End time (in seconds) for displaying the comment
|
||||
age_limit: Age restriction for the video, as an integer (years)
|
||||
webpage_url: The URL to the video webpage, if given to yt-dlp it
|
||||
should allow to get the same result again. (It will be set
|
||||
@@ -875,13 +877,8 @@ class InfoExtractor:
|
||||
else:
|
||||
self._downloader._first_webpage_request = False
|
||||
|
||||
if note is None:
|
||||
self.report_download_webpage(video_id)
|
||||
elif note is not False:
|
||||
if video_id is None:
|
||||
self.to_screen(str(note))
|
||||
else:
|
||||
self.to_screen(f'{video_id}: {note}')
|
||||
if note is not False:
|
||||
self.report_download_webpage(video_id, note=note)
|
||||
|
||||
# Some sites check X-Forwarded-For HTTP header in order to figure out
|
||||
# the origin of the client behind proxy. This allows bypassing geo
|
||||
@@ -920,9 +917,9 @@ class InfoExtractor:
|
||||
|
||||
errmsg = f'{errnote}: {err}'
|
||||
if fatal:
|
||||
raise ExtractorError(errmsg, cause=err)
|
||||
raise ExtractorError(errmsg, cause=err, video_id=video_id)
|
||||
else:
|
||||
self.report_warning(errmsg)
|
||||
self.report_warning(errmsg, video_id=video_id)
|
||||
return False
|
||||
|
||||
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True,
|
||||
@@ -1071,9 +1068,9 @@ class InfoExtractor:
|
||||
|
||||
def __print_error(self, errnote, fatal, video_id, err):
|
||||
if fatal:
|
||||
raise ExtractorError(f'{video_id}: {errnote}', cause=err)
|
||||
raise ExtractorError(f'{errnote}', cause=err, video_id=video_id)
|
||||
elif errnote:
|
||||
self.report_warning(f'{video_id}: {errnote}: {err}')
|
||||
self.report_warning(f'{errnote}: {err}', video_id=video_id)
|
||||
|
||||
def _parse_xml(self, xml_string, video_id, transform_source=None, fatal=True, errnote=None):
|
||||
if transform_source:
|
||||
@@ -1234,9 +1231,9 @@ class InfoExtractor:
|
||||
"""Report information extraction."""
|
||||
self.to_screen(f'{id_or_name}: Extracting information')
|
||||
|
||||
def report_download_webpage(self, video_id):
|
||||
def report_download_webpage(self, video_id, *, note=None):
|
||||
"""Report webpage download."""
|
||||
self.to_screen(f'{video_id}: Downloading webpage')
|
||||
self.to_screen(join_nonempty(video_id, note or 'Downloading webpage', delim=': '))
|
||||
|
||||
def report_age_confirmation(self):
|
||||
"""Report attempt to confirm age."""
|
||||
@@ -3073,8 +3070,8 @@ class InfoExtractor:
|
||||
# %(...) counterparts to be used with % operator
|
||||
t = re.sub(r'\$({})\$'.format('|'.join(identifiers)), r'%(\1)d', t)
|
||||
t = re.sub(r'\$({})%([^$]+)\$'.format('|'.join(identifiers)), r'%(\1)\2', t)
|
||||
t.replace('$$', '$')
|
||||
return t
|
||||
|
||||
return t.replace('$$', '$')
|
||||
|
||||
# @initialization is a regular template like @media one
|
||||
# so it should be handled just the same way (see
|
||||
|
||||
@@ -30,7 +30,7 @@ class UnicodeBOMIE(InfoExtractor):
|
||||
_VALID_URL = r'(?P<bom>\ufeff)(?P<id>.*)$'
|
||||
|
||||
_TESTS = [{
|
||||
'url': '\ufeffhttp://www.youtube.com/watch?v=BaW_jenozKc',
|
||||
'url': '\ufeffhttp://www.youtube.com/watch?v=YE7VzlLtp-4',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class ViewSourceIE(InfoExtractor):
|
||||
_VALID_URL = r'view-source:(?P<url>.+)'
|
||||
|
||||
_TEST = {
|
||||
'url': 'view-source:https://www.youtube.com/watch?v=BaW_jenozKc',
|
||||
'url': 'view-source:https://www.youtube.com/watch?v=YE7VzlLtp-4',
|
||||
'only_matching': True,
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ class ERRArhiivIE(InfoExtractor):
|
||||
'title': 'Kontsertpalad: 255 | L. Beethoveni sonaat c-moll, "Pateetiline"',
|
||||
'description': 'md5:a70f4ff23c3618f3be63f704bccef063',
|
||||
'series': 'Kontsertpalad',
|
||||
'episode_id': 255,
|
||||
'episode_id': '255',
|
||||
'timestamp': 1666152162,
|
||||
'upload_date': '20221019',
|
||||
'release_year': 1970,
|
||||
@@ -282,7 +282,7 @@ class ERRArhiivIE(InfoExtractor):
|
||||
'title': ('title', {str}),
|
||||
'series': ('seriesTitle', {str}, filter),
|
||||
'series_id': ('seriesId', {str}, filter),
|
||||
'episode_id': ('episode', {int_or_none}),
|
||||
'episode_id': ('episode', {str}, filter),
|
||||
'description': ('synopsis', {str}, filter),
|
||||
'timestamp': ('uploadDate', {parse_iso8601}),
|
||||
'modified_timestamp': ('dateModified', {parse_iso8601}),
|
||||
|
||||
@@ -778,7 +778,7 @@ class GenericIE(InfoExtractor):
|
||||
if default_search == 'auto_warning':
|
||||
if re.match(r'^(?:url|URL)$', url):
|
||||
raise ExtractorError(
|
||||
f'Invalid URL: {url!r} . Call yt-dlp like this: yt-dlp -v "https://www.youtube.com/watch?v=BaW_jenozKc" ',
|
||||
f'Invalid URL: {url!r} . Call yt-dlp like this: yt-dlp -v "https://www.youtube.com/watch?v=YE7VzlLtp-4" ',
|
||||
expected=True)
|
||||
else:
|
||||
self.report_warning(
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import hashlib
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import ExtractorError, try_get
|
||||
|
||||
|
||||
class GofileIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?gofile\.io/d/(?P<id>[^/]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://gofile.io/d/AMZyDw',
|
||||
'info_dict': {
|
||||
'id': 'AMZyDw',
|
||||
},
|
||||
'playlist_mincount': 2,
|
||||
'playlist': [{
|
||||
'info_dict': {
|
||||
'id': 'de571ac1-5edc-42e2-8ec2-bdac83ad4a31',
|
||||
'filesize': 928116,
|
||||
'ext': 'mp4',
|
||||
'title': 'nuuh',
|
||||
'release_timestamp': 1638338704,
|
||||
'release_date': '20211201',
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
'url': 'https://gofile.io/d/is8lKr',
|
||||
'info_dict': {
|
||||
'id': 'TMjXd9',
|
||||
'ext': 'mp4',
|
||||
},
|
||||
'playlist_count': 0,
|
||||
'skip': 'No video/audio found at provided URL.',
|
||||
}, {
|
||||
'url': 'https://gofile.io/d/TMjXd9',
|
||||
'info_dict': {
|
||||
'id': 'TMjXd9',
|
||||
},
|
||||
'playlist_count': 1,
|
||||
}, {
|
||||
'url': 'https://gofile.io/d/gqOtRf',
|
||||
'info_dict': {
|
||||
'id': 'gqOtRf',
|
||||
},
|
||||
'playlist_mincount': 1,
|
||||
'params': {
|
||||
'videopassword': 'password',
|
||||
},
|
||||
}]
|
||||
_STATIC_TOKEN = '4fd6sg89d7s6' # From https://gofile.io/dist/js/config.js
|
||||
_TOKEN = None
|
||||
|
||||
def _real_initialize(self):
|
||||
token = self._get_cookies('https://gofile.io/').get('accountToken')
|
||||
if token:
|
||||
self._TOKEN = token.value
|
||||
return
|
||||
|
||||
account_data = self._download_json(
|
||||
'https://api.gofile.io/accounts', None, 'Getting a new guest account', data=b'{}')
|
||||
self._TOKEN = account_data['data']['token']
|
||||
self._set_cookie('.gofile.io', 'accountToken', self._TOKEN)
|
||||
|
||||
def _entries(self, file_id):
|
||||
query_params = {}
|
||||
if password := self.get_param('videopassword'):
|
||||
query_params['password'] = hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
files = self._download_json(
|
||||
f'https://api.gofile.io/contents/{file_id}', file_id, 'Getting filelist',
|
||||
query=query_params, headers={
|
||||
'Authorization': f'Bearer {self._TOKEN}',
|
||||
'X-Website-Token': self._STATIC_TOKEN,
|
||||
})
|
||||
|
||||
status = files['status']
|
||||
if status == 'error-passwordRequired':
|
||||
raise ExtractorError(
|
||||
'This video is protected by a password, use the --video-password option', expected=True)
|
||||
elif status != 'ok':
|
||||
raise ExtractorError(f'{self.IE_NAME} said: status {status}', expected=True)
|
||||
|
||||
found_files = False
|
||||
for file in (try_get(files, lambda x: x['data']['children'], dict) or {}).values():
|
||||
file_type, file_format = file.get('mimetype').split('/', 1)
|
||||
if file_type not in ('video', 'audio') and file_format != 'vnd.mts':
|
||||
continue
|
||||
|
||||
found_files = True
|
||||
file_url = file.get('link')
|
||||
if file_url:
|
||||
yield {
|
||||
'id': file['id'],
|
||||
'title': file['name'].rsplit('.', 1)[0],
|
||||
'url': file_url,
|
||||
'filesize': file.get('size'),
|
||||
'release_timestamp': file.get('createTime'),
|
||||
}
|
||||
|
||||
if not found_files:
|
||||
raise ExtractorError('No video/audio found at provided URL.', expected=True)
|
||||
|
||||
def _real_extract(self, url):
|
||||
file_id = self._match_id(url)
|
||||
return self.playlist_result(self._entries(file_id), playlist_id=file_id)
|
||||
@@ -74,7 +74,7 @@ class HRTiBaseIE(InfoExtractor):
|
||||
data=json.dumps(auth_data).encode())
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, HTTPError) and e.cause.status == 406:
|
||||
auth_info = self._parse_json(e.cause.response.read().encode(), None)
|
||||
auth_info = self._parse_json(e.cause.response.read().decode(), None)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
@@ -125,20 +125,20 @@ class IchinanaLiveClipIE(InfoExtractor):
|
||||
formats = []
|
||||
if view_data.get('videoURL'):
|
||||
formats.append({
|
||||
'id': 'video',
|
||||
'format_id': 'video',
|
||||
'url': view_data['videoURL'],
|
||||
'quality': -1,
|
||||
})
|
||||
if view_data.get('transcodeURL'):
|
||||
formats.append({
|
||||
'id': 'transcode',
|
||||
'format_id': 'transcode',
|
||||
'url': view_data['transcodeURL'],
|
||||
'quality': -1,
|
||||
})
|
||||
if view_data.get('srcVideoURL'):
|
||||
# highest quality
|
||||
formats.append({
|
||||
'id': 'srcVideo',
|
||||
'format_id': 'srcVideo',
|
||||
'url': view_data['srcVideoURL'],
|
||||
'quality': 1,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none, unified_timestamp, url_or_none
|
||||
from ..utils import int_or_none, str_or_none, unified_timestamp, url_or_none
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class IdagioPlaylistBaseIE(InfoExtractor):
|
||||
recording_id = track_data['recording']['id']
|
||||
yield self.url_result(
|
||||
f'https://app.idagio.com/recordings/{recording_id}?trackId={track_id}',
|
||||
ie=IdagioTrackIE, video_id=track_id)
|
||||
ie=IdagioTrackIE, video_id=str_or_none(track_id))
|
||||
|
||||
def _real_extract(self, url):
|
||||
playlist_id = self._match_id(url)
|
||||
|
||||
+161
-186
@@ -1,3 +1,4 @@
|
||||
import functools
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
@@ -16,11 +17,10 @@ from ..utils import (
|
||||
get_element_by_attribute,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
lowercase_escape,
|
||||
str_or_none,
|
||||
str_to_int,
|
||||
traverse_obj,
|
||||
url_or_none,
|
||||
urlencode_postdata,
|
||||
)
|
||||
|
||||
_ENCODING_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||
@@ -41,13 +41,31 @@ def _id_to_pk(shortcode):
|
||||
|
||||
class InstagramBaseIE(InfoExtractor):
|
||||
_API_BASE_URL = 'https://i.instagram.com/api/v1'
|
||||
_BASE_URL = 'https://www.instagram.com/'
|
||||
_LOGIN_URL = 'https://www.instagram.com/accounts/login'
|
||||
_APP_IDS = {
|
||||
'ios': '124024574287414',
|
||||
'web': '936619743392459', # default
|
||||
}
|
||||
|
||||
@property
|
||||
def _is_logged_in(self):
|
||||
return bool(self._get_cookies(self._BASE_URL).get('sessionid'))
|
||||
|
||||
@functools.cached_property
|
||||
def _app_id(self):
|
||||
user_input = self._configuration_arg('app_id', [None], ie_key=InstagramIE)[0]
|
||||
return self._APP_IDS.get(user_input, user_input or self._APP_IDS['web'])
|
||||
|
||||
@property
|
||||
def _is_web_app(self):
|
||||
return self._app_id == self._APP_IDS['web']
|
||||
|
||||
@property
|
||||
def _api_headers(self):
|
||||
return {
|
||||
'X-IG-App-ID': self._configuration_arg('app_id', ['936619743392459'], ie_key=InstagramIE)[0],
|
||||
'X-ASBD-ID': '198387',
|
||||
'X-IG-App-ID': self._app_id,
|
||||
'X-ASBD-ID': '359341',
|
||||
'X-IG-WWW-Claim': '0',
|
||||
'Origin': 'https://www.instagram.com',
|
||||
'Accept': '*/*',
|
||||
@@ -106,71 +124,83 @@ class InstagramBaseIE(InfoExtractor):
|
||||
}
|
||||
|
||||
def _extract_product_media(self, product_media):
|
||||
media_id = product_media.get('code') or _pk_to_id(product_media.get('pk'))
|
||||
vcodec = product_media.get('video_codec')
|
||||
dash_manifest_raw = product_media.get('video_dash_manifest')
|
||||
videos_list = product_media.get('video_versions')
|
||||
if not (dash_manifest_raw or videos_list):
|
||||
return {}
|
||||
video_id = traverse_obj(product_media, ('pk', {_pk_to_id}))
|
||||
|
||||
formats = [{
|
||||
'format_id': fmt.get('id'),
|
||||
'url': fmt.get('url'),
|
||||
'width': fmt.get('width'),
|
||||
'height': fmt.get('height'),
|
||||
'vcodec': vcodec,
|
||||
} for fmt in videos_list or []]
|
||||
if dash_manifest_raw:
|
||||
formats.extend(self._parse_mpd_formats(self._parse_xml(dash_manifest_raw, media_id), mpd_id='dash'))
|
||||
formats = traverse_obj(product_media, ('video_versions', lambda _, v: url_or_none(v['url']), {
|
||||
'url': 'url',
|
||||
'format_id': (('id', {str}), ('type', {int}, {str_or_none}), any),
|
||||
'width': ('width', {int_or_none}),
|
||||
'height': ('height', {int_or_none}),
|
||||
}))
|
||||
format_info = traverse_obj(product_media, {
|
||||
'vcodec': ('video_codec', {str}),
|
||||
'acodec': ('has_audio', {bool}, {lambda x: 'none' if x is False else None}),
|
||||
})
|
||||
for f in formats:
|
||||
f.update(format_info)
|
||||
|
||||
dash = traverse_obj(product_media, ('video_dash_manifest', {str}))
|
||||
if dash:
|
||||
formats.extend(self._parse_mpd_formats(self._parse_xml(dash, video_id), mpd_id='dash'))
|
||||
|
||||
thumbnails = [{
|
||||
'url': thumbnail.get('url'),
|
||||
'width': thumbnail.get('width'),
|
||||
'height': thumbnail.get('height'),
|
||||
} for thumbnail in traverse_obj(product_media, ('image_versions2', 'candidates')) or []]
|
||||
return {
|
||||
'id': media_id,
|
||||
'duration': float_or_none(product_media.get('video_duration')),
|
||||
'id': video_id,
|
||||
'formats': formats,
|
||||
'thumbnails': thumbnails,
|
||||
'duration': traverse_obj(product_media, ('video_duration', {float_or_none})),
|
||||
'thumbnails': list(reversed(traverse_obj(product_media, (
|
||||
'image_versions2', 'candidates',
|
||||
lambda _, v: url_or_none(v['url']), {
|
||||
'url': 'url',
|
||||
'width': ('width', {int}),
|
||||
'height': 'height',
|
||||
},
|
||||
)))),
|
||||
}
|
||||
|
||||
def _extract_product(self, product_info):
|
||||
def _extract_product(self, product_info, video_id=None, get_comments=True):
|
||||
if isinstance(product_info, list):
|
||||
product_info = product_info[0]
|
||||
|
||||
user_info = product_info.get('user') or {}
|
||||
info_dict = {
|
||||
'id': _pk_to_id(traverse_obj(product_info, 'pk', 'id', expected_type=str_or_none)[:19]),
|
||||
'title': product_info.get('title') or f'Video by {user_info.get("username")}',
|
||||
'description': traverse_obj(product_info, ('caption', 'text'), expected_type=str_or_none),
|
||||
'timestamp': int_or_none(product_info.get('taken_at')),
|
||||
'channel': user_info.get('username'),
|
||||
'uploader': user_info.get('full_name'),
|
||||
'uploader_id': str_or_none(user_info.get('pk')),
|
||||
'view_count': int_or_none(product_info.get('view_count')),
|
||||
'like_count': int_or_none(product_info.get('like_count')),
|
||||
'comment_count': int_or_none(product_info.get('comment_count')),
|
||||
'__post_extractor': self.extract_comments(_pk_to_id(product_info.get('pk'))),
|
||||
'id': video_id,
|
||||
'title': format_field(product_info, [('user', 'username', {str})], 'Video by %s', default=None),
|
||||
**traverse_obj(product_info, {
|
||||
'id': ('pk', {_pk_to_id}),
|
||||
'title': ('title', {str}),
|
||||
'description': ('caption', 'text', {str}),
|
||||
'channel': ('user', 'username', {str}),
|
||||
'uploader_id': ('user', 'pk', {str_or_none}),
|
||||
'uploader': ('user', 'full_name', {str}),
|
||||
'timestamp': ('taken_at', {int_or_none}),
|
||||
'view_count': ('view_count', {int_or_none}),
|
||||
'like_count': ('like_count', {int_or_none}),
|
||||
'comment_count': ('comment_count', {int_or_none}),
|
||||
}),
|
||||
'http_headers': {
|
||||
'Referer': 'https://www.instagram.com/',
|
||||
},
|
||||
}
|
||||
carousel_media = product_info.get('carousel_media')
|
||||
if carousel_media:
|
||||
|
||||
if carousel_media := traverse_obj(product_info, ('carousel_media', ..., {dict})):
|
||||
comments = None
|
||||
if get_comments and self.get_param('getcomments'):
|
||||
comments = self._get_comments(info_dict.get('id'))
|
||||
|
||||
return {
|
||||
'_type': 'playlist',
|
||||
**info_dict,
|
||||
'title': f'Post by {user_info.get("username")}',
|
||||
'title': format_field(info_dict, 'channel', 'Post by %s', default=None),
|
||||
'entries': [{
|
||||
**info_dict,
|
||||
**self._extract_product_media(product_media),
|
||||
'comments': comments,
|
||||
} for product_media in carousel_media],
|
||||
}
|
||||
|
||||
return {
|
||||
**info_dict,
|
||||
**self._extract_product_media(product_info),
|
||||
'__post_extractor': self.extract_comments(info_dict.get('id')) if get_comments else None,
|
||||
}
|
||||
|
||||
def _get_comments(self, video_id):
|
||||
@@ -182,9 +212,9 @@ class InstagramBaseIE(InfoExtractor):
|
||||
for comment_dict in comment_data or []:
|
||||
yield {
|
||||
'author': traverse_obj(comment_dict, ('node', 'owner', 'username'), ('user', 'username')),
|
||||
'author_id': traverse_obj(comment_dict, ('node', 'owner', 'id'), ('user', 'pk')),
|
||||
'author_id': traverse_obj(comment_dict, ('node', 'owner', 'id'), ('user', 'pk'), expected_type=str_or_none),
|
||||
'author_thumbnail': traverse_obj(comment_dict, ('node', 'owner', 'profile_pic_url'), ('user', 'profile_pic_url'), expected_type=url_or_none),
|
||||
'id': traverse_obj(comment_dict, ('node', 'id'), 'pk'),
|
||||
'id': traverse_obj(comment_dict, ('node', 'id'), 'pk', expected_type=str_or_none),
|
||||
'text': traverse_obj(comment_dict, ('node', 'text'), 'text'),
|
||||
'like_count': traverse_obj(comment_dict, ('node', 'edge_liked_by', 'count'), 'comment_like_count', expected_type=int_or_none),
|
||||
'timestamp': traverse_obj(comment_dict, ('node', 'created_at'), 'created_at', expected_type=int_or_none),
|
||||
@@ -350,6 +380,8 @@ class InstagramIE(InstagramBaseIE):
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
_lsd_token = None
|
||||
|
||||
@classmethod
|
||||
def _extract_embed_urls(cls, url, webpage):
|
||||
res = tuple(super()._extract_embed_urls(url, webpage))
|
||||
@@ -361,165 +393,107 @@ class InstagramIE(InstagramBaseIE):
|
||||
if mobj:
|
||||
return [mobj.group('link')]
|
||||
|
||||
def _real_initialize(self):
|
||||
if self._is_logged_in:
|
||||
return
|
||||
if not self._lsd_token:
|
||||
webpage = self._download_webpage(
|
||||
self._BASE_URL, None, 'Setting up session',
|
||||
impersonate=True, require_impersonation=True)
|
||||
eqmc = self._search_json(
|
||||
r'<script\b[^>]* id="__eqmc"[^>]*>', webpage, 'eqmc JSON', None, default={})
|
||||
self._lsd_token = (
|
||||
traverse_obj(eqmc, ('l', {str}))
|
||||
or self._search_regex(r'\["LSD",\[\],\{"token":"([^"]+)"', webpage, 'LSD token'))
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id, url = self._match_valid_url(url).group('id', 'url')
|
||||
media, webpage = {}, ''
|
||||
media_id = str(_id_to_pk(video_id))
|
||||
|
||||
if self._get_cookies(url).get('sessionid'):
|
||||
info = traverse_obj(self._download_json(
|
||||
f'{self._API_BASE_URL}/media/{_id_to_pk(video_id)}/info/', video_id,
|
||||
fatal=False, errnote='Video info extraction failed',
|
||||
note='Downloading video info', headers=self._api_headers), ('items', 0))
|
||||
if info:
|
||||
media.update(info)
|
||||
return self._extract_product(media)
|
||||
if self._is_logged_in:
|
||||
return self._extract_product(self._download_json(
|
||||
f'{self._API_BASE_URL}/media/{media_id}/info/', video_id,
|
||||
'Downloading video info', 'Video info extraction failed',
|
||||
impersonate=self._is_web_app, headers=self._api_headers)['items'][0])
|
||||
|
||||
api_check = self._download_json(
|
||||
f'{self._API_BASE_URL}/web/get_ruling_for_content/?content_type=MEDIA&target_id={_id_to_pk(video_id)}',
|
||||
video_id, headers=self._api_headers, fatal=False, note='Setting up session', errnote=False) or {}
|
||||
csrf_token = self._get_cookies('https://www.instagram.com').get('csrftoken')
|
||||
f'{self._API_BASE_URL}/web/get_ruling_for_content/', video_id,
|
||||
'Checking post accessibility', errnote=False, fatal=False,
|
||||
impersonate=True, require_impersonation=True, headers=self._api_headers,
|
||||
query={'content_type': 'MEDIA', 'target_id': media_id}) or {}
|
||||
|
||||
csrf_token = self._get_cookies('https://www.instagram.com').get('csrftoken')
|
||||
if not csrf_token:
|
||||
self.report_warning('No csrf token set by Instagram API', video_id)
|
||||
self.report_warning('No CSRF token set by Instagram API', video_id)
|
||||
else:
|
||||
csrf_token = csrf_token.value if api_check.get('status') == 'ok' else None
|
||||
if not csrf_token:
|
||||
self.report_warning('Instagram API is not granting access', video_id)
|
||||
|
||||
variables = {
|
||||
'shortcode': video_id,
|
||||
'child_comment_count': 3,
|
||||
'fetch_comment_count': 40,
|
||||
'parent_comment_count': 24,
|
||||
'has_threaded_comments': True,
|
||||
}
|
||||
general_info = self._download_json(
|
||||
'https://www.instagram.com/graphql/query/', video_id, fatal=False, errnote=False,
|
||||
headers={
|
||||
response = self._download_json(
|
||||
'https://www.instagram.com/api/graphql', video_id,
|
||||
impersonate=True, require_impersonation=True,
|
||||
headers=filter_dict({
|
||||
**self._api_headers,
|
||||
'X-CSRFToken': csrf_token or '',
|
||||
'X-FB-Friendly-Name': 'PolarisLoggedOutDesktopWWWPostRootContentQuery',
|
||||
'X-CSRFToken': csrf_token,
|
||||
'X-FB-LSD': self._lsd_token,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Referer': url,
|
||||
}, query={
|
||||
'doc_id': '8845758582119845',
|
||||
'variables': json.dumps(variables, separators=(',', ':')),
|
||||
})
|
||||
}), data=urlencode_postdata({
|
||||
'av': '0',
|
||||
'__d': 'www',
|
||||
'__user': '0',
|
||||
'dpr': '1',
|
||||
'lsd': self._lsd_token,
|
||||
'fb_api_caller_class': 'RelayModern',
|
||||
'fb_api_req_friendly_name': 'PolarisLoggedOutDesktopWWWPostRootContentQuery',
|
||||
'server_timestamps': 'true',
|
||||
'variables': json.dumps({'media_id': media_id}, separators=(',', ':')),
|
||||
'doc_id': '27130156389949648',
|
||||
}))
|
||||
|
||||
if not general_info:
|
||||
self.report_warning('General metadata extraction failed (some metadata might be missing).', video_id)
|
||||
webpage, urlh = self._download_webpage_handle(url, video_id)
|
||||
shared_data = self._search_json(
|
||||
r'window\._sharedData\s*=', webpage, 'shared data', video_id, fatal=False) or {}
|
||||
media = traverse_obj(response, ('data', 'xig_polaris_media', {dict}))
|
||||
product_info = traverse_obj(media, ('if_not_gated_logged_out', {dict}))
|
||||
if not product_info:
|
||||
error = join_nonempty('title', 'description', delim=': ', from_dict=api_check)
|
||||
if 'Restricted Video' in error:
|
||||
self.raise_login_required(error)
|
||||
elif error:
|
||||
raise ExtractorError(error, expected=True)
|
||||
elif len(video_id) > 28:
|
||||
# It's a private post (video_id == shortcode + 28 extra characters)
|
||||
# Only raise after getting empty response; sometimes "long"-shortcode posts are public
|
||||
self.raise_login_required(
|
||||
'This content is only available for registered users who follow this account')
|
||||
raise ExtractorError(
|
||||
'Instagram sent an empty media response. Check if this post is accessible in your '
|
||||
f'browser without being logged-in. If it is not, then u{self._login_hint()[1:]}. '
|
||||
'Otherwise, if the post is accessible in browser without being logged-in'
|
||||
f'{bug_reports_message(before=",")}', expected=True)
|
||||
|
||||
if shared_data and self._LOGIN_URL not in urlh.url:
|
||||
media.update(traverse_obj(
|
||||
shared_data, ('entry_data', 'PostPage', 0, 'graphql', 'shortcode_media'),
|
||||
('entry_data', 'PostPage', 0, 'media'), expected_type=dict) or {})
|
||||
else:
|
||||
self.report_warning('Main webpage is locked behind the login page. Retrying with embed webpage (some metadata might be missing).')
|
||||
webpage = self._download_webpage(
|
||||
f'{url}/embed/', video_id, note='Downloading embed webpage', fatal=False) or ''
|
||||
additional_data = self._search_json(
|
||||
r'window\.__additionalDataLoaded\s*\(\s*[^,]+,', webpage, 'additional data', video_id, fatal=False)
|
||||
if not additional_data and not media:
|
||||
self.raise_login_required('Requested content is not available, rate-limit reached or login required')
|
||||
info_dict = self._extract_product(product_info, video_id=video_id, get_comments=False)
|
||||
is_playlist = info_dict.get('_type') == 'playlist'
|
||||
if not is_playlist and not info_dict.get('formats'):
|
||||
self.raise_no_formats('There is no video in this post', expected=True)
|
||||
|
||||
product_item = traverse_obj(additional_data, ('items', 0), expected_type=dict)
|
||||
if product_item:
|
||||
media.update(product_item)
|
||||
return self._extract_product(media)
|
||||
|
||||
media.update(traverse_obj(
|
||||
additional_data, ('graphql', 'shortcode_media'), 'shortcode_media', expected_type=dict) or {})
|
||||
comments = traverse_obj(media, (
|
||||
'comments_connection', 'edges', lambda _, v: v['node']['text'], 'node', {
|
||||
'author': ('user', 'username', {str}),
|
||||
'author_id': ('user', 'pk', {str_or_none}),
|
||||
'id': ('pk', {str_or_none}),
|
||||
'text': ('text', {str}),
|
||||
'timestamp': ('created_at', {int_or_none}),
|
||||
'like_count': ('comment_like_count', {int_or_none}),
|
||||
}))
|
||||
|
||||
if is_playlist:
|
||||
for entry in info_dict['entries']:
|
||||
entry['comments'] = comments
|
||||
else:
|
||||
xdt_shortcode_media = traverse_obj(general_info, ('data', 'xdt_shortcode_media', {dict})) or {}
|
||||
if not xdt_shortcode_media:
|
||||
error = join_nonempty('title', 'description', delim=': ', from_dict=api_check)
|
||||
if 'Restricted Video' in error:
|
||||
self.raise_login_required(error)
|
||||
elif error:
|
||||
raise ExtractorError(error, expected=True)
|
||||
elif len(video_id) > 28:
|
||||
# It's a private post (video_id == shortcode + 28 extra characters)
|
||||
# Only raise after getting empty response; sometimes "long"-shortcode posts are public
|
||||
self.raise_login_required(
|
||||
'This content is only available for registered users who follow this account')
|
||||
raise ExtractorError(
|
||||
'Instagram sent an empty media response. Check if this post is accessible in your '
|
||||
f'browser without being logged-in. If it is not, then u{self._login_hint()[1:]}. '
|
||||
'Otherwise, if the post is accessible in browser without being logged-in'
|
||||
f'{bug_reports_message(before=",")}', expected=True)
|
||||
media.update(xdt_shortcode_media)
|
||||
info_dict['comments'] = comments
|
||||
|
||||
username = traverse_obj(media, ('owner', 'username')) or self._search_regex(
|
||||
r'"owner"\s*:\s*{\s*"username"\s*:\s*"(.+?)"', webpage, 'username', fatal=False)
|
||||
|
||||
description = (
|
||||
traverse_obj(media, ('edge_media_to_caption', 'edges', 0, 'node', 'text'), expected_type=str)
|
||||
or media.get('caption'))
|
||||
if not description:
|
||||
description = self._search_regex(
|
||||
r'"caption"\s*:\s*"(.+?)"', webpage, 'description', default=None)
|
||||
if description is not None:
|
||||
description = lowercase_escape(description)
|
||||
|
||||
video_url = media.get('video_url')
|
||||
if not video_url:
|
||||
nodes = traverse_obj(media, ('edge_sidecar_to_children', 'edges', ..., 'node'), expected_type=dict) or []
|
||||
if nodes:
|
||||
return self.playlist_result(
|
||||
self._extract_nodes(nodes, True), video_id,
|
||||
format_field(username, None, 'Post by %s'), description)
|
||||
raise ExtractorError('There is no video in this post', expected=True)
|
||||
|
||||
formats = [{
|
||||
'url': video_url,
|
||||
'width': self._get_dimension('width', media, webpage),
|
||||
'height': self._get_dimension('height', media, webpage),
|
||||
}]
|
||||
dash = traverse_obj(media, ('dash_info', 'video_dash_manifest'))
|
||||
if dash:
|
||||
formats.extend(self._parse_mpd_formats(self._parse_xml(dash, video_id), mpd_id='dash'))
|
||||
|
||||
comment_data = traverse_obj(media, ('edge_media_to_parent_comment', 'edges'))
|
||||
comments = [{
|
||||
'author': traverse_obj(comment_dict, ('node', 'owner', 'username')),
|
||||
'author_id': traverse_obj(comment_dict, ('node', 'owner', 'id')),
|
||||
'id': traverse_obj(comment_dict, ('node', 'id')),
|
||||
'text': traverse_obj(comment_dict, ('node', 'text')),
|
||||
'timestamp': traverse_obj(comment_dict, ('node', 'created_at'), expected_type=int_or_none),
|
||||
} for comment_dict in comment_data] if comment_data else None
|
||||
|
||||
display_resources = (
|
||||
media.get('display_resources')
|
||||
or [{'src': media.get(key)} for key in ('display_src', 'display_url')]
|
||||
or [{'src': self._og_search_thumbnail(webpage)}])
|
||||
thumbnails = [{
|
||||
'url': thumbnail['src'],
|
||||
'width': thumbnail.get('config_width'),
|
||||
'height': thumbnail.get('config_height'),
|
||||
} for thumbnail in display_resources if thumbnail.get('src')]
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'formats': formats,
|
||||
'title': media.get('title') or f'Video by {username}',
|
||||
'description': description,
|
||||
'duration': float_or_none(media.get('video_duration')),
|
||||
'timestamp': traverse_obj(media, 'taken_at_timestamp', 'date', expected_type=int_or_none),
|
||||
'uploader_id': traverse_obj(media, ('owner', 'id')),
|
||||
'uploader': traverse_obj(media, ('owner', 'full_name')),
|
||||
'channel': username,
|
||||
'like_count': self._get_count(media, 'likes', 'preview_like') or str_to_int(self._search_regex(
|
||||
r'data-log-event="likeCountClick"[^>]*>[^\d]*([\d,\.]+)', webpage, 'like count', fatal=False)),
|
||||
'comment_count': self._get_count(media, 'comments', 'preview_comment', 'to_comment', 'to_parent_comment'),
|
||||
'comments': comments,
|
||||
'thumbnails': thumbnails,
|
||||
'http_headers': {
|
||||
'Referer': 'https://www.instagram.com/',
|
||||
},
|
||||
}
|
||||
return info_dict
|
||||
|
||||
|
||||
class InstagramPlaylistBaseIE(InstagramBaseIE):
|
||||
@@ -697,12 +671,12 @@ class InstagramStoryIE(InstagramBaseIE):
|
||||
if username == 'highlights' and not story_id: # story id is only mandatory for highlights
|
||||
raise ExtractorError('Input URL is missing a highlight ID', expected=True)
|
||||
display_id = story_id or username
|
||||
story_info = self._download_webpage(url, display_id)
|
||||
story_info = self._download_webpage(url, display_id, impersonate=self._is_web_app)
|
||||
user_info = self._search_json(r'"user":', story_info, 'user info', display_id, fatal=False)
|
||||
if not user_info:
|
||||
self.raise_login_required('This content is unreachable')
|
||||
|
||||
user_id = traverse_obj(user_info, 'pk', 'id', expected_type=str)
|
||||
user_id = traverse_obj(user_info, 'pk', 'id', expected_type=str_or_none)
|
||||
if username == 'highlights':
|
||||
story_info_url = f'highlight:{story_id}'
|
||||
else:
|
||||
@@ -712,7 +686,8 @@ class InstagramStoryIE(InstagramBaseIE):
|
||||
|
||||
videos = traverse_obj(self._download_json(
|
||||
f'{self._API_BASE_URL}/feed/reels_media/?reel_ids={story_info_url}',
|
||||
display_id, errnote=False, fatal=False, headers=self._api_headers), 'reels')
|
||||
display_id, errnote=False, fatal=False, impersonate=self._is_web_app,
|
||||
headers=self._api_headers), 'reels')
|
||||
if not videos:
|
||||
self.raise_login_required('You need to log in to access this content')
|
||||
user_info = traverse_obj(videos, (user_id, 'user', {dict})) or {}
|
||||
|
||||
@@ -457,12 +457,12 @@ class IqIE(InfoExtractor):
|
||||
if video_format.get('m3u8Url'):
|
||||
extracted_formats.extend(self._extract_m3u8_formats(
|
||||
urljoin(format_data.get('dm3u8', 'https://cache-m.iq.com/dc/dt/'), video_format['m3u8Url']),
|
||||
'mp4', m3u8_id=bid, fatal=False))
|
||||
video_id, 'mp4', m3u8_id=bid, fatal=False))
|
||||
if video_format.get('mpdUrl'):
|
||||
# TODO: Properly extract mpd hostname
|
||||
extracted_formats.extend(self._extract_mpd_formats(
|
||||
urljoin(format_data.get('dm3u8', 'https://cache-m.iq.com/dc/dt/'), video_format['mpdUrl']),
|
||||
mpd_id=bid, fatal=False))
|
||||
video_id, mpd_id=bid, fatal=False))
|
||||
if video_format.get('m3u8'):
|
||||
ff = video_format.get('ff', 'ts')
|
||||
if ff == 'ts':
|
||||
|
||||
@@ -13,6 +13,7 @@ from ..utils import (
|
||||
merge_dicts,
|
||||
parse_duration,
|
||||
smuggle_url,
|
||||
str_or_none,
|
||||
try_get,
|
||||
url_basename,
|
||||
url_or_none,
|
||||
@@ -247,7 +248,7 @@ class ITVBTCCIE(InfoExtractor):
|
||||
for video in json_map:
|
||||
if not any(traverse_obj(video, ('data', attr)) == 'Brightcove' for attr in ('name', 'type')):
|
||||
continue
|
||||
video_id = video['data']['id']
|
||||
video_id = str_or_none(video['data']['id'])
|
||||
account_id = video['data']['accountId']
|
||||
player_id = video['data']['playerId']
|
||||
entries.append(self.url_result(
|
||||
|
||||
@@ -5,7 +5,6 @@ from ..utils import (
|
||||
ExtractorError,
|
||||
clean_html,
|
||||
determine_ext,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
str_or_none,
|
||||
url_or_none,
|
||||
@@ -130,7 +129,7 @@ class LecturioIE(LecturioBaseIE):
|
||||
f = {
|
||||
'url': file_url,
|
||||
'format_id': label,
|
||||
'filesize': float_or_none(filesize, invscale=1000),
|
||||
'filesize': int_or_none(filesize, invscale=1000),
|
||||
}
|
||||
if label:
|
||||
mobj = re.match(r'(\d+)p\s*\(([^)]+)\)', label)
|
||||
|
||||
@@ -69,7 +69,7 @@ class LibsynIE(InfoExtractor):
|
||||
|
||||
episode_title = data.get('item_title') or get_element_by_class('episode-title', webpage)
|
||||
if not episode_title:
|
||||
self._search_regex(
|
||||
episode_title = self._search_regex(
|
||||
[r'data-title="([^"]+)"', r'<title>(.+?)</title>'],
|
||||
webpage, 'episode title')
|
||||
episode_title = episode_title.strip()
|
||||
|
||||
@@ -4,7 +4,6 @@ import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
extract_attributes,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
@@ -12,44 +11,11 @@ from ..utils import (
|
||||
srt_subtitles_timecode,
|
||||
try_get,
|
||||
url_or_none,
|
||||
urlencode_postdata,
|
||||
urljoin,
|
||||
)
|
||||
from ..utils.traversal import find_elements, require, traverse_obj
|
||||
|
||||
|
||||
class LinkedInBaseIE(InfoExtractor):
|
||||
_NETRC_MACHINE = 'linkedin'
|
||||
_logged_in = False
|
||||
|
||||
def _perform_login(self, username, password):
|
||||
if self._logged_in:
|
||||
return
|
||||
|
||||
login_page = self._download_webpage(
|
||||
self._LOGIN_URL, None, 'Downloading login page')
|
||||
action_url = urljoin(self._LOGIN_URL, self._search_regex(
|
||||
r'<form[^>]+action=(["\'])(?P<url>.+?)\1', login_page, 'post url',
|
||||
default='https://www.linkedin.com/uas/login-submit', group='url'))
|
||||
data = self._hidden_inputs(login_page)
|
||||
data.update({
|
||||
'session_key': username,
|
||||
'session_password': password,
|
||||
})
|
||||
login_submit_page = self._download_webpage(
|
||||
action_url, None, 'Logging in',
|
||||
data=urlencode_postdata(data))
|
||||
error = self._search_regex(
|
||||
r'<span[^>]+class="error"[^>]*>\s*(.+?)\s*</span>',
|
||||
login_submit_page, 'error', default=None)
|
||||
if error:
|
||||
raise ExtractorError(error, expected=True)
|
||||
LinkedInBaseIE._logged_in = True
|
||||
|
||||
|
||||
class LinkedInLearningBaseIE(LinkedInBaseIE):
|
||||
_LOGIN_URL = 'https://www.linkedin.com/uas/login?trk=learning'
|
||||
|
||||
class LinkedInLearningBaseIE(InfoExtractor):
|
||||
def _call_api(self, course_slug, fields, video_slug=None, resolution=None):
|
||||
query = {
|
||||
'courseSlug': course_slug,
|
||||
@@ -82,7 +48,7 @@ class LinkedInLearningBaseIE(LinkedInBaseIE):
|
||||
return self._get_urn_id(video_data) or f'{course_slug}/{video_slug}'
|
||||
|
||||
|
||||
class LinkedInIE(LinkedInBaseIE):
|
||||
class LinkedInIE(InfoExtractor):
|
||||
_VALID_URL = [
|
||||
r'https?://(?:www\.)?linkedin\.com/posts/[^/?#]+-(?P<id>\d+)-\w{4}/?(?:[?#]|$)',
|
||||
r'https?://(?:www\.)?linkedin\.com/feed/update/urn:li:activity:(?P<id>\d+)',
|
||||
@@ -280,7 +246,7 @@ class LinkedInLearningCourseIE(LinkedInLearningBaseIE):
|
||||
course_data.get('description'))
|
||||
|
||||
|
||||
class LinkedInEventsIE(LinkedInBaseIE):
|
||||
class LinkedInEventsIE(InfoExtractor):
|
||||
IE_NAME = 'linkedin:events'
|
||||
_VALID_URL = r'https?://(?:www\.)?linkedin\.com/events/(?P<id>[\w-]+)'
|
||||
_TESTS = [{
|
||||
|
||||
@@ -9,6 +9,7 @@ from ..utils import (
|
||||
int_or_none,
|
||||
parse_resolution,
|
||||
traverse_obj,
|
||||
try_call,
|
||||
try_get,
|
||||
url_or_none,
|
||||
urljoin,
|
||||
@@ -94,12 +95,18 @@ class MGTVIE(InfoExtractor):
|
||||
raise
|
||||
|
||||
stream_data = self._download_json(
|
||||
'https://pcweb.api.mgtv.com/player/getSource', video_id, query={
|
||||
'https://tinker.glb.mgtv.com/player/getSource', video_id, query={
|
||||
'_support': '10000000',
|
||||
'tk2': tk2,
|
||||
'pm2': api_data['atc']['pm2'],
|
||||
'video_id': video_id,
|
||||
'type': 'pch5',
|
||||
'auth_mode': '1',
|
||||
'cxid': '',
|
||||
'supportMse': '1',
|
||||
'src': 'intelmgtv',
|
||||
'abroad': try_call(lambda: self._get_cookies('https://www.mgtv.com')['abroad'].value) or '10',
|
||||
'allowedRC': '1',
|
||||
}, headers=self.geo_verification_headers())['data']
|
||||
stream_domain = traverse_obj(stream_data, ('stream_domain', ..., {url_or_none}), get_all=False)
|
||||
|
||||
@@ -110,7 +117,8 @@ class MGTVIE(InfoExtractor):
|
||||
self._RESOLUTIONS, (stream_name, 1 if stream.get('scale') == '16:9' else 0))
|
||||
format_url = traverse_obj(self._download_json(
|
||||
urljoin(stream_domain, stream['url']), video_id, fatal=False,
|
||||
note=f'Downloading video info for format {resolution or stream_name}'),
|
||||
note=f'Downloading video info for format {resolution or stream_name}',
|
||||
headers=self.geo_verification_headers()),
|
||||
('info', {url_or_none}))
|
||||
if not format_url:
|
||||
continue
|
||||
|
||||
@@ -59,7 +59,7 @@ class MirrativIE(MirrativBaseIE):
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': self._og_search_title(webpage, default=None) or self._search_regex(
|
||||
r'<title>\s*(.+?) - Mirrativ\s*</title>', webpage) or live_response.get('title'),
|
||||
r'<title>\s*(.+?) - Mirrativ\s*</title>', webpage, 'title', default=None) or live_response.get('title'),
|
||||
'is_live': is_live,
|
||||
'description': live_response.get('description'),
|
||||
'formats': formats,
|
||||
|
||||
+390
-180
@@ -1,240 +1,450 @@
|
||||
import itertools
|
||||
import urllib.parse
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
UnsupportedError,
|
||||
clean_html,
|
||||
determine_ext,
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
traverse_obj,
|
||||
try_get,
|
||||
join_nonempty,
|
||||
orderedSet,
|
||||
parse_iso8601,
|
||||
str_or_none,
|
||||
url_or_none,
|
||||
urljoin,
|
||||
)
|
||||
from ..utils.traversal import find_element, traverse_obj
|
||||
|
||||
|
||||
class MxplayerIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?mxplayer\.in/(?P<type>movie|show/[-\w]+/[-\w]+)/(?P<display_id>[-\w]+)-(?P<id>\w+)'
|
||||
class MxplayerBaseIE(InfoExtractor):
|
||||
_BASE_URL = 'https://www.mxplayer.in'
|
||||
_GEO_BYPASS = False
|
||||
_GEO_COUNTRIES = ['IN']
|
||||
|
||||
def _extract_mxs(self, url, item_id):
|
||||
webpage = self._download_webpage(url, item_id)
|
||||
if error_msg := traverse_obj(webpage, (
|
||||
{find_element(cls='sub-message')}, {clean_html}, filter,
|
||||
)):
|
||||
self.raise_geo_restricted(error_msg, countries=self._GEO_COUNTRIES)
|
||||
|
||||
return self._search_json(r'window\.__mxs__\s*=', webpage, 'mxs', item_id)
|
||||
|
||||
|
||||
class MxplayerIE(MxplayerBaseIE):
|
||||
IE_NAME = 'mxplayer'
|
||||
IE_DESC = 'Amazon MX Player'
|
||||
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://(?:www\.)?mxplayer\.in/
|
||||
(?:movie|shorts|show/[\w-]+/(?!seasons/)[\w-]+)/
|
||||
(?P<display_id>[\w-]+)-(?P<id>[0-9a-f]{32})(?:[/?#]|$)
|
||||
'''
|
||||
_TESTS = [{
|
||||
'url': 'https://www.mxplayer.in/show/watch-my-girlfriend-is-an-alien-hindi-dubbed/season-1/episode-1-online-9d2013d31d5835bb8400e3b3c5e7bb72',
|
||||
# show, mxplay
|
||||
'url': 'https://www.mxplayer.in/show/watch-my-girlfriend-is-an-alien/season-1/episode-1-online-9d2013d31d5835bb8400e3b3c5e7bb72',
|
||||
'info_dict': {
|
||||
'id': '9d2013d31d5835bb8400e3b3c5e7bb72',
|
||||
'display_id': 'episode-1-online',
|
||||
'ext': 'mp4',
|
||||
'title': 'Episode 1',
|
||||
'description': 'md5:62ed43eb9fec5efde5cf3bd1040b7670',
|
||||
'season_number': 1,
|
||||
'age_limit': 16,
|
||||
'cast': 'count:10',
|
||||
'creators': 'count:2',
|
||||
'description': 'md5:e90dc55a393f557049284eb36efdb773',
|
||||
'display_id': 'episode-1-online',
|
||||
'episode': 'Episode 1',
|
||||
'episode_number': 1,
|
||||
'duration': 2451,
|
||||
'genres': 'count:8',
|
||||
'release_date': '20241201',
|
||||
'release_timestamp': 1733077800,
|
||||
'season': 'Season 1',
|
||||
'series': 'My Girlfriend Is An Alien (Hindi Dubbed)',
|
||||
'episode': 'Episode 1',
|
||||
},
|
||||
'params': {
|
||||
'format': 'bv',
|
||||
'skip_download': True,
|
||||
'season_number': 1,
|
||||
'series': 'My Girlfriend Is An Alien',
|
||||
'tags': 'count:31',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1732435849,
|
||||
'upload_date': '20241124',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/movie/watch-knock-knock-hindi-dubbed-movie-online-b9fa28df3bfb8758874735bbd7d2655a?watch=true',
|
||||
# show, mxplay, NFSW
|
||||
'url': 'https://www.mxplayer.in/show/watch-miya-biwi-aur-murder/season-1/episode-1-online-ca17972f052449de6633129ddd7db90b',
|
||||
'info_dict': {
|
||||
'id': 'b9fa28df3bfb8758874735bbd7d2655a',
|
||||
'id': 'ca17972f052449de6633129ddd7db90b',
|
||||
'ext': 'mp4',
|
||||
'title': 'Episode 1',
|
||||
'age_limit': 18,
|
||||
'cast': 'count:10',
|
||||
'creators': 'count:1',
|
||||
'description': 'md5:bd8e55b81094e55026eb0c4f702c32cc',
|
||||
'display_id': 'episode-1-online',
|
||||
'ext': 'mp4',
|
||||
'title': 'Knock Knock (Hindi Dubbed)',
|
||||
'description': 'md5:4160f2dfc3b87c524261366f6b736329',
|
||||
'duration': 5970,
|
||||
},
|
||||
'params': {
|
||||
'format': 'bv',
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'No longer available',
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/show/watch-shaitaan/season-1/the-infamous-taxi-gang-of-meerut-online-45055d5bcff169ad48f2ad7552a83d6c',
|
||||
'info_dict': {
|
||||
'id': '45055d5bcff169ad48f2ad7552a83d6c',
|
||||
'ext': 'mp4',
|
||||
'title': 'The infamous taxi gang of Meerut',
|
||||
'description': 'md5:033a0a7e3fd147be4fb7e07a01a3dc28',
|
||||
'season_number': 1,
|
||||
'episode_number': 1,
|
||||
'duration': 2332,
|
||||
'season': 'Season 1',
|
||||
'series': 'Shaitaan',
|
||||
'episode': 'Episode 1',
|
||||
},
|
||||
'params': {
|
||||
'format': 'best',
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'No longer available.',
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/show/watch-aashram/chapter-1/duh-swapna-online-d445579792b0135598ba1bc9088a84cb',
|
||||
'info_dict': {
|
||||
'id': 'd445579792b0135598ba1bc9088a84cb',
|
||||
'display_id': 'duh-swapna-online',
|
||||
'ext': 'mp4',
|
||||
'title': 'Duh Swapna',
|
||||
'description': 'md5:35ff39c4bdac403c53be1e16a04192d8',
|
||||
'season_number': 1,
|
||||
'episode_number': 3,
|
||||
'duration': 2568,
|
||||
'season': 'Season 1',
|
||||
'series': 'Aashram',
|
||||
'episode': 'Episode 3',
|
||||
},
|
||||
'params': {
|
||||
'format': 'bv',
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/show/watch-dangerous/season-1/chapter-1-online-5a351b4f9fb69436f6bd6ae3a1a75292',
|
||||
'info_dict': {
|
||||
'id': '5a351b4f9fb69436f6bd6ae3a1a75292',
|
||||
'display_id': 'chapter-1-online',
|
||||
'ext': 'mp4',
|
||||
'title': 'Chapter 1',
|
||||
'description': 'md5:233886b8598bc91648ac098abe1d288f',
|
||||
'season_number': 1,
|
||||
'episode_number': 1,
|
||||
'duration': 1305,
|
||||
'duration': 1348,
|
||||
'genres': 'count:4',
|
||||
'release_date': '20220630',
|
||||
'release_timestamp': 1656613800,
|
||||
'season': 'Season 1',
|
||||
'series': 'Dangerous',
|
||||
'episode': 'Episode 1',
|
||||
},
|
||||
'params': {
|
||||
'format': 'bv',
|
||||
'skip_download': True,
|
||||
'season_number': 1,
|
||||
'series': 'Miya Biwi Aur Murder',
|
||||
'tags': 'count:12',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1656581738,
|
||||
'upload_date': '20220630',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/movie/watch-the-attacks-of-2611-movie-online-0452f0d80226c398d63ce7e3ea40fa2d',
|
||||
# shorts, mxplay
|
||||
'url': 'https://www.mxplayer.in/shorts/watch-official-trailer-made-in-india-a-titan-story-online-c36b0ad18e56f1da0743ad826fe9e14b',
|
||||
'info_dict': {
|
||||
'id': '0452f0d80226c398d63ce7e3ea40fa2d',
|
||||
'id': 'c36b0ad18e56f1da0743ad826fe9e14b',
|
||||
'ext': 'mp4',
|
||||
'title': 'The Attacks of 26/11',
|
||||
'description': 'md5:689bacd29e97b3f31eaf519eb14127e5',
|
||||
'duration': 6085,
|
||||
'title': 'Official Trailer | Made In India: A Titan Story',
|
||||
'age_limit': 13,
|
||||
'description': 'md5:37924586e54d6e86a6e991048aec5442',
|
||||
'display_id': 'watch-official-trailer-made-in-india-a-titan-story-online',
|
||||
'episode': 'Episode 0',
|
||||
'episode_number': 0,
|
||||
'duration': 125,
|
||||
'genres': 'count:2',
|
||||
'release_date': '20260525',
|
||||
'release_timestamp': 1779733800,
|
||||
'tags': 'count:3',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {
|
||||
'format': 'best',
|
||||
'skip_download': True,
|
||||
},
|
||||
'skip': 'No longer available. Cannot be played on browser',
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/movie/watch-kitne-door-kitne-paas-movie-online-a9e9c76c566205955f70d8b2cb88a6a2',
|
||||
# movie, mxplay
|
||||
'url': 'https://www.mxplayer.in/movie/watch-thiruchitrambalam-movie-online-fbba39c1fdf8b14cffcea05f0635da84',
|
||||
'info_dict': {
|
||||
'id': 'a9e9c76c566205955f70d8b2cb88a6a2',
|
||||
'display_id': 'watch-kitne-door-kitne-paas-movie-online',
|
||||
'title': 'Kitne Door Kitne Paas',
|
||||
'duration': 8458,
|
||||
'id': 'fbba39c1fdf8b14cffcea05f0635da84',
|
||||
'ext': 'mp4',
|
||||
'description': 'md5:fb825f3c542513088024dcafef0921b4',
|
||||
},
|
||||
'params': {
|
||||
'format': 'bv',
|
||||
'skip_download': True,
|
||||
'title': 'Thiruchitrambalam',
|
||||
'age_limit': 13,
|
||||
'cast': 'count:7',
|
||||
'creators': 'count:1',
|
||||
'description': 'md5:66c24674756099adbecd1e56cfa9d6e6',
|
||||
'display_id': 'watch-thiruchitrambalam-movie-online',
|
||||
'episode': 'Episode 0',
|
||||
'episode_number': 0,
|
||||
'duration': 7894,
|
||||
'genres': 'count:3',
|
||||
'release_date': '20220817',
|
||||
'release_timestamp': 1660761000,
|
||||
'tags': 'count:8',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1759808876,
|
||||
'upload_date': '20251007',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/show/watch-ek-thi-begum-hindi/season-2/game-of-power-online-5e5305c28f1409847cdc4520b6ad77cf',
|
||||
# movie, thirdParty
|
||||
'url': 'https://www.mxplayer.in/movie/watch-drishyam-2-hindi-movie-online-2c580670b2104d156e2d9a3fd42a413e',
|
||||
'info_dict': {
|
||||
'id': '5e5305c28f1409847cdc4520b6ad77cf',
|
||||
'display_id': 'game-of-power-online',
|
||||
'title': 'Game Of Power',
|
||||
'duration': 1845,
|
||||
'id': '2c580670b2104d156e2d9a3fd42a413e',
|
||||
'ext': 'mp4',
|
||||
'description': 'md5:1d0948d2a5312d7013792d53542407f9',
|
||||
'series': 'Ek Thi Begum (Hindi)',
|
||||
'season': 'Season 2',
|
||||
'season_number': 2,
|
||||
'episode': 'Episode 2',
|
||||
'episode_number': 2,
|
||||
'title': 'Drishyam 2 (Hindi)',
|
||||
'age_limit': 13,
|
||||
'cast': 'count:10',
|
||||
'creators': 'count:1',
|
||||
'description': 'md5:b4f803532ea357b78ca183d0829eca2a',
|
||||
'display_id': 'watch-drishyam-2-hindi-movie-online',
|
||||
'episode': 'Episode 0',
|
||||
'episode_number': 0,
|
||||
'duration': 8399,
|
||||
'genres': 'count:3',
|
||||
'release_date': '20221117',
|
||||
'release_timestamp': 1668709800,
|
||||
'tags': 'count:4',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1736823124,
|
||||
'upload_date': '20250114',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {
|
||||
'format': 'bv',
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.mxplayer.in/movie/watch-deewane-huye-paagal-movie-online-4f9175c40a11c3994182a65afdd37ec6?watch=true',
|
||||
'info_dict': {
|
||||
'id': '4f9175c40a11c3994182a65afdd37ec6',
|
||||
'display_id': 'watch-deewane-huye-paagal-movie-online',
|
||||
'title': 'Deewane Huye Paagal',
|
||||
'duration': 9037,
|
||||
'ext': 'mp4',
|
||||
'description': 'md5:d17bd5c651016c4ed2e6f8a4ace15534',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
'params': {'skip_download': True},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_type, display_id, video_id = self._match_valid_url(url).group('type', 'display_id', 'id')
|
||||
if 'show' in video_type:
|
||||
video_type = 'episode'
|
||||
display_id, video_id = self._match_valid_url(url).group('display_id', 'id')
|
||||
mxs = self._extract_mxs(url, video_id)
|
||||
|
||||
data_json = self._download_json(
|
||||
f'https://api.mxplay.com/v1/web/detail/video?type={video_type}&id={video_id}', display_id)
|
||||
config = traverse_obj(mxs, ('config', {dict}))
|
||||
cdn_base = traverse_obj(config, (
|
||||
'videoCdnBaseUrl', {url_or_none}, {lambda x: f'{x.rstrip("/")}/'}))
|
||||
img_base = traverse_obj(config, (
|
||||
'imageBaseUrl', {url_or_none}, {lambda x: f'{x.rstrip("/")}/'}))
|
||||
|
||||
entities = traverse_obj(mxs, ('entities', video_id, {dict}))
|
||||
stream = traverse_obj(entities, ('stream', {dict}))
|
||||
if traverse_obj(stream, ('drmProtect', {bool})):
|
||||
self.report_drm(video_id)
|
||||
|
||||
formats, subtitles = [], {}
|
||||
m3u8_url = urljoin('https://llvod.mxplay.com/', traverse_obj(
|
||||
data_json, ('stream', (('thirdParty', 'hlsUrl'), ('hls', 'high'))), get_all=False))
|
||||
if m3u8_url:
|
||||
formats, subtitles = self._extract_m3u8_formats_and_subtitles(m3u8_url, display_id, 'mp4', fatal=False)
|
||||
mpd_url = urljoin('https://llvod.mxplay.com/', traverse_obj(
|
||||
data_json, ('stream', (('thirdParty', 'dashUrl'), ('dash', 'high'))), get_all=False))
|
||||
if mpd_url:
|
||||
fmts, subs = self._extract_mpd_formats_and_subtitles(mpd_url, display_id, fatal=False)
|
||||
for manifest_url in traverse_obj(stream, ((
|
||||
('thirdParty', ..., {url_or_none}),
|
||||
((None, 'mxplay'), ('hls', 'dash'), 'high', {urljoin(cdn_base)}),
|
||||
), all, {orderedSet})):
|
||||
ext = determine_ext(manifest_url)
|
||||
if ext == 'm3u8':
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
manifest_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
|
||||
elif ext == 'mpd':
|
||||
fmts, subs = self._extract_mpd_formats_and_subtitles(
|
||||
manifest_url, video_id, mpd_id='dash', fatal=False)
|
||||
for fmt in fmts:
|
||||
if tbr := traverse_obj(fmt, ('tbr', {float_or_none})):
|
||||
fmt['tbr'] = tbr / 4.2
|
||||
else:
|
||||
self.report_warning(f'Unsupported stream type: {ext}')
|
||||
continue
|
||||
|
||||
formats.extend(fmts)
|
||||
self._merge_subtitles(subs, target=subtitles)
|
||||
self._remove_duplicate_formats(formats)
|
||||
|
||||
season = traverse_obj(data_json, ('container', 'title'))
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': data_json.get('title'),
|
||||
'display_id': display_id,
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'display_id': display_id,
|
||||
'duration': data_json.get('duration'),
|
||||
'series': traverse_obj(data_json, ('container', 'container', 'title')),
|
||||
'description': data_json.get('description'),
|
||||
'season': season,
|
||||
'season_number': int_or_none(
|
||||
self._search_regex(r'Season (\d+)', season, 'Season Number', default=None)),
|
||||
'episode_number': data_json.get('sequence') or None,
|
||||
**traverse_obj(entities, {
|
||||
'title': ('title', {clean_html}, filter),
|
||||
'age_limit': ('rating', {int_or_none}),
|
||||
'description': ('description', {clean_html}, filter),
|
||||
'duration': ('duration', {int_or_none}),
|
||||
'episode_number': ('sequence', {int_or_none}),
|
||||
'genres': ('genres', ..., {clean_html}, filter, all, filter),
|
||||
'release_timestamp': ('releaseDate', {parse_iso8601}),
|
||||
'tags': ('tags', ..., 'name', {clean_html}, filter, all, filter),
|
||||
'thumbnails': (('imageInfo', 'titleContentImageInfo'), ..., {
|
||||
'id': ('type', {str_or_none}),
|
||||
'height': ('height', {int_or_none}),
|
||||
'url': ('url', {urljoin(img_base)}),
|
||||
'width': ('width', {int_or_none}),
|
||||
}),
|
||||
'timestamp': ('publishTime', {parse_iso8601}),
|
||||
'view_count': ('viewCount', {int_or_none}),
|
||||
}),
|
||||
**traverse_obj(entities, ('container', {
|
||||
'season': ('title', {clean_html}, filter),
|
||||
'season_number': ('sequence', {int_or_none}),
|
||||
'series': ('container', 'title', {clean_html}, filter),
|
||||
})),
|
||||
**traverse_obj(entities, ('contributors', {
|
||||
'cast': (lambda _, v: v['type'] == 'actor', 'name', {clean_html}, filter, all, filter),
|
||||
'creators': (lambda _, v: v['type'] == 'director', 'name', {clean_html}, filter, all, filter),
|
||||
})),
|
||||
}
|
||||
|
||||
|
||||
class MxplayerShowIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?mxplayer\.in/show/(?P<display_id>[-\w]+)-(?P<id>\w+)/?(?:$|[#?])'
|
||||
class MxplayerSeasonIE(MxplayerBaseIE):
|
||||
IE_NAME = 'mxplayer:season'
|
||||
|
||||
_PAGE_SIZE = 10
|
||||
_VALID_URL = r'https?://(?:www\.)?mxplayer\.in/show/[\w-]+/seasons/[\w-]+-(?P<id>[0-9a-f]{32})(?:[/?#]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.mxplayer.in/show/watch-chakravartin-ashoka-samrat-series-online-a8f44e3cc0814b5601d17772cedf5417',
|
||||
'playlist_mincount': 440,
|
||||
'url': 'https://www.mxplayer.in/show/watch-campus-beats/seasons/season-4-925794ae840c597b3c36f2d8f2f138b8',
|
||||
'info_dict': {
|
||||
'id': 'a8f44e3cc0814b5601d17772cedf5417',
|
||||
'title': 'Watch Chakravartin Ashoka Samrat Series Online',
|
||||
'id': '925794ae840c597b3c36f2d8f2f138b8',
|
||||
'title': 'Campus Beats - Season 4',
|
||||
},
|
||||
'playlist_mincount': 15,
|
||||
}]
|
||||
|
||||
_API_SHOW_URL = 'https://api.mxplay.com/v1/web/detail/tab/tvshowseasons?type=tv_show&id={}&device-density=2&platform=com.mxplay.desktop&content-languages=hi,en'
|
||||
_API_EPISODES_URL = 'https://api.mxplay.com/v1/web/detail/tab/tvshowepisodes?type=season&id={}&device-density=1&platform=com.mxplay.desktop&content-languages=hi,en&{}'
|
||||
def _entries(self, entities, season_id):
|
||||
query = None
|
||||
api_url = traverse_obj(entities, (
|
||||
'tabs', ..., 'api', {urljoin('https://api.mxplayer.in/v1/web/')}, any))
|
||||
|
||||
def _entries(self, show_id):
|
||||
show_json = self._download_json(
|
||||
self._API_SHOW_URL.format(show_id),
|
||||
video_id=show_id, headers={'Referer': 'https://mxplayer.in'})
|
||||
page_num = 0
|
||||
for season in show_json.get('items') or []:
|
||||
season_id = try_get(season, lambda x: x['id'], str)
|
||||
next_url = ''
|
||||
while next_url is not None:
|
||||
page_num += 1
|
||||
season_json = self._download_json(
|
||||
self._API_EPISODES_URL.format(season_id, next_url),
|
||||
video_id=season_id,
|
||||
headers={'Referer': 'https://mxplayer.in'},
|
||||
note=f'Downloading JSON metadata page {page_num}')
|
||||
for episode in season_json.get('items') or []:
|
||||
video_url = episode['webUrl']
|
||||
yield self.url_result(
|
||||
f'https://mxplayer.in{video_url}',
|
||||
ie=MxplayerIE.ie_key(), video_id=video_url.split('-')[-1])
|
||||
next_url = season_json.get('next')
|
||||
for page in itertools.count(1):
|
||||
season_data = self._download_json(
|
||||
api_url, season_id, f'Downloading Page {page}', query=query)
|
||||
|
||||
share_urls = traverse_obj(season_data, (
|
||||
'items', ..., 'shareUrl', {urljoin(f'{self._BASE_URL}/')}))
|
||||
for share_url in share_urls:
|
||||
yield self.url_result(share_url, MxplayerRedirectIE)
|
||||
if len(share_urls) < self._PAGE_SIZE:
|
||||
break
|
||||
|
||||
query = traverse_obj(season_data, ('next', {urllib.parse.parse_qs}))
|
||||
if not query:
|
||||
break
|
||||
|
||||
def _real_extract(self, url):
|
||||
display_id, show_id = self._match_valid_url(url).groups()
|
||||
season_id = self._match_id(url)
|
||||
mxs = self._extract_mxs(url, season_id)
|
||||
|
||||
entities = traverse_obj(mxs, ('entities', season_id, {dict}))
|
||||
series_title = traverse_obj(entities, ('container', 'title', {clean_html}))
|
||||
season_title = traverse_obj(entities, ('title', {clean_html}))
|
||||
|
||||
return self.playlist_result(
|
||||
self._entries(show_id), playlist_id=show_id,
|
||||
playlist_title=display_id.replace('-', ' ').title())
|
||||
self._entries(entities, season_id),
|
||||
season_id, join_nonempty(series_title, season_title, delim=' - '))
|
||||
|
||||
|
||||
class MxplayerShowIE(MxplayerBaseIE):
|
||||
IE_NAME = 'mxplayer:show'
|
||||
|
||||
_VALID_URL = r'https?://(?:www\.)?mxplayer\.in/show/[\w-]+-(?P<id>[0-9a-f]{32})(?:[/?#]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.mxplayer.in/show/watch-bhaukaal-series-online-775ad3b682fde6a608559a60986b230d',
|
||||
'info_dict': {
|
||||
'id': '775ad3b682fde6a608559a60986b230d',
|
||||
'title': 'Bhaukaal',
|
||||
},
|
||||
'playlist_mincount': 2,
|
||||
}]
|
||||
|
||||
def _entries(self, entities):
|
||||
for season_id in traverse_obj(entities, (
|
||||
'tabs', ..., 'containers', ..., 'id', {str_or_none},
|
||||
)):
|
||||
yield self.url_result(
|
||||
f'{self._BASE_URL}/detail/season/{season_id}', MxplayerRedirectIE)
|
||||
|
||||
def _real_extract(self, url):
|
||||
show_id = self._match_id(url)
|
||||
mxs = self._extract_mxs(url, show_id)
|
||||
|
||||
entities = traverse_obj(mxs, ('entities', show_id, {dict}))
|
||||
|
||||
return self.playlist_result(
|
||||
self._entries(entities), show_id,
|
||||
traverse_obj(entities, ('title', {clean_html}, filter)))
|
||||
|
||||
|
||||
class MxplayerRedirectIE(MxplayerBaseIE):
|
||||
IE_NAME = 'mxplayer:redirect'
|
||||
IE_DESC = False
|
||||
|
||||
_IE_MAP = {
|
||||
'episode': MxplayerIE,
|
||||
'movie': MxplayerIE,
|
||||
'season': MxplayerSeasonIE,
|
||||
'shorts': MxplayerIE,
|
||||
'tvshow': MxplayerShowIE,
|
||||
}
|
||||
_VALID_URL = rf'https?://(?:www\.)?mxplayer\.in/detail/(?P<type>{"|".join(_IE_MAP)})/(?P<id>[0-9a-f]{{32}})(?:[/?#]|$)'
|
||||
_TESTS = [{
|
||||
# episode
|
||||
# https://www.mxplayer.in/show/watch-that-time-i-got-reincarnated-as-a-slime/season-2/megiddo-online-3eda0b3baf27f2892d3fca2fd650fb95
|
||||
'url': 'https://www.mxplayer.in/detail/episode/3eda0b3baf27f2892d3fca2fd650fb95',
|
||||
'info_dict': {
|
||||
'id': '3eda0b3baf27f2892d3fca2fd650fb95',
|
||||
'ext': 'mp4',
|
||||
'title': 'Megiddo',
|
||||
'age_limit': 16,
|
||||
'cast': 'count:6',
|
||||
'creators': 'count:11',
|
||||
'description': 'md5:f4ac8210c2e7bdcaab3b34636cf754b4',
|
||||
'display_id': 'megiddo-online',
|
||||
'episode': 'Episode 10',
|
||||
'episode_number': 10,
|
||||
'duration': 1427,
|
||||
'genres': 'count:6',
|
||||
'release_date': '20210315',
|
||||
'release_timestamp': 1615833000,
|
||||
'season': 'Season 2',
|
||||
'season_number': 2,
|
||||
'series': 'That Time I Got Reincarnated As A Slime',
|
||||
'tags': 'count:21',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1774843170,
|
||||
'upload_date': '20260330',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
# movie
|
||||
# https://www.mxplayer.in/movie/watch-sardar-udham-movie-online-0c586053d7ac563ca911ddfe08cf922f
|
||||
'url': 'https://www.mxplayer.in/detail/movie/0c586053d7ac563ca911ddfe08cf922f',
|
||||
'info_dict': {
|
||||
'id': '0c586053d7ac563ca911ddfe08cf922f',
|
||||
'ext': 'mp4',
|
||||
'title': 'Sardar Udham',
|
||||
'age_limit': 13,
|
||||
'cast': 'count:6',
|
||||
'creators': 'count:1',
|
||||
'description': 'md5:58f0d90f802898aaf328ac2942e878e5',
|
||||
'display_id': 'watch-sardar-udham-movie-online',
|
||||
'episode': 'Episode 0',
|
||||
'episode_number': 0,
|
||||
'duration': 9770,
|
||||
'genres': 'count:3',
|
||||
'release_date': '20211015',
|
||||
'release_timestamp': 1634322600,
|
||||
'tags': 'count:4',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'timestamp': 1739952608,
|
||||
'upload_date': '20250219',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
# shorts
|
||||
# https://www.mxplayer.in/shorts/watch-official-trailer-lafangey-sapne-dosti-duniya-online-55c77863e406c7b71f6bab28f7fbbe85
|
||||
'url': 'https://www.mxplayer.in/detail/shorts/55c77863e406c7b71f6bab28f7fbbe85',
|
||||
'info_dict': {
|
||||
'id': '55c77863e406c7b71f6bab28f7fbbe85',
|
||||
'ext': 'mp4',
|
||||
'title': 'Official Trailer| Lafangey - Sapne, Dosti, Duniya',
|
||||
'age_limit': 18,
|
||||
'description': 'md5:623d53e4543155dd732e96587711481c',
|
||||
'display_id': 'watch-official-trailer-lafangey-sapne-dosti-duniya-online',
|
||||
'episode': 'Episode 0',
|
||||
'episode_number': 0,
|
||||
'duration': 118,
|
||||
'genres': 'count:2',
|
||||
'release_date': '20250601',
|
||||
'release_timestamp': 1748802600,
|
||||
'tags': 'count:3',
|
||||
'thumbnail': r're:https?://.+',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
# season
|
||||
# https://www.mxplayer.in/show/watch-demon-slayer/seasons/season-3-41a6d7432bea96f2f0d06a625b39d9b1
|
||||
'url': 'https://www.mxplayer.in/detail/season/41a6d7432bea96f2f0d06a625b39d9b1',
|
||||
'info_dict': {
|
||||
'id': '41a6d7432bea96f2f0d06a625b39d9b1',
|
||||
'title': 'Demon Slayer - Season 3',
|
||||
},
|
||||
'playlist_mincount': 11,
|
||||
}, {
|
||||
# tvshow
|
||||
# https://www.mxplayer.in/show/watch-my-hero-academia-series-online-e7c68fb3951e61986af073a719c2ee4f
|
||||
'url': 'https://www.mxplayer.in/detail/tvshow/e7c68fb3951e61986af073a719c2ee4f',
|
||||
'info_dict': {
|
||||
'id': 'e7c68fb3951e61986af073a719c2ee4f',
|
||||
'title': 'My Hero Academia',
|
||||
},
|
||||
'playlist_mincount': 4,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
redirect_type, redirect_id = self._match_valid_url(url).group('type', 'id')
|
||||
ie = self._IE_MAP[redirect_type]
|
||||
|
||||
detail = self._download_json(
|
||||
'https://seo.mxplayer.in/v1/api/seo/get-url-details',
|
||||
redirect_id, query={'url': urllib.parse.urlsplit(url).path})
|
||||
|
||||
if redirect_url := traverse_obj(detail, (
|
||||
'data', 'redirect', {urljoin(f'{self._BASE_URL}/')},
|
||||
)):
|
||||
if self.suitable(redirect_url):
|
||||
raise UnsupportedError(redirect_url)
|
||||
return self.url_result(redirect_url, ie)
|
||||
|
||||
raise ExtractorError('Unable to resolve redirect URL')
|
||||
|
||||
@@ -658,7 +658,7 @@ class NetEaseMusicDjRadioIE(NetEaseMusicBaseIE):
|
||||
|
||||
entries.extend(self.url_result(
|
||||
f'http://music.163.com/#/program?id={program["id"]}', NetEaseMusicProgramIE,
|
||||
program['id'], program.get('name')) for program in info['programs'])
|
||||
str_or_none(program['id']), program.get('name')) for program in info['programs'])
|
||||
if not metainfo:
|
||||
metainfo = traverse_obj(info, ('programs', 0, 'radio', {
|
||||
'title': ('name', {str}),
|
||||
|
||||
@@ -104,7 +104,7 @@ class NiconicoIE(NiconicoBaseIE):
|
||||
IE_NAME = 'niconico'
|
||||
IE_DESC = 'ニコニコ動画'
|
||||
|
||||
_VALID_URL = r'https?://(?:(?:embed|sp|www)\.)?nicovideo\.jp/watch/(?P<id>(?:[a-z]{2})?\d+)'
|
||||
_VALID_URL = r'https?://(?:(?:embed|sp|www)\.)?nicovideo\.jp/(?:shorts|watch)/(?P<id>(?:[a-z]{2})?\d+)'
|
||||
_ERROR_MAP = {
|
||||
'FORBIDDEN': {
|
||||
'ADMINISTRATOR_DELETE_VIDEO': 'Video unavailable, possibly removed by admins',
|
||||
@@ -361,6 +361,29 @@ class NiconicoIE(NiconicoBaseIE):
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
'skip': 'Channel members only; specified continuous membership period required',
|
||||
}, {
|
||||
'url': 'https://www.nicovideo.jp/shorts/ss46441082',
|
||||
'info_dict': {
|
||||
'id': 'ss46441082',
|
||||
'ext': 'mp4',
|
||||
'title': '『超かぐや姫!』WEB予告 < アクション編 >',
|
||||
'availability': 'public',
|
||||
'channel': '『超かぐや姫!』公式',
|
||||
'channel_id': '141907929',
|
||||
'comment_count': int,
|
||||
'description': 'md5:86cd619f675377c7d77ddc13b4dda8bf',
|
||||
'duration': 15,
|
||||
'genres': ['アニメ'],
|
||||
'like_count': int,
|
||||
'tags': 'mincount:5',
|
||||
'thumbnail': r're:https?://img\.cdn\.nimg\.jp/s/nicovideo/thumbnails/.+',
|
||||
'timestamp': 1781600400,
|
||||
'upload_date': '20260616',
|
||||
'uploader': '『超かぐや姫!』公式',
|
||||
'uploader_id': '141907929',
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}]
|
||||
|
||||
def _extract_formats(self, api_data, video_id):
|
||||
@@ -428,7 +451,7 @@ class NiconicoIE(NiconicoBaseIE):
|
||||
'actionTrackId': f'AAAAAAAAAA_{round(time_seconds() * 1000)}',
|
||||
}, expected_status=[400, 404])
|
||||
|
||||
api_data = api_resp['data']
|
||||
api_data = traverse_obj(api_resp, ('data', {dict}))
|
||||
scheduled_time = traverse_obj(api_data, ('publishScheduledAt', {str}))
|
||||
status = traverse_obj(api_resp, ('meta', 'status', {int}))
|
||||
|
||||
@@ -465,7 +488,7 @@ class NiconicoIE(NiconicoBaseIE):
|
||||
if not formats and err_msg:
|
||||
self.raise_login_required(err_msg, metadata_available=True)
|
||||
|
||||
thumb_prefs = qualities(['url', 'middleUrl', 'largeUrl', 'player', 'ogp'])
|
||||
thumb_prefs = qualities(['url', 'middleUrl', 'largeUrl', 'player', 'ogp', 'short'])
|
||||
|
||||
return {
|
||||
'availability': availability,
|
||||
@@ -482,7 +505,8 @@ class NiconicoIE(NiconicoBaseIE):
|
||||
'url': url,
|
||||
**parse_resolution(url, lenient=True),
|
||||
} for key, url in traverse_obj(api_data, (
|
||||
'video', 'thumbnail', {dict}), default={}).items()],
|
||||
'video', 'thumbnail', {dict.items}, lambda _, v: url_or_none(v[1])),
|
||||
)],
|
||||
**traverse_obj(api_data, (('channel', 'owner'), any, {
|
||||
'channel': (('name', 'nickname'), {str}, any),
|
||||
'channel_id': ('id', {str_or_none}),
|
||||
|
||||
@@ -110,7 +110,7 @@ class NovaEmbedIE(InfoExtractor):
|
||||
|
||||
title = self._og_search_title(
|
||||
webpage, default=None) or self._search_regex(
|
||||
(r'<value>(?P<title>[^<]+)',
|
||||
(r'<value>(?P<value>[^<]+)',
|
||||
r'videoTitle\s*:\s*(["\'])(?P<value>(?:(?!\1).)+)\1'), webpage,
|
||||
'title', group='value')
|
||||
thumbnail = self._og_search_thumbnail(
|
||||
|
||||
@@ -74,7 +74,7 @@ class PandaTvIE(InfoExtractor):
|
||||
'http_headers': http_headers,
|
||||
**traverse_obj(video_meta, ('media', {
|
||||
'title': ('title', {str}),
|
||||
'release_timestamp': ('startTime', {parse_iso8601(delim=' ')}),
|
||||
'release_timestamp': ('startTime', {parse_iso8601(delimiter=' ')}),
|
||||
'thumbnail': ('ivsThumbnail', {url_or_none}),
|
||||
'channel': ('userNick', {str}),
|
||||
'concurrent_view_count': ('user', {int_or_none}),
|
||||
|
||||
@@ -65,8 +65,9 @@ class PatreonBaseIE(InfoExtractor):
|
||||
|
||||
class PatreonIE(PatreonBaseIE):
|
||||
IE_NAME = 'patreon'
|
||||
_VALID_URL = r'https?://(?:www\.)?patreon\.com/(?:creation\?hid=|posts/(?:[\w-]+-)?)(?P<id>\d+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?patreon\.com/(?:creation\?hid=|(?:[^/?#]+/)?posts/(?:[\w-]+-)?)(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
# FIXME: Fails due to no description extracted
|
||||
'url': 'http://www.patreon.com/creation?hid=743933',
|
||||
'md5': 'e25505eec1053a6e6813b8ed369875cc',
|
||||
'info_dict': {
|
||||
@@ -107,17 +108,17 @@ class PatreonIE(PatreonBaseIE):
|
||||
'id': 'SU4fj_aEMVw',
|
||||
'ext': 'mp4',
|
||||
'title': 'I\'m on Patreon!',
|
||||
'uploader': 'TraciJHines',
|
||||
'uploader': 'Traci Oden',
|
||||
'thumbnail': 're:^https?://.*$',
|
||||
'upload_date': '20150211',
|
||||
'description': 'md5:8af6425f50bd46fbf29f3db0fc3a8364',
|
||||
'uploader_id': '@TraciHinesMusic',
|
||||
'uploader_id': '@TraciOden',
|
||||
'categories': ['Entertainment'],
|
||||
'duration': 282,
|
||||
'view_count': int,
|
||||
'tags': 'count:39',
|
||||
'age_limit': 0,
|
||||
'channel': 'TraciJHines',
|
||||
'channel': 'Traci Oden',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCGLim4T2loE5rwCMdpCIPVg',
|
||||
'live_status': 'not_live',
|
||||
'like_count': int,
|
||||
@@ -125,7 +126,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
'availability': 'public',
|
||||
'channel_follower_count': int,
|
||||
'playable_in_embed': True,
|
||||
'uploader_url': 'https://www.youtube.com/@TraciHinesMusic',
|
||||
'uploader_url': 'https://www.youtube.com/@TraciOden',
|
||||
'comment_count': int,
|
||||
'channel_is_verified': True,
|
||||
'chapters': 'count:4',
|
||||
@@ -157,6 +158,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
},
|
||||
'skip': 'Patron-only content',
|
||||
}, {
|
||||
# FIXME: Fails due to no description extracted
|
||||
# m3u8 video (https://github.com/yt-dlp/yt-dlp/issues/2277)
|
||||
'url': 'https://www.patreon.com/posts/video-sketchbook-32452882',
|
||||
'info_dict': {
|
||||
@@ -220,6 +222,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
'channel_id': '2147162',
|
||||
'uploader_url': 'https://www.patreon.com/yaboyroshi',
|
||||
},
|
||||
'skip': 'HTTP Error 401 for m3u8 request; site now requires login to play the video',
|
||||
}, {
|
||||
# NSFW vimeo embed URL
|
||||
'url': 'https://www.patreon.com/posts/4k-spiderman-4k-96414599',
|
||||
@@ -242,6 +245,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
'expected_warnings': ['Failed to parse XML: not well-formed'],
|
||||
'skip': 'Video removed',
|
||||
}, {
|
||||
# multiple attachments/embeds
|
||||
'url': 'https://www.patreon.com/posts/holy-wars-solos-100601977',
|
||||
@@ -285,6 +289,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
},
|
||||
'params': {'getcomments': True},
|
||||
}, {
|
||||
# FIXME: Error: No supported media found in this post
|
||||
# Inlined media in post; uses _extract_from_media_api
|
||||
'url': 'https://www.patreon.com/posts/scottfalco-146966245',
|
||||
'info_dict': {
|
||||
@@ -304,6 +309,26 @@ class PatreonIE(PatreonBaseIE):
|
||||
'timestamp': 1767061800,
|
||||
'upload_date': '20251230',
|
||||
},
|
||||
}, {
|
||||
# FIXME: need to extract description
|
||||
'url': 'https://www.patreon.com/Insanimate/posts/meatcanyon-in-142663524',
|
||||
'md5': '132332e3bb345f75d8b471242346dee6',
|
||||
'info_dict': {
|
||||
'id': '142663524',
|
||||
'ext': 'mp4',
|
||||
'title': 'Meatcanyon in Playground',
|
||||
'uploader': 'Insanimate',
|
||||
'uploader_id': '2828146',
|
||||
'uploader_url': 'https://www.patreon.com/Insanimate',
|
||||
'channel_id': '6260877',
|
||||
'channel_url': 'https://www.patreon.com/Insanimate',
|
||||
'channel_follower_count': int,
|
||||
'comment_count': int,
|
||||
'like_count': int,
|
||||
'thumbnail': 're:^https?://.*$',
|
||||
'timestamp': 1762101034,
|
||||
'upload_date': '20251102',
|
||||
},
|
||||
}]
|
||||
_RETURN_TYPE = 'video'
|
||||
_HTTP_HEADERS = {
|
||||
@@ -357,7 +382,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
post = self._call_api(
|
||||
f'posts/{video_id}', video_id, query={
|
||||
'fields[media]': 'download_url,mimetype,size_bytes,file_name',
|
||||
'fields[post]': 'comment_count,content,embed,image,like_count,post_file,published_at,title,current_user_can_view',
|
||||
'fields[post]': 'comment_count,content,content_teaser_text,cleaned_teaser_text,embed,image,like_count,post_file,published_at,title,current_user_can_view',
|
||||
'fields[user]': 'full_name,url',
|
||||
'fields[post_tag]': 'value',
|
||||
'fields[campaign]': 'url,name,patron_count',
|
||||
@@ -367,7 +392,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
attributes = post['data']['attributes']
|
||||
info = traverse_obj(attributes, {
|
||||
'title': ('title', {str.strip}),
|
||||
'description': ('content', {clean_html}),
|
||||
'description': (('content', 'content_teaser_text', 'cleaned_teaser_text'), {clean_html}, any),
|
||||
'thumbnail': ('image', ('large_url', 'url'), {url_or_none}, any),
|
||||
'timestamp': ('published_at', {parse_iso8601}),
|
||||
'like_count': ('like_count', {int_or_none}),
|
||||
|
||||
@@ -4,6 +4,7 @@ import re
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
OnDemandPagedList,
|
||||
filter_dict,
|
||||
format_field,
|
||||
int_or_none,
|
||||
parse_resolution,
|
||||
@@ -1358,7 +1359,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'E2E tests',
|
||||
'categories': ['Unknown'],
|
||||
'channel': 'Main chocobozzz channel',
|
||||
'channel': 'Chocobozzz test channel',
|
||||
'channel_id': '5187',
|
||||
'channel_url': 'https://peertube2.cpy.re/video-channels/chocobozzz_channel',
|
||||
'description': 'md5:67daf92c833c41c95db874e18fcb2786',
|
||||
@@ -1382,7 +1383,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'E2E tests',
|
||||
'categories': ['Unknown'],
|
||||
'channel': 'Main chocobozzz channel',
|
||||
'channel': 'Chocobozzz test channel',
|
||||
'channel_id': '5187',
|
||||
'channel_url': 'https://peertube2.cpy.re/video-channels/chocobozzz_channel',
|
||||
'description': 'md5:67daf92c833c41c95db874e18fcb2786',
|
||||
@@ -1406,7 +1407,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'E2E tests',
|
||||
'categories': ['Unknown'],
|
||||
'channel': 'Main chocobozzz channel',
|
||||
'channel': 'Chocobozzz test channel',
|
||||
'channel_id': '5187',
|
||||
'channel_url': 'https://peertube2.cpy.re/video-channels/chocobozzz_channel',
|
||||
'description': 'md5:67daf92c833c41c95db874e18fcb2786',
|
||||
@@ -1452,6 +1453,36 @@ class PeerTubeIE(InfoExtractor):
|
||||
}, {
|
||||
'url': 'peertube:framatube.org:b37a5b9f-e6b5-415c-b700-04a5cd6ec205',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://videos.john-livingston.fr/w/mna1A6SxZ94cra4hMtjRQm',
|
||||
'md5': '6a5faad22916e41ba4078ef59c33bc9f',
|
||||
'info_dict': {
|
||||
'id': 'mna1A6SxZ94cra4hMtjRQm',
|
||||
'ext': 'mp4',
|
||||
'title': 'test yt-dlp',
|
||||
'description': 'md5:d8556ee790ad9b3fac6f0bb3eb5b67bd',
|
||||
'thumbnail': r're:https?://videos.john-livingston\.fr/lazy-static/thumbnails/.+\.jpg',
|
||||
'timestamp': 1780645286,
|
||||
'upload_date': '20260605',
|
||||
'uploader': 'John Livingston',
|
||||
'uploader_id': '5',
|
||||
'uploader_url': 'https://videos.john-livingston.fr/accounts/john',
|
||||
'channel': 'john_livingston',
|
||||
'channel_id': '4',
|
||||
'channel_url': 'https://videos.john-livingston.fr/video-channels/john_livingston',
|
||||
'license': 'Unknown',
|
||||
'duration': 16,
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'dislike_count': int,
|
||||
'tags': 'count:0',
|
||||
'categories': ['Unknown'],
|
||||
},
|
||||
'params': {
|
||||
'videopassword': 'thepassword',
|
||||
'format': '600p',
|
||||
},
|
||||
'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest'],
|
||||
}]
|
||||
_WEBPAGE_TESTS = [{
|
||||
'url': 'https://video.macver.org/w/6gvhZpUGQVd4SQ6oYDc9pC',
|
||||
@@ -1492,6 +1523,9 @@ class PeerTubeIE(InfoExtractor):
|
||||
'>We are sorry but it seems that PeerTube is not compatible with your web browser.<')):
|
||||
return 'peertube:{}:{}'.format(*mobj.group('host', 'id'))
|
||||
|
||||
def _get_headers(self):
|
||||
return filter_dict({'x-peertube-video-password': self.get_param('videopassword')})
|
||||
|
||||
@classmethod
|
||||
def _extract_embed_urls(cls, url, webpage):
|
||||
embeds = tuple(super()._extract_embed_urls(url, webpage))
|
||||
@@ -1505,7 +1539,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
def _call_api(self, host, video_id, path, note=None, errnote=None, fatal=True):
|
||||
return self._download_json(
|
||||
self._API_BASE % (host, video_id, path), video_id,
|
||||
note=note, errnote=errnote, fatal=fatal)
|
||||
note=note, errnote=errnote, fatal=fatal, headers=self._get_headers())
|
||||
|
||||
def _get_subtitles(self, host, video_id):
|
||||
captions = self._call_api(
|
||||
@@ -1545,7 +1579,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
if playlist_url := url_or_none(playlist.get('playlistUrl')):
|
||||
is_live = True
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
playlist_url, video_id, fatal=False, live=True))
|
||||
playlist_url, video_id, fatal=False, live=True, headers=self._get_headers()))
|
||||
playlist_files = playlist.get('files')
|
||||
if not (playlist_files and isinstance(playlist_files, list)):
|
||||
continue
|
||||
@@ -1629,6 +1663,8 @@ class PeerTubeIE(InfoExtractor):
|
||||
'subtitles': subtitles,
|
||||
'is_live': is_live,
|
||||
'webpage_url': webpage_url,
|
||||
# Headers are needed for ALL format requests, but not thumbnails
|
||||
'http_headers': self._get_headers(),
|
||||
}
|
||||
|
||||
|
||||
@@ -1650,7 +1686,7 @@ class PeerTubePlaylistIE(InfoExtractor):
|
||||
'description': 'Diversas palestras do Richard Stallman no Brasil.',
|
||||
'title': 'Richard Stallman no Brasil',
|
||||
'channel': 'debianbrazilteam',
|
||||
'channel_id': 1522,
|
||||
'channel_id': '1522',
|
||||
'thumbnail': r're:https?://peertube\.debian\.social/lazy-static/thumbnails/.+\.jpg',
|
||||
'timestamp': 1599676222,
|
||||
'upload_date': '20200909',
|
||||
@@ -1662,7 +1698,7 @@ class PeerTubePlaylistIE(InfoExtractor):
|
||||
'id': 'chocobozzz',
|
||||
'title': 'chocobozzz',
|
||||
'channel': 'chocobozzz',
|
||||
'channel_id': 37855,
|
||||
'channel_id': '37855',
|
||||
'thumbnail': '',
|
||||
'timestamp': 1553874564,
|
||||
'upload_date': '20190329',
|
||||
@@ -1674,7 +1710,7 @@ class PeerTubePlaylistIE(InfoExtractor):
|
||||
'id': 'bf54d359-cfad-4935-9d45-9d6be93f63e8',
|
||||
'title': 'Les vidéos de Framasoft',
|
||||
'channel': 'framasoft',
|
||||
'channel_id': 3,
|
||||
'channel_id': '3',
|
||||
'thumbnail': '',
|
||||
'timestamp': 1519917377,
|
||||
'upload_date': '20180301',
|
||||
@@ -1684,9 +1720,10 @@ class PeerTubePlaylistIE(InfoExtractor):
|
||||
'url': 'https://peertube2.cpy.re/c/blender_open_movies@video.blender.org/videos',
|
||||
'info_dict': {
|
||||
'id': 'blender_open_movies@video.blender.org',
|
||||
'title': 'Official Blender Open Movies',
|
||||
'title': 'Blender Open Movies',
|
||||
'description': 'md5:3dfabff00857fe3896fb222eb2cb1b80',
|
||||
'channel': 'blender',
|
||||
'channel_id': 1926,
|
||||
'channel_id': '1926',
|
||||
'thumbnail': '',
|
||||
'timestamp': 1540472902,
|
||||
'upload_date': '20181025',
|
||||
@@ -1720,7 +1757,7 @@ class PeerTubePlaylistIE(InfoExtractor):
|
||||
playlist_description = info.get('description')
|
||||
playlist_timestamp = unified_timestamp(info.get('createdAt'))
|
||||
channel = try_get(info, lambda x: x['ownerAccount']['name']) or info.get('displayName')
|
||||
channel_id = try_get(info, lambda x: x['ownerAccount']['id']) or info.get('id')
|
||||
channel_id = str_or_none(try_get(info, lambda x: x['ownerAccount']['id']) or info.get('id'))
|
||||
thumbnail = format_field(info, 'thumbnailPath', f'https://{host}%s')
|
||||
|
||||
entries = OnDemandPagedList(functools.partial(
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
clean_html,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
str_or_none,
|
||||
unescapeHTML,
|
||||
url_or_none,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
@@ -18,29 +21,24 @@ class PeriscopeBaseIE(InfoExtractor):
|
||||
item_id, query=query)
|
||||
|
||||
def _parse_broadcast_data(self, broadcast, video_id):
|
||||
title = broadcast.get('status') or 'Periscope Broadcast'
|
||||
uploader = broadcast.get('user_display_name') or broadcast.get('username')
|
||||
title = f'{uploader} - {title}' if uploader else title
|
||||
thumbnails = [{
|
||||
'url': broadcast[image],
|
||||
} for image in ('image_url', 'image_url_medium', 'image_url_small') if broadcast.get(image)]
|
||||
|
||||
return {
|
||||
'id': broadcast.get('id') or video_id,
|
||||
'title': title,
|
||||
'timestamp': parse_iso8601(broadcast.get('created_at')) or int_or_none(
|
||||
broadcast.get('created_at_ms'), scale=1000),
|
||||
'release_timestamp': int_or_none(broadcast.get('scheduled_start_ms'), scale=1000),
|
||||
'uploader': uploader,
|
||||
'uploader_id': broadcast.get('user_id') or broadcast.get('username'),
|
||||
'thumbnails': thumbnails,
|
||||
'view_count': int_or_none(broadcast.get('total_watched')),
|
||||
'concurrent_view_count': int_or_none(broadcast.get('total_watching')),
|
||||
'tags': broadcast.get('tags'),
|
||||
'display_id': video_id,
|
||||
'live_status': {
|
||||
'running': 'is_live',
|
||||
'not_started': 'is_upcoming',
|
||||
}.get(traverse_obj(broadcast, ('state', {str.lower}))) or 'was_live',
|
||||
**traverse_obj(broadcast, {
|
||||
'id': ('id', {str_or_none}),
|
||||
'title': ('status', {clean_html}, filter),
|
||||
'concurrent_view_count': ('total_watching', {int_or_none}),
|
||||
'release_timestamp': (('scheduled_start_ms', 'start_ms'), {int_or_none(scale=1000)}, any),
|
||||
'tags': ('tags', ..., {clean_html}, filter, all, filter),
|
||||
'thumbnails': (('image_url_small', 'image_url_medium', 'image_url'), {'url': {url_or_none}}),
|
||||
'timestamp': ((('created_at', {parse_iso8601}), ('created_at_ms', {int_or_none(scale=1000)})), any),
|
||||
'uploader': ('user_display_name', {clean_html}, filter),
|
||||
'uploader_id': ('username', {clean_html}, filter),
|
||||
'view_count': ('total_watched', {int_or_none}),
|
||||
}),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -69,22 +67,22 @@ class PeriscopeBaseIE(InfoExtractor):
|
||||
class PeriscopeIE(PeriscopeBaseIE):
|
||||
IE_DESC = 'Periscope'
|
||||
IE_NAME = 'periscope'
|
||||
_VALID_URL = r'https?://(?:www\.)?(?:periscope|pscp)\.tv/[^/]+/(?P<id>[^/?#]+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?(?:periscope|pscp)\.tv/[^/?#]+/(?P<id>[^/?#]+)'
|
||||
_EMBED_REGEX = [r'<iframe[^>]+src=([\'"])(?P<url>(?:https?:)?//(?:www\.)?(?:periscope|pscp)\.tv/(?:(?!\1).)+)\1']
|
||||
# Alive example URLs can be found here https://www.periscope.tv/
|
||||
_TESTS = [{
|
||||
'url': 'https://www.periscope.tv/w/aJUQnjY3MjA3ODF8NTYxMDIyMDl2zCg2pECBgwTqRpQuQD352EMPTKQjT4uqlM3cgWFA-g==',
|
||||
'md5': '65b57957972e503fcbbaeed8f4fa04ca',
|
||||
'url': 'https://www.periscope.tv/LularoeHusbandMike/1mrGmgaXAVqxy',
|
||||
'info_dict': {
|
||||
'id': '56102209',
|
||||
'id': '1mrGmgaXAVqxy',
|
||||
'ext': 'mp4',
|
||||
'title': 'Bec Boop - 🚠✈️🇬🇧 Fly above #London in Emirates Air Line cable car at night 🇬🇧✈️🚠 #BoopScope 🎀💗',
|
||||
'timestamp': 1438978559,
|
||||
'upload_date': '20150807',
|
||||
'uploader': 'Bec Boop',
|
||||
'uploader_id': '1465763',
|
||||
'title': '🎉👍🏼 BROWSE OUR ENTIRE 1,900 +PIECE INVENTORY! 👍🏼🎉 #lularoe',
|
||||
'live_status': 'was_live',
|
||||
'tags': 'count:1',
|
||||
'thumbnail': r're:https?://prod-fastly-us-east-1\.video\.pscp\.tv/.+',
|
||||
'timestamp': 1498621952,
|
||||
'upload_date': '20170628',
|
||||
'uploader': 'LuLaRoe Husband Mike',
|
||||
'uploader_id': 'LularoeHusbandMike',
|
||||
},
|
||||
'skip': 'Expires in 24 hours',
|
||||
}, {
|
||||
'url': 'https://www.periscope.tv/w/1ZkKzPbMVggJv',
|
||||
'only_matching': True,
|
||||
@@ -109,11 +107,6 @@ class PeriscopeIE(PeriscopeBaseIE):
|
||||
width = int_or_none(broadcast.get('width'))
|
||||
height = int_or_none(broadcast.get('height'))
|
||||
|
||||
def add_width_and_height(f):
|
||||
for key, val in (('width', width), ('height', height)):
|
||||
if not f.get(key):
|
||||
f[key] = val
|
||||
|
||||
video_urls = set()
|
||||
formats = []
|
||||
for format_id in ('replay', 'rtmp', 'hls', 'https_hls', 'lhls', 'lhlsweb'):
|
||||
@@ -130,7 +123,7 @@ class PeriscopeIE(PeriscopeBaseIE):
|
||||
'url': video_url,
|
||||
'ext': 'flv' if format_id == 'rtmp' else 'mp4',
|
||||
}
|
||||
self._add_width_and_height(rtmp_format)
|
||||
self._add_width_and_height(rtmp_format, width, height)
|
||||
formats.append(rtmp_format)
|
||||
|
||||
info['formats'] = formats
|
||||
|
||||
@@ -79,7 +79,6 @@ class QQMusicIE(QQMusicBaseIE):
|
||||
'creators': ['ケーキ姫', 'JUMA'],
|
||||
'genres': ['Pop'],
|
||||
'description': 'md5:b5261f3d595657ae561e9e6aee7eb7d9',
|
||||
'size': 4501244,
|
||||
'thumbnail': r're:^https?://.*\.jpg(?:$|[#?])',
|
||||
'subtitles': 'count:1',
|
||||
},
|
||||
@@ -113,7 +112,6 @@ class QQMusicIE(QQMusicBaseIE):
|
||||
'creators': ['李季美'],
|
||||
'genres': [],
|
||||
'description': 'md5:fc711212aa623b28534954dc4bd67385',
|
||||
'size': 3535730,
|
||||
'thumbnail': r're:^https?://.*\.jpg(?:$|[#?])',
|
||||
},
|
||||
}, {
|
||||
@@ -191,7 +189,7 @@ class QQMusicIE(QQMusicBaseIE):
|
||||
'url': urljoin('https://dl.stream.qqmusic.qq.com', media_info['purl']),
|
||||
'format': format_id,
|
||||
'format_id': format_id,
|
||||
'size': traverse_obj(info_data, ('file', f'size_{format_id}', {int_or_none})),
|
||||
'filesize': traverse_obj(info_data, ('file', f'size_{format_id}', {int_or_none})),
|
||||
'quality': format_info.get('preference'),
|
||||
'abr': format_info.get('abr'),
|
||||
'ext': format_info.get('ext'),
|
||||
@@ -261,7 +259,6 @@ class QQMusicSingerIE(QQMusicBaseIE):
|
||||
'creators': ['桃几OvO'],
|
||||
'genres': ['Pop'],
|
||||
'description': 'md5:4296005a04edcb5cdbe0889d5055a7ae',
|
||||
'size': 3970822,
|
||||
'thumbnail': r're:^https?://.*\.jpg(?:$|[#?])',
|
||||
},
|
||||
}],
|
||||
|
||||
@@ -129,8 +129,8 @@ class RadioCanadaIE(InfoExtractor):
|
||||
'thumbnail': get_meta('imageHR') or get_meta('imageMR') or get_meta('imageBR'),
|
||||
'duration': int_or_none(get_meta('length')),
|
||||
'series': get_meta('Emission'),
|
||||
'season_number': int_or_none('SrcSaison'),
|
||||
'episode_number': int_or_none('SrcEpisode'),
|
||||
'season_number': int_or_none(get_meta('SrcSaison')),
|
||||
'episode_number': int_or_none(get_meta('SrcEpisode')),
|
||||
'upload_date': unified_strdate(get_meta('Date')),
|
||||
'subtitles': subtitles,
|
||||
'formats': formats,
|
||||
|
||||
@@ -7,6 +7,7 @@ from ..networking.exceptions import HTTPError
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
dict_get,
|
||||
str_or_none,
|
||||
strip_or_none,
|
||||
traverse_obj,
|
||||
try_get,
|
||||
@@ -80,7 +81,11 @@ class RCTIPlusIE(RCTIPlusBaseIE):
|
||||
'timestamp': 1587561540,
|
||||
'upload_date': '20200422',
|
||||
'series': 'iNews Malam',
|
||||
'channel': 'INews',
|
||||
'channel': 'INEWS',
|
||||
'channel_id': '4',
|
||||
'thumbnail': 'https://static.rctiplus.id/media/2000/files/fta_rcti/Portrait/iNews_Malam/inews_malam_768x1152.jpg',
|
||||
'categories': ['Hard News'],
|
||||
'live_status': 'not_live',
|
||||
},
|
||||
}, { # Missed event/replay
|
||||
'url': 'https://www.rctiplus.com/missed-event/2507/mou-signing-ceremony-27-juli-2021-1400-wib',
|
||||
@@ -117,12 +122,13 @@ class RCTIPlusIE(RCTIPlusBaseIE):
|
||||
'url': 'https://www.rctiplus.com/live-event/1/rcti',
|
||||
'info_dict': {
|
||||
'id': 'v_lt1',
|
||||
'title': 'RCTI',
|
||||
'title': r're:RCTI \d{4}-\d{2}-\d{2} \d{2}:\d{2}',
|
||||
'display_id': 'rcti',
|
||||
'ext': 'mp4',
|
||||
'timestamp': 1546344000,
|
||||
'upload_date': '20190101',
|
||||
'is_live': True,
|
||||
'thumbnail': 'https://static.rctiplus.id/media/2000/files/fta_rcti/Channel_Logo/por-RCTI.png',
|
||||
'live_status': 'is_live',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
@@ -207,7 +213,7 @@ class RCTIPlusIE(RCTIPlusBaseIE):
|
||||
'season_number': video_meta.get('season'),
|
||||
'episode_number': video_meta.get('episode'),
|
||||
'channel': video_json.get('tv_name'),
|
||||
'channel_id': video_json.get('tv_id'),
|
||||
'channel_id': str_or_none(video_json.get('tv_id')),
|
||||
'formats': formats,
|
||||
'thumbnails': thumbnails,
|
||||
'is_live': video_type == 'live-event' and not is_upcoming,
|
||||
@@ -292,7 +298,7 @@ class RCTIPlusSeriesIE(RCTIPlusBaseIE):
|
||||
}
|
||||
|
||||
def _series_entries(self, series_id, display_id=None, video_type=None, metadata={}):
|
||||
if not video_type or video_type in 'episodes':
|
||||
if not video_type or video_type == 'episodes':
|
||||
try:
|
||||
seasons_list = self._call_api(
|
||||
f'https://api.rctiplus.com/api/v1/program/{series_id}/season',
|
||||
@@ -305,11 +311,11 @@ class RCTIPlusSeriesIE(RCTIPlusBaseIE):
|
||||
yield from self._entries(
|
||||
f'https://api.rctiplus.com/api/v2/program/{series_id}/episode?season={season["season"]}',
|
||||
display_id, f'Downloading season {season["season"]} episode entries', metadata)
|
||||
if not video_type or video_type in 'extras':
|
||||
if not video_type or video_type == 'extras':
|
||||
yield from self._entries(
|
||||
f'https://api.rctiplus.com/api/v2/program/{series_id}/extra?content_id=0',
|
||||
display_id, 'Downloading extra entries', metadata)
|
||||
if not video_type or video_type in 'clips':
|
||||
if not video_type or video_type == 'clips':
|
||||
yield from self._entries(
|
||||
f'https://api.rctiplus.com/api/v2/program/{series_id}/clip?content_id=0',
|
||||
display_id, 'Downloading clip entries', metadata)
|
||||
|
||||
@@ -13,12 +13,10 @@ from ..utils import (
|
||||
unescapeHTML,
|
||||
update_url_query,
|
||||
url_or_none,
|
||||
urlencode_postdata,
|
||||
)
|
||||
|
||||
|
||||
class RedditIE(InfoExtractor):
|
||||
_NETRC_MACHINE = 'reddit'
|
||||
_VALID_URL = r'https?://(?:\w+\.)?reddit(?:media)?\.com/(?P<slug>(?:(?:r|user)/[^/]+/)?comments/(?P<id>[^/?#&]+))'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.reddit.com/r/videos/comments/6rrwyj/that_small_heart_attack/',
|
||||
@@ -269,28 +267,6 @@ class RedditIE(InfoExtractor):
|
||||
def _is_logged_in(self):
|
||||
return bool(self._get_cookies('https://www.reddit.com/').get('reddit_session'))
|
||||
|
||||
def _perform_login(self, username, password):
|
||||
if self._is_logged_in:
|
||||
return
|
||||
|
||||
captcha = self._download_json(
|
||||
'https://www.reddit.com/api/requires_captcha/login.json', None,
|
||||
'Checking login requirement')['required']
|
||||
if captcha:
|
||||
raise ExtractorError('Reddit is requiring captcha before login', expected=True)
|
||||
login = self._download_json(
|
||||
f'https://www.reddit.com/api/login/{username}', None, data=urlencode_postdata({
|
||||
'op': 'login-main',
|
||||
'user': username,
|
||||
'passwd': password,
|
||||
'api_type': 'json',
|
||||
}), note='Logging in', errnote='Login request failed')
|
||||
errors = '; '.join(traverse_obj(login, ('json', 'errors', ..., 1)))
|
||||
if errors:
|
||||
raise ExtractorError(f'Unable to login, Reddit API says {errors}', expected=True)
|
||||
elif not traverse_obj(login, ('json', 'data', 'cookie', {str})):
|
||||
raise ExtractorError('Unable to login, no cookie was returned')
|
||||
|
||||
def _real_initialize(self):
|
||||
if not self._is_logged_in:
|
||||
# We need to get a 'loid' cookie to access the API anonymously
|
||||
|
||||
@@ -30,7 +30,7 @@ class RedGifsBaseIE(InfoExtractor):
|
||||
quality = qualities(tuple(self._FORMATS.keys()))
|
||||
|
||||
orig_height = int_or_none(gif_data.get('height'))
|
||||
aspect_ratio = try_get(gif_data, lambda x: orig_height / x['width'])
|
||||
aspect_ratio = try_get(gif_data, lambda x: x['width'] / orig_height)
|
||||
|
||||
formats = []
|
||||
for format_id, height in self._FORMATS.items():
|
||||
@@ -41,7 +41,7 @@ class RedGifsBaseIE(InfoExtractor):
|
||||
formats.append({
|
||||
'url': video_url,
|
||||
'format_id': format_id,
|
||||
'width': height * aspect_ratio if aspect_ratio else None,
|
||||
'width': int_or_none(height * aspect_ratio) if aspect_ratio else None,
|
||||
'height': height,
|
||||
'quality': quality(format_id),
|
||||
})
|
||||
|
||||
@@ -77,7 +77,7 @@ class Rule34VideoIE(InfoExtractor):
|
||||
formats.append({
|
||||
'url': url,
|
||||
'ext': ext.lower(),
|
||||
'quality': quality,
|
||||
'quality': int_or_none(quality),
|
||||
})
|
||||
|
||||
categories, creators, uploader, uploader_url = [None] * 4
|
||||
|
||||
@@ -48,7 +48,7 @@ class SaitosanIE(InfoExtractor):
|
||||
|
||||
self._download_webpage(base, b_id, note='Polling socket')
|
||||
payload = f'420["room_start_join",{{"room_id":"{b_id}"}}]'
|
||||
payload = f'{len(payload)}:{payload}'
|
||||
payload = f'{len(payload)}:{payload}'.encode()
|
||||
|
||||
self._download_webpage(base, b_id, data=payload, note='Polling socket with payload')
|
||||
response = self._download_socket_json(base, b_id, note='Polling socket')
|
||||
@@ -59,11 +59,11 @@ class SaitosanIE(InfoExtractor):
|
||||
else 'The socket reported that the broadcast could not be joined. Maybe it\'s offline or the URL is incorrect',
|
||||
expected=True, video_id=b_id)
|
||||
|
||||
self._download_webpage(base, b_id, data='26:421["room_finish_join",{}]', note='Polling socket')
|
||||
self._download_webpage(base, b_id, data=b'26:421["room_finish_join",{}]', note='Polling socket')
|
||||
b_data = self._download_socket_json(base, b_id, note='Getting broadcast metadata from socket')
|
||||
m3u8_url = b_data.get('url')
|
||||
|
||||
self._download_webpage(base, b_id, data='1:1', note='Closing socket', fatal=False)
|
||||
self._download_webpage(base, b_id, data=b'1:1', note='Closing socket', fatal=False)
|
||||
|
||||
return {
|
||||
'id': b_id,
|
||||
|
||||
@@ -94,7 +94,7 @@ class SonyLIVIE(InfoExtractor):
|
||||
'mobileNumber': username,
|
||||
'channelPartnerID': 'MSMIND',
|
||||
'country': 'IN',
|
||||
'timestamp': dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%MZ'),
|
||||
'timestamp': dt.datetime.now(dt.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
'otpSize': 6,
|
||||
'loginType': 'REGISTERORSIGNIN',
|
||||
'isMobileMandatory': True,
|
||||
@@ -111,7 +111,7 @@ class SonyLIVIE(InfoExtractor):
|
||||
'otp': self._get_tfa_info('OTP'),
|
||||
'dmaId': 'IN',
|
||||
'ageConfirmation': True,
|
||||
'timestamp': dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%MZ'),
|
||||
'timestamp': dt.datetime.now(dt.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
'isMobileMandatory': True,
|
||||
}).encode())
|
||||
if otp_verify_json['resultCode'] == 'KO':
|
||||
|
||||
@@ -65,6 +65,7 @@ class SoundcloudEmbedIE(InfoExtractor):
|
||||
'description': '',
|
||||
'uploader': 'Guitar Player',
|
||||
'uploader_id': '489924156',
|
||||
'uploader_url': 'https://soundcloud.com/user-630852220',
|
||||
},
|
||||
'playlist_mincount': 7,
|
||||
}]
|
||||
@@ -428,12 +429,55 @@ class SoundcloudBaseIE(InfoExtractor):
|
||||
'tags': traverse_obj(info, ('tag_list', {self._TAGS_RE.findall}, ..., ..., filter)),
|
||||
'artists': traverse_obj(info, ('publisher_metadata', 'artist', {str}, filter, all, filter)),
|
||||
'formats': formats if not extract_flat else None,
|
||||
'__post_extractor': self.extract_comments(track_id),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _resolv_url(cls, url):
|
||||
return cls._API_V2_BASE + 'resolve?url=' + url
|
||||
|
||||
def _get_comments(self, track_id):
|
||||
available_filters = ('newest', 'oldest', 'track-timestamp')
|
||||
sort_by = self._configuration_arg('comments_sort_by', default=[None], ie_key='soundcloud')[0]
|
||||
if not sort_by:
|
||||
sort_by = available_filters[0]
|
||||
self.to_screen(
|
||||
f'Defaulting to sort comments by {sort_by}. '
|
||||
f'Configure this with --extractor-args soundcloud:comments_sort_by=FILTER . '
|
||||
f'Available filters: {", ".join(available_filters)}')
|
||||
elif sort_by not in available_filters:
|
||||
raise ExtractorError(f'Invalid comments_sort_by filter: {sort_by}', expected=True)
|
||||
else:
|
||||
self.to_screen(f'Sorting comments by {sort_by}')
|
||||
|
||||
next_url = update_url_query(
|
||||
f'{self._API_V2_BASE}tracks/{track_id}/comments', {
|
||||
'sort': sort_by,
|
||||
'limit': '20',
|
||||
'offset': '0',
|
||||
'threaded': '1',
|
||||
})
|
||||
for page_num in itertools.count(1):
|
||||
page = self._call_api(next_url, track_id, note=f'Downloading comments page {page_num}')
|
||||
|
||||
for comment_dict in traverse_obj(page, ('collection', lambda _, v: isinstance(v['id'], int))):
|
||||
yield traverse_obj(comment_dict, {
|
||||
'id': ('id', {int}, {str_or_none}),
|
||||
'author_id': ('user', 'id', {int}, {str_or_none}),
|
||||
'author': ('user', 'username', {str}),
|
||||
'author_thumbnail': ('user', 'avatar_url', {url_or_none}),
|
||||
'author_url': ('user', 'permalink_url', {url_or_none}),
|
||||
'author_is_verified': ('user', 'verified', {bool}),
|
||||
'timestamp': ('created_at', {unified_timestamp}),
|
||||
'text': ('body', {str}),
|
||||
'start_time': ('timestamp', {float_or_none(scale=1000)}),
|
||||
'end_time': ('timestamp', {float_or_none(scale=1000)}),
|
||||
})
|
||||
|
||||
next_url = page.get('next_href')
|
||||
if not next_url:
|
||||
break
|
||||
|
||||
|
||||
class SoundcloudIE(SoundcloudBaseIE):
|
||||
"""Information extractor for soundcloud.com
|
||||
@@ -459,10 +503,9 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
IE_NAME = 'soundcloud'
|
||||
_TESTS = [{
|
||||
'url': 'http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy',
|
||||
'md5': 'de9bac153e7427a7333b4b0c1b6a18d2',
|
||||
'info_dict': {
|
||||
'id': '62986583',
|
||||
'ext': 'opus',
|
||||
'ext': 'm4a',
|
||||
'title': 'Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1',
|
||||
'track': 'Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1',
|
||||
'description': 'md5:7b6074e00887ad79f59b647c8fb6d5ae',
|
||||
@@ -470,7 +513,7 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
'uploader_id': '1571244',
|
||||
'timestamp': 1349920598,
|
||||
'upload_date': '20121011',
|
||||
'duration': 143.216,
|
||||
'duration': 143.206,
|
||||
'license': 'all-rights-reserved',
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
@@ -480,31 +523,7 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
'uploader_url': 'https://soundcloud.com/ethmusic',
|
||||
'tags': 'count:14',
|
||||
},
|
||||
}, {
|
||||
# Geo-restricted
|
||||
'url': 'https://soundcloud.com/the-concept-band/goldrushed-mastered?in=the-concept-band/sets/the-royal-concept-ep',
|
||||
'info_dict': {
|
||||
'id': '47127627',
|
||||
'ext': 'opus',
|
||||
'title': 'Goldrushed',
|
||||
'track': 'Goldrushed',
|
||||
'description': 'md5:c0080b79a3710811d60234f94f391a40',
|
||||
'uploader': 'The Royal Concept',
|
||||
'uploader_id': '9615865',
|
||||
'timestamp': 1337635207,
|
||||
'upload_date': '20120521',
|
||||
'duration': 227.103,
|
||||
'license': 'all-rights-reserved',
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'comment_count': int,
|
||||
'repost_count': int,
|
||||
'uploader_url': 'https://soundcloud.com/the-concept-band',
|
||||
'thumbnail': r're:https?://[ai]1\.sndcdn\.com/.+\.(?:jpg|png)',
|
||||
'genres': ['Alternative'],
|
||||
'artists': ['The Royal Concept'],
|
||||
'tags': [],
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
# private link
|
||||
'url': 'https://soundcloud.com/jaimemf/youtube-dl-test-video-a-y-baw/s-8Pjrp',
|
||||
@@ -558,10 +577,9 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
}, {
|
||||
# downloadable song
|
||||
'url': 'https://soundcloud.com/the80m/the-following',
|
||||
'md5': 'ecb87d7705d5f53e6c02a63760573c75', # wav: '9ffcddb08c87d74fb5808a3c183a1d04'
|
||||
'info_dict': {
|
||||
'id': '343609555',
|
||||
'ext': 'opus', # wav original available with auth
|
||||
'ext': 'm4a', # wav original available with auth
|
||||
'title': 'The Following',
|
||||
'track': 'The Following',
|
||||
'description': '',
|
||||
@@ -570,7 +588,7 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
'uploader_url': 'https://soundcloud.com/the80m',
|
||||
'upload_date': '20170922',
|
||||
'timestamp': 1506120436,
|
||||
'duration': 397.228,
|
||||
'duration': 397.175,
|
||||
'thumbnail': r're:https?://[ai]1\.sndcdn\.com/.+\.(?:jpg|png)',
|
||||
'license': 'all-rights-reserved',
|
||||
'like_count': int,
|
||||
@@ -581,15 +599,16 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
'artists': ['80M'],
|
||||
'tags': 'count:4',
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
'expected_warnings': ['Original download format is only available for registered users'],
|
||||
}, {
|
||||
# private link, downloadable format
|
||||
# tags with spaces (e.g. "Uplifting Trance", "Ori Uplift")
|
||||
'url': 'https://soundcloud.com/oriuplift/uponly-238-no-talking-wav/s-AyZUd',
|
||||
'md5': '2e1530d0e9986a833a67cb34fc90ece0', # wav: '64a60b16e617d41d0bef032b7f55441e'
|
||||
'md5': '59ed00579679f3b660dfdb8d2dbf8589', # wav: '64a60b16e617d41d0bef032b7f55441e'
|
||||
'info_dict': {
|
||||
'id': '340344461',
|
||||
'ext': 'opus', # wav original available with auth
|
||||
'ext': 'mp3', # wav original available with auth
|
||||
'title': 'Uplifting Only 238 [No Talking] (incl. Alex Feed Guestmix) (Aug 31, 2017) [wav]',
|
||||
'track': 'Uplifting Only 238 [No Talking] (incl. Alex Feed Guestmix) (Aug 31, 2017) [wav]',
|
||||
'description': 'md5:fa20ee0fca76a3d6df8c7e57f3715366',
|
||||
@@ -616,7 +635,7 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
'md5': '59c7872bc44e5d99b7211891664760c2',
|
||||
'info_dict': {
|
||||
'id': '309699954',
|
||||
'ext': 'mp3',
|
||||
'ext': 'm4a',
|
||||
'title': 'Sideways (Prod. Mad Real)',
|
||||
'track': 'Sideways (Prod. Mad Real)',
|
||||
'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
|
||||
@@ -624,7 +643,7 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
'uploader_id': '2366352',
|
||||
'timestamp': 1488152409,
|
||||
'upload_date': '20170226',
|
||||
'duration': 207.012,
|
||||
'duration': 206.988,
|
||||
'thumbnail': r're:https?://[ai]1\.sndcdn\.com/.+\.(?:jpg|png)',
|
||||
'license': 'all-rights-reserved',
|
||||
'view_count': int,
|
||||
@@ -698,6 +717,30 @@ class SoundcloudIE(SoundcloudBaseIE):
|
||||
],
|
||||
},
|
||||
'params': {'skip_download': 'm3u8', 'format': 'hls_aac_160k'},
|
||||
}, {
|
||||
'url': 'https://soundcloud.com/user615617514/dagames',
|
||||
'info_dict': {
|
||||
'id': '309858375',
|
||||
'ext': 'm4a',
|
||||
'title': 'BENDY AND THE INK MACHINE SONG (Build Our Machine) INSTRUMENTAL by DAGAMES',
|
||||
'description': '',
|
||||
'uploader': 'xXwolffykittyXx',
|
||||
'uploader_id': '157677999',
|
||||
'uploader_url': 'https://soundcloud.com/user615617514',
|
||||
'comment_count': int,
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'repost_count': int,
|
||||
'duration': 241.601,
|
||||
'thumbnail': 'https://i1.sndcdn.com/artworks-000209893581-orfv6t-original.jpg',
|
||||
'tags': [],
|
||||
'artists': ['BENDY AND THE INK MACHINE SONG (Build Our Machine) INSTRUMENTAL '],
|
||||
'track': 'BENDY AND THE INK MACHINE SONG (Build Our Machine) INSTRUMENTAL by DAGAMES',
|
||||
'timestamp': 1488232827,
|
||||
'upload_date': '20170227',
|
||||
'license': 'all-rights-reserved',
|
||||
},
|
||||
'params': {'get_comments': True, 'skip_download': 'm3u8'},
|
||||
}, {
|
||||
# AAC HQ format available (account with active subscription needed)
|
||||
'url': 'https://soundcloud.com/wandw/the-chainsmokers-ft-daya-dont-let-me-down-ww-remix-1',
|
||||
@@ -783,6 +826,7 @@ class SoundcloudPlaylistBaseIE(SoundcloudBaseIE):
|
||||
**traverse_obj(playlist, {
|
||||
'uploader': ('user', 'username', {str}),
|
||||
'uploader_id': ('user', 'id', {str_or_none}),
|
||||
'uploader_url': ('user', 'permalink_url', {url_or_none}),
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -798,6 +842,7 @@ class SoundcloudSetIE(SoundcloudPlaylistBaseIE):
|
||||
'description': 'md5:71d07087c7a449e8941a70a29e34671e',
|
||||
'uploader': 'The Royal Concept',
|
||||
'uploader_id': '9615865',
|
||||
'uploader_url': 'https://soundcloud.com/the-concept-band',
|
||||
'album': 'The Royal Concept EP',
|
||||
'album_artists': ['The Royal Concept'],
|
||||
'album_type': 'ep',
|
||||
@@ -1114,6 +1159,7 @@ class SoundcloudPlaylistIE(SoundcloudPlaylistBaseIE):
|
||||
'description': 'md5:e4373f7177fe3db292a8552b4ec41bc6',
|
||||
'uploader': 'Non-Site Records',
|
||||
'uploader_id': '33660914',
|
||||
'uploader_url': 'https://soundcloud.com/non-site_records',
|
||||
'album_artists': ['Non-Site Records'],
|
||||
'album_type': 'playlist',
|
||||
'album': 'TILT Brass - Bowery Poetry Club, August \'03 [Non-Site SCR 02]',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from .bunnycdn import BunnyCdnIE
|
||||
from .common import InfoExtractor
|
||||
from ..utils import make_archive_id, try_get, unified_timestamp
|
||||
from ..utils import make_archive_id, str_or_none, try_get, unified_timestamp
|
||||
|
||||
|
||||
class SovietsClosetBaseIE(InfoExtractor):
|
||||
@@ -182,7 +182,7 @@ class SovietsClosetPlaylistIE(SovietsClosetBaseIE):
|
||||
entries = [{
|
||||
**self.url_result(f'https://sovietscloset.com/video/{stream["id"]}', ie=SovietsClosetIE.ie_key()),
|
||||
**self.video_meta(
|
||||
video_id=stream['id'], game_name=game['name'], category_name=category.get('name'),
|
||||
video_id=str_or_none(stream['id']), game_name=game['name'], category_name=category.get('name'),
|
||||
episode_number=i + 1, stream_date=stream.get('date')),
|
||||
} for i, stream in enumerate(category['streams'])]
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ class SpankBangIE(InfoExtractor):
|
||||
|
||||
for mobj in re.finditer(
|
||||
rf'{STREAM_URL_PREFIX}(?P<id>[^\s=]+)\s*=\s*(["\'])(?P<url>(?:(?!\2).)+)\2', webpage):
|
||||
extract_format(mobj.group('id', 'url'))
|
||||
extract_format(*mobj.group('id', 'url'))
|
||||
|
||||
if not formats:
|
||||
stream_key = self._search_regex(
|
||||
|
||||
@@ -39,7 +39,7 @@ def _extract_episode(data, episode_id=None):
|
||||
return {
|
||||
'id': str(episode_id or data['episode_id']),
|
||||
'url': download_url,
|
||||
'display_id': data.get('permalink'),
|
||||
'display_id': str_or_none(data.get('permalink')),
|
||||
'title': title,
|
||||
'description': data.get('description'),
|
||||
'timestamp': unified_timestamp(data.get('published_at')),
|
||||
|
||||
+12
-20
@@ -6,6 +6,8 @@ from ..utils import (
|
||||
determine_ext,
|
||||
dict_get,
|
||||
int_or_none,
|
||||
js_to_json,
|
||||
str_or_none,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
)
|
||||
@@ -17,6 +19,7 @@ from ..utils.traversal import (
|
||||
|
||||
class SVTBaseIE(InfoExtractor):
|
||||
_GEO_COUNTRIES = ['SE']
|
||||
_GEO_BYPASS = False
|
||||
|
||||
def _extract_video(self, video_info, video_id):
|
||||
is_live = dict_get(video_info, ('live', 'simulcast'), default=False)
|
||||
@@ -252,32 +255,21 @@ class SVTPlayIE(SVTBaseIE):
|
||||
mobj = self._match_valid_url(url)
|
||||
video_id = mobj.group('id')
|
||||
svt_id = mobj.group('svt_id') or mobj.group('modal_id')
|
||||
if svt_id:
|
||||
return self._extract_by_video_id(svt_id)
|
||||
webpage = self._download_webpage(url, video_id, fatal=not svt_id) or ''
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
data = traverse_obj(self._search_nextjs_data(webpage, video_id), (
|
||||
'props', 'urqlState', ..., 'data', {json.loads},
|
||||
'detailsPageByPath', {dict}, any, {require('video data')}))
|
||||
details = traverse_obj(data, (
|
||||
'modules', lambda _, v: v['details']['smartStart']['item']['videos'], 'details', any))
|
||||
svt_id = traverse_obj(details, (
|
||||
'smartStart', 'item', 'videos',
|
||||
# There can be 'AudioDescribed' and 'SignInterpreted' variants; try 'Default' or else get first
|
||||
(lambda _, v: v['accessibility'] == 'Default', 0),
|
||||
'svtId', {str}, any))
|
||||
if not svt_id:
|
||||
svt_id = traverse_obj(data, ('video', 'svtId', {str}, {require('SVT ID')}))
|
||||
|
||||
data = self._search_json(
|
||||
r'(?<=[\s.])URQL_DATA\s*=', webpage, 'json data', video_id,
|
||||
transform_source=js_to_json)
|
||||
svt_id = traverse_obj(data, (
|
||||
{dict.values}, ..., 'data', {json.loads}, 'detailsPageByPath',
|
||||
'smartStart', 'videoSvtId', {str_or_none}, any, {require('SVT ID')}))
|
||||
info_dict = self._extract_by_video_id(svt_id)
|
||||
|
||||
if not info_dict.get('title'):
|
||||
info_dict['title'] = re.sub(r'\s*\|\s*.+?$', '', self._og_search_title(webpage))
|
||||
if not info_dict.get('thumbnail'):
|
||||
info_dict['thumbnail'] = self._og_search_thumbnail(webpage)
|
||||
if not info_dict.get('description'):
|
||||
info_dict['description'] = traverse_obj(details, ('description', {str}))
|
||||
info_dict['description'] = self._og_search_description(webpage, default=None)
|
||||
info_dict['thumbnail'] = self._og_search_thumbnail(webpage)
|
||||
|
||||
return info_dict
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class TeamTreeHouseIE(InfoExtractor):
|
||||
'_type': 'url_transparent',
|
||||
'id': self._match_id(page_url),
|
||||
'url': page_url,
|
||||
'id_key': self.ie_key(),
|
||||
'ie_key': self.ie_key(),
|
||||
}
|
||||
if extract_info:
|
||||
entry.update(extract_info)
|
||||
|
||||
@@ -14,10 +14,9 @@ def _fmt_url(url):
|
||||
|
||||
|
||||
class TelewebionIE(InfoExtractor):
|
||||
_WORKING = False
|
||||
_VALID_URL = r'https?://(?:www\.)?telewebion\.com/episode/(?P<id>(?:0x[a-fA-F\d]+|\d+))'
|
||||
_VALID_URL = r'https?://(?:www\.)?telewebion\.ir/episode/(?P<id>(?:0x[a-fA-F\d]+|\d+))'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.telewebion.com/episode/0x1b3139c/',
|
||||
'url': 'http://www.telewebion.ir/episode/0x1b3139c/',
|
||||
'info_dict': {
|
||||
'id': '0x1b3139c',
|
||||
'ext': 'mp4',
|
||||
@@ -26,7 +25,7 @@ class TelewebionIE(InfoExtractor):
|
||||
'series_id': '0x1b2505c',
|
||||
'channel': 'شبکه 3',
|
||||
'channel_id': '0x1b1a761',
|
||||
'channel_url': 'https://telewebion.com/live/tv3',
|
||||
'channel_url': 'https://telewebion.ir/live/tv3',
|
||||
'timestamp': 1425522414,
|
||||
'upload_date': '20150305',
|
||||
'release_timestamp': 1425517020,
|
||||
@@ -34,11 +33,11 @@ class TelewebionIE(InfoExtractor):
|
||||
'duration': 420,
|
||||
'view_count': int,
|
||||
'tags': ['ورزشی', 'لیگ اروپا', 'اروپا'],
|
||||
'thumbnail': 'https://static.telewebion.com/episodeImages/YjFhM2MxMDBkMDNiZTU0MjE5YjQ3ZDY0Mjk1ZDE0ZmUwZWU3OTE3OWRmMDAyODNhNzNkNjdmMWMzMWIyM2NmMA/default',
|
||||
'thumbnail': 'https://static.telewebion.ir/episodeImages/YjFhM2MxMDBkMDNiZTU0MjE5YjQ3ZDY0Mjk1ZDE0ZmUwZWU3OTE3OWRmMDAyODNhNzNkNjdmMWMzMWIyM2NmMA/default',
|
||||
},
|
||||
'skip_download': 'm3u8',
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
'url': 'https://telewebion.com/episode/162175536',
|
||||
'url': 'https://telewebion.ir/episode/162175536',
|
||||
'info_dict': {
|
||||
'id': '0x9aa9a30',
|
||||
'ext': 'mp4',
|
||||
@@ -47,7 +46,7 @@ class TelewebionIE(InfoExtractor):
|
||||
'series_id': '0x29a7426',
|
||||
'channel': 'شبکه 2',
|
||||
'channel_id': '0x1b1a719',
|
||||
'channel_url': 'https://telewebion.com/live/tv2',
|
||||
'channel_url': 'https://telewebion.ir/live/tv2',
|
||||
'timestamp': 1699979968,
|
||||
'upload_date': '20231114',
|
||||
'release_timestamp': 1699991638,
|
||||
@@ -55,9 +54,9 @@ class TelewebionIE(InfoExtractor):
|
||||
'duration': 78,
|
||||
'view_count': int,
|
||||
'tags': ['کلیپ های منتخب', ' کلیپ طنز ', ' کلیپ سیاست ', 'پاورقی', 'ویژه فلسطین'],
|
||||
'thumbnail': 'https://static.telewebion.com/episodeImages/871e9455-7567-49a5-9648-34c22c197f5f/default',
|
||||
'thumbnail': 'https://static.telewebion.ir/episodeImages/871e9455-7567-49a5-9648-34c22c197f5f/default',
|
||||
},
|
||||
'skip_download': 'm3u8',
|
||||
'skip': 'Dead link',
|
||||
}]
|
||||
|
||||
def _call_graphql_api(
|
||||
@@ -70,7 +69,7 @@ class TelewebionIE(InfoExtractor):
|
||||
parameters = ', '.join(f'${name}: {type_}' for name, (type_, _) in variables.items())
|
||||
parameters = f'({parameters})'
|
||||
|
||||
result = self._download_json('https://graph.telewebion.com/graphql', video_id, note, data=json.dumps({
|
||||
result = self._download_json('https://graph.telewebion.ir/graphql', video_id, note, data=json.dumps({
|
||||
'operationName': operation,
|
||||
'query': f'query {operation}{parameters} @cacheControl(maxAge: 60) {{{query}\n}}\n',
|
||||
'variables': {name: value for name, (_, value) in (variables or {}).items()},
|
||||
@@ -123,11 +122,11 @@ class TelewebionIE(InfoExtractor):
|
||||
'series_id': ('program', 'ProgramID', {str}),
|
||||
'channel': ('channel', 'name', {str}),
|
||||
'channel_id': ('channel', 'ChannelID', {str}),
|
||||
'channel_url': ('channel', 'descriptor', {_fmt_url('https://telewebion.com/live/%s')}),
|
||||
'thumbnail': ('image', {_fmt_url('https://static.telewebion.com/episodeImages/%s/default')}),
|
||||
'channel_url': ('channel', 'descriptor', {_fmt_url('https://telewebion.ir/live/%s')}),
|
||||
'thumbnail': ('image', {_fmt_url('https://static.telewebion.ir/episodeImages/%s/default')}),
|
||||
'formats': (
|
||||
'channel', 'descriptor', {str},
|
||||
{_fmt_url(f'https://cdna.telewebion.com/%s/episode/{video_id}/playlist.m3u8')},
|
||||
{_fmt_url(f'https://cdna.telewebion.ir/%s/episode/{video_id}/playlist.m3u8')},
|
||||
{functools.partial(self._extract_m3u8_formats, video_id=video_id, ext='mp4', m3u8_id='hls')}),
|
||||
}))
|
||||
info_dict['id'] = video_id
|
||||
|
||||
@@ -10,7 +10,7 @@ from ..utils import (
|
||||
|
||||
|
||||
class TruthIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://truthsocial\.com/@[^/]+/posts/(?P<id>\d+)'
|
||||
_VALID_URL = r'https?://truthsocial\.com/@[^/?#]+(?:/posts)?/(?P<id>\d+)'
|
||||
_TESTS = [
|
||||
{
|
||||
'url': 'https://truthsocial.com/@realDonaldTrump/posts/108779000807761862',
|
||||
@@ -47,6 +47,11 @@ class TruthIE(InfoExtractor):
|
||||
'like_count': int,
|
||||
},
|
||||
},
|
||||
{
|
||||
# Share URL
|
||||
'url': 'https://truthsocial.com/@realDonaldTrump/108779000807761862',
|
||||
'only_matching': True,
|
||||
},
|
||||
]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from .common import InfoExtractor
|
||||
from ..utils import traverse_obj
|
||||
from ..utils import int_or_none, js_to_json
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class TVIPlayerIE(InfoExtractor):
|
||||
@@ -11,21 +12,10 @@ class TVIPlayerIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'duration': 4167,
|
||||
'title': 'Jornal das 8 - 26 de dezembro de 2021',
|
||||
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/61c8ee630cf2cc58e7d98d9f/',
|
||||
'thumbnail': 'https://img.iol.pt/image/id/61c8ee630cf2cc58e7d98d9f/',
|
||||
'season_number': 8,
|
||||
'season': 'Season 8',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://tviplayer.iol.pt/programa/isabel/62b471090cf26256cd2a8594/video/62be445f0cf2ea4f0a5218e5',
|
||||
'info_dict': {
|
||||
'id': '62be445f0cf2ea4f0a5218e5',
|
||||
'ext': 'mp4',
|
||||
'duration': 3255,
|
||||
'season': 'Season 1',
|
||||
'title': 'Isabel - Episódio 1',
|
||||
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/62beac200cf2f9a86eab856b/',
|
||||
'season_number': 1,
|
||||
},
|
||||
}, {
|
||||
# no /programa/
|
||||
'url': 'https://tviplayer.iol.pt/video/62c4131c0cf2f9a86eac06bb',
|
||||
@@ -33,10 +23,8 @@ class TVIPlayerIE(InfoExtractor):
|
||||
'id': '62c4131c0cf2f9a86eac06bb',
|
||||
'ext': 'mp4',
|
||||
'title': 'David e Mickael Carreira respondem: «Qual é o próximo a ser pai?»',
|
||||
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/62c416490cf2ea367d4433fd/',
|
||||
'season': 'Season 2',
|
||||
'thumbnail': 'https://img.iol.pt/image/id/62c416490cf2ea367d4433fd/',
|
||||
'duration': 148,
|
||||
'season_number': 2,
|
||||
},
|
||||
}, {
|
||||
# episodio url
|
||||
@@ -46,9 +34,11 @@ class TVIPlayerIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'season': 'Season 1',
|
||||
'title': 'Quem denunciou Pedro?',
|
||||
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/62eda30b0cf2ea367d48973b/',
|
||||
'thumbnail': 'https://img.iol.pt/image/id/62eda30b0cf2ea367d48973b/',
|
||||
'duration': 1250,
|
||||
'season_number': 1,
|
||||
'episode_number': 187,
|
||||
'episode': 'Episode 187',
|
||||
},
|
||||
}]
|
||||
|
||||
@@ -62,7 +52,8 @@ class TVIPlayerIE(InfoExtractor):
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
json_data = self._search_json(
|
||||
r'<script>\s*jsonData\s*=', webpage, 'json_data', video_id)
|
||||
r'\sopts\s*=\s*\{\s*video\s*:\s*\[', webpage, 'JSON data', video_id,
|
||||
transform_source=js_to_json)
|
||||
|
||||
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
|
||||
f'{json_data["videoUrl"]}?wmsAuthSign={self.wms_auth_sign_token}',
|
||||
@@ -71,8 +62,11 @@ class TVIPlayerIE(InfoExtractor):
|
||||
'id': video_id,
|
||||
'title': json_data.get('title') or self._og_search_title(webpage),
|
||||
'thumbnail': json_data.get('cover') or self._og_search_thumbnail(webpage),
|
||||
'duration': json_data.get('duration'),
|
||||
'duration': int_or_none(json_data.get('duration')),
|
||||
'formats': formats,
|
||||
'subtitles': subtitles,
|
||||
'season_number': traverse_obj(json_data, ('program', 'seasonNum')),
|
||||
'season_number': traverse_obj(
|
||||
self._yield_json_ld(webpage, video_id, default=None),
|
||||
(lambda _, v: v['@type'] == 'TVEpisode', 'seasonNumber', {int_or_none}, any)),
|
||||
'episode_number': int_or_none(json_data.get('episodeNum')),
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ class TVPStreamIE(InfoExtractor):
|
||||
|
||||
def _real_extract(self, url):
|
||||
channel_id = self._match_id(url)
|
||||
channel_url = self._proto_relative_url(f'//stream.tvp.pl/?channel_id={channel_id}' or 'default')
|
||||
channel_url = self._proto_relative_url(f'//stream.tvp.pl/?channel_id={channel_id}')
|
||||
webpage = self._download_webpage(channel_url, channel_id or 'default', 'Downloading channel webpage')
|
||||
channels = self._search_json(
|
||||
r'window\.__channels\s*=', webpage, 'channel list', channel_id,
|
||||
|
||||
@@ -10,6 +10,7 @@ from ..jsinterp import js_number_to_string
|
||||
from ..networking.exceptions import HTTPError
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
clean_html,
|
||||
dict_get,
|
||||
filter_dict,
|
||||
float_or_none,
|
||||
@@ -1451,16 +1452,18 @@ class TwitterBroadcastIE(TwitterBaseIE, PeriscopeBaseIE):
|
||||
|
||||
_VALID_URL = TwitterBaseIE._BASE_REGEX + r'i/(?P<type>broadcasts|events)/(?P<id>\w+)'
|
||||
_TESTS = [{
|
||||
# untitled Periscope video
|
||||
# Untitled broadcast
|
||||
'url': 'https://twitter.com/i/broadcasts/1yNGaQLWpejGj',
|
||||
'info_dict': {
|
||||
'id': '1yNGaQLWpejGj',
|
||||
'ext': 'mp4',
|
||||
'title': 'Andrea May Sahouri - Periscope Broadcast',
|
||||
'title': 'Andrea May Sahouri - Twitter Broadcast',
|
||||
'display_id': '1yNGaQLWpejGj',
|
||||
'uploader': 'Andrea May Sahouri',
|
||||
'uploader_id': 'andreamsahouri',
|
||||
'uploader_url': 'https://twitter.com/andreamsahouri',
|
||||
'release_date': '20200601',
|
||||
'release_timestamp': 1590973647,
|
||||
'timestamp': 1590973638,
|
||||
'upload_date': '20200601',
|
||||
'thumbnail': r're:^https?://[^?#]+\.jpg\?token=',
|
||||
@@ -1478,6 +1481,8 @@ class TwitterBroadcastIE(TwitterBaseIE, PeriscopeBaseIE):
|
||||
'uploader': 'SpaceX',
|
||||
'uploader_id': 'SpaceX',
|
||||
'uploader_url': 'https://twitter.com/SpaceX',
|
||||
'release_date': '20210303',
|
||||
'release_timestamp': 1614812964,
|
||||
'timestamp': 1614812942,
|
||||
'upload_date': '20210303',
|
||||
'thumbnail': r're:^https?://[^?#]+\.jpg\?token=',
|
||||
@@ -1495,6 +1500,8 @@ class TwitterBroadcastIE(TwitterBaseIE, PeriscopeBaseIE):
|
||||
'uploader': 'SpaceX',
|
||||
'uploader_id': 'SpaceX',
|
||||
'uploader_url': 'https://twitter.com/SpaceX',
|
||||
'release_date': '20230420',
|
||||
'release_timestamp': 1681994486,
|
||||
'timestamp': 1681993964,
|
||||
'upload_date': '20230420',
|
||||
'thumbnail': r're:^https?://[^?#]+\.jpg\?token=',
|
||||
@@ -1544,7 +1551,9 @@ class TwitterBroadcastIE(TwitterBaseIE, PeriscopeBaseIE):
|
||||
info = self._parse_broadcast_data(broadcast, broadcast_id)
|
||||
info.update({
|
||||
'display_id': display_id,
|
||||
'title': broadcast.get('status') or info.get('title'),
|
||||
'title': traverse_obj(broadcast, (
|
||||
'status', {clean_html}, filter,
|
||||
)) or join_nonempty(info.get('uploader'), 'Twitter Broadcast', delim=' - '),
|
||||
'uploader_id': broadcast.get('twitter_username') or info.get('uploader_id'),
|
||||
'uploader_url': format_field(
|
||||
broadcast, 'twitter_username', 'https://twitter.com/%s', default=None),
|
||||
|
||||
@@ -303,6 +303,7 @@ class KnownPiracyIE(UnsupportedInfoExtractor):
|
||||
r'xanimu\.com',
|
||||
r'musicdex\.org',
|
||||
r'duboku\.io',
|
||||
r'gofile\.io',
|
||||
)
|
||||
|
||||
_TESTS = [{
|
||||
@@ -311,9 +312,42 @@ class KnownPiracyIE(UnsupportedInfoExtractor):
|
||||
}, {
|
||||
'url': 'https://thisav.com/en/terms',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://gofile.io/d/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
raise ExtractorError(
|
||||
f'This website is no longer supported since it has been determined to be primarily used for piracy.{LF}'
|
||||
f'{self._downloader._format_err("DO NOT", self._downloader.Styles.ERROR)} open issues for it', expected=True)
|
||||
|
||||
|
||||
class KnownLiabilityIE(UnsupportedInfoExtractor):
|
||||
"""Sites that would be a liability to the project if supported
|
||||
|
||||
In order for this to not end up being a catalog of sketchy sites,
|
||||
only sites that were once supported should be added to this list
|
||||
"""
|
||||
|
||||
URLS = (
|
||||
r'motherless\.com',
|
||||
r'suno\.com',
|
||||
r'udio\.com',
|
||||
)
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://motherless.com/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://suno.com/song/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.udio.com/songs/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
raise ExtractorError(
|
||||
f'This website is not supported and will not be supported.{LF}'
|
||||
f'{self._downloader._format_err("DO NOT", self._downloader.Styles.ERROR)} open issues for it', expected=True)
|
||||
|
||||
@@ -88,7 +88,7 @@ class WDRIE(InfoExtractor):
|
||||
manifest_url, video_id, f4m_id='hds', fatal=False))
|
||||
elif ext == 'smil':
|
||||
formats.extend(self._extract_smil_formats(
|
||||
medium_url, 'stream', fatal=False))
|
||||
medium_url, video_id, fatal=False))
|
||||
else:
|
||||
a_format = {
|
||||
'url': medium_url,
|
||||
|
||||
@@ -94,7 +94,7 @@ class WeiboBaseIE(InfoExtractor):
|
||||
**parse_resolution(resolution),
|
||||
**traverse_obj(media_info, (
|
||||
'video_details', lambda _, v: v['label'].startswith(format_id), {
|
||||
'size': ('size', {int_or_none}),
|
||||
'filesize': ('size', {int_or_none}),
|
||||
'tbr': ('bitrate', {int_or_none}),
|
||||
},
|
||||
), get_all=False),
|
||||
|
||||
@@ -36,11 +36,11 @@ class XimalayaIE(XimalayaBaseIE):
|
||||
'thumbnail': r're:^https?://.*\.jpg',
|
||||
'thumbnails': [
|
||||
{
|
||||
'name': 'cover_url',
|
||||
'id': 'cover_url',
|
||||
'url': r're:^https?://.*\.jpg',
|
||||
},
|
||||
{
|
||||
'name': 'cover_url_142',
|
||||
'id': 'cover_url_142',
|
||||
'url': r're:^https?://.*\.jpg',
|
||||
'width': 180,
|
||||
'height': 180,
|
||||
@@ -65,11 +65,11 @@ class XimalayaIE(XimalayaBaseIE):
|
||||
'thumbnail': r're:^https?://.*\.jpg',
|
||||
'thumbnails': [
|
||||
{
|
||||
'name': 'cover_url',
|
||||
'id': 'cover_url',
|
||||
'url': r're:^https?://.*\.jpg',
|
||||
},
|
||||
{
|
||||
'name': 'cover_url_142',
|
||||
'id': 'cover_url_142',
|
||||
'url': r're:^https?://.*\.jpg',
|
||||
'width': 180,
|
||||
'height': 180,
|
||||
@@ -172,7 +172,7 @@ class XimalayaIE(XimalayaBaseIE):
|
||||
for k in audio_info:
|
||||
# cover pics kyes like: cover_url', 'cover_url_142'
|
||||
if k.startswith('cover_url'):
|
||||
thumbnail = {'name': k, 'url': audio_info[k]}
|
||||
thumbnail = {'id': k, 'url': audio_info[k]}
|
||||
if k == 'cover_url_142':
|
||||
thumbnail['width'] = 180
|
||||
thumbnail['height'] = 180
|
||||
|
||||
@@ -121,7 +121,7 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
|
||||
'Downloading track location JSON',
|
||||
query={'format': 'json'})
|
||||
key = hashlib.md5(('XGRlBW9FXlekgbPrRHuSiA' + fd_data['path'][1:] + fd_data['s']).encode()).hexdigest()
|
||||
f_url = 'http://{}/get-mp3/{}/{}?track-id={} '.format(fd_data['host'], key, fd_data['ts'] + fd_data['path'], track['id'])
|
||||
f_url = 'http://{}/get-mp3/{}/{}?track-id={}'.format(fd_data['host'], key, fd_data['ts'] + fd_data['path'], track['id'])
|
||||
|
||||
thumbnail = None
|
||||
cover_uri = track.get('albums', [{}])[0].get('coverUri')
|
||||
|
||||
@@ -1037,8 +1037,10 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
return next_continuation
|
||||
|
||||
return traverse_obj(renderer, (
|
||||
('contents', 'items', 'rows', 'subThreads'), ..., 'continuationItemRenderer',
|
||||
('continuationEndpoint', ('button', 'buttonRenderer', 'command')),
|
||||
('contents', 'items', 'rows', 'subThreads'), ..., (
|
||||
('continuationItemRenderer', ('continuationEndpoint', ('button', 'buttonRenderer', 'command'))),
|
||||
('continuationItemViewModel', 'continuationCommand', 'innertubeCommand'),
|
||||
),
|
||||
), get_all=False, expected_type=cls._extract_continuation_ep_data)
|
||||
|
||||
@classmethod
|
||||
@@ -1150,6 +1152,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
|
||||
def _get_count(self, data, *path_list):
|
||||
count_text = self._get_text(data, *path_list) or ''
|
||||
if count_text.lower().startswith('no '):
|
||||
return 0
|
||||
count = parse_count(count_text)
|
||||
if count is None:
|
||||
count = str_to_int(
|
||||
@@ -1180,8 +1184,20 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
})
|
||||
return thumbnails
|
||||
|
||||
@staticmethod
|
||||
def extract_relative_time(relative_time_text):
|
||||
# Map abbreviated relative-time units to the long-form unit names that
|
||||
# datetime_from_str() understands.
|
||||
_RELATIVE_TIME_UNIT_MAP = {
|
||||
's': 'second', 'sec': 'second', 'second': 'second',
|
||||
'min': 'minute', 'minute': 'minute',
|
||||
'h': 'hour', 'hr': 'hour', 'hour': 'hour',
|
||||
'd': 'day', 'day': 'day',
|
||||
'w': 'week', 'wk': 'week', 'week': 'week',
|
||||
'mo': 'month', 'month': 'month',
|
||||
'y': 'year', 'yr': 'year', 'year': 'year',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def extract_relative_time(cls, relative_time_text):
|
||||
"""
|
||||
Extracts a relative time from string and converts to dt object
|
||||
e.g. 'streamed 6 days ago', '5 seconds ago (edited)', 'updated today', '8 yr ago'
|
||||
@@ -1191,15 +1207,19 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
# The relative time text strings are roughly the same as what
|
||||
# Javascript's Intl.RelativeTimeFormat function generates.
|
||||
# See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
|
||||
# Sort longest-first: regex alternation matches left-to-right, so short
|
||||
# keys like 's' must come after 'sec'/'second' to avoid premature matches.
|
||||
units = '|'.join(map(re.escape, sorted(cls._RELATIVE_TIME_UNIT_MAP, key=len, reverse=True)))
|
||||
mobj = re.search(
|
||||
r'(?P<start>today|yesterday|now)|(?P<time>\d+)\s*(?P<unit>sec(?:ond)?|s|min(?:ute)?|h(?:our|r)?|d(?:ay)?|w(?:eek|k)?|mo(?:nth)?|y(?:ear|r)?)s?\s*ago',
|
||||
rf'(?P<start>today|yesterday|now)|(?P<time>\d+)\s*(?P<unit>{units})s?\s*ago',
|
||||
relative_time_text)
|
||||
if mobj:
|
||||
start = mobj.group('start')
|
||||
if start:
|
||||
return datetime_from_str(start)
|
||||
unit = cls._RELATIVE_TIME_UNIT_MAP[mobj.group('unit')]
|
||||
try:
|
||||
return datetime_from_str('now-{}{}'.format(mobj.group('time'), mobj.group('unit')))
|
||||
return datetime_from_str(f'now-{mobj.group("time")}{unit}')
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ class YoutubeTruncatedURLIE(YoutubeBaseInfoExtractor):
|
||||
'Did you forget to quote the URL? Remember that & is a meta '
|
||||
'character in most shells, so you want to put the URL in quotes, '
|
||||
'like yt-dlp '
|
||||
'"https://www.youtube.com/watch?feature=foo&v=BaW_jenozKc" '
|
||||
' or simply yt-dlp BaW_jenozKc .',
|
||||
'"https://www.youtube.com/watch?feature=foo&v=YE7VzlLtp-4" '
|
||||
' or simply yt-dlp YE7VzlLtp-4 .',
|
||||
expected=True)
|
||||
|
||||
|
||||
|
||||
+245
-142
@@ -333,20 +333,69 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
only_once=True)
|
||||
return
|
||||
|
||||
lockup_mdvm = traverse_obj(view_model, ('metadata', 'lockupMetadataViewModel', {dict}))
|
||||
content_mdvm = traverse_obj(lockup_mdvm, ('metadata', 'contentMetadataViewModel', {dict}))
|
||||
|
||||
thumbnail_badge_view_models = traverse_obj(view_model, (
|
||||
'contentImage', 'thumbnailViewModel', 'overlays', ..., (
|
||||
('thumbnailBottomOverlayViewModel', 'badges'),
|
||||
('thumbnailOverlayBadgeViewModel', 'thumbnailBadges'),
|
||||
), ..., 'thumbnailBadgeViewModel', {dict}))
|
||||
duration_text = traverse_obj(thumbnail_badge_view_models, (..., 'text', {str.lower}, any))
|
||||
thumbnail_badge_styles = traverse_obj(thumbnail_badge_view_models, (..., 'badgeStyle', {str}))
|
||||
|
||||
channel_traversal_dict = {
|
||||
'channel': ('content', {str}),
|
||||
'channel_id': ('commandRuns', 0, 'onTap', 'innertubeCommand', 'browseEndpoint', 'browseId', {self.ucid_or_none}),
|
||||
'uploader': ('content', {str}),
|
||||
'uploader_id': ('commandRuns', 0, 'onTap', 'innertubeCommand', 'browseEndpoint', 'canonicalBaseUrl', {self.handle_from_url}),
|
||||
}
|
||||
|
||||
channel_info = traverse_obj(content_mdvm, (
|
||||
'metadataRows', ..., 'metadataParts',
|
||||
lambda _, v: v['text']['commandRuns'][0]['onTap']['innertubeCommand']['browseEndpoint']['browseId'],
|
||||
'text', any, channel_traversal_dict))
|
||||
|
||||
# For videos credited to multiple channels / "collaborators"
|
||||
collaborator_data = traverse_obj(lockup_mdvm, (
|
||||
'image', 'avatarStackViewModel', 'rendererContext', 'commandContext', 'onTap', 'innertubeCommand',
|
||||
'showDialogCommand', 'panelLoadingStrategy', 'inlineContent', 'dialogViewModel', 'customContent',
|
||||
'listViewModel', 'listItems', ..., 'listItemViewModel', 'title', {dict}))
|
||||
if not channel_info:
|
||||
channel_info = traverse_obj(collaborator_data, (0, channel_traversal_dict))
|
||||
|
||||
views_and_time = traverse_obj(content_mdvm, (
|
||||
'metadataRows', lambda _, v: 'accessibilityLabel' in v['metadataParts'][-1],
|
||||
'metadataParts', ...))
|
||||
relative_time_text = traverse_obj(views_and_time, (-1, 'text', 'content', {str.lower}))
|
||||
|
||||
badge_styles = traverse_obj(content_mdvm, (
|
||||
'metadataRows', ..., 'badges', ..., 'badgeViewModel', 'badgeStyle', {str}))
|
||||
|
||||
return self.url_result(
|
||||
url, ie, content_id,
|
||||
title=traverse_obj(view_model, (
|
||||
'metadata', 'lockupMetadataViewModel', 'title', 'content', {str})),
|
||||
title=traverse_obj(lockup_mdvm, ('title', 'content', {str})),
|
||||
thumbnails=self._extract_thumbnails(view_model, (
|
||||
'contentImage', *thumb_keys, 'thumbnailViewModel', 'image'), final_key='sources'),
|
||||
duration=traverse_obj(view_model, (
|
||||
'contentImage', 'thumbnailViewModel', 'overlays', ...,
|
||||
(('thumbnailBottomOverlayViewModel', 'badges'), ('thumbnailOverlayBadgeViewModel', 'thumbnailBadges')),
|
||||
..., 'thumbnailBadgeViewModel', 'text', {parse_duration}, any)),
|
||||
timestamp=(traverse_obj(view_model, (
|
||||
'metadata', 'lockupMetadataViewModel', 'metadata', 'contentMetadataViewModel', 'metadataRows',
|
||||
..., 'metadataParts', ..., 'text', 'content', {lambda t: self._parse_time_text(t, report_failure=False)}, any))
|
||||
if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE) else None))
|
||||
duration=parse_duration(duration_text),
|
||||
view_count=(
|
||||
traverse_obj(views_and_time, (0, 'text', 'content', {parse_count}))
|
||||
# view_count isn't always available; only extract if this metadataRow is 2 metadataParts
|
||||
if len(views_and_time) == 2 else None),
|
||||
timestamp=(
|
||||
self._parse_time_text(relative_time_text, report_failure=False)
|
||||
if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE) else None),
|
||||
live_status=(
|
||||
'is_upcoming' if duration_text == 'upcoming'
|
||||
else 'is_live' if 'THUMBNAIL_OVERLAY_BADGE_STYLE_LIVE' in thumbnail_badge_styles
|
||||
else 'was_live' if relative_time_text and 'streamed' in relative_time_text
|
||||
else None),
|
||||
# XXX: We cannot assume 'public' since we have no way to differentiate from 'unlisted'
|
||||
availability=self._availability(needs_subscription='BADGE_MEMBERS_ONLY' in badge_styles),
|
||||
channel_url=format_field(channel_info, 'channel_id', 'https://www.youtube.com/channel/%s', default=None),
|
||||
uploader_url=format_field(channel_info, 'uploader_id', 'https://www.youtube.com/%s', default=None),
|
||||
creators=traverse_obj(collaborator_data, (..., 'content', {str}, all, filter)),
|
||||
**channel_info)
|
||||
|
||||
def _rich_entries(self, rich_grid_renderer):
|
||||
if lockup_view_model := traverse_obj(rich_grid_renderer, ('content', 'lockupViewModel', {dict})):
|
||||
@@ -714,8 +763,15 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
})
|
||||
|
||||
channel_badges = self._extract_badges(traverse_obj(data, ('header', ..., 'badges'), get_all=False))
|
||||
if self._has_badge(channel_badges, BadgeType.VERIFIED):
|
||||
if (
|
||||
self._has_badge(channel_badges, BadgeType.VERIFIED)
|
||||
or any(image_name in traverse_obj(page_header_view_model, (
|
||||
'title', 'dynamicTextViewModel', 'text', 'attachmentRuns', ..., 'element', 'type',
|
||||
'imageType', 'image', 'sources', ..., 'clientResource', 'imageName', {str},
|
||||
)) for image_name in ('CHECK_CIRCLE_FILLED', 'AUDIO_BADGE'))
|
||||
):
|
||||
info['channel_is_verified'] = True
|
||||
|
||||
# Playlist stats is a text runs array containing [video count, view count, last updated].
|
||||
# last updated or (view count and last updated) may be missing.
|
||||
playlist_stats = get_first(
|
||||
@@ -817,13 +873,13 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
def _extract_availability(self, data):
|
||||
"""
|
||||
Gets the availability of a given playlist/tab.
|
||||
Note: Unless YouTube tells us explicitly, we do not assume it is public
|
||||
@param data: response
|
||||
"""
|
||||
sidebar_renderer = self._extract_sidebar_info_renderer(data, 'playlistSidebarPrimaryInfoRenderer') or {}
|
||||
playlist_header_renderer = traverse_obj(data, ('header', 'playlistHeaderRenderer')) or {}
|
||||
sidebar_renderer = self._extract_sidebar_info_renderer(data, ('playlistSidebarPrimaryInfoRenderer', {dict})) or {}
|
||||
playlist_header_renderer = traverse_obj(data, ('header', 'playlistHeaderRenderer', {dict})) or {}
|
||||
player_header_privacy = playlist_header_renderer.get('privacy')
|
||||
|
||||
# NB: Badges are currently not extracted from playlists/tabs
|
||||
badges = self._extract_badges(traverse_obj(sidebar_renderer, 'badges'))
|
||||
|
||||
# Personal playlists, when authenticated, have a dropdown visibility selector instead of a badge
|
||||
@@ -833,27 +889,35 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
lambda _, v: v['privacyDropdownItemRenderer']['isSelected'], 'privacyDropdownItemRenderer', 'icon', 'iconType'),
|
||||
expected_type=str)
|
||||
|
||||
microformats_is_unlisted = traverse_obj(
|
||||
data, ('microformat', 'microformatDataRenderer', 'unlisted'), expected_type=bool)
|
||||
microformat_data_renderer = traverse_obj(data, ('microformat', 'microformatDataRenderer', {dict}))
|
||||
|
||||
return (
|
||||
'public' if (
|
||||
self._has_badge(badges, BadgeType.AVAILABILITY_PUBLIC)
|
||||
or player_header_privacy == 'PUBLIC'
|
||||
or privacy_setting_icon == 'PRIVACY_PUBLIC')
|
||||
else self._availability(
|
||||
is_private=(
|
||||
self._has_badge(badges, BadgeType.AVAILABILITY_PRIVATE)
|
||||
or player_header_privacy == 'PRIVATE' if player_header_privacy is not None
|
||||
else privacy_setting_icon == 'PRIVACY_PRIVATE' if privacy_setting_icon is not None else None),
|
||||
is_unlisted=(
|
||||
self._has_badge(badges, BadgeType.AVAILABILITY_UNLISTED)
|
||||
or player_header_privacy == 'UNLISTED' if player_header_privacy is not None
|
||||
else privacy_setting_icon == 'PRIVACY_UNLISTED' if privacy_setting_icon is not None
|
||||
else microformats_is_unlisted if microformats_is_unlisted is not None else None),
|
||||
needs_subscription=self._has_badge(badges, BadgeType.AVAILABILITY_SUBSCRIPTION) or None,
|
||||
needs_premium=self._has_badge(badges, BadgeType.AVAILABILITY_PREMIUM) or None,
|
||||
needs_auth=False))
|
||||
if availability := self._availability(
|
||||
# Private playlists are only viewable by the creator
|
||||
is_private=(
|
||||
self._has_badge(badges, BadgeType.AVAILABILITY_PRIVATE)
|
||||
or player_header_privacy == 'PRIVATE' if player_header_privacy is not None
|
||||
else privacy_setting_icon == 'PRIVACY_PRIVATE' if privacy_setting_icon is not None else None),
|
||||
is_unlisted=(
|
||||
self._has_badge(badges, BadgeType.AVAILABILITY_UNLISTED)
|
||||
or player_header_privacy == 'UNLISTED' if player_header_privacy is not None
|
||||
else privacy_setting_icon == 'PRIVACY_UNLISTED' if privacy_setting_icon is not None
|
||||
else traverse_obj(microformat_data_renderer, ('unlisted', {bool}))),
|
||||
# It is unlikely that members-only or premium-only playlists/tabs exist
|
||||
needs_subscription=self._has_badge(badges, BadgeType.AVAILABILITY_SUBSCRIPTION) or None,
|
||||
needs_premium=self._has_badge(badges, BadgeType.AVAILABILITY_PREMIUM) or None,
|
||||
needs_auth=False,
|
||||
):
|
||||
return availability
|
||||
|
||||
if (
|
||||
self._has_badge(badges, BadgeType.AVAILABILITY_PUBLIC)
|
||||
or player_header_privacy == 'PUBLIC'
|
||||
or privacy_setting_icon == 'PRIVACY_PUBLIC'
|
||||
or traverse_obj(microformat_data_renderer, ('noindex', {bool})) is False
|
||||
):
|
||||
return 'public'
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _extract_sidebar_info_renderer(data, info_renderer, expected_type=dict):
|
||||
@@ -1032,16 +1096,17 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 94,
|
||||
'info_dict': {
|
||||
'id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'title': 'Igor Kleiner - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать на мой канал! Здесь вы найдете видео .{504}/a1/50b/10a$',
|
||||
'uploader': 'Igor Kleiner ',
|
||||
'title': 'Igor DS: ИИ, Наука и Творчество - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать! Здесь сложные технологии встречаются.+\n$',
|
||||
'uploader': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'uploader_id': '@IgorDataScience',
|
||||
'uploader_url': 'https://www.youtube.com/@IgorDataScience',
|
||||
'channel': 'Igor Kleiner ',
|
||||
'channel': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'tags': 'count:23',
|
||||
'tags': 'count:19',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'channel_follower_count': int,
|
||||
'availability': 'public',
|
||||
},
|
||||
}, {
|
||||
'note': 'playlists, multipage, different order',
|
||||
@@ -1049,19 +1114,19 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 94,
|
||||
'info_dict': {
|
||||
'id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'title': 'Igor Kleiner - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать на мой канал! Здесь вы найдете видео .{504}/a1/50b/10a$',
|
||||
'uploader': 'Igor Kleiner ',
|
||||
'title': 'Igor DS: ИИ, Наука и Творчество - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать! Здесь сложные технологии встречаются.+\n$',
|
||||
'uploader': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'uploader_id': '@IgorDataScience',
|
||||
'uploader_url': 'https://www.youtube.com/@IgorDataScience',
|
||||
'tags': 'count:23',
|
||||
'tags': 'count:19',
|
||||
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'channel': 'Igor Kleiner ',
|
||||
'channel': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'channel_follower_count': int,
|
||||
'availability': 'public',
|
||||
},
|
||||
}, {
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'note': 'playlists, series',
|
||||
'url': 'https://www.youtube.com/c/3blue1brown/playlists?view=50&sort=dd&shelf_id=3',
|
||||
'playlist_mincount': 5,
|
||||
@@ -1078,6 +1143,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'tags': ['Mathematics'],
|
||||
'channel_follower_count': int,
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
},
|
||||
}, {
|
||||
'note': 'playlists, singlepage',
|
||||
@@ -1095,12 +1161,12 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'tags': 'count:12',
|
||||
'channel': 'ThirstForScience',
|
||||
'channel_follower_count': int,
|
||||
'availability': 'public',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www.youtube.com/c/ChristophLaimer/playlists',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# TODO: fix availability and view_count extraction
|
||||
'note': 'basic, single video playlist',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlSLRHmI1qNm0wjyVNWw1pCU',
|
||||
'info_dict': {
|
||||
@@ -1135,96 +1201,101 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader_url': 'https://www.youtube.com/@sergeym.6173',
|
||||
'uploader_id': '@sergeym.6173',
|
||||
'uploader': 'Sergey M.',
|
||||
'view_count': int,
|
||||
},
|
||||
'playlist_count': 0,
|
||||
}, {
|
||||
'note': 'Home tab',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/featured',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/featured',
|
||||
'info_dict': {
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Home',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Home',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 2,
|
||||
'playlist_mincount': 6,
|
||||
}, {
|
||||
'note': 'Videos tab',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/videos',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/videos',
|
||||
'info_dict': {
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Videos',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Videos',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 975,
|
||||
'playlist_mincount': 239,
|
||||
}, {
|
||||
'note': 'Videos tab, sorted by popular',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/videos?view=0&sort=p&flow=grid',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/videos?view=0&sort=p&flow=grid',
|
||||
'info_dict': {
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Videos',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Videos',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 199,
|
||||
'playlist_mincount': 239,
|
||||
}, {
|
||||
'note': 'Playlists tab',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/playlists',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/playlists',
|
||||
'info_dict': {
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Playlists',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Playlists',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 17,
|
||||
'playlist_mincount': 20,
|
||||
}, {
|
||||
'note': 'Posts tab',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/community',
|
||||
'url': 'https://www.youtube.com/channel/UCtS3BcCw-tITPFYSvkbP0Bg/posts',
|
||||
'info_dict': {
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Posts',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'channel': 'lex will',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'id': 'UCtS3BcCw-tITPFYSvkbP0Bg',
|
||||
'title': 'Office Hours Live with Tim Heidecker - Posts',
|
||||
'description': 'md5:01ec1460ea6c6e2aa47d3be9c756559c',
|
||||
'uploader': 'Office Hours Live with Tim Heidecker',
|
||||
'uploader_id': '@OfficeHoursLive',
|
||||
'uploader_url': 'https://www.youtube.com/@OfficeHoursLive',
|
||||
'channel': 'Office Hours Live with Tim Heidecker',
|
||||
'channel_id': 'UCtS3BcCw-tITPFYSvkbP0Bg',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCtS3BcCw-tITPFYSvkbP0Bg',
|
||||
'channel_follower_count': int,
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'uploader_id': '@lexwill718',
|
||||
'uploader': 'lex will',
|
||||
'tags': 'count:17',
|
||||
'availability': 'public',
|
||||
'channel_is_verified': True,
|
||||
},
|
||||
'playlist_mincount': 18,
|
||||
'skip': 'This Community isn\'t available',
|
||||
'playlist_mincount': 145,
|
||||
}, {
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'note': 'Search tab',
|
||||
'url': 'https://www.youtube.com/c/3blue1brown/search?query=linear%20algebra',
|
||||
'playlist_mincount': 40,
|
||||
@@ -1241,6 +1312,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader_id': '@3blue1brown',
|
||||
'uploader': '3Blue1Brown',
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://invidio.us/channel/UCmlqkdCBesrv2Lak1mF_MxA',
|
||||
@@ -1252,7 +1324,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'url': 'https://music.youtube.com/channel/UCmlqkdCBesrv2Lak1mF_MxA',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'Playlist with deleted videos (#651). As a bonus, the video #51 is also twice in this list.',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC',
|
||||
'info_dict': {
|
||||
@@ -1296,6 +1367,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'url': 'http://www.youtube.com/user/NASAgovVideo/videos',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# The 'note' below is outdated: there is no longer a "Load more" button
|
||||
'note': 'Buggy playlist: the webpage has a "Load more" button but it doesn\'t have more videos',
|
||||
'url': 'https://www.youtube.com/playlist?list=UUXw-G3eDE9trcvY2sBMM_aA',
|
||||
'info_dict': {
|
||||
@@ -1313,9 +1385,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader': 'Interstellar Movie',
|
||||
'uploader_url': 'https://www.youtube.com/@InterstellarMovie',
|
||||
},
|
||||
'playlist_mincount': 21,
|
||||
'playlist_mincount': 10,
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'Playlist with "show unavailable videos" button',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLYwq8WOe86_xGmR7FrcJq8Sb7VW8K3Tt2',
|
||||
'info_dict': {
|
||||
@@ -1356,7 +1427,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 1000,
|
||||
'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'https://github.com/ytdl-org/youtube-dl/issues/21844',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLzH6n4zXuckpfMu_4Ff8E7Z1behQks5ba',
|
||||
'info_dict': {
|
||||
@@ -1407,7 +1477,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
}, {
|
||||
'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live',
|
||||
'info_dict': {
|
||||
'id': 'VFGoUmo74wE', # This will keep changing
|
||||
'id': '8GbcigMs1_c', # This will keep changing
|
||||
'ext': 'mp4',
|
||||
'title': str,
|
||||
'upload_date': r're:\d{8}',
|
||||
@@ -1441,20 +1511,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'expected_warnings': ['Ignoring subtitle tracks found in '],
|
||||
}, {
|
||||
'url': 'https://www.youtube.com/user/TheYoungTurks/live',
|
||||
'info_dict': {
|
||||
'id': 'a48o2S1cPoo',
|
||||
'ext': 'mp4',
|
||||
'title': 'The Young Turks - Live Main Show',
|
||||
'upload_date': '20150715',
|
||||
'license': 'Standard YouTube License',
|
||||
'description': 'md5:438179573adcdff3c97ebb1ee632b891',
|
||||
'categories': ['News & Politics'],
|
||||
'tags': ['Cenk Uygur (TV Program Creator)', 'The Young Turks (Award-Winning Work)', 'Talk Show (TV Genre)'],
|
||||
'like_count': int,
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://www.youtube.com/channel/UC1yBKRuGpC1tSM73A0ZjYjQ/live',
|
||||
@@ -1521,7 +1577,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'url': 'https://music.youtube.com/browse/UC1a8OFewdjuLq6KlF8M_8Ng',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'VLPL, should redirect to playlist?list=PL...',
|
||||
'url': 'https://music.youtube.com/browse/VLPLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
|
||||
'info_dict': {
|
||||
@@ -1586,6 +1641,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_count': 50,
|
||||
'expected_warnings': ['YouTube Music is not directly supported'],
|
||||
}, {
|
||||
# YoutubeTab_25: use to test _extract_lockup_view_model
|
||||
'note': 'unlisted single video playlist',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
|
||||
'info_dict': {
|
||||
@@ -1716,13 +1772,13 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader_url': 'https://www.youtube.com/@coletdjnz',
|
||||
'uploader_id': '@coletdjnz',
|
||||
'uploader': 'cole-dlp-test-acc',
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 1,
|
||||
'params': {'extractor_args': {'youtube': {'lang': ['ja']}}},
|
||||
'expected_warnings': ['Preferring "ja"'],
|
||||
}, {
|
||||
# XXX: this should really check flat playlist entries, but the test suite doesn't support that
|
||||
# TODO: fix availability extraction
|
||||
'note': 'preferred lang set with playlist with translated video titles',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQAaPZ5Z-rJoTdbT-45Q7c0',
|
||||
'info_dict': {
|
||||
@@ -1745,11 +1801,11 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
}, {
|
||||
# shorts audio pivot for 2GtVksBMYFM.
|
||||
'url': 'https://www.youtube.com/feed/sfv_audio_pivot?bp=8gUrCikSJwoLMkd0VmtzQk1ZRk0SCzJHdFZrc0JNWUZNGgsyR3RWa3NCTVlGTQ==',
|
||||
# TODO: fix extraction
|
||||
'info_dict': {
|
||||
'id': 'sfv_audio_pivot',
|
||||
'title': 'sfv_audio_pivot',
|
||||
'tags': [],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 50,
|
||||
|
||||
@@ -1761,6 +1817,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'id': 'UCEH7P7kyJIkS_gJf93VYbmg',
|
||||
'title': 'UCEH7P7kyJIkS_gJf93VYbmg - Live',
|
||||
'tags': [],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
}, {
|
||||
@@ -1770,6 +1827,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'id': 'UCQvWX73GQygcwXOTSf_VDVg',
|
||||
'title': 'UCQvWX73GQygcwXOTSf_VDVg - Let\'s play',
|
||||
'tags': [],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 8,
|
||||
}, {
|
||||
@@ -1779,11 +1837,11 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'id': 'UCQvWX73GQygcwXOTSf_VDVg',
|
||||
'title': 'UCQvWX73GQygcwXOTSf_VDVg - Home',
|
||||
'tags': [],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 8,
|
||||
}, {
|
||||
# Should get three playlists for videos, shorts and streams tabs
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'url': 'https://www.youtube.com/channel/UCK9V2B22uJYu3N7eR_BT9QA',
|
||||
'info_dict': {
|
||||
'id': 'UCK9V2B22uJYu3N7eR_BT9QA',
|
||||
@@ -1798,18 +1856,18 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader': 'Polka Ch. 尾丸ポルカ',
|
||||
'uploader_id': '@OmaruPolka',
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_count': 3,
|
||||
}, {
|
||||
# Shorts tab with channel with handle
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'url': 'https://www.youtube.com/@NotJustBikes/shorts',
|
||||
'info_dict': {
|
||||
'id': 'UC0intLFzLaudFG-xAvUEO-A',
|
||||
'title': 'Not Just Bikes - Shorts',
|
||||
'tags': 'count:10',
|
||||
'channel_url': 'https://www.youtube.com/channel/UC0intLFzLaudFG-xAvUEO-A',
|
||||
'description': 'md5:295758591d0d43d8594277be54584da7',
|
||||
'description': 'md5:2cb3ccdafa58608fa016f1de4930ec54',
|
||||
'channel_follower_count': int,
|
||||
'channel_id': 'UC0intLFzLaudFG-xAvUEO-A',
|
||||
'channel': 'Not Just Bikes',
|
||||
@@ -1817,6 +1875,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader': 'Not Just Bikes',
|
||||
'uploader_id': '@NotJustBikes',
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 10,
|
||||
}, {
|
||||
@@ -1831,9 +1890,10 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'channel': '中村悠一',
|
||||
'channel_follower_count': int,
|
||||
'description': 'md5:76b312b48a26c3b0e4d90e2dfc1b417d',
|
||||
'uploader_url': 'https://www.youtube.com/@Yuichi-Nakamura',
|
||||
'uploader_id': '@Yuichi-Nakamura',
|
||||
'uploader_url': 'https://www.youtube.com/@中村悠一のあそびば',
|
||||
'uploader_id': '@中村悠一のあそびば',
|
||||
'uploader': '中村悠一',
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 60,
|
||||
}, {
|
||||
@@ -1897,6 +1957,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader_url': 'https://www.youtube.com/@coletdjnz',
|
||||
'uploader_id': '@coletdjnz',
|
||||
'uploader': 'cole-dlp-test-acc',
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist': [{
|
||||
'info_dict': {
|
||||
@@ -1918,6 +1979,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'id': 'UCQvWX73GQygcwXOTSf_VDVg',
|
||||
'title': 'UCQvWX73GQygcwXOTSf_VDVg - Live', # TODO: should be Minecraft - Live or Minecraft - Topic - Live
|
||||
'tags': [],
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist': [{
|
||||
'info_dict': {
|
||||
@@ -1977,7 +2039,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'params': {'extract_flat': True},
|
||||
'skip': 'channels tab removed',
|
||||
}, {
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'url': 'https://www.youtube.com/@3blue1brown/about',
|
||||
'info_dict': {
|
||||
'id': '@3blue1brown',
|
||||
@@ -1992,6 +2053,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader_id': '@3blue1brown',
|
||||
'uploader': '3Blue1Brown',
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_count': 0,
|
||||
}, {
|
||||
@@ -2009,11 +2071,11 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'tags': [],
|
||||
'channel': '99% Invisible',
|
||||
'uploader_id': '@99percentinvisiblepodcast',
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_count': 5,
|
||||
'playlist_mincount': 5,
|
||||
}, {
|
||||
# Releases tab, with rich entry playlistRenderers (same as Podcasts tab)
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'url': 'https://www.youtube.com/@AHimitsu/releases',
|
||||
'info_dict': {
|
||||
'id': 'UCgFwu-j5-xNJml2FtTrrB3A',
|
||||
@@ -2028,6 +2090,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'channel_url': 'https://www.youtube.com/channel/UCgFwu-j5-xNJml2FtTrrB3A',
|
||||
'channel_follower_count': int,
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist_mincount': 10,
|
||||
}, {
|
||||
@@ -2053,7 +2116,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 100,
|
||||
'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
|
||||
}, {
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'note': 'Tags containing spaces',
|
||||
'url': 'https://www.youtube.com/channel/UC7_YxT-KID8kRbqZo7MyscQ',
|
||||
'playlist_count': 3,
|
||||
@@ -2069,6 +2131,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader': 'Markiplier',
|
||||
'channel_url': 'https://www.youtube.com/channel/UC7_YxT-KID8kRbqZo7MyscQ',
|
||||
'channel_is_verified': True,
|
||||
'availability': 'public',
|
||||
'tags': ['markiplier', 'comedy', 'gaming', 'funny videos', 'funny moments',
|
||||
'sketch comedy', 'laughing', 'lets play', 'challenge videos', 'hilarious',
|
||||
'challenges', 'sketches', 'scary games', 'funny games', 'rage games',
|
||||
@@ -2091,6 +2154,49 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader': 'sbcitygov',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCH6-qfQwlUgz9SAf05jvc_w',
|
||||
'tags': [],
|
||||
'availability': 'public',
|
||||
},
|
||||
}, {
|
||||
# https://github.com/yt-dlp/yt-dlp/issues/17044
|
||||
'note': 'flat playlist entry credited to multiple channels / "collaborators"',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLzMNc_TBkmzei1ejIkbjNUP9J7q0hmepM',
|
||||
'info_dict': {
|
||||
'id': 'PLzMNc_TBkmzei1ejIkbjNUP9J7q0hmepM',
|
||||
'title': 'Music for Creating, Building, Imagining 1',
|
||||
'description': 'md5:329cd827b1c70910e80c108cfefdb37d',
|
||||
'uploader': 'Sebtt',
|
||||
'uploader_id': '@Sebtt',
|
||||
'uploader_url': 'https://www.youtube.com/@Sebtt',
|
||||
'channel': 'Sebtt',
|
||||
'channel_id': 'UC-EmUaZucfuL81RYKcKjpbw',
|
||||
'channel_url': 'https://www.youtube.com/channel/UC-EmUaZucfuL81RYKcKjpbw',
|
||||
'view_count': int,
|
||||
'tags': [],
|
||||
'modified_date': str,
|
||||
'availability': 'public',
|
||||
},
|
||||
'playlist': [{
|
||||
'info_dict': {
|
||||
'title': 'Thomas Bergersen - Made of Fire',
|
||||
'id': 'KUokLzs947k',
|
||||
'_type': 'url',
|
||||
'ie_key': 'Youtube',
|
||||
'url': 'https://www.youtube.com/watch?v=KUokLzs947k',
|
||||
'uploader': 'Two Steps From Hell',
|
||||
'uploader_id': '@TwoStepsFromHell-Official',
|
||||
'uploader_url': 'https://www.youtube.com/@TwoStepsFromHell-Official',
|
||||
'channel': 'Two Steps From Hell',
|
||||
'channel_id': 'UC3swwxiALG5c0Tvom83tPGg',
|
||||
'channel_url': 'https://www.youtube.com/channel/UC3swwxiALG5c0Tvom83tPGg',
|
||||
'view_count': int,
|
||||
'duration': 341.0,
|
||||
'creators': ['Two Steps From Hell', 'Thomas Bergersen'],
|
||||
},
|
||||
}],
|
||||
'playlist_count': 1,
|
||||
'params': {
|
||||
'extract_flat': True,
|
||||
'playlist_items': '75',
|
||||
},
|
||||
}]
|
||||
|
||||
@@ -2310,7 +2416,6 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
)
|
||||
IE_NAME = 'youtube:playlist'
|
||||
_TESTS = [{
|
||||
# TODO: fix availability extraction
|
||||
'note': 'issue #673',
|
||||
'url': 'PLBB231211A4F62143',
|
||||
'info_dict': {
|
||||
@@ -2338,7 +2443,6 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
'playlist_count': 2,
|
||||
'skip': 'This playlist is private',
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'embedded',
|
||||
'url': 'https://www.youtube.com/embed/videoseries?list=PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu',
|
||||
'playlist_count': 4,
|
||||
@@ -2359,7 +2463,6 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
||||
},
|
||||
'expected_warnings': [r'[Uu]navailable videos? (is|are|will be) hidden', 'Retrying', 'Giving up'],
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'url': 'http://www.youtube.com/embed/_xDOZElKyNU?list=PLsyOSbh5bs16vubvKePAQ1x3PhKavfBIl',
|
||||
'playlist_mincount': 455,
|
||||
'info_dict': {
|
||||
|
||||
@@ -152,37 +152,38 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
|
||||
IE_NAME = 'youtube'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&t=1s&end=9',
|
||||
'url': 'https://www.youtube.com/watch?v=YE7VzlLtp-4&t=1s&end=9',
|
||||
'info_dict': {
|
||||
'id': 'BaW_jenozKc',
|
||||
'id': 'YE7VzlLtp-4',
|
||||
'ext': 'mp4',
|
||||
'title': 'youtube-dl test video "\'/\\ä↭𝕐',
|
||||
'age_limit': 0,
|
||||
'availability': 'public',
|
||||
'categories': ['Science & Technology'],
|
||||
'channel': 'Philipp Hagemeister',
|
||||
'title': 'Big Buck Bunny',
|
||||
'description': 'md5:e95316924b5eca2a74b87ab0b290724a',
|
||||
'media_type': 'video',
|
||||
'uploader': 'Blender',
|
||||
'uploader_id': '@BlenderOfficial',
|
||||
'uploader_url': 'https://www.youtube.com/@BlenderOfficial',
|
||||
'channel': 'Blender',
|
||||
'channel_id': 'UCSMOQeBJ2RAnuFungnQOxLg',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCSMOQeBJ2RAnuFungnQOxLg',
|
||||
'channel_is_verified': True,
|
||||
'channel_follower_count': int,
|
||||
'channel_id': 'UCLqxVugv74EIW3VWh2NOa3Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCLqxVugv74EIW3VWh2NOa3Q',
|
||||
'comment_count': int,
|
||||
'description': 'md5:8fb536f4877b8a7455c2ec23794dbc22',
|
||||
'duration': 10,
|
||||
'end_time': 9,
|
||||
'heatmap': 'count:100',
|
||||
'like_count': int,
|
||||
'live_status': 'not_live',
|
||||
'playable_in_embed': True,
|
||||
'start_time': 1,
|
||||
'tags': 'count:1',
|
||||
'thumbnail': r're:https?://i\.ytimg\.com/.+',
|
||||
'timestamp': 1349198244,
|
||||
'upload_date': '20121002',
|
||||
'uploader': 'Philipp Hagemeister',
|
||||
'uploader_id': '@PhilippHagemeister',
|
||||
'uploader_url': 'https://www.youtube.com/@PhilippHagemeister',
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'age_limit': 0,
|
||||
'duration': 597,
|
||||
'thumbnail': 'https://i.ytimg.com/vi/YE7VzlLtp-4/maxresdefault.jpg',
|
||||
'heatmap': 'count:100',
|
||||
'start_time': 1.0,
|
||||
'end_time': 9.0,
|
||||
'categories': ['Film & Animation'],
|
||||
'tags': 'count:16',
|
||||
'timestamp': 1212060266,
|
||||
'upload_date': '20080529',
|
||||
'playable_in_embed': True,
|
||||
'availability': 'public',
|
||||
'live_status': 'not_live',
|
||||
},
|
||||
'skip': 'Video unavailable',
|
||||
}, {
|
||||
'note': 'Embed-only video (#1746)',
|
||||
'url': '//www.YouTube.com/watch?v=yZIXLfi8CZQ',
|
||||
@@ -197,35 +198,36 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'skip': 'Private video',
|
||||
}, {
|
||||
'note': 'Use the first video ID in the URL',
|
||||
'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&v=yZIXLfi8CZQ',
|
||||
'url': 'https://www.youtube.com/watch?v=YE7VzlLtp-4&v=BaW_jenozKc',
|
||||
'info_dict': {
|
||||
'id': 'BaW_jenozKc',
|
||||
'id': 'YE7VzlLtp-4',
|
||||
'ext': 'mp4',
|
||||
'title': 'youtube-dl test video "\'/\\ä↭𝕐',
|
||||
'age_limit': 0,
|
||||
'availability': 'public',
|
||||
'categories': ['Science & Technology'],
|
||||
'channel': 'Philipp Hagemeister',
|
||||
'title': 'Big Buck Bunny',
|
||||
'description': 'md5:e95316924b5eca2a74b87ab0b290724a',
|
||||
'media_type': 'video',
|
||||
'uploader': 'Blender',
|
||||
'uploader_id': '@BlenderOfficial',
|
||||
'uploader_url': 'https://www.youtube.com/@BlenderOfficial',
|
||||
'channel': 'Blender',
|
||||
'channel_id': 'UCSMOQeBJ2RAnuFungnQOxLg',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCSMOQeBJ2RAnuFungnQOxLg',
|
||||
'channel_is_verified': True,
|
||||
'channel_follower_count': int,
|
||||
'channel_id': 'UCLqxVugv74EIW3VWh2NOa3Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCLqxVugv74EIW3VWh2NOa3Q',
|
||||
'comment_count': int,
|
||||
'description': 'md5:8fb536f4877b8a7455c2ec23794dbc22',
|
||||
'duration': 10,
|
||||
'heatmap': 'count:100',
|
||||
'like_count': int,
|
||||
'live_status': 'not_live',
|
||||
'playable_in_embed': True,
|
||||
'tags': 'count:1',
|
||||
'thumbnail': r're:https?://i\.ytimg\.com/.+',
|
||||
'timestamp': 1349198244,
|
||||
'upload_date': '20121002',
|
||||
'uploader': 'Philipp Hagemeister',
|
||||
'uploader_id': '@PhilippHagemeister',
|
||||
'uploader_url': 'https://www.youtube.com/@PhilippHagemeister',
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'age_limit': 0,
|
||||
'duration': 597,
|
||||
'thumbnail': 'https://i.ytimg.com/vi/YE7VzlLtp-4/maxresdefault.jpg',
|
||||
'heatmap': 'count:100',
|
||||
'categories': ['Film & Animation'],
|
||||
'tags': 'count:16',
|
||||
'timestamp': 1212060266,
|
||||
'upload_date': '20080529',
|
||||
'playable_in_embed': True,
|
||||
'availability': 'public',
|
||||
'live_status': 'not_live',
|
||||
},
|
||||
'skip': 'Video unavailable',
|
||||
}, {
|
||||
'note': '256k DASH audio (format 141) via DASH manifest',
|
||||
'url': 'https://www.youtube.com/watch?v=a9LDPn-MO4I',
|
||||
@@ -823,10 +825,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'url': 'sJL6WA-aGkQ',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://invidio.us/watch?v=BaW_jenozKc',
|
||||
'url': 'https://invidio.us/watch?v=YE7VzlLtp-4',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://redirect.invidious.io/watch?v=BaW_jenozKc',
|
||||
'url': 'https://redirect.invidious.io/watch?v=YE7VzlLtp-4',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# from https://nitter.pussthecat.org/YouTube/status/1360363141947944964#m
|
||||
@@ -3446,7 +3448,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
self._report_pot_format_skipped(video_id, client_name, proto)
|
||||
return None
|
||||
|
||||
name = fmt_stream.get('qualityLabel') or quality.replace('audio_quality_', '') or ''
|
||||
name = fmt_stream.get('qualityLabel') or (quality or '').replace('audio_quality_', '')
|
||||
fps = int_or_none(fmt_stream.get('fps')) or 0
|
||||
dct = {
|
||||
'asr': int_or_none(fmt_stream.get('audioSampleRate')),
|
||||
@@ -3819,11 +3821,20 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
wait_seconds = 0
|
||||
|
||||
for renderer in traverse_obj(player_response, (
|
||||
'adSlots', lambda _, v: v['adSlotRenderer']['adSlotMetadata']['triggerEvent'] == 'SLOT_TRIGGER_EVENT_BEFORE_CONTENT',
|
||||
'adSlotRenderer', 'fulfillmentContent', 'fulfilledLayout', 'playerBytesAdLayoutRenderer', 'renderingContent', (
|
||||
None,
|
||||
('playerBytesSequentialLayoutRenderer', 'sequentialLayouts', ..., 'playerBytesAdLayoutRenderer', 'renderingContent'),
|
||||
), 'instreamVideoAdRenderer', {dict},
|
||||
(
|
||||
(
|
||||
'adPlacements', lambda _, v: v['adPlacementRenderer']['config']['adPlacementConfig']['kind'] == 'AD_PLACEMENT_KIND_START',
|
||||
'adPlacementRenderer', 'renderer',
|
||||
),
|
||||
(
|
||||
'adSlots', lambda _, v: v['adSlotRenderer']['adSlotMetadata']['triggerEvent'] == 'SLOT_TRIGGER_EVENT_BEFORE_CONTENT',
|
||||
'adSlotRenderer', 'fulfillmentContent', 'fulfilledLayout', 'playerBytesAdLayoutRenderer', 'renderingContent', (
|
||||
None,
|
||||
('playerBytesSequentialLayoutRenderer', 'sequentialLayouts', ..., 'playerBytesAdLayoutRenderer', 'renderingContent'),
|
||||
),
|
||||
),
|
||||
),
|
||||
'instreamVideoAdRenderer', {dict},
|
||||
)):
|
||||
duration = traverse_obj(renderer, ('playerVars', {urllib.parse.parse_qs}, 'length_seconds', -1, {int_or_none}))
|
||||
ad = 'an ad' if duration is None else f'a {duration}s ad'
|
||||
@@ -4363,7 +4374,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
if not release_year:
|
||||
release_year = release_date[:4]
|
||||
info.update({
|
||||
'album': mobj.group('album'.strip()),
|
||||
'album': mobj.group('album').strip(),
|
||||
'artists': ([a] if (a := mobj.group('clean_artist'))
|
||||
else [a.strip() for a in mobj.group('artist').split(' · ')]),
|
||||
'track': mobj.group('track').strip(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user