mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-07 16:01:27 +01:00
Compare commits
5 Commits
fe6c82ccff
...
66a0127d45
Author | SHA1 | Date | |
---|---|---|---|
|
66a0127d45 | ||
|
3f90813f06 | ||
|
64de1a4c25 | ||
|
f96ab86cd8 | ||
|
f4b95acafc |
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
|
@ -377,8 +377,8 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with: # 3.7 is used for Vista support. See https://github.com/yt-dlp/yt-dlp/issues/390
|
||||
python-version: "3.7"
|
||||
with:
|
||||
python-version: "3.8"
|
||||
architecture: "x86"
|
||||
- name: Install Requirements
|
||||
run: |
|
||||
|
@ -436,7 +436,16 @@ jobs:
|
|||
run: |
|
||||
cat >> _update_spec << EOF
|
||||
# This file is used for regulating self-update
|
||||
lock 2022.08.18.36 .+ Python 3.6
|
||||
lock 2022.08.18.36 .+ Python 3\.6
|
||||
lock 2023.11.16 (?!win_x86_exe).+ Python 3\.7
|
||||
lock 2023.11.16 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
lockV2 yt-dlp/yt-dlp 2022.08.18.36 .+ Python 3\.6
|
||||
lockV2 yt-dlp/yt-dlp 2023.11.16 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 yt-dlp/yt-dlp 2023.11.16 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
lockV2 yt-dlp/yt-dlp-nightly-builds 2023.11.15.232826 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 yt-dlp/yt-dlp-nightly-builds 2023.11.15.232826 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
lockV2 yt-dlp/yt-dlp-master-builds 2023.11.15.232812 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 yt-dlp/yt-dlp-master-builds 2023.11.15.232812 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
EOF
|
||||
|
||||
- name: Sign checksum files
|
||||
|
|
6
.github/workflows/core.yml
vendored
6
.github/workflows/core.yml
vendored
|
@ -13,12 +13,12 @@ jobs:
|
|||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
# CPython 3.11 is in quick-test
|
||||
python-version: ['3.8', '3.9', '3.10', '3.12', pypy-3.7, pypy-3.8, pypy-3.10]
|
||||
python-version: ['3.8', '3.9', '3.10', '3.12', pypy-3.8, pypy-3.10]
|
||||
run-tests-ext: [sh]
|
||||
include:
|
||||
# atleast one of each CPython/PyPy tests must be in windows
|
||||
- os: windows-latest
|
||||
python-version: '3.7'
|
||||
python-version: '3.8'
|
||||
run-tests-ext: bat
|
||||
- os: windows-latest
|
||||
python-version: '3.12'
|
||||
|
@ -32,7 +32,7 @@ jobs:
|
|||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
- name: Install test requirements
|
||||
run: pip install pytest -r requirements.txt
|
||||
- name: Run tests
|
||||
continue-on-error: False
|
||||
|
|
8
.github/workflows/download.yml
vendored
8
.github/workflows/download.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
|||
with:
|
||||
python-version: 3.9
|
||||
- name: Install test requirements
|
||||
run: pip install pytest
|
||||
run: pip install pytest -r requirements.txt
|
||||
- name: Run tests
|
||||
continue-on-error: true
|
||||
run: ./devscripts/run_tests.sh download
|
||||
|
@ -28,7 +28,7 @@ jobs:
|
|||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
python-version: ['3.7', '3.10', '3.12', pypy-3.7, pypy-3.8, pypy-3.10]
|
||||
python-version: ['3.10', '3.11', '3.12', pypy-3.8, pypy-3.10]
|
||||
run-tests-ext: [sh]
|
||||
include:
|
||||
# atleast one of each CPython/PyPy tests must be in windows
|
||||
|
@ -44,8 +44,8 @@ jobs:
|
|||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install pytest
|
||||
run: pip install pytest
|
||||
- name: Install test requirements
|
||||
run: pip install pytest -r requirements.txt
|
||||
- name: Run tests
|
||||
continue-on-error: true
|
||||
run: ./devscripts/run_tests.${{ matrix.run-tests-ext }} download
|
||||
|
|
2
.github/workflows/quick-test.yml
vendored
2
.github/workflows/quick-test.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
|||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install test requirements
|
||||
run: pip install pytest pycryptodomex
|
||||
run: pip install pytest -r requirements.txt
|
||||
- name: Run tests
|
||||
run: |
|
||||
python3 -m yt_dlp -v || true
|
||||
|
|
|
@ -222,7 +222,7 @@ After you have ensured this site is distributing its content legally, you can fo
|
|||
|
||||
$ flake8 yt_dlp/extractor/yourextractor.py
|
||||
|
||||
1. Make sure your code works under all [Python](https://www.python.org/) versions supported by yt-dlp, namely CPython and PyPy for Python 3.7 and above. Backward compatibility is not required for even older versions of Python.
|
||||
1. Make sure your code works under all [Python](https://www.python.org/) versions supported by yt-dlp, namely CPython and PyPy for Python 3.8 and above. Backward compatibility is not required for even older versions of Python.
|
||||
1. When the tests pass, [add](https://git-scm.com/docs/git-add) the new files, [commit](https://git-scm.com/docs/git-commit) them and [push](https://git-scm.com/docs/git-push) the result, like this:
|
||||
|
||||
$ git add yt_dlp/extractor/_extractors.py
|
||||
|
|
|
@ -131,7 +131,7 @@ Features marked with a **\*** have been back-ported to youtube-dl
|
|||
|
||||
Some of yt-dlp's default options are different from that of youtube-dl and youtube-dlc:
|
||||
|
||||
* yt-dlp supports only [Python 3.7+](## "Windows 7"), and *may* remove support for more versions as they [become EOL](https://devguide.python.org/versions/#python-release-cycle); while [youtube-dl still supports Python 2.6+ and 3.2+](https://github.com/ytdl-org/youtube-dl/issues/30568#issue-1118238743)
|
||||
* yt-dlp supports only [Python 3.8+](## "Windows 7"), and *may* remove support for more versions as they [become EOL](https://devguide.python.org/versions/#python-release-cycle); while [youtube-dl still supports Python 2.6+ and 3.2+](https://github.com/ytdl-org/youtube-dl/issues/30568#issue-1118238743)
|
||||
* The options `--auto-number` (`-A`), `--title` (`-t`) and `--literal` (`-l`), no longer work. See [removed options](#Removed) for details
|
||||
* `avconv` is not supported as an alternative to `ffmpeg`
|
||||
* yt-dlp stores config files in slightly different locations to youtube-dl. See [CONFIGURATION](#configuration) for a list of correct locations
|
||||
|
@ -266,7 +266,7 @@ gpg --verify SHA2-512SUMS.sig SHA2-512SUMS
|
|||
**Note**: The manpages, shell completion (autocomplete) files etc. are available inside the [source tarball](https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.tar.gz)
|
||||
|
||||
## DEPENDENCIES
|
||||
Python versions 3.7+ (CPython and PyPy) are supported. Other versions and implementations may or may not work correctly.
|
||||
Python versions 3.8+ (CPython and PyPy) are supported. Other versions and implementations may or may not work correctly.
|
||||
|
||||
<!-- Python 3.5+ uses VC++14 and it is already embedded in the binary created
|
||||
<!x-- https://www.microsoft.com/en-us/download/details.aspx?id=26999 --x>
|
||||
|
@ -334,7 +334,7 @@ On some systems, you may need to use `py` or `python` instead of `python3`.
|
|||
**Important**: Running `pyinstaller` directly **without** using `pyinst.py` is **not** officially supported. This may or may not work correctly.
|
||||
|
||||
### Platform-independent Binary (UNIX)
|
||||
You will need the build tools `python` (3.7+), `zip`, `make` (GNU), `pandoc`\* and `pytest`\*.
|
||||
You will need the build tools `python` (3.8+), `zip`, `make` (GNU), `pandoc`\* and `pytest`\*.
|
||||
|
||||
After installing these, simply run `make`.
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ markers =
|
|||
|
||||
[tox:tox]
|
||||
skipsdist = true
|
||||
envlist = py{36,37,38,39,310,311},pypy{36,37,38,39}
|
||||
envlist = py{38,39,310,311,312},pypy{38,39,310}
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv] # tox
|
||||
|
@ -39,7 +39,7 @@ setenv =
|
|||
|
||||
|
||||
[isort]
|
||||
py_version = 37
|
||||
py_version = 38
|
||||
multi_line_output = VERTICAL_HANGING_INDENT
|
||||
line_length = 80
|
||||
reverse_relative = true
|
||||
|
|
4
setup.py
4
setup.py
|
@ -152,7 +152,7 @@ def main():
|
|||
url='https://github.com/yt-dlp/yt-dlp',
|
||||
packages=packages(),
|
||||
install_requires=REQUIREMENTS,
|
||||
python_requires='>=3.7',
|
||||
python_requires='>=3.8',
|
||||
project_urls={
|
||||
'Documentation': 'https://github.com/yt-dlp/yt-dlp#readme',
|
||||
'Source': 'https://github.com/yt-dlp/yt-dlp',
|
||||
|
@ -164,11 +164,11 @@ def main():
|
|||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Programming Language :: Python :: Implementation',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
|
|
|
@ -68,25 +68,34 @@ TEST_API_DATA = {
|
|||
},
|
||||
}
|
||||
|
||||
TEST_LOCKFILE_V1 = '''# This file is used for regulating self-update
|
||||
lock 2022.08.18.36 .+ Python 3.6
|
||||
lock 2023.11.13 .+ Python 3.7
|
||||
TEST_LOCKFILE_COMMENT = '# This file is used for regulating self-update'
|
||||
|
||||
TEST_LOCKFILE_V1 = r'''%s
|
||||
lock 2022.08.18.36 .+ Python 3\.6
|
||||
lock 2023.11.16 (?!win_x86_exe).+ Python 3\.7
|
||||
lock 2023.11.16 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
''' % TEST_LOCKFILE_COMMENT
|
||||
|
||||
TEST_LOCKFILE_V2_TMPL = r'''%s
|
||||
lockV2 yt-dlp/yt-dlp 2022.08.18.36 .+ Python 3\.6
|
||||
lockV2 yt-dlp/yt-dlp 2023.11.16 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 yt-dlp/yt-dlp 2023.11.16 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
lockV2 yt-dlp/yt-dlp-nightly-builds 2023.11.15.232826 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 yt-dlp/yt-dlp-nightly-builds 2023.11.15.232826 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
lockV2 yt-dlp/yt-dlp-master-builds 2023.11.15.232812 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 yt-dlp/yt-dlp-master-builds 2023.11.15.232812 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
'''
|
||||
|
||||
TEST_LOCKFILE_V2 = '''# This file is used for regulating self-update
|
||||
lockV2 yt-dlp/yt-dlp 2022.08.18.36 .+ Python 3.6
|
||||
lockV2 yt-dlp/yt-dlp 2023.11.13 .+ Python 3.7
|
||||
'''
|
||||
TEST_LOCKFILE_V2 = TEST_LOCKFILE_V2_TMPL % TEST_LOCKFILE_COMMENT
|
||||
|
||||
TEST_LOCKFILE_V1_V2 = '''# This file is used for regulating self-update
|
||||
lock 2022.08.18.36 .+ Python 3.6
|
||||
lock 2023.11.13 .+ Python 3.7
|
||||
lockV2 yt-dlp/yt-dlp 2022.08.18.36 .+ Python 3.6
|
||||
lockV2 yt-dlp/yt-dlp 2023.11.13 .+ Python 3.7
|
||||
TEST_LOCKFILE_ACTUAL = TEST_LOCKFILE_V2_TMPL % TEST_LOCKFILE_V1.rstrip('\n')
|
||||
|
||||
TEST_LOCKFILE_FORK = r'''%s# Test if a fork blocks updates to non-numeric tags
|
||||
lockV2 fork/yt-dlp pr0000 .+ Python 3.6
|
||||
lockV2 fork/yt-dlp pr1234 .+ Python 3.7
|
||||
lockV2 fork/yt-dlp pr1234 (?!win_x86_exe).+ Python 3\.7
|
||||
lockV2 fork/yt-dlp pr1234 win_x86_exe .+ Windows-(?:Vista|2008Server)
|
||||
lockV2 fork/yt-dlp pr9999 .+ Python 3.11
|
||||
'''
|
||||
''' % TEST_LOCKFILE_ACTUAL
|
||||
|
||||
|
||||
class FakeUpdater(Updater):
|
||||
|
@ -97,7 +106,7 @@ class FakeUpdater(Updater):
|
|||
_origin = 'yt-dlp/yt-dlp'
|
||||
|
||||
def _download_update_spec(self, *args, **kwargs):
|
||||
return TEST_LOCKFILE_V1_V2
|
||||
return TEST_LOCKFILE_ACTUAL
|
||||
|
||||
def _call_api(self, tag):
|
||||
tag = f'tags/{tag}' if tag != 'latest' else tag
|
||||
|
@ -112,7 +121,7 @@ class TestUpdate(unittest.TestCase):
|
|||
|
||||
def test_update_spec(self):
|
||||
ydl = FakeYDL()
|
||||
updater = FakeUpdater(ydl, 'stable@latest')
|
||||
updater = FakeUpdater(ydl, 'stable')
|
||||
|
||||
def test(lockfile, identifier, input_tag, expect_tag, exact=False, repo='yt-dlp/yt-dlp'):
|
||||
updater._identifier = identifier
|
||||
|
@ -124,35 +133,46 @@ class TestUpdate(unittest.TestCase):
|
|||
f'{identifier!r} requesting {repo}@{input_tag} (exact={exact}) '
|
||||
f'returned {result!r} instead of {expect_tag!r}')
|
||||
|
||||
test(TEST_LOCKFILE_V1, 'zip Python 3.11.0', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1, 'zip stable Python 3.11.0', '2023.11.13', '2023.11.13', exact=True)
|
||||
test(TEST_LOCKFILE_V1, 'zip Python 3.6.0', '2023.11.13', '2022.08.18.36')
|
||||
test(TEST_LOCKFILE_V1, 'zip stable Python 3.6.0', '2023.11.13', None, exact=True)
|
||||
test(TEST_LOCKFILE_V1, 'zip Python 3.7.0', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1, 'zip stable Python 3.7.1', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1, 'zip Python 3.7.1', '2023.12.31', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1, 'zip stable Python 3.7.1', '2023.12.31', '2023.11.13')
|
||||
for lockfile in (TEST_LOCKFILE_V1, TEST_LOCKFILE_V2, TEST_LOCKFILE_ACTUAL, TEST_LOCKFILE_FORK):
|
||||
# Normal operation
|
||||
test(lockfile, 'zip Python 3.12.0', '2023.12.31', '2023.12.31')
|
||||
test(lockfile, 'zip stable Python 3.12.0', '2023.12.31', '2023.12.31', exact=True)
|
||||
# Python 3.6 --update should update only to its lock
|
||||
test(lockfile, 'zip Python 3.6.0', '2023.11.16', '2022.08.18.36')
|
||||
# --update-to an exact version later than the lock should return None
|
||||
test(lockfile, 'zip stable Python 3.6.0', '2023.11.16', None, exact=True)
|
||||
# Python 3.7 should be able to update to its lock
|
||||
test(lockfile, 'zip Python 3.7.0', '2023.11.16', '2023.11.16')
|
||||
test(lockfile, 'zip stable Python 3.7.1', '2023.11.16', '2023.11.16', exact=True)
|
||||
# Non-win_x86_exe builds on py3.7 must be locked
|
||||
test(lockfile, 'zip Python 3.7.1', '2023.12.31', '2023.11.16')
|
||||
test(lockfile, 'zip stable Python 3.7.1', '2023.12.31', None, exact=True)
|
||||
test( # Windows Vista w/ win_x86_exe must be locked
|
||||
lockfile, 'win_x86_exe stable Python 3.7.9 (CPython x86 32bit) - Windows-Vista-6.0.6003-SP2',
|
||||
'2023.12.31', '2023.11.16')
|
||||
test( # Windows 2008Server w/ win_x86_exe must be locked
|
||||
lockfile, 'win_x86_exe Python 3.7.9 (CPython x86 32bit) - Windows-2008Server',
|
||||
'2023.12.31', None, exact=True)
|
||||
test( # Windows 7 w/ win_x86_exe py3.7 build should be able to update beyond lock
|
||||
lockfile, 'win_x86_exe stable Python 3.7.9 (CPython x86 32bit) - Windows-7-6.1.7601-SP1',
|
||||
'2023.12.31', '2023.12.31')
|
||||
test( # Windows 8.1 w/ '2008Server' in platform string should be able to update beyond lock
|
||||
lockfile, 'win_x86_exe Python 3.7.9 (CPython x86 32bit) - Windows-post2008Server-6.2.9200',
|
||||
'2023.12.31', '2023.12.31', exact=True)
|
||||
|
||||
test(TEST_LOCKFILE_V2, 'zip Python 3.11.1', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V2, 'zip stable Python 3.11.1', '2023.12.31', '2023.12.31')
|
||||
test(TEST_LOCKFILE_V2, 'zip Python 3.6.1', '2023.11.13', '2022.08.18.36')
|
||||
test(TEST_LOCKFILE_V2, 'zip stable Python 3.7.2', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V2, 'zip Python 3.7.2', '2023.12.31', '2023.11.13')
|
||||
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.11.2', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip stable Python 3.11.2', '2023.12.31', '2023.12.31')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.6.2', '2023.11.13', '2022.08.18.36')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip stable Python 3.7.3', '2023.11.13', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.7.3', '2023.12.31', '2023.11.13')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.6.3', 'pr0000', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip stable Python 3.7.4', 'pr0000', 'pr0000', repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.6.4', 'pr0000', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.7.4', 'pr1234', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip stable Python 3.8.1', 'pr1234', 'pr1234', repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.7.5', 'pr1234', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.11.3', 'pr9999', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip stable Python 3.12.0', 'pr9999', 'pr9999', repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_V1_V2, 'zip Python 3.11.4', 'pr9999', None, repo='fork/yt-dlp')
|
||||
# Forks can block updates to non-numeric tags rather than lock
|
||||
test(TEST_LOCKFILE_FORK, 'zip Python 3.6.3', 'pr0000', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_FORK, 'zip stable Python 3.7.4', 'pr0000', 'pr0000', repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_FORK, 'zip stable Python 3.7.4', 'pr1234', None, repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_FORK, 'zip Python 3.8.1', 'pr1234', 'pr1234', repo='fork/yt-dlp', exact=True)
|
||||
test(
|
||||
TEST_LOCKFILE_FORK, 'win_x86_exe stable Python 3.7.9 (CPython x86 32bit) - Windows-Vista-6.0.6003-SP2',
|
||||
'pr1234', None, repo='fork/yt-dlp')
|
||||
test(
|
||||
TEST_LOCKFILE_FORK, 'win_x86_exe stable Python 3.7.9 (CPython x86 32bit) - Windows-7-6.1.7601-SP1',
|
||||
'2023.12.31', '2023.12.31', repo='fork/yt-dlp')
|
||||
test(TEST_LOCKFILE_FORK, 'zip Python 3.11.2', 'pr9999', None, repo='fork/yt-dlp', exact=True)
|
||||
test(TEST_LOCKFILE_FORK, 'zip stable Python 3.12.0', 'pr9999', 'pr9999', repo='fork/yt-dlp')
|
||||
|
||||
def test_query_update(self):
|
||||
ydl = FakeYDL()
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
try:
|
||||
import contextvars # noqa: F401
|
||||
except Exception:
|
||||
raise Exception(
|
||||
f'You are using an unsupported version of Python. Only Python versions 3.7 and above are supported by yt-dlp') # noqa: F541
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
raise ImportError(
|
||||
f'You are using an unsupported version of Python. Only Python versions 3.8 and above are supported by yt-dlp') # noqa: F541
|
||||
|
||||
__license__ = 'Public Domain'
|
||||
|
||||
|
@ -12,7 +12,6 @@ import itertools
|
|||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from .compat import compat_shlex_quote
|
||||
|
|
|
@ -10,17 +10,3 @@ try:
|
|||
cache # >= 3.9
|
||||
except NameError:
|
||||
cache = lru_cache(maxsize=None)
|
||||
|
||||
try:
|
||||
cached_property # >= 3.8
|
||||
except NameError:
|
||||
class cached_property:
|
||||
def __init__(self, func):
|
||||
update_wrapper(self, func)
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance, _):
|
||||
if instance is None:
|
||||
return self
|
||||
setattr(instance, self.func.__name__, self.func(instance))
|
||||
return getattr(instance, self.func.__name__)
|
||||
|
|
|
@ -82,6 +82,10 @@ from .airtv import AirTVIE
|
|||
from .aitube import AitubeKZVideoIE
|
||||
from .aljazeera import AlJazeeraIE
|
||||
from .alphaporno import AlphaPornoIE
|
||||
from .altcensored import (
|
||||
AltCensoredIE,
|
||||
AltCensoredChannelIE,
|
||||
)
|
||||
from .amara import AmaraIE
|
||||
from .alura import (
|
||||
AluraIE,
|
||||
|
@ -541,6 +545,7 @@ from .dropout import (
|
|||
DropoutSeasonIE,
|
||||
DropoutIE
|
||||
)
|
||||
from .duoplay import DuoplayIE
|
||||
from .dw import (
|
||||
DWIE,
|
||||
DWArticleIE,
|
||||
|
@ -2588,6 +2593,9 @@ from .zingmp3 import (
|
|||
ZingMp3ChartMusicVideoIE,
|
||||
ZingMp3UserIE,
|
||||
ZingMp3HubIE,
|
||||
ZingMp3LiveRadioIE,
|
||||
ZingMp3PodcastEpisodeIE,
|
||||
ZingMp3PodcastIE,
|
||||
)
|
||||
from .zoom import ZoomIE
|
||||
from .zype import ZypeIE
|
||||
|
|
96
yt_dlp/extractor/altcensored.py
Normal file
96
yt_dlp/extractor/altcensored.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
import re
|
||||
|
||||
from .archiveorg import ArchiveOrgIE
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
InAdvancePagedList,
|
||||
int_or_none,
|
||||
orderedSet,
|
||||
str_to_int,
|
||||
urljoin,
|
||||
)
|
||||
|
||||
|
||||
class AltCensoredIE(InfoExtractor):
|
||||
IE_NAME = 'altcensored'
|
||||
_VALID_URL = r'https?://(?:www\.)?altcensored\.com/(?:watch\?v=|embed/)(?P<id>[^/?#]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.altcensored.com/watch?v=k0srjLSkga8',
|
||||
'info_dict': {
|
||||
'id': 'youtube-k0srjLSkga8',
|
||||
'ext': 'webm',
|
||||
'title': "QUELLES SONT LES CONSÉQUENCES DE L'HYPERSEXUALISATION DE LA SOCIÉTÉ ?",
|
||||
'display_id': 'k0srjLSkga8.webm',
|
||||
'release_date': '20180403',
|
||||
'creator': 'Virginie Vota',
|
||||
'release_year': 2018,
|
||||
'upload_date': '20230318',
|
||||
'uploader': 'admin@altcensored.com',
|
||||
'description': 'md5:0b38a8fc04103579d5c1db10a247dc30',
|
||||
'timestamp': 1679161343,
|
||||
'track': 'k0srjLSkga8',
|
||||
'duration': 926.09,
|
||||
'thumbnail': 'https://archive.org/download/youtube-k0srjLSkga8/youtube-k0srjLSkga8.thumbs/k0srjLSkga8_000925.jpg',
|
||||
'view_count': int,
|
||||
'categories': ['News & Politics'],
|
||||
}
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
|
||||
return {
|
||||
'_type': 'url_transparent',
|
||||
'url': f'https://archive.org/details/youtube-{video_id}',
|
||||
'ie_key': ArchiveOrgIE.ie_key(),
|
||||
'view_count': str_to_int(self._html_search_regex(
|
||||
r'YouTube Views:(?:\s| )*([\d,]+)', webpage, 'view count', default=None)),
|
||||
'categories': self._html_search_regex(
|
||||
r'<a href="/category/\d+">\s*\n?\s*([^<]+)</a>',
|
||||
webpage, 'category', default='').split() or None,
|
||||
}
|
||||
|
||||
|
||||
class AltCensoredChannelIE(InfoExtractor):
|
||||
IE_NAME = 'altcensored:channel'
|
||||
_VALID_URL = r'https?://(?:www\.)?altcensored\.com/channel/(?!page|table)(?P<id>[^/?#]+)'
|
||||
_PAGE_SIZE = 24
|
||||
_TESTS = [{
|
||||
'url': 'https://www.altcensored.com/channel/UCFPTO55xxHqFqkzRZHu4kcw',
|
||||
'info_dict': {
|
||||
'title': 'Virginie Vota',
|
||||
'id': 'UCFPTO55xxHqFqkzRZHu4kcw',
|
||||
},
|
||||
'playlist_count': 91
|
||||
}, {
|
||||
'url': 'https://altcensored.com/channel/UC9CcJ96HKMWn0LZlcxlpFTw',
|
||||
'info_dict': {
|
||||
'title': 'yukikaze775',
|
||||
'id': 'UC9CcJ96HKMWn0LZlcxlpFTw',
|
||||
},
|
||||
'playlist_count': 4
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
channel_id = self._match_id(url)
|
||||
webpage = self._download_webpage(
|
||||
url, channel_id, 'Download channel webpage', 'Unable to get channel webpage')
|
||||
title = self._html_search_meta('altcen_title', webpage, 'title', fatal=False)
|
||||
page_count = int_or_none(self._html_search_regex(
|
||||
r'<a[^>]+href="/channel/\w+/page/(\d+)">(?:\1)</a>',
|
||||
webpage, 'page count', default='1'))
|
||||
|
||||
def page_func(page_num):
|
||||
page_num += 1
|
||||
webpage = self._download_webpage(
|
||||
f'https://altcensored.com/channel/{channel_id}/page/{page_num}',
|
||||
channel_id, note=f'Downloading page {page_num}')
|
||||
|
||||
items = re.findall(r'<a[^>]+href="(/watch\?v=[^"]+)', webpage)
|
||||
return [self.url_result(urljoin('https://www.altcensored.com', path), AltCensoredIE)
|
||||
for path in orderedSet(items)]
|
||||
|
||||
return self.playlist_result(
|
||||
InAdvancePagedList(page_func, page_count, self._PAGE_SIZE),
|
||||
playlist_id=channel_id, playlist_title=title)
|
|
@ -209,7 +209,7 @@ class DRTVIE(InfoExtractor):
|
|||
elif access_service == 'StandardVideo':
|
||||
preference = 1
|
||||
fmts, subs = self._extract_m3u8_formats_and_subtitles(
|
||||
stream.get('url'), video_id, preference=preference, m3u8_id=format_id, fatal=False)
|
||||
stream.get('url'), video_id, ext='mp4', preference=preference, m3u8_id=format_id, fatal=False)
|
||||
formats.extend(fmts)
|
||||
|
||||
api_subtitles = traverse_obj(stream, ('subtitles', lambda _, v: url_or_none(v['link']), {dict}))
|
||||
|
|
119
yt_dlp/extractor/duoplay.py
Normal file
119
yt_dlp/extractor/duoplay.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
extract_attributes,
|
||||
get_element_text_and_html_by_tag,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
str_or_none,
|
||||
try_call,
|
||||
unified_timestamp,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class DuoplayIE(InfoExtractor):
|
||||
_VALID_URL = r'https://duoplay\.ee/(?P<id>\d+)/[\w-]+/?(?:\?(?:[^#]+&)?ep=(?P<ep>\d+))?'
|
||||
_TESTS = [{
|
||||
'note': 'Siberi võmm S02E12',
|
||||
'url': 'https://duoplay.ee/4312/siberi-vomm?ep=24',
|
||||
'md5': '1ff59d535310ac9c5cf5f287d8f91b2d',
|
||||
'info_dict': {
|
||||
'id': '4312_24',
|
||||
'ext': 'mp4',
|
||||
'title': 'Operatsioon "Öö"',
|
||||
'thumbnail': r're:https://.+\.jpg(?:\?c=\d+)?$',
|
||||
'description': 'md5:8ef98f38569d6b8b78f3d350ccc6ade8',
|
||||
'upload_date': '20170523',
|
||||
'timestamp': 1495567800,
|
||||
'series': 'Siberi võmm',
|
||||
'series_id': '4312',
|
||||
'season': 'Season 2',
|
||||
'season_number': 2,
|
||||
'episode': 'Operatsioon "Öö"',
|
||||
'episode_number': 12,
|
||||
'episode_id': 24,
|
||||
},
|
||||
}, {
|
||||
'note': 'Empty title',
|
||||
'url': 'https://duoplay.ee/17/uhikarotid?ep=14',
|
||||
'md5': '6aca68be71112314738dd17cced7f8bf',
|
||||
'info_dict': {
|
||||
'id': '17_14',
|
||||
'ext': 'mp4',
|
||||
'title': 'Ühikarotid',
|
||||
'thumbnail': r're:https://.+\.jpg(?:\?c=\d+)?$',
|
||||
'description': 'md5:4719b418e058c209def41d48b601276e',
|
||||
'upload_date': '20100916',
|
||||
'timestamp': 1284661800,
|
||||
'series': 'Ühikarotid',
|
||||
'series_id': '17',
|
||||
'season': 'Season 2',
|
||||
'season_number': 2,
|
||||
'episode_id': 14,
|
||||
'release_year': 2010,
|
||||
},
|
||||
}, {
|
||||
'note': 'Movie',
|
||||
'url': 'https://duoplay.ee/4325/naljamangud',
|
||||
'md5': '2b0bcac4159a08b1844c2bfde06b1199',
|
||||
'info_dict': {
|
||||
'id': '4325',
|
||||
'ext': 'mp4',
|
||||
'title': 'Näljamängud',
|
||||
'thumbnail': r're:https://.+\.jpg(?:\?c=\d+)?$',
|
||||
'description': 'md5:fb35f5eb2ff46cdb82e4d5fbe7b49a13',
|
||||
'cast': ['Jennifer Lawrence', 'Josh Hutcherson', 'Liam Hemsworth'],
|
||||
'upload_date': '20231109',
|
||||
'timestamp': 1699552800,
|
||||
'release_year': 2012,
|
||||
},
|
||||
}, {
|
||||
'note': 'Movie without expiry',
|
||||
'url': 'https://duoplay.ee/5501/pilvede-all.-neljas-ode',
|
||||
'md5': '7abf63d773a49ef7c39f2c127842b8fd',
|
||||
'info_dict': {
|
||||
'id': '5501',
|
||||
'ext': 'mp4',
|
||||
'title': 'Pilvede all. Neljas õde',
|
||||
'thumbnail': r're:https://.+\.jpg(?:\?c=\d+)?$',
|
||||
'description': 'md5:d86a70f8f31e82c369d4d4f4c79b1279',
|
||||
'cast': 'count:9',
|
||||
'upload_date': '20221214',
|
||||
'timestamp': 1671054000,
|
||||
'release_year': 2018,
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
telecast_id, episode = self._match_valid_url(url).group('id', 'ep')
|
||||
video_id = join_nonempty(telecast_id, episode, delim='_')
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
video_player = try_call(lambda: extract_attributes(
|
||||
get_element_text_and_html_by_tag('video-player', webpage)[1]))
|
||||
if not video_player or not video_player.get('manifest-url'):
|
||||
raise ExtractorError('No video found', expected=True)
|
||||
|
||||
episode_attr = self._parse_json(video_player.get(':episode') or '', video_id, fatal=False) or {}
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'formats': self._extract_m3u8_formats(video_player['manifest-url'], video_id, 'mp4'),
|
||||
**traverse_obj(episode_attr, {
|
||||
'title': 'title',
|
||||
'description': 'synopsis',
|
||||
'thumbnail': ('images', 'original'),
|
||||
'timestamp': ('airtime', {lambda x: unified_timestamp(x + ' +0200')}),
|
||||
'cast': ('cast', {lambda x: x.split(', ')}),
|
||||
'release_year': ('year', {int_or_none}),
|
||||
}),
|
||||
**(traverse_obj(episode_attr, {
|
||||
'title': (None, ('subtitle', ('episode_nr', {lambda x: f'Episode {x}' if x else None}))),
|
||||
'series': 'title',
|
||||
'series_id': ('telecast_id', {str_or_none}),
|
||||
'season_number': ('season_id', {int_or_none}),
|
||||
'episode': 'subtitle',
|
||||
'episode_number': ('episode_nr', {int_or_none}),
|
||||
'episode_id': ('episode_id', {int_or_none}),
|
||||
}, get_all=False) if episode_attr.get('category') != 'movies' else {}),
|
||||
}
|
|
@ -5,7 +5,15 @@ import json
|
|||
import urllib.parse
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import int_or_none, traverse_obj, try_call, urljoin
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
try_call,
|
||||
urljoin,
|
||||
url_or_none
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
|
||||
|
||||
class ZingMp3BaseIE(InfoExtractor):
|
||||
|
@ -20,9 +28,17 @@ class ZingMp3BaseIE(InfoExtractor):
|
|||
'video-clip': '/api/v2/page/get/video',
|
||||
'lyric': '/api/v2/lyric/get/lyric',
|
||||
'song-streaming': '/api/v2/song/get/streaming',
|
||||
'liveradio': '/api/v2/livestream/get/info',
|
||||
'eps': '/api/v2/page/get/podcast-episode',
|
||||
'episode-streaming': '/api/v2/podcast/episode/get/streaming',
|
||||
# Playlist
|
||||
'playlist': '/api/v2/page/get/playlist',
|
||||
'album': '/api/v2/page/get/playlist',
|
||||
'pgr': '/api/v2/page/get/podcast-program',
|
||||
'pgr-list': '/api/v2/podcast/episode/get/list',
|
||||
'cgr': '/api/v2/page/get/podcast-category',
|
||||
'cgr-list': '/api/v2/podcast/program/get/list-by-cate',
|
||||
'cgrs': '/api/v2/page/get/podcast-categories',
|
||||
# Chart
|
||||
'zing-chart': '/api/v2/page/get/chart-home',
|
||||
'zing-chart-tuan': '/api/v2/page/get/week-chart',
|
||||
|
@ -33,6 +49,10 @@ class ZingMp3BaseIE(InfoExtractor):
|
|||
'user-list-song': '/api/v2/song/get/list',
|
||||
'user-list-video': '/api/v2/video/get/list',
|
||||
'hub': '/api/v2/page/get/hub-detail',
|
||||
'new-release': '/api/v2/chart/get/new-release',
|
||||
'top100': '/api/v2/page/get/top-100',
|
||||
'podcast-new': '/api/v2/podcast/program/get/list-by-type',
|
||||
'top-podcast': '/api/v2/podcast/program/get/top-episode',
|
||||
}
|
||||
|
||||
def _api_url(self, url_type, params):
|
||||
|
@ -78,7 +98,7 @@ class ZingMp3BaseIE(InfoExtractor):
|
|||
|
||||
|
||||
class ZingMp3IE(ZingMp3BaseIE):
|
||||
_VALID_URL = ZingMp3BaseIE._VALID_URL_TMPL % 'bai-hat|video-clip|embed'
|
||||
_VALID_URL = ZingMp3BaseIE._VALID_URL_TMPL % 'bai-hat|video-clip|embed|eps'
|
||||
IE_NAME = 'zingmp3'
|
||||
IE_DESC = 'zingmp3.vn'
|
||||
_TESTS = [{
|
||||
|
@ -102,7 +122,7 @@ class ZingMp3IE(ZingMp3BaseIE):
|
|||
},
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/video-clip/Suong-Hoa-Dua-Loi-K-ICM-RYO/ZO8ZF7C7.html',
|
||||
'md5': '3c2081e79471a2f4a3edd90b70b185ea',
|
||||
'md5': '92c6e7a019f06b4682a6c35ae5785fab',
|
||||
'info_dict': {
|
||||
'id': 'ZO8ZF7C7',
|
||||
'title': 'Sương Hoa Đưa Lối',
|
||||
|
@ -128,6 +148,20 @@ class ZingMp3IE(ZingMp3BaseIE):
|
|||
'album': 'Người Yêu Tôi Lạnh Lùng Sắt Đá (Single)',
|
||||
'album_artist': 'Mr. Siro',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/eps/Cham-x-Ban-Noi-Goi-La-Nha/ZZD9ACWI.html',
|
||||
'md5': 'd52f9f63e2631e004e4f15188eedcf80',
|
||||
'info_dict': {
|
||||
'id': 'ZZD9ACWI',
|
||||
'title': 'Chạm x Bạn - Nơi Gọi Là Nhà',
|
||||
'ext': 'mp3',
|
||||
'duration': 3716,
|
||||
'thumbnail': r're:^https?://.+\.jpg',
|
||||
'track': 'Chạm x Bạn - Nơi Gọi Là Nhà',
|
||||
'artist': 'On Air',
|
||||
'album': 'Top Podcast',
|
||||
'album_artist': 'On Air',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/embed/song/ZWZEI76B?start=false',
|
||||
'only_matching': True,
|
||||
|
@ -147,6 +181,8 @@ class ZingMp3IE(ZingMp3BaseIE):
|
|||
'http://api.mp3.zing.vn/api/mobile/video/getvideoinfo', item_id,
|
||||
query={'requestdata': json.dumps({'id': item_id})},
|
||||
note='Downloading mp4 JSON metadata').get('source')
|
||||
elif url_type == 'eps':
|
||||
source = self._call_api('episode-streaming', {'id': item_id})
|
||||
else:
|
||||
source = self._call_api('song-streaming', {'id': item_id})
|
||||
|
||||
|
@ -189,9 +225,10 @@ class ZingMp3IE(ZingMp3BaseIE):
|
|||
'thumbnail': traverse_obj(item, 'thumbnail', 'thumbnailM'),
|
||||
'duration': int_or_none(item.get('duration')),
|
||||
'track': traverse_obj(item, 'title', 'alias'),
|
||||
'artist': traverse_obj(item, 'artistsNames', 'artists_names'),
|
||||
'album': traverse_obj(item, ('album', ('name', 'title')), get_all=False),
|
||||
'album_artist': traverse_obj(item, ('album', ('artistsNames', 'artists_names')), get_all=False),
|
||||
'artist': traverse_obj(item, 'artistsNames', 'artists_names', ('artists', 0, 'name')),
|
||||
'album': traverse_obj(item, ('album', ('name', 'title')), ('genres', 0, 'name'), get_all=False),
|
||||
'album_artist': traverse_obj(item, ('album', ('artistsNames', 'artists_names')),
|
||||
('artists', 0, 'name'), get_all=False),
|
||||
'formats': formats,
|
||||
'subtitles': {'origin': [{'url': lyric}]} if lyric else None,
|
||||
}
|
||||
|
@ -200,12 +237,12 @@ class ZingMp3IE(ZingMp3BaseIE):
|
|||
class ZingMp3AlbumIE(ZingMp3BaseIE):
|
||||
_VALID_URL = ZingMp3BaseIE._VALID_URL_TMPL % 'album|playlist'
|
||||
_TESTS = [{
|
||||
'url': 'http://mp3.zing.vn/album/Lau-Dai-Tinh-Ai-Bang-Kieu-Minh-Tuyet/ZWZBWDAF.html',
|
||||
'url': 'https://zingmp3.vn/album/Ca-Phe-Quan-Quen-Hoang-Dung-My-Anh-Da-LAB-Thinh-Suy/ZOC7WUZC.html',
|
||||
'info_dict': {
|
||||
'id': 'ZWZBWDAF',
|
||||
'title': 'Lâu Đài Tình Ái',
|
||||
'id': 'ZOC7WUZC',
|
||||
'title': 'Cà Phê Quán Quen',
|
||||
},
|
||||
'playlist_mincount': 9,
|
||||
'playlist_mincount': 10,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/album/Nhung-Bai-Hat-Hay-Nhat-Cua-Mr-Siro-Mr-Siro/ZWZAEZZD.html',
|
||||
'info_dict': {
|
||||
|
@ -231,7 +268,7 @@ class ZingMp3AlbumIE(ZingMp3BaseIE):
|
|||
|
||||
|
||||
class ZingMp3ChartHomeIE(ZingMp3BaseIE):
|
||||
_VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<id>(?:zing-chart|moi-phat-hanh))/?(?:[#?]|$)'
|
||||
_VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<id>(?:zing-chart|moi-phat-hanh|top100|podcast-discover))/?(?:[#?]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://zingmp3.vn/zing-chart',
|
||||
'info_dict': {
|
||||
|
@ -244,13 +281,34 @@ class ZingMp3ChartHomeIE(ZingMp3BaseIE):
|
|||
'id': 'moi-phat-hanh',
|
||||
},
|
||||
'playlist_mincount': 100,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/top100',
|
||||
'info_dict': {
|
||||
'id': 'top100',
|
||||
},
|
||||
'playlist_mincount': 50,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/podcast-discover',
|
||||
'info_dict': {
|
||||
'id': 'podcast-discover',
|
||||
},
|
||||
'playlist_mincount': 4,
|
||||
}]
|
||||
IE_NAME = 'zingmp3:chart-home'
|
||||
|
||||
def _real_extract(self, url):
|
||||
url_type = self._match_id(url)
|
||||
data = self._call_api(url_type, {'id': url_type})
|
||||
items = traverse_obj(data, ('RTChart', 'items') if url_type == 'zing-chart' else 'items')
|
||||
params = {'id': url_type}
|
||||
if url_type == 'podcast-discover':
|
||||
params['type'] = 'discover'
|
||||
data = self._call_api(url_type, params)
|
||||
items = []
|
||||
if url_type == 'top100':
|
||||
items.extend(traverse_obj(data, (..., 'items', ..., {dict})))
|
||||
elif url_type == 'zing-chart':
|
||||
items.extend(traverse_obj(data, ('RTChart', 'items', ..., {dict})))
|
||||
else:
|
||||
items.extend(traverse_obj(data, ('items', ..., {dict})))
|
||||
return self.playlist_result(self._parse_items(items), url_type)
|
||||
|
||||
|
||||
|
@ -334,7 +392,7 @@ class ZingMp3ChartMusicVideoIE(ZingMp3BaseIE):
|
|||
|
||||
|
||||
class ZingMp3UserIE(ZingMp3BaseIE):
|
||||
_VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<user>[^/]+)/(?P<type>bai-hat|single|album|video)/?(?:[?#]|$)'
|
||||
_VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<user>[^/]+)/(?P<type>bai-hat|single|album|video|song)/?(?:[?#]|$)'
|
||||
IE_NAME = 'zingmp3:user'
|
||||
_TESTS = [{
|
||||
'url': 'https://zingmp3.vn/Mr-Siro/bai-hat',
|
||||
|
@ -368,6 +426,18 @@ class ZingMp3UserIE(ZingMp3BaseIE):
|
|||
'description': 'md5:5bdcf45e955dc1b8d7f518f322ffef36',
|
||||
},
|
||||
'playlist_mincount': 15,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/new-release/song',
|
||||
'info_dict': {
|
||||
'id': 'new-release-song',
|
||||
},
|
||||
'playlist_mincount': 50,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/new-release/album',
|
||||
'info_dict': {
|
||||
'id': 'new-release-album',
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
}]
|
||||
|
||||
def _fetch_page(self, user_id, url_type, page):
|
||||
|
@ -380,20 +450,28 @@ class ZingMp3UserIE(ZingMp3BaseIE):
|
|||
})
|
||||
|
||||
def _real_extract(self, url):
|
||||
user_alias, url_type = self._match_valid_url(url).group('user', 'type')
|
||||
alias, url_type = self._match_valid_url(url).group('user', 'type')
|
||||
if not url_type:
|
||||
url_type = 'bai-hat'
|
||||
|
||||
user_info = self._call_api('info-artist', {}, user_alias, query={'alias': user_alias})
|
||||
if url_type in ('bai-hat', 'video'):
|
||||
entries = self._paged_list(user_info['id'], url_type)
|
||||
user_info = self._call_api('info-artist', {}, alias, query={'alias': alias})
|
||||
|
||||
# Handle for new-release
|
||||
if alias == 'new-release' and url_type in ('song', 'album'):
|
||||
_id = f'{alias}-{url_type}'
|
||||
return self.playlist_result(self._parse_items(
|
||||
self._call_api('new-release', params={'type': url_type}, display_id=_id)), _id)
|
||||
else:
|
||||
entries = self._parse_items(traverse_obj(user_info, (
|
||||
'sections',
|
||||
lambda _, v: v['sectionId'] == 'aAlbum' if url_type == 'album' else v['sectionId'] == 'aSingle',
|
||||
'items', ...)))
|
||||
return self.playlist_result(
|
||||
entries, user_info['id'], f'{user_info.get("name")} - {url_type}', user_info.get('biography'))
|
||||
# Handle for user/artist
|
||||
if url_type in ('bai-hat', 'video'):
|
||||
entries = self._paged_list(user_info['id'], url_type)
|
||||
else:
|
||||
section_id = 'aAlbum' if url_type == 'album' else 'aSingle'
|
||||
entries = self._parse_items(traverse_obj(user_info, (
|
||||
'sections', lambda _, v: v['sectionId'] == section_id, 'items', ...)))
|
||||
return self.playlist_result(
|
||||
entries, user_info['id'], join_nonempty(user_info.get('name'), url_type, delim=' - '),
|
||||
user_info.get('biography'))
|
||||
|
||||
|
||||
class ZingMp3HubIE(ZingMp3BaseIE):
|
||||
|
@ -403,7 +481,7 @@ class ZingMp3HubIE(ZingMp3BaseIE):
|
|||
'url': 'https://zingmp3.vn/hub/Nhac-Moi/IWZ9Z0CA.html',
|
||||
'info_dict': {
|
||||
'id': 'IWZ9Z0CA',
|
||||
'title': 'Nhạc Mới',
|
||||
'title': 'BXH Nhạc Mới',
|
||||
'description': 'md5:1cc31b68a6f746427b07b2756c22a558',
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
|
@ -424,3 +502,129 @@ class ZingMp3HubIE(ZingMp3BaseIE):
|
|||
'sections', lambda _, v: v['sectionId'] == 'hub', 'items', ...)))
|
||||
return self.playlist_result(
|
||||
entries, song_id, hub_detail.get('title'), hub_detail.get('description'))
|
||||
|
||||
|
||||
class ZingMp3LiveRadioIE(ZingMp3BaseIE):
|
||||
IE_NAME = 'zingmp3:liveradio'
|
||||
_VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<type>(?:liveradio))/(?P<id>\w+)(?:\.html|\?)'
|
||||
_TESTS = [{
|
||||
'url': 'https://zingmp3.vn/liveradio/IWZ979UB.html',
|
||||
'info_dict': {
|
||||
'id': 'IWZ979UB',
|
||||
'title': r're:^V\-POP',
|
||||
'description': 'md5:aa857f8a91dc9ce69e862a809e4bdc10',
|
||||
'protocol': 'm3u8_native',
|
||||
'ext': 'mp4',
|
||||
'view_count': int,
|
||||
'thumbnail': r're:^https?://.*\.jpg',
|
||||
'like_count': int,
|
||||
'live_status': 'is_live',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/liveradio/IWZ97CWB.html',
|
||||
'info_dict': {
|
||||
'id': 'IWZ97CWB',
|
||||
'title': r're:^Live\s247',
|
||||
'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
|
||||
'protocol': 'm3u8_native',
|
||||
'ext': 'm4a',
|
||||
'view_count': int,
|
||||
'thumbnail': r're:^https?://.*\.jpg',
|
||||
'like_count': int,
|
||||
'live_status': 'is_live',
|
||||
},
|
||||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
url_type, live_radio_id = self._match_valid_url(url).group('type', 'id')
|
||||
info = self._call_api(url_type, {'id': live_radio_id})
|
||||
manifest_url = info.get('streaming')
|
||||
if not manifest_url:
|
||||
raise ExtractorError('This radio is offline.', expected=True)
|
||||
fmts, subtitles = self._extract_m3u8_formats_and_subtitles(manifest_url, live_radio_id, fatal=False)
|
||||
return {
|
||||
'id': live_radio_id,
|
||||
'is_live': True,
|
||||
'formats': fmts,
|
||||
'subtitles': subtitles,
|
||||
**traverse_obj(info, {
|
||||
'title': 'title',
|
||||
'thumbnail': (('thumbnail', 'thumbnailM', 'thumbnailV', 'thumbnailH'), {url_or_none}),
|
||||
'view_count': ('activeUsers', {int_or_none}),
|
||||
'like_count': ('totalReaction', {int_or_none}),
|
||||
'description': 'description',
|
||||
}, get_all=False),
|
||||
}
|
||||
|
||||
|
||||
class ZingMp3PodcastEpisodeIE(ZingMp3BaseIE):
|
||||
IE_NAME = 'zingmp3:podcast-episode'
|
||||
_VALID_URL = ZingMp3BaseIE._VALID_URL_TMPL % 'pgr|cgr'
|
||||
_TESTS = [{
|
||||
'url': 'https://zingmp3.vn/pgr/Nhac-Moi-Moi-Ngay/68Z9W66B.html',
|
||||
'info_dict': {
|
||||
'id': '68Z9W66B',
|
||||
'title': 'Nhạc Mới Mỗi Ngày',
|
||||
'description': 'md5:2875dfa951f8e5356742f1610cf20691'
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/cgr/Am-nhac/IWZ980AO.html',
|
||||
'info_dict': {
|
||||
'id': 'IWZ980AO',
|
||||
'title': 'Âm nhạc'
|
||||
},
|
||||
'playlist_mincount': 2,
|
||||
}]
|
||||
|
||||
def _fetch_page(self, eps_id, url_type, page):
|
||||
return self._call_api(url_type, {
|
||||
'id': eps_id,
|
||||
'page': page,
|
||||
'count': self._PER_PAGE
|
||||
})
|
||||
|
||||
def _real_extract(self, url):
|
||||
podcast_id, url_type = self._match_valid_url(url).group('id', 'type')
|
||||
podcast_info = self._call_api(url_type, {'id': podcast_id})
|
||||
entries = self._paged_list(podcast_id, 'pgr-list' if url_type == 'pgr' else 'cgr-list')
|
||||
return self.playlist_result(
|
||||
entries, podcast_id, podcast_info.get('title'), podcast_info.get('description'))
|
||||
|
||||
|
||||
class ZingMp3PodcastIE(ZingMp3BaseIE):
|
||||
IE_NAME = 'zingmp3:podcast'
|
||||
_VALID_URL = r'https?://(?:mp3\.zing|zingmp3)\.vn/(?P<id>(?:cgr|top-podcast|podcast-new))/?(?:[#?]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://zingmp3.vn/cgr',
|
||||
'info_dict': {
|
||||
'id': 'cgr',
|
||||
},
|
||||
'playlist_mincount': 5,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/top-podcast',
|
||||
'info_dict': {
|
||||
'id': 'top-podcast',
|
||||
},
|
||||
'playlist_mincount': 7,
|
||||
}, {
|
||||
'url': 'https://zingmp3.vn/podcast-new',
|
||||
'info_dict': {
|
||||
'id': 'podcast-new',
|
||||
},
|
||||
'playlist_mincount': 4,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
url_type = self._match_id(url)
|
||||
params = {'id': url_type}
|
||||
if url_type == 'podcast-new':
|
||||
params['type'] = 'new'
|
||||
items = self._call_api('cgrs' if url_type == 'cgr' else url_type, params)['items']
|
||||
return self.playlist_result(self._parse_items(items), url_type)
|
||||
|
|
|
@ -131,7 +131,7 @@ def _get_binary_name():
|
|||
|
||||
|
||||
def _get_system_deprecation():
|
||||
MIN_SUPPORTED, MIN_RECOMMENDED = (3, 7), (3, 8)
|
||||
MIN_SUPPORTED, MIN_RECOMMENDED = (3, 8), (3, 8)
|
||||
|
||||
if sys.version_info > MIN_RECOMMENDED:
|
||||
return None
|
||||
|
@ -140,16 +140,8 @@ def _get_system_deprecation():
|
|||
if sys.version_info < MIN_SUPPORTED:
|
||||
msg = f'Python version {major}.{minor} is no longer supported'
|
||||
else:
|
||||
msg = f'Support for Python version {major}.{minor} has been deprecated. '
|
||||
# Temporary until `win_x86_exe` uses 3.8, which will deprecate Vista and Server 2008
|
||||
if detect_variant() == 'win_x86_exe':
|
||||
platform_name = platform.platform()
|
||||
if any(platform_name.startswith(f'Windows-{name}') for name in ('Vista', '2008Server')):
|
||||
msg = 'Support for Windows Vista/Server 2008 has been deprecated. '
|
||||
else:
|
||||
return None
|
||||
msg += ('See https://github.com/yt-dlp/yt-dlp/issues/7803 for details.'
|
||||
'\nYou may stop receiving updates on this version at any time')
|
||||
msg = (f'Support for Python version {major}.{minor} has been deprecated. '
|
||||
'\nYou may stop receiving updates on this version at any time')
|
||||
|
||||
major, minor = MIN_RECOMMENDED
|
||||
return f'{msg}! Please update to Python {major}.{minor} or above'
|
||||
|
|
Loading…
Reference in New Issue
Block a user