mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-01-11 17:31:31 +00:00
Compare commits
10 Commits
2025.08.20
...
2025.08.22
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1ba9f4ddb | ||
|
|
5c8bcfdbc6 | ||
|
|
895e762a83 | ||
|
|
39b7b8ddc7 | ||
|
|
526410b4af | ||
|
|
f29acc4a6e | ||
|
|
4dbe96459d | ||
|
|
a03c37b44e | ||
|
|
fcea3edb5c | ||
|
|
415b6d9ca8 |
25
.github/workflows/build.yml
vendored
25
.github/workflows/build.yml
vendored
@@ -343,16 +343,16 @@ jobs:
|
||||
include:
|
||||
- arch: 'x64'
|
||||
runner: windows-2025
|
||||
suffix: ''
|
||||
python_version: '3.10'
|
||||
suffix: ''
|
||||
- arch: 'x86'
|
||||
runner: windows-2025
|
||||
suffix: '_x86'
|
||||
python_version: '3.10'
|
||||
suffix: '_x86'
|
||||
- arch: 'arm64'
|
||||
runner: windows-11-arm
|
||||
suffix: '_arm64'
|
||||
python_version: '3.13' # arm64 only has Python >= 3.11 available
|
||||
suffix: '_arm64'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -385,6 +385,7 @@ jobs:
|
||||
run: |
|
||||
python devscripts/update-version.py -c "${{ inputs.channel }}" -r "${{ needs.process.outputs.origin }}" "${{ inputs.version }}"
|
||||
python devscripts/make_lazy_extractors.py
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
/yt-dlp-build-venv/Scripts/Activate.ps1
|
||||
@@ -393,32 +394,18 @@ jobs:
|
||||
Compress-Archive -Path ./dist/yt-dlp${{ matrix.suffix }}/* -DestinationPath ./dist/yt-dlp_win${{ matrix.suffix }}.zip
|
||||
|
||||
- name: Verify --update-to
|
||||
# if: vars.UPDATE_TO_VERIFICATION
|
||||
# Temporarily skip for arm64 until there is a release that it can --update-to
|
||||
if: |
|
||||
vars.UPDATE_TO_VERIFICATION && matrix.arch != 'arm64'
|
||||
if: vars.UPDATE_TO_VERIFICATION
|
||||
run: |
|
||||
foreach ($name in @("yt-dlp${{ matrix.suffix }}")) {
|
||||
Copy-Item "./dist/${name}.exe" "./dist/${name}_downgraded.exe"
|
||||
$version = & "./dist/${name}.exe" --version
|
||||
& "./dist/${name}_downgraded.exe" -v --update-to yt-dlp/yt-dlp@2023.03.04
|
||||
& "./dist/${name}_downgraded.exe" -v --update-to yt-dlp/yt-dlp@2025.08.20
|
||||
$downgraded_version = & "./dist/${name}_downgraded.exe" --version
|
||||
if ($version -eq $downgraded_version) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# TODO: remove when there is a windows_arm64 release that we can --update-to
|
||||
- name: Verify arm64 executable
|
||||
if: |
|
||||
vars.UPDATE_TO_VERIFICATION && matrix.arch == 'arm64'
|
||||
run: |
|
||||
foreach ($name in @("yt-dlp${{ matrix.suffix }}")) {
|
||||
& "./dist/${name}.exe" -v --print-traffic --impersonate chrome "https://tls.browserleaks.com/json" -o ./resp.json
|
||||
& cat ./resp.json
|
||||
& "./dist/${name}.exe" --version
|
||||
}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
17
Changelog.md
17
Changelog.md
@@ -4,6 +4,23 @@
|
||||
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
||||
-->
|
||||
|
||||
### 2025.08.22
|
||||
|
||||
#### Core changes
|
||||
- **cookies**: [Fix `--cookies-from-browser` with Firefox 142+](https://github.com/yt-dlp/yt-dlp/commit/f29acc4a6e73a9dc091686d40951288acae5a46d) ([#14114](https://github.com/yt-dlp/yt-dlp/issues/14114)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K) (With fixes in [526410b](https://github.com/yt-dlp/yt-dlp/commit/526410b4af9c1ca73aa3503cdaf4d32e42308fd6) by [bashonly](https://github.com/bashonly))
|
||||
|
||||
#### Extractor changes
|
||||
- **mediaklikk**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/4dbe96459d7e632d397826d0bb323f3f0ac8b057) ([#13975](https://github.com/yt-dlp/yt-dlp/issues/13975)) by [zhallgato](https://github.com/zhallgato)
|
||||
- **steam**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/fcea3edb5c5648638357f27431500c0aaf08b147) ([#14093](https://github.com/yt-dlp/yt-dlp/issues/14093)) by [doe1080](https://github.com/doe1080)
|
||||
- **youtube**
|
||||
- [Improve `tv` client context](https://github.com/yt-dlp/yt-dlp/commit/39b7b8ddc7a4d0669e0cf39105c3bb84cb2736cc) ([#14122](https://github.com/yt-dlp/yt-dlp/issues/14122)) by [bashonly](https://github.com/bashonly)
|
||||
- [Optimize playback wait times](https://github.com/yt-dlp/yt-dlp/commit/5c8bcfdbc638dfde13e93157637d8521413ed774) ([#14124](https://github.com/yt-dlp/yt-dlp/issues/14124)) by [bashonly](https://github.com/bashonly)
|
||||
- [Replace `ios` with `tv_simply` in default clients](https://github.com/yt-dlp/yt-dlp/commit/895e762a834bbd729ab822c7d17329fdf815aaf2) ([#14123](https://github.com/yt-dlp/yt-dlp/issues/14123)) by [bashonly](https://github.com/bashonly), [coletdjnz](https://github.com/coletdjnz)
|
||||
- [Update `tv` client config](https://github.com/yt-dlp/yt-dlp/commit/a03c37b44ec8f50fd472c409115096f92410346d) ([#14101](https://github.com/yt-dlp/yt-dlp/issues/14101)) by [seproDev](https://github.com/seproDev)
|
||||
|
||||
#### Misc. changes
|
||||
- **build**: [Post-release workflow cleanup](https://github.com/yt-dlp/yt-dlp/commit/415b6d9ca868032a45b30b9139a50c5c06be2feb) ([#14090](https://github.com/yt-dlp/yt-dlp/issues/14090)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
### 2025.08.20
|
||||
|
||||
#### Core changes
|
||||
|
||||
@@ -1802,7 +1802,7 @@ The following extractors use this feature:
|
||||
#### youtube
|
||||
* `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube/_base.py](https://github.com/yt-dlp/yt-dlp/blob/415b4c9f955b1a0391204bd24a7132590e7b3bdb/yt_dlp/extractor/youtube/_base.py#L402-L409) for the list of supported content language codes
|
||||
* `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
|
||||
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv`, `tv_simply` and `tv_embedded`. By default, `tv,ios,web` is used, or `tv,web` is used when authenticating with cookies. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
|
||||
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv`, `tv_simply` and `tv_embedded`. By default, `tv,tv_simply,web` is used, but `tv,web_safari,web` is used when authenticating with cookies and `tv,web_creator,web` is used with premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
|
||||
* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details
|
||||
* `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests
|
||||
* `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
|
||||
|
||||
@@ -1386,6 +1386,7 @@ The only reliable way to check if a site is supported is to try it.
|
||||
- **startrek**: STAR TREK
|
||||
- **startv**
|
||||
- **Steam**
|
||||
- **SteamCommunity**
|
||||
- **SteamCommunityBroadcast**
|
||||
- **Stitcher**
|
||||
- **StitcherShow**
|
||||
|
||||
@@ -125,6 +125,8 @@ def extract_cookies_from_browser(browser_name, profile=None, logger=YDLLogger(),
|
||||
|
||||
|
||||
def _extract_firefox_cookies(profile, container, logger):
|
||||
MAX_SUPPORTED_DB_SCHEMA_VERSION = 16
|
||||
|
||||
logger.info('Extracting cookies from firefox')
|
||||
if not sqlite3:
|
||||
logger.warning('Cannot extract cookies from firefox without sqlite3 support. '
|
||||
@@ -159,9 +161,11 @@ def _extract_firefox_cookies(profile, container, logger):
|
||||
raise ValueError(f'could not find firefox container "{container}" in containers.json')
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='yt_dlp') as tmpdir:
|
||||
cursor = None
|
||||
try:
|
||||
cursor = _open_database_copy(cookie_database_path, tmpdir)
|
||||
cursor = _open_database_copy(cookie_database_path, tmpdir)
|
||||
with contextlib.closing(cursor.connection):
|
||||
db_schema_version = cursor.execute('PRAGMA user_version;').fetchone()[0]
|
||||
if db_schema_version > MAX_SUPPORTED_DB_SCHEMA_VERSION:
|
||||
logger.warning(f'Possibly unsupported firefox cookies database version: {db_schema_version}')
|
||||
if isinstance(container_id, int):
|
||||
logger.debug(
|
||||
f'Only loading cookies from firefox container "{container}", ID {container_id}')
|
||||
@@ -180,6 +184,10 @@ def _extract_firefox_cookies(profile, container, logger):
|
||||
total_cookie_count = len(table)
|
||||
for i, (host, name, value, path, expiry, is_secure) in enumerate(table):
|
||||
progress_bar.print(f'Loading cookie {i: 6d}/{total_cookie_count: 6d}')
|
||||
# FF142 upgraded cookies DB to schema version 16 and started using milliseconds for cookie expiry
|
||||
# Ref: https://github.com/mozilla-firefox/firefox/commit/5869af852cd20425165837f6c2d9971f3efba83d
|
||||
if db_schema_version >= 16 and expiry is not None:
|
||||
expiry /= 1000
|
||||
cookie = http.cookiejar.Cookie(
|
||||
version=0, name=name, value=value, port=None, port_specified=False,
|
||||
domain=host, domain_specified=bool(host), domain_initial_dot=host.startswith('.'),
|
||||
@@ -188,9 +196,6 @@ def _extract_firefox_cookies(profile, container, logger):
|
||||
jar.set_cookie(cookie)
|
||||
logger.info(f'Extracted {len(jar)} cookies from firefox')
|
||||
return jar
|
||||
finally:
|
||||
if cursor is not None:
|
||||
cursor.connection.close()
|
||||
|
||||
|
||||
def _firefox_browser_dirs():
|
||||
|
||||
@@ -1959,6 +1959,7 @@ from .startrek import StarTrekIE
|
||||
from .startv import StarTVIE
|
||||
from .steam import (
|
||||
SteamCommunityBroadcastIE,
|
||||
SteamCommunityIE,
|
||||
SteamIE,
|
||||
)
|
||||
from .stitcher import (
|
||||
|
||||
@@ -11,121 +11,65 @@ from ..utils import (
|
||||
|
||||
class MediaKlikkIE(InfoExtractor):
|
||||
_VALID_URL = r'''(?x)https?://(?:www\.)?
|
||||
(?:mediaklikk|m4sport|hirado|petofilive)\.hu/.*?(?:videok?|cikk)/
|
||||
(?:mediaklikk|m4sport|hirado)\.hu/.*?(?:videok?|cikk)/
|
||||
(?:(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/(?P<day>[0-9]{1,2})/)?
|
||||
(?P<id>[^/#?_]+)'''
|
||||
|
||||
_TESTS = [{
|
||||
'url': 'https://mediaklikk.hu/filmajanlo/cikk/az-ajto/',
|
||||
# mediaklikk
|
||||
'url': 'https://mediaklikk.hu/ajanlo/video/2025/08/04/heviz-dzsungel-a-viz-alatt-ajanlo-08-10/',
|
||||
'info_dict': {
|
||||
'id': '668177',
|
||||
'title': 'Az ajtó',
|
||||
'display_id': 'az-ajto',
|
||||
'id': '8573769',
|
||||
'title': 'Hévíz - dzsungel a víz alatt – Ajánló (08.10.)',
|
||||
'display_id': 'heviz-dzsungel-a-viz-alatt-ajanlo-08-10',
|
||||
'ext': 'mp4',
|
||||
'thumbnail': 'https://cdn.cms.mtv.hu/wp-content/uploads/sites/4/2016/01/vlcsnap-2023-07-31-14h18m52s111.jpg',
|
||||
'upload_date': '20250804',
|
||||
'thumbnail': 'https://cdn.cms.mtv.hu/wp-content/uploads/sites/4/2025/08/vlcsnap-2025-08-04-13h48m24s336.jpg',
|
||||
},
|
||||
}, {
|
||||
# (old) mediaklikk. date in html.
|
||||
'url': 'https://mediaklikk.hu/video/hazajaro-delnyugat-bacska-a-duna-menten-palankatol-doroszloig/',
|
||||
# mediaklikk - date in html
|
||||
'url': 'https://mediaklikk.hu/video/hazajaro-bilo-hegyseg-verocei-barangolas-a-drava-menten/',
|
||||
'info_dict': {
|
||||
'id': '4754129',
|
||||
'title': 'Hazajáró, DÉLNYUGAT-BÁCSKA – A Duna mentén Palánkától Doroszlóig',
|
||||
'id': '8482167',
|
||||
'title': 'Hazajáró, Bilo-hegység - Verőcei barangolás a Dráva mentén',
|
||||
'display_id': 'hazajaro-bilo-hegyseg-verocei-barangolas-a-drava-menten',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20210901',
|
||||
'thumbnail': 'http://mediaklikk.hu/wp-content/uploads/sites/4/2014/02/hazajarouj_JO.jpg',
|
||||
'upload_date': '20250703',
|
||||
'thumbnail': 'https://cdn.cms.mtv.hu/wp-content/uploads/sites/4/2025/07/2024-000307-M0010-01_3700_cover_01.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to 404 page',
|
||||
}, {
|
||||
# mediaklikk. date in html.
|
||||
'url': 'https://mediaklikk.hu/video/hazajaro-fabova-hegyseg-kishont-koronaja/',
|
||||
'info_dict': {
|
||||
'id': '6696133',
|
||||
'title': 'Hazajáró, Fabova-hegység - Kishont koronája',
|
||||
'display_id': 'hazajaro-fabova-hegyseg-kishont-koronaja',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20230903',
|
||||
'thumbnail': 'https://mediaklikk.hu/wp-content/uploads/sites/4/2014/02/hazajarouj_JO.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to 404 page',
|
||||
}, {
|
||||
# (old) m4sport
|
||||
'url': 'https://m4sport.hu/video/2021/08/30/gyemant-liga-parizs/',
|
||||
'info_dict': {
|
||||
'id': '4754999',
|
||||
'title': 'Gyémánt Liga, Párizs',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20210830',
|
||||
'thumbnail': 'http://m4sport.hu/wp-content/uploads/sites/4/2021/08/vlcsnap-2021-08-30-18h21m20s10-1024x576.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to 404 page',
|
||||
}, {
|
||||
# m4sport
|
||||
'url': 'https://m4sport.hu/sportkozvetitesek/video/2023/09/08/atletika-gyemant-liga-brusszel/',
|
||||
'url': 'https://m4sport.hu/video/2025/08/07/holnap-kezdodik-a-12-vilagjatekok/',
|
||||
'info_dict': {
|
||||
'id': '6711136',
|
||||
'title': 'Atlétika – Gyémánt Liga, Brüsszel',
|
||||
'display_id': 'atletika-gyemant-liga-brusszel',
|
||||
'id': '8581887',
|
||||
'title': 'Holnap kezdődik a 12. Világjátékok',
|
||||
'display_id': 'holnap-kezdodik-a-12-vilagjatekok',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20230908',
|
||||
'thumbnail': 'https://m4sport.hu/wp-content/uploads/sites/4/2023/09/vlcsnap-2023-09-08-22h43m18s691.jpg',
|
||||
'upload_date': '20250807',
|
||||
'thumbnail': 'https://cdn.cms.mtv.hu/wp-content/uploads/sites/4/2025/08/vlcsnap-2025-08-06-20h30m48s817.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to 404 page',
|
||||
}, {
|
||||
# m4sport with *video/ url and no date
|
||||
'url': 'https://m4sport.hu/bl-video/real-madrid-chelsea-1-1/',
|
||||
'info_dict': {
|
||||
'id': '4492099',
|
||||
'title': 'Real Madrid - Chelsea 1-1',
|
||||
'display_id': 'real-madrid-chelsea-1-1',
|
||||
'ext': 'mp4',
|
||||
'thumbnail': 'https://m4sport.hu/wp-content/uploads/sites/4/2021/04/Sequence-01.Still001-1024x576.png',
|
||||
},
|
||||
'skip': 'Webpage redirects to 404 page',
|
||||
}, {
|
||||
# (old) hirado
|
||||
'url': 'https://hirado.hu/videok/felteteleket-szabott-a-fovaros/',
|
||||
'info_dict': {
|
||||
'id': '4760120',
|
||||
'title': 'Feltételeket szabott a főváros',
|
||||
'ext': 'mp4',
|
||||
'thumbnail': 'http://hirado.hu/wp-content/uploads/sites/4/2021/09/vlcsnap-2021-09-01-20h20m37s165.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to video list page',
|
||||
}, {
|
||||
# hirado
|
||||
'url': 'https://hirado.hu/belfold/video/2023/09/11/marad-az-eves-elszamolas-a-napelemekre-beruhazo-csaladoknal',
|
||||
'url': 'https://hirado.hu/video/2025/08/09/idojaras-jelentes-2025-augusztus-9-2230',
|
||||
'info_dict': {
|
||||
'id': '6716068',
|
||||
'title': 'Marad az éves elszámolás a napelemekre beruházó családoknál',
|
||||
'display_id': 'marad-az-eves-elszamolas-a-napelemekre-beruhazo-csaladoknal',
|
||||
'id': '8592033',
|
||||
'title': 'Időjárás-jelentés, 2025. augusztus 9. 22:30',
|
||||
'display_id': 'idojaras-jelentes-2025-augusztus-9-2230',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20230911',
|
||||
'thumbnail': 'https://hirado.hu/wp-content/uploads/sites/4/2023/09/vlcsnap-2023-09-11-09h16m09s882.jpg',
|
||||
'upload_date': '20250809',
|
||||
'thumbnail': 'https://cdn.cms.mtv.hu/wp-content/uploads/sites/4/2025/08/Idojaras-jelentes-35-1.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to video list page',
|
||||
}, {
|
||||
# (old) petofilive
|
||||
'url': 'https://petofilive.hu/video/2021/06/07/tha-shudras-az-akusztikban/',
|
||||
# hirado - subcategory
|
||||
'url': 'https://hirado.hu/belfold/video/2025/08/09/nyitott-porta-napok-2025/',
|
||||
'info_dict': {
|
||||
'id': '4571948',
|
||||
'title': 'Tha Shudras az Akusztikban',
|
||||
'id': '8590581',
|
||||
'title': 'Nyitott Porta Napok 2025',
|
||||
'display_id': 'nyitott-porta-napok-2025',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20210607',
|
||||
'thumbnail': 'http://petofilive.hu/wp-content/uploads/sites/4/2021/06/vlcsnap-2021-06-07-22h14m23s915-1024x576.jpg',
|
||||
'upload_date': '20250809',
|
||||
'thumbnail': 'https://cdn.cms.mtv.hu/wp-content/uploads/sites/4/2025/08/vlcsnap-2025-08-09-10h35m01s887.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to empty page',
|
||||
}, {
|
||||
# petofilive
|
||||
'url': 'https://petofilive.hu/video/2023/09/09/futball-fesztival-a-margitszigeten/',
|
||||
'info_dict': {
|
||||
'id': '6713233',
|
||||
'title': 'Futball Fesztivál a Margitszigeten',
|
||||
'display_id': 'futball-fesztival-a-margitszigeten',
|
||||
'ext': 'mp4',
|
||||
'upload_date': '20230909',
|
||||
'thumbnail': 'https://petofilive.hu/wp-content/uploads/sites/4/2023/09/Clipboard11-2.jpg',
|
||||
},
|
||||
'skip': 'Webpage redirects to video list page',
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -133,9 +77,8 @@ class MediaKlikkIE(InfoExtractor):
|
||||
display_id = mobj.group('id')
|
||||
webpage = self._download_webpage(url, display_id)
|
||||
|
||||
player_data_str = self._html_search_regex(
|
||||
r'mtva_player_manager\.player\(document.getElementById\(.*\),\s?(\{.*\}).*\);', webpage, 'player data')
|
||||
player_data = self._parse_json(player_data_str, display_id, urllib.parse.unquote)
|
||||
player_data = self._search_json(
|
||||
r'loadPlayer\((?:\s*["\'][^"\']+["\']\s*,)?', webpage, 'player data', mobj)
|
||||
video_id = str(player_data['contentId'])
|
||||
title = player_data.get('title') or self._og_search_title(webpage, fatal=False) or \
|
||||
self._html_search_regex(r'<h\d+\b[^>]+\bclass="article_title">([^<]+)<', webpage, 'title')
|
||||
@@ -146,7 +89,7 @@ class MediaKlikkIE(InfoExtractor):
|
||||
upload_date = unified_strdate(self._html_search_regex(
|
||||
r'<p+\b[^>]+\bclass="article_date">([^<]+)<', webpage, 'upload date', default=None))
|
||||
|
||||
player_data['video'] = player_data.pop('token')
|
||||
player_data['video'] = urllib.parse.unquote(player_data.pop('token'))
|
||||
player_page = self._download_webpage(
|
||||
'https://player.mediaklikk.hu/playernew/player.php', video_id,
|
||||
query=player_data, headers={'Referer': url})
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from .common import InfoExtractor
|
||||
from .youtube import YoutubeIE
|
||||
from ..utils import (
|
||||
ExtractorError,
|
||||
clean_html,
|
||||
extract_attributes,
|
||||
join_nonempty,
|
||||
js_to_json,
|
||||
str_or_none,
|
||||
url_or_none,
|
||||
)
|
||||
@@ -18,19 +20,9 @@ from ..utils.traversal import (
|
||||
|
||||
|
||||
class SteamIE(InfoExtractor):
|
||||
_VALID_URL = r'''(?x)
|
||||
https?://(?:store\.steampowered|steamcommunity)\.com/
|
||||
(?:agecheck/)?
|
||||
(?P<urltype>video|app)/ #If the page is only for videos or for a game
|
||||
(?P<gameID>\d+)/?
|
||||
(?P<videoID>\d*)(?P<extra>\??) # For urltype == video we sometimes get the videoID
|
||||
|
|
||||
https?://(?:www\.)?steamcommunity\.com/sharedfiles/filedetails/\?id=(?P<fileID>[0-9]+)
|
||||
'''
|
||||
_VIDEO_PAGE_TEMPLATE = 'https://store.steampowered.com/video/%s/'
|
||||
_AGECHECK_TEMPLATE = 'https://store.steampowered.com/agecheck/video/%s/?snr=1_agecheck_agecheck__age-gate&ageDay=1&ageMonth=January&ageYear=1970'
|
||||
_VALID_URL = r'https?://store\.steampowered\.com(?:/agecheck)?/app/(?P<id>\d+)/?(?:[^?/#]+/?)?(?:[?#]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://store.steampowered.com/video/105600/',
|
||||
'url': 'https://store.steampowered.com/app/105600',
|
||||
'info_dict': {
|
||||
'id': '105600',
|
||||
'title': 'Terraria',
|
||||
@@ -46,28 +38,15 @@ class SteamIE(InfoExtractor):
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
m = self._match_valid_url(url)
|
||||
file_id = m.group('fileID')
|
||||
if file_id:
|
||||
video_url = url
|
||||
playlist_id = file_id
|
||||
else:
|
||||
game_id = m.group('gameID')
|
||||
playlist_id = game_id
|
||||
video_url = self._VIDEO_PAGE_TEMPLATE % playlist_id
|
||||
app_id = self._match_id(url)
|
||||
|
||||
self._set_cookie('steampowered.com', 'wants_mature_content', '1')
|
||||
self._set_cookie('steampowered.com', 'birthtime', '944006401')
|
||||
self._set_cookie('steampowered.com', 'lastagecheckage', '1-0-2000')
|
||||
|
||||
webpage = self._download_webpage(video_url, playlist_id)
|
||||
|
||||
if re.search('<div[^>]+>Please enter your birth date to continue:</div>', webpage) is not None:
|
||||
video_url = self._AGECHECK_TEMPLATE % playlist_id
|
||||
self.report_age_confirmation()
|
||||
webpage = self._download_webpage(video_url, playlist_id)
|
||||
self._set_cookie('store.steampowered.com', 'wants_mature_content', '1')
|
||||
self._set_cookie('store.steampowered.com', 'birthtime', '946652401')
|
||||
self._set_cookie('store.steampowered.com', 'lastagecheckage', '1-January-2000')
|
||||
|
||||
webpage = self._download_webpage(url, app_id)
|
||||
app_name = traverse_obj(webpage, ({find_element(cls='apphub_AppName')}, {clean_html}))
|
||||
|
||||
entries = []
|
||||
for data_prop in traverse_obj(webpage, (
|
||||
{find_elements(cls='highlight_player_item highlight_movie', html=True)},
|
||||
@@ -76,50 +55,121 @@ class SteamIE(InfoExtractor):
|
||||
formats = []
|
||||
if hls_manifest := traverse_obj(data_prop, ('hlsManifest', {url_or_none})):
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
hls_manifest, playlist_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||
|
||||
hls_manifest, app_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||
for dash_manifest in traverse_obj(data_prop, ('dashManifests', ..., {url_or_none})):
|
||||
formats.extend(self._extract_mpd_formats(
|
||||
dash_manifest, playlist_id, mpd_id='dash', fatal=False))
|
||||
dash_manifest, app_id, mpd_id='dash', fatal=False))
|
||||
|
||||
movie_id = traverse_obj(data_prop, ('id', {trim_str(start='highlight_movie_')}))
|
||||
entries.append({
|
||||
'id': movie_id,
|
||||
'title': f'{app_name} video {movie_id}',
|
||||
'title': join_nonempty(app_name, 'video', movie_id, delim=' '),
|
||||
'formats': formats,
|
||||
'series': app_name,
|
||||
'series_id': app_id,
|
||||
'thumbnail': traverse_obj(data_prop, ('screenshot', {url_or_none})),
|
||||
})
|
||||
|
||||
embedded_videos = re.findall(r'(<iframe[^>]+>)', webpage)
|
||||
for evideos in embedded_videos:
|
||||
evideos = extract_attributes(evideos).get('src')
|
||||
video_id = self._search_regex(r'youtube\.com/embed/([0-9A-Za-z_-]{11})', evideos, 'youtube_video_id', default=None)
|
||||
if video_id:
|
||||
entries.append({
|
||||
'_type': 'url_transparent',
|
||||
'id': video_id,
|
||||
'url': video_id,
|
||||
'ie_key': 'Youtube',
|
||||
})
|
||||
if not entries:
|
||||
raise ExtractorError('Could not find any videos')
|
||||
return self.playlist_result(entries, app_id, app_name)
|
||||
|
||||
return self.playlist_result(entries, playlist_id, app_name)
|
||||
|
||||
class SteamCommunityIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?steamcommunity\.com/sharedfiles/filedetails(?:/?\?(?:[^#]+&)?id=|/)(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://steamcommunity.com/sharedfiles/filedetails/2717708756',
|
||||
'info_dict': {
|
||||
'id': '39Sp2mB1Ly8',
|
||||
'ext': 'mp4',
|
||||
'title': 'Gmod Stamina System + Customisable HUD',
|
||||
'age_limit': 0,
|
||||
'availability': 'public',
|
||||
'categories': ['Gaming'],
|
||||
'channel': 'Zworld Gmod',
|
||||
'channel_follower_count': int,
|
||||
'channel_id': 'UCER1FWFSdMMiTKBnnEDBPaw',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCER1FWFSdMMiTKBnnEDBPaw',
|
||||
'chapters': 'count:3',
|
||||
'comment_count': int,
|
||||
'description': 'md5:0ba8d8e550231211fa03fac920e5b0bf',
|
||||
'duration': 162,
|
||||
'like_count': int,
|
||||
'live_status': 'not_live',
|
||||
'media_type': 'video',
|
||||
'playable_in_embed': True,
|
||||
'tags': 'count:20',
|
||||
'thumbnail': r're:https?://i\.ytimg\.com/vi/.+',
|
||||
'timestamp': 1641955348,
|
||||
'upload_date': '20220112',
|
||||
'uploader': 'Zworld Gmod',
|
||||
'uploader_id': '@gmod-addons',
|
||||
'uploader_url': 'https://www.youtube.com/@gmod-addons',
|
||||
'view_count': int,
|
||||
},
|
||||
'add_ie': ['Youtube'],
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
'url': 'https://steamcommunity.com/sharedfiles/filedetails/?id=3544291945',
|
||||
'info_dict': {
|
||||
'id': '5JZZlsAdsvI',
|
||||
'ext': 'mp4',
|
||||
'title': 'Memories',
|
||||
'age_limit': 0,
|
||||
'availability': 'public',
|
||||
'categories': ['Gaming'],
|
||||
'channel': 'Bombass Team',
|
||||
'channel_follower_count': int,
|
||||
'channel_id': 'UCIJgtNyCV53IeSkzg3FWSFA',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCIJgtNyCV53IeSkzg3FWSFA',
|
||||
'comment_count': int,
|
||||
'description': 'md5:1b8a103a5d67a3c48d07c065de7e2c63',
|
||||
'duration': 83,
|
||||
'like_count': int,
|
||||
'live_status': 'not_live',
|
||||
'media_type': 'video',
|
||||
'playable_in_embed': True,
|
||||
'tags': 'count:10',
|
||||
'thumbnail': r're:https?://i\.ytimg\.com/vi/.+',
|
||||
'timestamp': 1754427291,
|
||||
'upload_date': '20250805',
|
||||
'uploader': 'Bombass Team',
|
||||
'uploader_id': '@BombassTeam',
|
||||
'uploader_url': 'https://www.youtube.com/@BombassTeam',
|
||||
'view_count': int,
|
||||
},
|
||||
'add_ie': ['Youtube'],
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
file_id = self._match_id(url)
|
||||
webpage = self._download_webpage(url, file_id)
|
||||
|
||||
flashvars = self._search_json(
|
||||
r'var\s+rgMovieFlashvars\s*=', webpage, 'flashvars',
|
||||
file_id, default={}, transform_source=js_to_json)
|
||||
youtube_id = (
|
||||
traverse_obj(flashvars, (..., 'YOUTUBE_VIDEO_ID', {str}, any))
|
||||
or traverse_obj(webpage, (
|
||||
{find_element(cls='movieFrame modal', html=True)}, {extract_attributes}, 'id', {str})))
|
||||
if not youtube_id:
|
||||
raise ExtractorError('No video found', expected=True)
|
||||
|
||||
return self.url_result(youtube_id, YoutubeIE)
|
||||
|
||||
|
||||
class SteamCommunityBroadcastIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://steamcommunity\.(?:com)/broadcast/watch/(?P<id>\d+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?steamcommunity\.com/broadcast/watch/(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://steamcommunity.com/broadcast/watch/76561199073851486',
|
||||
'info_dict': {
|
||||
'id': '76561199073851486',
|
||||
'title': r're:Steam Community :: pepperm!nt :: Broadcast 2022-06-26 \d{2}:\d{2}',
|
||||
'ext': 'mp4',
|
||||
'title': str,
|
||||
'uploader_id': '1113585758',
|
||||
'uploader': 'pepperm!nt',
|
||||
'live_status': 'is_live',
|
||||
},
|
||||
'skip': 'Stream has ended',
|
||||
'params': {'skip_download': 'Livestream'},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
||||
@@ -259,9 +259,8 @@ INNERTUBE_CLIENTS = {
|
||||
not_required_with_player_token=True,
|
||||
),
|
||||
# HLS Livestreams require POT 30 seconds in
|
||||
# TODO: Rolling out
|
||||
StreamingProtocol.HLS: GvsPoTokenPolicy(
|
||||
required=False,
|
||||
required=True,
|
||||
recommended=True,
|
||||
not_required_with_player_token=True,
|
||||
),
|
||||
@@ -306,7 +305,8 @@ INNERTUBE_CLIENTS = {
|
||||
'client': {
|
||||
'clientName': 'TVHTML5',
|
||||
'clientVersion': '7.20250312.16.00',
|
||||
'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/Version',
|
||||
# See: https://github.com/youtube/cobalt/blob/main/cobalt/browser/user_agent/user_agent_platform_info.cc#L506
|
||||
'userAgent': 'Mozilla/5.0 (ChromiumStylePlatform) Cobalt/25.lts.30.1034943-gold (unlike Gecko), Unknown_TV_Unknown_0/Unknown (Unknown, Unknown)',
|
||||
},
|
||||
},
|
||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 7,
|
||||
@@ -951,7 +951,16 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
headers=traverse_obj(self._get_default_ytcfg(client), {
|
||||
'User-Agent': ('INNERTUBE_CONTEXT', 'client', 'userAgent', {str}),
|
||||
}))
|
||||
return self.extract_ytcfg(video_id, webpage) or {}
|
||||
|
||||
ytcfg = self.extract_ytcfg(video_id, webpage) or {}
|
||||
|
||||
# Workaround for https://github.com/yt-dlp/yt-dlp/issues/12563
|
||||
if client == 'tv':
|
||||
config_info = traverse_obj(ytcfg, (
|
||||
'INNERTUBE_CONTEXT', 'client', 'configInfo', {dict})) or {}
|
||||
config_info.pop('appInstallData', None)
|
||||
|
||||
return ytcfg
|
||||
|
||||
@staticmethod
|
||||
def _build_api_continuation_query(continuation, ctp=None):
|
||||
|
||||
@@ -79,6 +79,7 @@ STREAMING_DATA_FETCH_GVS_PO_TOKEN = '__yt_dlp_fetch_gvs_po_token'
|
||||
STREAMING_DATA_PLAYER_TOKEN_PROVIDED = '__yt_dlp_player_token_provided'
|
||||
STREAMING_DATA_INNERTUBE_CONTEXT = '__yt_dlp_innertube_context'
|
||||
STREAMING_DATA_IS_PREMIUM_SUBSCRIBER = '__yt_dlp_is_premium_subscriber'
|
||||
STREAMING_DATA_FETCHED_TIMESTAMP = '__yt_dlp_fetched_timestamp'
|
||||
|
||||
PO_TOKEN_GUIDE_URL = 'https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide'
|
||||
|
||||
@@ -256,10 +257,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'401': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'av01.0.12M.08'},
|
||||
}
|
||||
_SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'srt', 'vtt')
|
||||
_DEFAULT_CLIENTS = ('tv', 'ios', 'web')
|
||||
_DEFAULT_AUTHED_CLIENTS = ('tv', 'web')
|
||||
_DEFAULT_CLIENTS = ('tv', 'tv_simply', 'web')
|
||||
_DEFAULT_AUTHED_CLIENTS = ('tv', 'web_safari', 'web')
|
||||
# Premium does not require POT (except for subtitles)
|
||||
_DEFAULT_PREMIUM_CLIENTS = ('tv', 'web')
|
||||
_DEFAULT_PREMIUM_CLIENTS = ('tv', 'web_creator', 'web')
|
||||
|
||||
_GEO_BYPASS = False
|
||||
|
||||
@@ -3244,6 +3245,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
elif pr:
|
||||
# Save client details for introspection later
|
||||
innertube_context = traverse_obj(player_ytcfg or self._get_default_ytcfg(client), 'INNERTUBE_CONTEXT')
|
||||
fetched_timestamp = int(time.time())
|
||||
sd = pr.setdefault('streamingData', {})
|
||||
sd[STREAMING_DATA_CLIENT_NAME] = client
|
||||
sd[STREAMING_DATA_FETCH_GVS_PO_TOKEN] = fetch_gvs_po_token_func
|
||||
@@ -3256,6 +3258,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
f[STREAMING_DATA_FETCH_GVS_PO_TOKEN] = fetch_gvs_po_token_func
|
||||
f[STREAMING_DATA_IS_PREMIUM_SUBSCRIBER] = is_premium_subscriber
|
||||
f[STREAMING_DATA_PLAYER_TOKEN_PROVIDED] = bool(player_po_token)
|
||||
f[STREAMING_DATA_FETCHED_TIMESTAMP] = fetched_timestamp
|
||||
if deprioritize_pr:
|
||||
deprioritized_prs.append(pr)
|
||||
else:
|
||||
@@ -3308,11 +3311,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
f'{video_id}: {client_name} client {proto} formats require a GVS PO Token which was not provided. '
|
||||
'They will be skipped as they may yield HTTP Error 403. '
|
||||
f'You can manually pass a GVS PO Token for this client with --extractor-args "youtube:po_token={client_name}.gvs+XXX". '
|
||||
f'For more information, refer to {PO_TOKEN_GUIDE_URL} . '
|
||||
'To enable these broken formats anyway, pass --extractor-args "youtube:formats=missing_pot"')
|
||||
f'For more information, refer to {PO_TOKEN_GUIDE_URL}')
|
||||
|
||||
# Only raise a warning for non-default clients, to not confuse users.
|
||||
# iOS HLS formats still work without PO Token, so we don't need to warn about them.
|
||||
if client_name in (*self._DEFAULT_CLIENTS, *self._DEFAULT_AUTHED_CLIENTS):
|
||||
self.write_debug(msg, only_once=True)
|
||||
else:
|
||||
@@ -3374,8 +3375,12 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
# save pots per client to avoid fetching again
|
||||
gvs_pots = {}
|
||||
|
||||
# For handling potential pre-playback required waiting period
|
||||
playback_wait = int_or_none(self._configuration_arg('playback_wait', [None])[0], default=6)
|
||||
|
||||
for fmt in streaming_formats:
|
||||
client_name = fmt[STREAMING_DATA_CLIENT_NAME]
|
||||
available_at = fmt[STREAMING_DATA_FETCHED_TIMESTAMP] + playback_wait
|
||||
if fmt.get('targetDurationSec'):
|
||||
continue
|
||||
|
||||
@@ -3557,6 +3562,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
if single_stream and dct.get('ext'):
|
||||
dct['container'] = dct['ext'] + '_dash'
|
||||
|
||||
# For handling potential pre-playback required waiting period
|
||||
if live_status not in ('is_live', 'post_live'):
|
||||
dct['available_at'] = available_at
|
||||
|
||||
if (all_formats or 'dashy' in format_types) and dct['filesize']:
|
||||
yield {
|
||||
**dct,
|
||||
@@ -3594,6 +3603,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
if not all_formats and key in itags[itag]:
|
||||
return False
|
||||
|
||||
# For handling potential pre-playback required waiting period
|
||||
if live_status not in ('is_live', 'post_live'):
|
||||
f['available_at'] = available_at
|
||||
|
||||
if f.get('source_preference') is None:
|
||||
f['source_preference'] = -1
|
||||
|
||||
@@ -3601,11 +3614,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
f['format_note'] = join_nonempty(f.get('format_note'), 'MISSING POT', delim=' ')
|
||||
f['source_preference'] -= 20
|
||||
|
||||
# XXX: Check if IOS HLS formats are affected by PO token enforcement; temporary
|
||||
# See https://github.com/yt-dlp/yt-dlp/issues/13511
|
||||
if proto == 'hls' and client_name == 'ios':
|
||||
f['__needs_testing'] = True
|
||||
|
||||
itags[itag].add(key)
|
||||
|
||||
if itag and all_formats:
|
||||
@@ -4044,12 +4052,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
if needs_live_processing:
|
||||
self._prepare_live_from_start_formats(
|
||||
formats, video_id, live_start_time, url, webpage_url, smuggled_data, live_status == 'is_live')
|
||||
elif live_status != 'is_live':
|
||||
# Handle pre-playback waiting period
|
||||
playback_wait = int_or_none(self._configuration_arg('playback_wait', [None])[0], default=6)
|
||||
available_at = int(time.time()) + playback_wait
|
||||
for fmt in formats:
|
||||
fmt['available_at'] = available_at
|
||||
|
||||
formats.extend(self._extract_storyboard(player_responses, duration))
|
||||
|
||||
@@ -4421,7 +4423,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
|
||||
if upload_date and live_status not in ('is_live', 'post_live', 'is_upcoming'):
|
||||
# Newly uploaded videos' HLS formats are potentially problematic and need to be checked
|
||||
# XXX: This is redundant for as long as we are already checking all IOS HLS formats
|
||||
upload_datetime = datetime_from_str(upload_date).replace(tzinfo=dt.timezone.utc)
|
||||
if upload_datetime >= datetime_from_str('today-2days'):
|
||||
for fmt in info['formats']:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Autogenerated by devscripts/update-version.py
|
||||
|
||||
__version__ = '2025.08.20'
|
||||
__version__ = '2025.08.22'
|
||||
|
||||
RELEASE_GIT_HEAD = 'c2fc4f3e7f6d757250183b177130c64beee50520'
|
||||
RELEASE_GIT_HEAD = '5c8bcfdbc638dfde13e93157637d8521413ed774'
|
||||
|
||||
VARIANT = None
|
||||
|
||||
@@ -12,4 +12,4 @@ CHANNEL = 'stable'
|
||||
|
||||
ORIGIN = 'yt-dlp/yt-dlp'
|
||||
|
||||
_pkg_version = '2025.08.20'
|
||||
_pkg_version = '2025.08.22'
|
||||
|
||||
Reference in New Issue
Block a user