mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-10-23 08:49:53 -07:00
+32









Aakash Gajjar
Remita Amine
Sergey M․
nmeum
Roxedus
Singwai Chan
cdarlint
Johannes N
jnozsc
Moritz Patelscheck
PB
Philipp Hagemeister
Xaver Hellauer
d2au
Jan 'Yenda' Trmal
jxu
Martin Ström
The Hatsune Daishi
tsia
3risian
Tristan Waddington
Devon Meunier
Felix Stupp
tom
AndrewMBL
willbeaufoy
Philipp Stehle
hh0rva1h
comsomisha
TotalCaesar659
Juan Francisco Cantero Hurtado
Dave Loyall
tlsssl
Rob
Michael Klein
JordanWeatherby
striker.sh
Matej Dujava
Glenn Slayden
MRWITEK
JChris246
TheRealDude2
b827ee921f
* [scrippsnetworks] Add new extractor(closes #19857)(closes #22981) * [teachable] Improve locked lessons detection (#23528) * [teachable] Fail with error message if no video URL found * [extractors] add missing import for ScrippsNetworksIE * [brightcove] cache brightcove player policy keys * [prosiebensat1] improve geo restriction handling(closes #23571) * [soundcloud] automatically update client id on failing requests * [spankbang] Fix extraction (closes #23307, closes #23423, closes #23444) * [spankbang] Improve removed video detection (#23423) * [brightcove] update policy key on failing requests * [pornhub] Fix extraction and add support for m3u8 formats (closes #22749, closes #23082) * [pornhub] Improve locked videos detection (closes #22449, closes #22780) * [brightcove] invalidate policy key cache on failing requests * [soundcloud] fix client id extraction for non fatal requests * [ChangeLog] Actualize [ci skip] * [devscripts/create-github-release] Switch to using PAT for authentication Basic authentication will be deprecated soon * release 2020.01.01 * [redtube] Detect private videos (#23518) * [vice] improve extraction(closes #23631) * [devscripts/create-github-release] Remove unused import * [wistia] improve format extraction and extract subtitles(closes #22590) * [nrktv:seriebase] Fix extraction (closes #23625) (#23537) * [discovery] fix anonymous token extraction(closes #23650) * [scrippsnetworks] add support for www.discovery.com videos * [scrippsnetworks] correct test case URL * [dctp] fix format extraction(closes #23656) * [pandatv] Remove extractor (#23630) * [naver] improve extraction - improve geo-restriction handling - extract automatic captions - extract uploader metadata - extract VLive HLS formats * [naver] improve metadata extraction * [cloudflarestream] improve extraction - add support for bytehighway.net domain - add support for signed URLs - extract thumbnail * [cloudflarestream] import embed URL extraction * [lego] fix extraction and extract subtitle(closes #23687) * [safari] Fix kaltura session extraction (closes #23679) (#23670) * [orf:fm4] Fix extraction (#23599) * [orf:radio] Clean description and improve extraction * [twitter] add support for promo_video_website cards(closes #23711) * [vodplatform] add support for embed.kwikmotion.com domain * [ndr:base:embed] Improve thumbnails extraction (closes #23731) * [canvas] Add support for new API endpoint and update tests (closes #17680, closes #18629) * [travis] Add flake8 job (#23720) * [yourporn] Fix extraction (closes #21645, closes #22255, closes #23459) * [ChangeLog] Actualize [ci skip] * release 2020.01.15 * [soundcloud] Restore previews extraction (closes #23739) * [orf:tvthek] Improve geo restricted videos detection (closes #23741) * [zype] improve extraction - extract subtitles(closes #21258) - support URLs with alternative keys/tokens(#21258) - extract more metadata * [americastestkitchen] fix extraction * [nbc] add support for nbc multi network URLs(closes #23049) * [ard] improve extraction(closes #23761) - simplify extraction - extract age limit and series - bypass geo-restriction * [ivi:compilation] Fix entries extraction (closes #23770) * [24video] Add support for 24video.vip (closes #23753) * [businessinsider] Fix jwplatform id extraction (closes #22929) (#22954) * [ard] add a missing condition * [azmedien] fix extraction(closes #23783) * [voicerepublic] fix extraction * [stretchinternet] fix extraction(closes #4319) * [youtube] Fix sigfunc name extraction (closes #23819) * [ChangeLog] Actualize [ci skip] * release 2020.01.24 * [soundcloud] imporve private playlist/set tracks extraction https://github.com/ytdl-org/youtube-dl/issues/3707#issuecomment-577873539 * [svt] fix article extraction(closes #22897)(closes #22919) * [svt] fix series extraction(closes #22297) * [viewlift] improve extraction - fix extraction(closes #23851) - add add support for authentication - add support for more domains * [vimeo] fix album extraction(closes #23864) * [tva] Relax _VALID_URL (closes #23903) * [tv5mondeplus] Fix extraction (closes #23907, closes #23911) * [twitch:stream] Lowercase channel id for stream request (closes #23917) * [sportdeutschland] Update to new sportdeutschland API They switched to SSL, but under a different host AND path... Remove the old test cases because these videos have become unavailable. * [popcorntimes] Add extractor (closes #23949) * [thisoldhouse] fix extraction(closes #23951) * [toggle] Add support for mewatch.sg (closes #23895) (#23930) * [compat] Introduce compat_realpath (refs #23991) * [update] Fix updating via symlinks (closes #23991) * [nytimes] improve format sorting(closes #24010) * [abc:iview] Support 720p (#22907) (#22921) * [nova:embed] Fix extraction (closes #23672) * [nova:embed] Improve (closes #23690) * [nova] Improve extraction (refs #23690) * [jpopsuki] Remove extractor (closes #23858) * [YoutubeDL] Fix playlist entry indexing with --playlist-items (closes #10591, closes #10622) * [test_YoutubeDL] Fix get_ids * [test_YoutubeDL] Add tests for #10591 (closes #23873) * [24video] Add support for porn.24video.net (closes #23779, closes #23784) * [npr] Add support for streams (closes #24042) * [ChangeLog] Actualize [ci skip] * release 2020.02.16 * [tv2dk:bornholm:play] Fix extraction (#24076) * [imdb] Fix extraction (closes #23443) * [wistia] Add support for multiple generic embeds (closes #8347, closes #11385) * [teachable] Add support for multiple videos per lecture (closes #24101) * [pornhd] Fix extraction (closes #24128) * [options] Remove duplicate short option -v for --version (#24162) * [extractor/common] Convert ISM manifest to unicode before processing on python 2 (#24152) * [YoutubeDL] Force redirect URL to unicode on python 2 * Remove no longer needed compat_str around geturl * [youjizz] Fix extraction (closes #24181) * [test_subtitles] Remove obsolete test * [zdf:channel] Fix tests * [zapiks] Fix test * [xtube] Fix metadata extraction (closes #21073, closes #22455) * [xtube:user] Fix test * [telecinco] Fix extraction (refs #24195) * [telecinco] Add support for article opening videos * [franceculture] Fix extraction (closes #24204) * [xhamster] Fix extraction (closes #24205) * [ChangeLog] Actualize [ci skip] * release 2020.03.01 * [vimeo] Fix subtitles URLs (#24209) * [servus] Add support for new URL schema (closes #23475, closes #23583, closes #24142) * [youtube:playlist] Fix tests (closes #23872) (#23885) * [peertube] Improve extraction * [peertube] Fix issues and improve extraction (closes #23657) * [pornhub] Improve title extraction (closes #24184) * [vimeo] fix showcase password protected video extraction(closes #24224) * [youtube] Fix age-gated videos support without login (closes #24248) * [youtube] Fix tests * [ChangeLog] Actualize [ci skip] * release 2020.03.06 * [nhk] update API version(closes #24270) * [youtube] Improve extraction in 429 error conditions (closes #24283) * [youtube] Improve age-gated videos extraction in 429 error conditions (refs #24283) * [youtube] Remove outdated code Additional get_video_info requests don't seem to provide any extra itags any longer * [README.md] Clarify 429 error * [pornhub] Add support for pornhubpremium.com (#24288) * [utils] Add support for cookies with spaces used instead of tabs * [ChangeLog] Actualize [ci skip] * release 2020.03.08 * Revert "[utils] Add support for cookies with spaces used instead of tabs" According to [1] TABs must be used as separators between fields. Files produces by some tools with spaces as separators are considered malformed. 1. https://curl.haxx.se/docs/http-cookies.html This reverts commitcff99c91d1. * [utils] Add reference to cookie file format * Revert "[vimeo] fix showcase password protected video extraction(closes #24224)" This reverts commit12ee431676. * [nhk] Relax _VALID_URL (#24329) * [nhk] Remove obsolete rtmp formats (closes #24329) * [nhk] Update m3u8 URL and use native hls (#24329) * [ndr] Fix extraction (closes #24326) * [xtube] Fix formats extraction (closes #24348) * [xtube] Fix typo * [hellporno] Fix extraction (closes #24399) * [cbc:watch] Add support for authentication * [cbc:watch] Fix authenticated device token caching (closes #19160) * [soundcloud] fix download url extraction(closes #24394) * [limelight] remove disabled API requests(closes #24255) * [bilibili] Add support for new URL schema with BV ids (closes #24439, closes #24442) * [bilibili] Add support for player.bilibili.com (closes #24402) * [teachable] Extract chapter metadata (closes #24421) * [generic] Look for teachable embeds before wistia * [teachable] Update upskillcourses domain New version does not use teachable platform any longer * [teachable] Update gns3 domain * [teachable] Update test * [ChangeLog] Actualize [ci skip] * [ChangeLog] Actualize [ci skip] * release 2020.03.24 * [spankwire] Fix extraction (closes #18924, closes #20648) * [spankwire] Add support for generic embeds (refs #24633) * [youporn] Add support form generic embeds * [mofosex] Add support for generic embeds (closes #24633) * [tele5] Fix extraction (closes #24553) * [extractor/common] Skip malformed ISM manifest XMLs while extracting ISM formats (#24667) * [tv4] Fix ISM formats extraction (closes #24667) * [twitch:clips] Extend _VALID_URL (closes #24290) (#24642) * [motherless] Fix extraction (closes #24699) * [nova:embed] Fix extraction (closes #24700) * [youtube] Skip broken multifeed videos (closes #24711) * [soundcloud] Extract AAC format * [soundcloud] Improve AAC format extraction (closes #19173, closes #24708) * [thisoldhouse] Fix video id extraction (closes #24548) Added support for: with of without "www." and either ".chorus.build" or ".com" It now validated correctly on older URL's ``` <iframe src="https://thisoldhouse.chorus.build/videos/zype/5e33baec27d2e50001d5f52f ``` and newer ones ``` <iframe src="https://www.thisoldhouse.com/videos/zype/5e2b70e95216cc0001615120 ``` * [thisoldhouse] Improve video id extraction (closes #24549) * [youtube] Fix DRM videos detection (refs #24736) * [options] Clarify doc on --exec command (closes #19087) (#24883) * [prosiebensat1] Improve extraction and remove 7tv.de support (#24948) * [prosiebensat1] Extract series metadata * [tenplay] Relax _VALID_URL (closes #25001) * [tvplay] fix Viafree extraction(closes #15189)(closes #24473)(closes #24789) * [yahoo] fix GYAO Player extraction and relax title URL regex(closes #24178)(closes #24778) * [youtube] Use redirected video id if any (closes #25063) * [youtube] Improve player id extraction and add tests * [extractor/common] Extract multiple JSON-LD entries * [crunchyroll] Fix and improve extraction (closes #25096, closes #25060) * [ChangeLog] Actualize [ci skip] * release 2020.05.03 * [puhutv] Remove no longer available HTTP formats (closes #25124) * [utils] Improve cookie files support + Add support for UTF-8 in cookie files * Skip malformed cookie file entries instead of crashing (invalid entry len, invalid expires at) * [dailymotion] Fix typo * [compat] Introduce compat_cookiejar_Cookie * [extractor/common] Use compat_cookiejar_Cookie for _set_cookie (closes #23256, closes #24776) To always ensure cookie name and value are bytestrings on python 2. * [orf] Add support for more radio stations (closes #24938) (#24968) * [uol] fix extraction(closes #22007) * [downloader/http] Finish downloading once received data length matches expected Always do this if possible, i.e. if Content-Length or expected length is known, not only in test. This will save unnecessary last extra loop trying to read 0 bytes. * [downloader/http] Request last data block of exact remaining size Always request last data block of exact size remaining to download if possible not the current block size. * [iprima] Improve extraction (closes #25138) * [youtube] Improve signature cipher extraction (closes #25188) * [ChangeLog] Actualize [ci skip] * release 2020.05.08 * [spike] fix Bellator mgid extraction(closes #25195) * [bbccouk] PEP8 * [mailru] Fix extraction (closes #24530) (#25239) * [README.md] flake8 HTTPS URL (#25230) * [youtube] Add support for yewtu.be (#25226) * [soundcloud] reduce API playlist page limit(closes #25274) * [vimeo] improve format extraction and sorting(closes #25285) * [redtube] Improve title extraction (#25208) * [indavideo] Switch to HTTPS for API request (#25191) * [utils] Fix file permissions in write_json_file (closes #12471) (#25122) * [redtube] Improve formats extraction and extract m3u8 formats (closes #25311, closes #25321) * [ard] Improve _VALID_URL (closes #25134) (#25198) * [giantbomb] Extend _VALID_URL (#25222) * [postprocessor/ffmpeg] Embed series metadata with --add-metadata * [youtube] Add support for more invidious instances (#25417) * [ard:beta] Extend _VALID_URL (closes #25405) * [ChangeLog] Actualize [ci skip] * release 2020.05.29 * [jwplatform] Improve embeds extraction (closes #25467) * [periscope] Fix untitled broadcasts (#25482) * [twitter:broadcast] Add untitled periscope broadcast test * [malltv] Add support for sk.mall.tv (#25445) * [brightcove] Fix subtitles extraction (closes #25540) * [brightcove] Sort imports * [twitch] Pass v5 accept header and fix thumbnails extraction (closes #25531) * [twitch:stream] Fix extraction (closes #25528) * [twitch:stream] Expect 400 and 410 HTTP errors from API * [tele5] Prefer jwplatform over nexx (closes #25533) * [jwplatform] Add support for bypass geo restriction * [tele5] Bypass geo restriction * [ChangeLog] Actualize [ci skip] * release 2020.06.06 * [kaltura] Add support for multiple embeds on a webpage (closes #25523) * [youtube] Extract chapters from JSON (closes #24819) * [facebook] Support single-video ID links I stumbled upon this at https://www.facebook.com/bwfbadminton/posts/10157127020046316 . No idea how prevalent it is yet. * [youtube] Fix playlist and feed extraction (closes #25675) * [youtube] Fix thumbnails extraction and remove uploader id extraction warning (closes #25676) * [youtube] Fix upload date extraction * [youtube] Improve view count extraction * [youtube] Fix uploader id and uploader URL extraction * [ChangeLog] Actualize [ci skip] * release 2020.06.16 * [youtube] Fix categories and improve tags extraction * [youtube] Force old layout (closes #25682, closes #25683, closes #25680, closes #25686) * [ChangeLog] Actualize [ci skip] * release 2020.06.16.1 * [brightcove] Improve embed detection (closes #25674) * [bellmedia] add support for cp24.com clip URLs(closes #25764) * [youtube:playlists] Extend _VALID_URL (closes #25810) * [youtube] Prevent excess HTTP 301 (#25786) * [wistia] Restrict embed regex (closes #25969) * [youtube] Improve description extraction (closes #25937) (#25980) * [youtube] Fix sigfunc name extraction (closes #26134, closes #26135, closes #26136, closes #26137) * [ChangeLog] Actualize [ci skip] * release 2020.07.28 * [xhamster] Extend _VALID_URL (closes #25789) (#25804) * [xhamster] Fix extraction (closes #26157) (#26254) * [xhamster] Extend _VALID_URL (closes #25927) Co-authored-by: Remita Amine <remitamine@gmail.com> Co-authored-by: Sergey M․ <dstftw@gmail.com> Co-authored-by: nmeum <soeren+github@soeren-tempel.net> Co-authored-by: Roxedus <me@roxedus.dev> Co-authored-by: Singwai Chan <c.singwai@gmail.com> Co-authored-by: cdarlint <cdarlint@users.noreply.github.com> Co-authored-by: Johannes N <31795504+jonolt@users.noreply.github.com> Co-authored-by: jnozsc <jnozsc@gmail.com> Co-authored-by: Moritz Patelscheck <moritz.patelscheck@campus.tu-berlin.de> Co-authored-by: PB <3854688+uno20001@users.noreply.github.com> Co-authored-by: Philipp Hagemeister <phihag@phihag.de> Co-authored-by: Xaver Hellauer <software@hellauer.bayern> Co-authored-by: d2au <d2au.dev@gmail.com> Co-authored-by: Jan 'Yenda' Trmal <jtrmal@gmail.com> Co-authored-by: jxu <7989982+jxu@users.noreply.github.com> Co-authored-by: Martin Ström <name@my-domain.se> Co-authored-by: The Hatsune Daishi <nao20010128@gmail.com> Co-authored-by: tsia <github@tsia.de> Co-authored-by: 3risian <59593325+3risian@users.noreply.github.com> Co-authored-by: Tristan Waddington <tristan.waddington@gmail.com> Co-authored-by: Devon Meunier <devon.meunier@gmail.com> Co-authored-by: Felix Stupp <felix.stupp@outlook.com> Co-authored-by: tom <tomster954@gmail.com> Co-authored-by: AndrewMBL <62922222+AndrewMBL@users.noreply.github.com> Co-authored-by: willbeaufoy <will@willbeaufoy.net> Co-authored-by: Philipp Stehle <anderschwiedu@googlemail.com> Co-authored-by: hh0rva1h <61889859+hh0rva1h@users.noreply.github.com> Co-authored-by: comsomisha <shmelev1996@mail.ru> Co-authored-by: TotalCaesar659 <14265316+TotalCaesar659@users.noreply.github.com> Co-authored-by: Juan Francisco Cantero Hurtado <iam@juanfra.info> Co-authored-by: Dave Loyall <dave@the-good-guys.net> Co-authored-by: tlsssl <63866177+tlsssl@users.noreply.github.com> Co-authored-by: Rob <ankenyr@gmail.com> Co-authored-by: Michael Klein <github@a98shuttle.de> Co-authored-by: JordanWeatherby <47519158+JordanWeatherby@users.noreply.github.com> Co-authored-by: striker.sh <19488257+strikersh@users.noreply.github.com> Co-authored-by: Matej Dujava <mdujava@gmail.com> Co-authored-by: Glenn Slayden <5589855+glenn-slayden@users.noreply.github.com> Co-authored-by: MRWITEK <mrvvitek@gmail.com> Co-authored-by: JChris246 <43832407+JChris246@users.noreply.github.com> Co-authored-by: TheRealDude2 <the.real.dude@gmx.de>
925 lines
36 KiB
Python
925 lines
36 KiB
Python
#!/usr/bin/env python
|
|
# coding: utf-8
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
# Allow direct execution
|
|
import os
|
|
import sys
|
|
import unittest
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import copy
|
|
|
|
from test.helper import FakeYDL, assertRegexpMatches
|
|
from youtube_dl import YoutubeDL
|
|
from youtube_dl.compat import compat_str, compat_urllib_error
|
|
from youtube_dl.extractor import YoutubeIE
|
|
from youtube_dl.extractor.common import InfoExtractor
|
|
from youtube_dl.postprocessor.common import PostProcessor
|
|
from youtube_dl.utils import ExtractorError, match_filter_func
|
|
|
|
TEST_URL = 'http://localhost/sample.mp4'
|
|
|
|
|
|
class YDL(FakeYDL):
|
|
def __init__(self, *args, **kwargs):
|
|
super(YDL, self).__init__(*args, **kwargs)
|
|
self.downloaded_info_dicts = []
|
|
self.msgs = []
|
|
|
|
def process_info(self, info_dict):
|
|
self.downloaded_info_dicts.append(info_dict)
|
|
|
|
def to_screen(self, msg):
|
|
self.msgs.append(msg)
|
|
|
|
|
|
def _make_result(formats, **kwargs):
|
|
res = {
|
|
'formats': formats,
|
|
'id': 'testid',
|
|
'title': 'testttitle',
|
|
'extractor': 'testex',
|
|
'extractor_key': 'TestEx',
|
|
}
|
|
res.update(**kwargs)
|
|
return res
|
|
|
|
|
|
class TestFormatSelection(unittest.TestCase):
|
|
def test_prefer_free_formats(self):
|
|
# Same resolution => download webm
|
|
ydl = YDL()
|
|
ydl.params['prefer_free_formats'] = True
|
|
formats = [
|
|
{'ext': 'webm', 'height': 460, 'url': TEST_URL},
|
|
{'ext': 'mp4', 'height': 460, 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['ext'], 'webm')
|
|
|
|
# Different resolution => download best quality (mp4)
|
|
ydl = YDL()
|
|
ydl.params['prefer_free_formats'] = True
|
|
formats = [
|
|
{'ext': 'webm', 'height': 720, 'url': TEST_URL},
|
|
{'ext': 'mp4', 'height': 1080, 'url': TEST_URL},
|
|
]
|
|
info_dict['formats'] = formats
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['ext'], 'mp4')
|
|
|
|
# No prefer_free_formats => prefer mp4 and flv for greater compatibility
|
|
ydl = YDL()
|
|
ydl.params['prefer_free_formats'] = False
|
|
formats = [
|
|
{'ext': 'webm', 'height': 720, 'url': TEST_URL},
|
|
{'ext': 'mp4', 'height': 720, 'url': TEST_URL},
|
|
{'ext': 'flv', 'height': 720, 'url': TEST_URL},
|
|
]
|
|
info_dict['formats'] = formats
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['ext'], 'mp4')
|
|
|
|
ydl = YDL()
|
|
ydl.params['prefer_free_formats'] = False
|
|
formats = [
|
|
{'ext': 'flv', 'height': 720, 'url': TEST_URL},
|
|
{'ext': 'webm', 'height': 720, 'url': TEST_URL},
|
|
]
|
|
info_dict['formats'] = formats
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['ext'], 'flv')
|
|
|
|
def test_format_selection(self):
|
|
formats = [
|
|
{'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
|
|
{'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL},
|
|
{'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL},
|
|
{'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL},
|
|
{'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': '20/47'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '47')
|
|
|
|
ydl = YDL({'format': '20/71/worst'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '35')
|
|
|
|
ydl = YDL()
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '2')
|
|
|
|
ydl = YDL({'format': 'webm/mp4'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '47')
|
|
|
|
ydl = YDL({'format': '3gp/40/mp4'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '35')
|
|
|
|
ydl = YDL({'format': 'example-with-dashes'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'example-with-dashes')
|
|
|
|
def test_format_selection_audio(self):
|
|
formats = [
|
|
{'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'bestaudio'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'audio-high')
|
|
|
|
ydl = YDL({'format': 'worstaudio'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'audio-low')
|
|
|
|
formats = [
|
|
{'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
|
|
{'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'bestaudio/worstaudio/best'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'vid-high')
|
|
|
|
def test_format_selection_audio_exts(self):
|
|
formats = [
|
|
{'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
|
|
{'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
|
|
{'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
|
|
{'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
|
|
{'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
|
|
]
|
|
|
|
info_dict = _make_result(formats)
|
|
ydl = YDL({'format': 'best'})
|
|
ie = YoutubeIE(ydl)
|
|
ie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(copy.deepcopy(info_dict))
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'aac-64')
|
|
|
|
ydl = YDL({'format': 'mp3'})
|
|
ie = YoutubeIE(ydl)
|
|
ie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(copy.deepcopy(info_dict))
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'mp3-64')
|
|
|
|
ydl = YDL({'prefer_free_formats': True})
|
|
ie = YoutubeIE(ydl)
|
|
ie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(copy.deepcopy(info_dict))
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'ogg-64')
|
|
|
|
def test_format_selection_video(self):
|
|
formats = [
|
|
{'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'bestvideo'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'dash-video-high')
|
|
|
|
ydl = YDL({'format': 'worstvideo'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'dash-video-low')
|
|
|
|
ydl = YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'dash-video-low')
|
|
|
|
formats = [
|
|
{'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'bestvideo[vcodec=avc1.123456]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'vid-vcodec-dot')
|
|
|
|
def test_format_selection_string_ops(self):
|
|
formats = [
|
|
{'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL},
|
|
{'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
# equals (=)
|
|
ydl = YDL({'format': '[format_id=abc-cba]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'abc-cba')
|
|
|
|
# does not equal (!=)
|
|
ydl = YDL({'format': '[format_id!=abc-cba]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'zxc-cxz')
|
|
|
|
ydl = YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'})
|
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
|
|
|
# starts with (^=)
|
|
ydl = YDL({'format': '[format_id^=abc]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'abc-cba')
|
|
|
|
# does not start with (!^=)
|
|
ydl = YDL({'format': '[format_id!^=abc]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'zxc-cxz')
|
|
|
|
ydl = YDL({'format': '[format_id!^=abc][format_id!^=zxc]'})
|
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
|
|
|
# ends with ($=)
|
|
ydl = YDL({'format': '[format_id$=cba]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'abc-cba')
|
|
|
|
# does not end with (!$=)
|
|
ydl = YDL({'format': '[format_id!$=cba]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'zxc-cxz')
|
|
|
|
ydl = YDL({'format': '[format_id!$=cba][format_id!$=cxz]'})
|
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
|
|
|
# contains (*=)
|
|
ydl = YDL({'format': '[format_id*=bc-cb]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'abc-cba')
|
|
|
|
# does not contain (!*=)
|
|
ydl = YDL({'format': '[format_id!*=bc-cb]'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'zxc-cxz')
|
|
|
|
ydl = YDL({'format': '[format_id!*=abc][format_id!*=zxc]'})
|
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
|
|
|
ydl = YDL({'format': '[format_id!*=-]'})
|
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
|
|
|
def test_youtube_format_selection(self):
|
|
order = [
|
|
'38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
|
|
# Apple HTTP Live Streaming
|
|
'96', '95', '94', '93', '92', '132', '151',
|
|
# 3D
|
|
'85', '84', '102', '83', '101', '82', '100',
|
|
# Dash video
|
|
'137', '248', '136', '247', '135', '246',
|
|
'245', '244', '134', '243', '133', '242', '160',
|
|
# Dash audio
|
|
'141', '172', '140', '171', '139',
|
|
]
|
|
|
|
def format_info(f_id):
|
|
info = YoutubeIE._formats[f_id].copy()
|
|
|
|
# XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
|
|
# and 'vcodec', while in tests such information is incomplete since
|
|
# commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
|
|
# test_YoutubeDL.test_youtube_format_selection is broken without
|
|
# this fix
|
|
if 'acodec' in info and 'vcodec' not in info:
|
|
info['vcodec'] = 'none'
|
|
elif 'vcodec' in info and 'acodec' not in info:
|
|
info['acodec'] = 'none'
|
|
|
|
info['format_id'] = f_id
|
|
info['url'] = 'url:' + f_id
|
|
return info
|
|
formats_order = [format_info(f_id) for f_id in order]
|
|
|
|
info_dict = _make_result(list(formats_order), extractor='youtube')
|
|
ydl = YDL({'format': 'bestvideo+bestaudio'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '137+141')
|
|
self.assertEqual(downloaded['ext'], 'mp4')
|
|
|
|
info_dict = _make_result(list(formats_order), extractor='youtube')
|
|
ydl = YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], '38')
|
|
|
|
info_dict = _make_result(list(formats_order), extractor='youtube')
|
|
ydl = YDL({'format': 'bestvideo/best,bestaudio'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
|
|
self.assertEqual(downloaded_ids, ['137', '141'])
|
|
|
|
info_dict = _make_result(list(formats_order), extractor='youtube')
|
|
ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
|
|
self.assertEqual(downloaded_ids, ['137+141', '248+141'])
|
|
|
|
info_dict = _make_result(list(formats_order), extractor='youtube')
|
|
ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
|
|
self.assertEqual(downloaded_ids, ['136+141', '247+141'])
|
|
|
|
info_dict = _make_result(list(formats_order), extractor='youtube')
|
|
ydl = YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
|
|
self.assertEqual(downloaded_ids, ['248+141'])
|
|
|
|
for f1, f2 in zip(formats_order, formats_order[1:]):
|
|
info_dict = _make_result([f1, f2], extractor='youtube')
|
|
ydl = YDL({'format': 'best/bestvideo'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], f1['format_id'])
|
|
|
|
info_dict = _make_result([f2, f1], extractor='youtube')
|
|
ydl = YDL({'format': 'best/bestvideo'})
|
|
yie = YoutubeIE(ydl)
|
|
yie._sort_formats(info_dict['formats'])
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], f1['format_id'])
|
|
|
|
def test_audio_only_extractor_format_selection(self):
|
|
# For extractors with incomplete formats (all formats are audio-only or
|
|
# video-only) best and worst should fallback to corresponding best/worst
|
|
# video-only or audio-only formats (as per
|
|
# https://github.com/ytdl-org/youtube-dl/pull/5556)
|
|
formats = [
|
|
{'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'best'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'high')
|
|
|
|
ydl = YDL({'format': 'worst'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'low')
|
|
|
|
def test_format_not_available(self):
|
|
formats = [
|
|
{'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL},
|
|
{'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
# This must fail since complete video-audio format does not match filter
|
|
# and extractor does not provide incomplete only formats (i.e. only
|
|
# video-only or audio-only).
|
|
ydl = YDL({'format': 'best[height>360]'})
|
|
self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
|
|
|
|
def test_format_selection_issue_10083(self):
|
|
# See https://github.com/ytdl-org/youtube-dl/issues/10083
|
|
formats = [
|
|
{'format_id': 'regular', 'height': 360, 'url': TEST_URL},
|
|
{'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
|
|
{'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL},
|
|
]
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'})
|
|
ydl.process_ie_result(info_dict.copy())
|
|
self.assertEqual(ydl.downloaded_info_dicts[0]['format_id'], 'video+audio')
|
|
|
|
def test_invalid_format_specs(self):
|
|
def assert_syntax_error(format_spec):
|
|
ydl = YDL({'format': format_spec})
|
|
info_dict = _make_result([{'format_id': 'foo', 'url': TEST_URL}])
|
|
self.assertRaises(SyntaxError, ydl.process_ie_result, info_dict)
|
|
|
|
assert_syntax_error('bestvideo,,best')
|
|
assert_syntax_error('+bestaudio')
|
|
assert_syntax_error('bestvideo+')
|
|
assert_syntax_error('/')
|
|
|
|
def test_format_filtering(self):
|
|
formats = [
|
|
{'format_id': 'A', 'filesize': 500, 'width': 1000},
|
|
{'format_id': 'B', 'filesize': 1000, 'width': 500},
|
|
{'format_id': 'C', 'filesize': 1000, 'width': 400},
|
|
{'format_id': 'D', 'filesize': 2000, 'width': 600},
|
|
{'format_id': 'E', 'filesize': 3000},
|
|
{'format_id': 'F'},
|
|
{'format_id': 'G', 'filesize': 1000000},
|
|
]
|
|
for f in formats:
|
|
f['url'] = 'http://_/'
|
|
f['ext'] = 'unknown'
|
|
info_dict = _make_result(formats)
|
|
|
|
ydl = YDL({'format': 'best[filesize<3000]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'D')
|
|
|
|
ydl = YDL({'format': 'best[filesize<=3000]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'E')
|
|
|
|
ydl = YDL({'format': 'best[filesize <= ? 3000]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'F')
|
|
|
|
ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'B')
|
|
|
|
ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'C')
|
|
|
|
ydl = YDL({'format': '[filesize>?1]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'G')
|
|
|
|
ydl = YDL({'format': '[filesize<1M]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'E')
|
|
|
|
ydl = YDL({'format': '[filesize<1MiB]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['format_id'], 'G')
|
|
|
|
ydl = YDL({'format': 'all[width>=400][width<=600]'})
|
|
ydl.process_ie_result(info_dict)
|
|
downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
|
|
self.assertEqual(downloaded_ids, ['B', 'C', 'D'])
|
|
|
|
ydl = YDL({'format': 'best[height<40]'})
|
|
try:
|
|
ydl.process_ie_result(info_dict)
|
|
except ExtractorError:
|
|
pass
|
|
self.assertEqual(ydl.downloaded_info_dicts, [])
|
|
|
|
def test_default_format_spec(self):
|
|
ydl = YDL({'simulate': True})
|
|
self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
|
|
|
|
ydl = YDL({})
|
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
|
|
|
|
ydl = YDL({'simulate': True})
|
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'bestvideo+bestaudio/best')
|
|
|
|
ydl = YDL({'outtmpl': '-'})
|
|
self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
|
|
|
|
ydl = YDL({})
|
|
self.assertEqual(ydl._default_format_spec({}, download=False), 'bestvideo+bestaudio/best')
|
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
|
|
|
|
|
|
class TestYoutubeDL(unittest.TestCase):
|
|
def test_subtitles(self):
|
|
def s_formats(lang, autocaption=False):
|
|
return [{
|
|
'ext': ext,
|
|
'url': 'http://localhost/video.%s.%s' % (lang, ext),
|
|
'_auto': autocaption,
|
|
} for ext in ['vtt', 'srt', 'ass']]
|
|
subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
|
|
auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
|
|
info_dict = {
|
|
'id': 'test',
|
|
'title': 'Test',
|
|
'url': 'http://localhost/video.mp4',
|
|
'subtitles': subtitles,
|
|
'automatic_captions': auto_captions,
|
|
'extractor': 'TEST',
|
|
}
|
|
|
|
def get_info(params={}):
|
|
params.setdefault('simulate', True)
|
|
ydl = YDL(params)
|
|
ydl.report_warning = lambda *args, **kargs: None
|
|
return ydl.process_video_result(info_dict, download=False)
|
|
|
|
result = get_info()
|
|
self.assertFalse(result.get('requested_subtitles'))
|
|
self.assertEqual(result['subtitles'], subtitles)
|
|
self.assertEqual(result['automatic_captions'], auto_captions)
|
|
|
|
result = get_info({'writesubtitles': True})
|
|
subs = result['requested_subtitles']
|
|
self.assertTrue(subs)
|
|
self.assertEqual(set(subs.keys()), set(['en']))
|
|
self.assertTrue(subs['en'].get('data') is None)
|
|
self.assertEqual(subs['en']['ext'], 'ass')
|
|
|
|
result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
|
|
subs = result['requested_subtitles']
|
|
self.assertEqual(subs['en']['ext'], 'srt')
|
|
|
|
result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
|
|
subs = result['requested_subtitles']
|
|
self.assertTrue(subs)
|
|
self.assertEqual(set(subs.keys()), set(['es', 'fr']))
|
|
|
|
result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
|
|
subs = result['requested_subtitles']
|
|
self.assertTrue(subs)
|
|
self.assertEqual(set(subs.keys()), set(['es', 'pt']))
|
|
self.assertFalse(subs['es']['_auto'])
|
|
self.assertTrue(subs['pt']['_auto'])
|
|
|
|
result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
|
|
subs = result['requested_subtitles']
|
|
self.assertTrue(subs)
|
|
self.assertEqual(set(subs.keys()), set(['es', 'pt']))
|
|
self.assertTrue(subs['es']['_auto'])
|
|
self.assertTrue(subs['pt']['_auto'])
|
|
|
|
def test_add_extra_info(self):
|
|
test_dict = {
|
|
'extractor': 'Foo',
|
|
}
|
|
extra_info = {
|
|
'extractor': 'Bar',
|
|
'playlist': 'funny videos',
|
|
}
|
|
YDL.add_extra_info(test_dict, extra_info)
|
|
self.assertEqual(test_dict['extractor'], 'Foo')
|
|
self.assertEqual(test_dict['playlist'], 'funny videos')
|
|
|
|
def test_prepare_filename(self):
|
|
info = {
|
|
'id': '1234',
|
|
'ext': 'mp4',
|
|
'width': None,
|
|
'height': 1080,
|
|
'title1': '$PATH',
|
|
'title2': '%PATH%',
|
|
}
|
|
|
|
def fname(templ):
|
|
ydl = YoutubeDL({'outtmpl': templ})
|
|
return ydl.prepare_filename(info)
|
|
self.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
|
|
self.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
|
|
# Replace missing fields with 'NA'
|
|
self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
|
|
self.assertEqual(fname('%(height)d.%(ext)s'), '1080.mp4')
|
|
self.assertEqual(fname('%(height)6d.%(ext)s'), ' 1080.mp4')
|
|
self.assertEqual(fname('%(height)-6d.%(ext)s'), '1080 .mp4')
|
|
self.assertEqual(fname('%(height)06d.%(ext)s'), '001080.mp4')
|
|
self.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
|
|
self.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
|
|
self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
|
|
self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
|
|
self.assertEqual(fname('%(height) 0 6d.%(ext)s'), ' 01080.mp4')
|
|
self.assertEqual(fname('%%'), '%')
|
|
self.assertEqual(fname('%%%%'), '%%')
|
|
self.assertEqual(fname('%%(height)06d.%(ext)s'), '%(height)06d.mp4')
|
|
self.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
|
|
self.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
|
|
self.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
|
|
self.assertEqual(fname('Hello %(title1)s'), 'Hello $PATH')
|
|
self.assertEqual(fname('Hello %(title2)s'), 'Hello %PATH%')
|
|
|
|
def test_format_note(self):
|
|
ydl = YoutubeDL()
|
|
self.assertEqual(ydl._format_note({}), '')
|
|
assertRegexpMatches(self, ydl._format_note({
|
|
'vbr': 10,
|
|
}), r'^\s*10k$')
|
|
assertRegexpMatches(self, ydl._format_note({
|
|
'fps': 30,
|
|
}), r'^30fps$')
|
|
|
|
def test_postprocessors(self):
|
|
filename = 'post-processor-testfile.mp4'
|
|
audiofile = filename + '.mp3'
|
|
|
|
class SimplePP(PostProcessor):
|
|
def run(self, info):
|
|
with open(audiofile, 'wt') as f:
|
|
f.write('EXAMPLE')
|
|
return [info['filepath']], info
|
|
|
|
def run_pp(params, PP):
|
|
with open(filename, 'wt') as f:
|
|
f.write('EXAMPLE')
|
|
ydl = YoutubeDL(params)
|
|
ydl.add_post_processor(PP())
|
|
ydl.post_process(filename, {'filepath': filename})
|
|
|
|
run_pp({'keepvideo': True}, SimplePP)
|
|
self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
|
|
self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
|
|
os.unlink(filename)
|
|
os.unlink(audiofile)
|
|
|
|
run_pp({'keepvideo': False}, SimplePP)
|
|
self.assertFalse(os.path.exists(filename), '%s exists' % filename)
|
|
self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
|
|
os.unlink(audiofile)
|
|
|
|
class ModifierPP(PostProcessor):
|
|
def run(self, info):
|
|
with open(info['filepath'], 'wt') as f:
|
|
f.write('MODIFIED')
|
|
return [], info
|
|
|
|
run_pp({'keepvideo': False}, ModifierPP)
|
|
self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
|
|
os.unlink(filename)
|
|
|
|
def test_match_filter(self):
|
|
class FilterYDL(YDL):
|
|
def __init__(self, *args, **kwargs):
|
|
super(FilterYDL, self).__init__(*args, **kwargs)
|
|
self.params['simulate'] = True
|
|
|
|
def process_info(self, info_dict):
|
|
super(YDL, self).process_info(info_dict)
|
|
|
|
def _match_entry(self, info_dict, incomplete):
|
|
res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
|
|
if res is None:
|
|
self.downloaded_info_dicts.append(info_dict)
|
|
return res
|
|
|
|
first = {
|
|
'id': '1',
|
|
'url': TEST_URL,
|
|
'title': 'one',
|
|
'extractor': 'TEST',
|
|
'duration': 30,
|
|
'filesize': 10 * 1024,
|
|
'playlist_id': '42',
|
|
'uploader': "變態妍字幕版 太妍 тест",
|
|
'creator': "тест ' 123 ' тест--",
|
|
}
|
|
second = {
|
|
'id': '2',
|
|
'url': TEST_URL,
|
|
'title': 'two',
|
|
'extractor': 'TEST',
|
|
'duration': 10,
|
|
'description': 'foo',
|
|
'filesize': 5 * 1024,
|
|
'playlist_id': '43',
|
|
'uploader': "тест 123",
|
|
}
|
|
videos = [first, second]
|
|
|
|
def get_videos(filter_=None):
|
|
ydl = FilterYDL({'match_filter': filter_})
|
|
for v in videos:
|
|
ydl.process_ie_result(v, download=True)
|
|
return [v['id'] for v in ydl.downloaded_info_dicts]
|
|
|
|
res = get_videos()
|
|
self.assertEqual(res, ['1', '2'])
|
|
|
|
def f(v):
|
|
if v['id'] == '1':
|
|
return None
|
|
else:
|
|
return 'Video id is not 1'
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1'])
|
|
|
|
f = match_filter_func('duration < 30')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['2'])
|
|
|
|
f = match_filter_func('description = foo')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['2'])
|
|
|
|
f = match_filter_func('description =? foo')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1', '2'])
|
|
|
|
f = match_filter_func('filesize > 5KiB')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1'])
|
|
|
|
f = match_filter_func('playlist_id = 42')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1'])
|
|
|
|
f = match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1'])
|
|
|
|
f = match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['2'])
|
|
|
|
f = match_filter_func('creator = "тест \' 123 \' тест--"')
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1'])
|
|
|
|
f = match_filter_func("creator = 'тест \\' 123 \\' тест--'")
|
|
res = get_videos(f)
|
|
self.assertEqual(res, ['1'])
|
|
|
|
f = match_filter_func(r"creator = 'тест \' 123 \' тест--' & duration > 30")
|
|
res = get_videos(f)
|
|
self.assertEqual(res, [])
|
|
|
|
def test_playlist_items_selection(self):
|
|
entries = [{
|
|
'id': compat_str(i),
|
|
'title': compat_str(i),
|
|
'url': TEST_URL,
|
|
} for i in range(1, 5)]
|
|
playlist = {
|
|
'_type': 'playlist',
|
|
'id': 'test',
|
|
'entries': entries,
|
|
'extractor': 'test:playlist',
|
|
'extractor_key': 'test:playlist',
|
|
'webpage_url': 'http://example.com',
|
|
}
|
|
|
|
def get_downloaded_info_dicts(params):
|
|
ydl = YDL(params)
|
|
# make a deep copy because the dictionary and nested entries
|
|
# can be modified
|
|
ydl.process_ie_result(copy.deepcopy(playlist))
|
|
return ydl.downloaded_info_dicts
|
|
|
|
def get_ids(params):
|
|
return [int(v['id']) for v in get_downloaded_info_dicts(params)]
|
|
|
|
result = get_ids({})
|
|
self.assertEqual(result, [1, 2, 3, 4])
|
|
|
|
result = get_ids({'playlistend': 10})
|
|
self.assertEqual(result, [1, 2, 3, 4])
|
|
|
|
result = get_ids({'playlistend': 2})
|
|
self.assertEqual(result, [1, 2])
|
|
|
|
result = get_ids({'playliststart': 10})
|
|
self.assertEqual(result, [])
|
|
|
|
result = get_ids({'playliststart': 2})
|
|
self.assertEqual(result, [2, 3, 4])
|
|
|
|
result = get_ids({'playlist_items': '2-4'})
|
|
self.assertEqual(result, [2, 3, 4])
|
|
|
|
result = get_ids({'playlist_items': '2,4'})
|
|
self.assertEqual(result, [2, 4])
|
|
|
|
result = get_ids({'playlist_items': '10'})
|
|
self.assertEqual(result, [])
|
|
|
|
result = get_ids({'playlist_items': '3-10'})
|
|
self.assertEqual(result, [3, 4])
|
|
|
|
result = get_ids({'playlist_items': '2-4,3-4,3'})
|
|
self.assertEqual(result, [2, 3, 4])
|
|
|
|
# Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
|
|
# @{
|
|
result = get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'})
|
|
self.assertEqual(result[0]['playlist_index'], 2)
|
|
self.assertEqual(result[1]['playlist_index'], 3)
|
|
|
|
result = get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'})
|
|
self.assertEqual(result[0]['playlist_index'], 2)
|
|
self.assertEqual(result[1]['playlist_index'], 3)
|
|
self.assertEqual(result[2]['playlist_index'], 4)
|
|
|
|
result = get_downloaded_info_dicts({'playlist_items': '4,2'})
|
|
self.assertEqual(result[0]['playlist_index'], 4)
|
|
self.assertEqual(result[1]['playlist_index'], 2)
|
|
# @}
|
|
|
|
def test_urlopen_no_file_protocol(self):
|
|
# see https://github.com/ytdl-org/youtube-dl/issues/8227
|
|
ydl = YDL()
|
|
self.assertRaises(compat_urllib_error.URLError, ydl.urlopen, 'file:///etc/passwd')
|
|
|
|
def test_do_not_override_ie_key_in_url_transparent(self):
|
|
ydl = YDL()
|
|
|
|
class Foo1IE(InfoExtractor):
|
|
_VALID_URL = r'foo1:'
|
|
|
|
def _real_extract(self, url):
|
|
return {
|
|
'_type': 'url_transparent',
|
|
'url': 'foo2:',
|
|
'ie_key': 'Foo2',
|
|
'title': 'foo1 title',
|
|
'id': 'foo1_id',
|
|
}
|
|
|
|
class Foo2IE(InfoExtractor):
|
|
_VALID_URL = r'foo2:'
|
|
|
|
def _real_extract(self, url):
|
|
return {
|
|
'_type': 'url',
|
|
'url': 'foo3:',
|
|
'ie_key': 'Foo3',
|
|
}
|
|
|
|
class Foo3IE(InfoExtractor):
|
|
_VALID_URL = r'foo3:'
|
|
|
|
def _real_extract(self, url):
|
|
return _make_result([{'url': TEST_URL}], title='foo3 title')
|
|
|
|
ydl.add_info_extractor(Foo1IE(ydl))
|
|
ydl.add_info_extractor(Foo2IE(ydl))
|
|
ydl.add_info_extractor(Foo3IE(ydl))
|
|
ydl.extract_info('foo1:')
|
|
downloaded = ydl.downloaded_info_dicts[0]
|
|
self.assertEqual(downloaded['url'], TEST_URL)
|
|
self.assertEqual(downloaded['title'], 'foo1 title')
|
|
self.assertEqual(downloaded['id'], 'testid')
|
|
self.assertEqual(downloaded['extractor'], 'testex')
|
|
self.assertEqual(downloaded['extractor_key'], 'TestEx')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|