aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rwxr-xr-xbundle/py2exe.py2
-rwxr-xr-xbundle/pyinstaller.py8
-rwxr-xr-xdevscripts/bash-completion.py8
-rw-r--r--devscripts/make_changelog.py10
-rwxr-xr-xdevscripts/make_readme.py12
-rw-r--r--devscripts/set-variant.py2
-rw-r--r--devscripts/update-version.py2
-rwxr-xr-xdevscripts/zsh-completion.py16
-rw-r--r--pyproject.toml127
-rw-r--r--test/conftest.py10
-rw-r--r--test/helper.py32
-rw-r--r--test/test_InfoExtractor.py138
-rw-r--r--test/test_YoutubeDL.py45
-rw-r--r--test/test_aes.py12
-rw-r--r--test/test_compat.py10
-rw-r--r--test/test_config.py2
-rw-r--r--test/test_cookies.py132
-rwxr-xr-xtest/test_download.py18
-rw-r--r--test/test_downloader_http.py6
-rw-r--r--test/test_http_proxy.py4
-rw-r--r--test/test_iqiyi_sdk_interpreter.py4
-rw-r--r--test/test_netrc.py2
-rw-r--r--test/test_networking.py56
-rw-r--r--test/test_networking_utils.py12
-rw-r--r--test/test_overwrites.py4
-rw-r--r--test/test_plugins.py2
-rw-r--r--test/test_post_hooks.py2
-rw-r--r--test/test_postprocessors.py137
-rw-r--r--test/test_socks.py8
-rw-r--r--test/test_subtitles.py11
-rw-r--r--test/test_traversal.py6
-rw-r--r--test/test_update.py8
-rw-r--r--test/test_utils.py64
-rw-r--r--test/test_websockets.py4
-rw-r--r--test/test_youtube_misc.py2
-rw-r--r--test/test_youtube_signature.py8
-rw-r--r--yt_dlp/YoutubeDL.py306
-rw-r--r--yt_dlp/__init__.py39
-rw-r--r--yt_dlp/aes.py36
-rw-r--r--yt_dlp/cache.py4
-rw-r--r--yt_dlp/compat/_legacy.py2
-rw-r--r--yt_dlp/compat/functools.py2
-rw-r--r--yt_dlp/cookies.py55
-rw-r--r--yt_dlp/downloader/common.py2
-rw-r--r--yt_dlp/downloader/external.py24
-rw-r--r--yt_dlp/downloader/f4m.py22
-rw-r--r--yt_dlp/downloader/fragment.py8
-rw-r--r--yt_dlp/downloader/hls.py9
-rw-r--r--yt_dlp/downloader/http.py14
-rw-r--r--yt_dlp/downloader/ism.py2
-rw-r--r--yt_dlp/downloader/mhtml.py53
-rw-r--r--yt_dlp/downloader/niconico.py14
-rw-r--r--yt_dlp/downloader/rtmp.py6
-rw-r--r--yt_dlp/downloader/youtube_live_chat.py2
-rw-r--r--yt_dlp/extractor/abc.py28
-rw-r--r--yt_dlp/extractor/abcnews.py2
-rw-r--r--yt_dlp/extractor/abcotvs.py5
-rw-r--r--yt_dlp/extractor/abematv.py22
-rw-r--r--yt_dlp/extractor/acast.py8
-rw-r--r--yt_dlp/extractor/acfun.py4
-rw-r--r--yt_dlp/extractor/adn.py19
-rw-r--r--yt_dlp/extractor/adobeconnect.py10
-rw-r--r--yt_dlp/extractor/adobepass.py866
-rw-r--r--yt_dlp/extractor/adobetv.py5
-rw-r--r--yt_dlp/extractor/adultswim.py6
-rw-r--r--yt_dlp/extractor/aenetworks.py34
-rw-r--r--yt_dlp/extractor/aeonco.py8
-rw-r--r--yt_dlp/extractor/afreecatv.py4
-rw-r--r--yt_dlp/extractor/agora.py6
-rw-r--r--yt_dlp/extractor/airtv.py4
-rw-r--r--yt_dlp/extractor/aitube.py2
-rw-r--r--yt_dlp/extractor/aliexpress.py3
-rw-r--r--yt_dlp/extractor/aljazeera.py14
-rw-r--r--yt_dlp/extractor/allocine.py5
-rw-r--r--yt_dlp/extractor/allstar.py26
-rw-r--r--yt_dlp/extractor/alphaporno.py2
-rw-r--r--yt_dlp/extractor/alsace20tv.py6
-rw-r--r--yt_dlp/extractor/altcensored.py2
-rw-r--r--yt_dlp/extractor/alura.py16
-rw-r--r--yt_dlp/extractor/amadeustv.py2
-rw-r--r--yt_dlp/extractor/amara.py10
-rw-r--r--yt_dlp/extractor/amazon.py8
-rw-r--r--yt_dlp/extractor/amazonminitv.py2
-rw-r--r--yt_dlp/extractor/amcnetworks.py12
-rw-r--r--yt_dlp/extractor/americastestkitchen.py14
-rw-r--r--yt_dlp/extractor/amp.py4
-rw-r--r--yt_dlp/extractor/anchorfm.py6
-rw-r--r--yt_dlp/extractor/angel.py10
-rw-r--r--yt_dlp/extractor/antenna.py2
-rw-r--r--yt_dlp/extractor/anvato.py8
-rw-r--r--yt_dlp/extractor/aol.py8
-rw-r--r--yt_dlp/extractor/apa.py4
-rw-r--r--yt_dlp/extractor/applepodcasts.py2
-rw-r--r--yt_dlp/extractor/appletrailers.py31
-rw-r--r--yt_dlp/extractor/archiveorg.py105
-rw-r--r--yt_dlp/extractor/arcpublishing.py10
-rw-r--r--yt_dlp/extractor/ard.py6
-rw-r--r--yt_dlp/extractor/arkena.py2
-rw-r--r--yt_dlp/extractor/arnes.py12
-rw-r--r--yt_dlp/extractor/art19.py2
-rw-r--r--yt_dlp/extractor/arte.py24
-rw-r--r--yt_dlp/extractor/atresplayer.py4
-rw-r--r--yt_dlp/extractor/atscaleconf.py10
-rw-r--r--yt_dlp/extractor/atvat.py16
-rw-r--r--yt_dlp/extractor/audimedia.py4
-rw-r--r--yt_dlp/extractor/audioboom.py4
-rw-r--r--yt_dlp/extractor/audiodraft.py13
-rw-r--r--yt_dlp/extractor/audiomack.py27
-rw-r--r--yt_dlp/extractor/audius.py46
-rw-r--r--yt_dlp/extractor/awaan.py27
-rw-r--r--yt_dlp/extractor/aws.py24
-rw-r--r--yt_dlp/extractor/azmedien.py6
-rw-r--r--yt_dlp/extractor/baidu.py7
-rw-r--r--yt_dlp/extractor/banbye.py9
-rw-r--r--yt_dlp/extractor/bandcamp.py31
-rw-r--r--yt_dlp/extractor/bannedvideo.py12
-rw-r--r--yt_dlp/extractor/bbc.py110
-rw-r--r--yt_dlp/extractor/beatport.py7
-rw-r--r--yt_dlp/extractor/beeg.py8
-rw-r--r--yt_dlp/extractor/behindkink.py2
-rw-r--r--yt_dlp/extractor/bellmedia.py2
-rw-r--r--yt_dlp/extractor/berufetv.py4
-rw-r--r--yt_dlp/extractor/bet.py8
-rw-r--r--yt_dlp/extractor/bfmtv.py4
-rw-r--r--yt_dlp/extractor/bigflix.py14
-rw-r--r--yt_dlp/extractor/bigo.py2
-rw-r--r--yt_dlp/extractor/bild.py4
-rw-r--r--yt_dlp/extractor/bilibili.py138
-rw-r--r--yt_dlp/extractor/bitchute.py14
-rw-r--r--yt_dlp/extractor/blackboardcollaborate.py2
-rw-r--r--yt_dlp/extractor/bleacherreport.py12
-rw-r--r--yt_dlp/extractor/blerp.py25
-rw-r--r--yt_dlp/extractor/blogger.py4
-rw-r--r--yt_dlp/extractor/bloomberg.py2
-rw-r--r--yt_dlp/extractor/bokecc.py15
-rw-r--r--yt_dlp/extractor/bongacams.py9
-rw-r--r--yt_dlp/extractor/bostonglobe.py3
-rw-r--r--yt_dlp/extractor/box.py6
-rw-r--r--yt_dlp/extractor/boxcast.py10
-rw-r--r--yt_dlp/extractor/br.py8
-rw-r--r--yt_dlp/extractor/brainpop.py14
-rw-r--r--yt_dlp/extractor/bravotv.py2
-rw-r--r--yt_dlp/extractor/breitbart.py4
-rw-r--r--yt_dlp/extractor/brightcove.py74
-rw-r--r--yt_dlp/extractor/bundesliga.py10
-rw-r--r--yt_dlp/extractor/businessinsider.py4
-rw-r--r--yt_dlp/extractor/buzzfeed.py6
-rw-r--r--yt_dlp/extractor/byutv.py2
-rw-r--r--yt_dlp/extractor/c56.py4
-rw-r--r--yt_dlp/extractor/callin.py16
-rw-r--r--yt_dlp/extractor/caltrans.py2
-rw-r--r--yt_dlp/extractor/cam4.py4
-rw-r--r--yt_dlp/extractor/camdemy.py33
-rw-r--r--yt_dlp/extractor/camfm.py4
-rw-r--r--yt_dlp/extractor/cammodels.py8
-rw-r--r--yt_dlp/extractor/camtasia.py6
-rw-r--r--yt_dlp/extractor/canalalpha.py8
-rw-r--r--yt_dlp/extractor/canalc2.py2
-rw-r--r--yt_dlp/extractor/canalplus.py5
-rw-r--r--yt_dlp/extractor/caracoltv.py4
-rw-r--r--yt_dlp/extractor/cartoonnetwork.py2
-rw-r--r--yt_dlp/extractor/cbc.py51
-rw-r--r--yt_dlp/extractor/cbs.py6
-rw-r--r--yt_dlp/extractor/ccc.py6
-rw-r--r--yt_dlp/extractor/ccma.py6
-rw-r--r--yt_dlp/extractor/cctv.py7
-rw-r--r--yt_dlp/extractor/cda.py29
-rw-r--r--yt_dlp/extractor/cellebrite.py4
-rw-r--r--yt_dlp/extractor/ceskatelevize.py24
-rw-r--r--yt_dlp/extractor/cgtn.py10
-rw-r--r--yt_dlp/extractor/chaturbate.py6
-rw-r--r--yt_dlp/extractor/cinemax.py2
-rw-r--r--yt_dlp/extractor/cinetecamilano.py8
-rw-r--r--yt_dlp/extractor/cineverse.py10
-rw-r--r--yt_dlp/extractor/ciscolive.py4
-rw-r--r--yt_dlp/extractor/ciscowebex.py4
-rw-r--r--yt_dlp/extractor/cjsw.py2
-rw-r--r--yt_dlp/extractor/clippit.py4
-rw-r--r--yt_dlp/extractor/cliprs.py2
-rw-r--r--yt_dlp/extractor/closertotruth.py10
-rw-r--r--yt_dlp/extractor/cloudflarestream.py2
-rw-r--r--yt_dlp/extractor/cloudycdn.py6
-rw-r--r--yt_dlp/extractor/clubic.py4
-rw-r--r--yt_dlp/extractor/clyp.py6
-rw-r--r--yt_dlp/extractor/cmt.py4
-rw-r--r--yt_dlp/extractor/cnn.py8
-rw-r--r--yt_dlp/extractor/common.py134
-rw-r--r--yt_dlp/extractor/commonmistakes.py6
-rw-r--r--yt_dlp/extractor/commonprotocols.py2
-rw-r--r--yt_dlp/extractor/condenast.py33
-rw-r--r--yt_dlp/extractor/contv.py2
-rw-r--r--yt_dlp/extractor/corus.py12
-rw-r--r--yt_dlp/extractor/coub.py8
-rw-r--r--yt_dlp/extractor/cozytv.py10
-rw-r--r--yt_dlp/extractor/cpac.py24
-rw-r--r--yt_dlp/extractor/cracked.py4
-rw-r--r--yt_dlp/extractor/crackle.py14
-rw-r--r--yt_dlp/extractor/craftsy.py2
-rw-r--r--yt_dlp/extractor/crooksandliars.py4
-rw-r--r--yt_dlp/extractor/crowdbunker.py28
-rw-r--r--yt_dlp/extractor/crtvg.py4
-rw-r--r--yt_dlp/extractor/crunchyroll.py6
-rw-r--r--yt_dlp/extractor/cspan.py26
-rw-r--r--yt_dlp/extractor/ctsnews.py4
-rw-r--r--yt_dlp/extractor/ctv.py4
-rw-r--r--yt_dlp/extractor/ctvnews.py6
-rw-r--r--yt_dlp/extractor/cultureunplugged.py8
-rw-r--r--yt_dlp/extractor/curiositystream.py9
-rw-r--r--yt_dlp/extractor/cwtv.py4
-rw-r--r--yt_dlp/extractor/cybrary.py20
-rw-r--r--yt_dlp/extractor/dailymail.py9
-rw-r--r--yt_dlp/extractor/dailymotion.py16
-rw-r--r--yt_dlp/extractor/dailywire.py6
-rw-r--r--yt_dlp/extractor/damtomo.py9
-rw-r--r--yt_dlp/extractor/daum.py28
-rw-r--r--yt_dlp/extractor/dbtv.py2
-rw-r--r--yt_dlp/extractor/dctp.py11
-rw-r--r--yt_dlp/extractor/deezer.py6
-rw-r--r--yt_dlp/extractor/democracynow.py8
-rw-r--r--yt_dlp/extractor/detik.py20
-rw-r--r--yt_dlp/extractor/deuxm.py16
-rw-r--r--yt_dlp/extractor/dfb.py4
-rw-r--r--yt_dlp/extractor/digitalconcerthall.py8
-rw-r--r--yt_dlp/extractor/digiteka.py2
-rw-r--r--yt_dlp/extractor/discovery.py10
-rw-r--r--yt_dlp/extractor/discoverygo.py5
-rw-r--r--yt_dlp/extractor/disney.py8
-rw-r--r--yt_dlp/extractor/dispeak.py10
-rw-r--r--yt_dlp/extractor/dlf.py36
-rw-r--r--yt_dlp/extractor/dlive.py8
-rw-r--r--yt_dlp/extractor/douyutv.py10
-rw-r--r--yt_dlp/extractor/dplay.py16
-rw-r--r--yt_dlp/extractor/drbonanza.py2
-rw-r--r--yt_dlp/extractor/dreisat.py4
-rw-r--r--yt_dlp/extractor/drooble.py6
-rw-r--r--yt_dlp/extractor/dropbox.py14
-rw-r--r--yt_dlp/extractor/dropout.py34
-rw-r--r--yt_dlp/extractor/drtuber.py10
-rw-r--r--yt_dlp/extractor/drtv.py18
-rw-r--r--yt_dlp/extractor/dtube.py6
-rw-r--r--yt_dlp/extractor/duboku.py29
-rw-r--r--yt_dlp/extractor/dumpert.py4
-rw-r--r--yt_dlp/extractor/dvtv.py14
-rw-r--r--yt_dlp/extractor/dw.py15
-rw-r--r--yt_dlp/extractor/eagleplatform.py20
-rw-r--r--yt_dlp/extractor/ebaumsworld.py2
-rw-r--r--yt_dlp/extractor/ebay.py4
-rw-r--r--yt_dlp/extractor/egghead.py11
-rw-r--r--yt_dlp/extractor/eighttracks.py49
-rw-r--r--yt_dlp/extractor/eitb.py8
-rw-r--r--yt_dlp/extractor/elpais.py4
-rw-r--r--yt_dlp/extractor/eltrecetv.py4
-rw-r--r--yt_dlp/extractor/epicon.py29
-rw-r--r--yt_dlp/extractor/epoch.py10
-rw-r--r--yt_dlp/extractor/eporner.py14
-rw-r--r--yt_dlp/extractor/erocast.py2
-rw-r--r--yt_dlp/extractor/eroprofile.py6
-rw-r--r--yt_dlp/extractor/err.py2
-rw-r--r--yt_dlp/extractor/ertgr.py17
-rw-r--r--yt_dlp/extractor/espn.py41
-rw-r--r--yt_dlp/extractor/ettutv.py2
-rw-r--r--yt_dlp/extractor/europa.py24
-rw-r--r--yt_dlp/extractor/europeantour.py8
-rw-r--r--yt_dlp/extractor/eurosport.py10
-rw-r--r--yt_dlp/extractor/euscreen.py18
-rw-r--r--yt_dlp/extractor/expressen.py2
-rw-r--r--yt_dlp/extractor/eyedotv.py12
-rw-r--r--yt_dlp/extractor/facebook.py49
-rw-r--r--yt_dlp/extractor/fancode.py35
-rw-r--r--yt_dlp/extractor/fc2.py18
-rw-r--r--yt_dlp/extractor/filmon.py11
-rw-r--r--yt_dlp/extractor/filmweb.py2
-rw-r--r--yt_dlp/extractor/firsttv.py27
-rw-r--r--yt_dlp/extractor/flickr.py14
-rw-r--r--yt_dlp/extractor/floatplane.py2
-rw-r--r--yt_dlp/extractor/folketinget.py5
-rw-r--r--yt_dlp/extractor/footyroom.py2
-rw-r--r--yt_dlp/extractor/fourtube.py41
-rw-r--r--yt_dlp/extractor/fox.py15
-rw-r--r--yt_dlp/extractor/fptplay.py2
-rw-r--r--yt_dlp/extractor/francetv.py6
-rw-r--r--yt_dlp/extractor/freesound.py2
-rw-r--r--yt_dlp/extractor/freetv.py10
-rw-r--r--yt_dlp/extractor/frontendmasters.py31
-rw-r--r--yt_dlp/extractor/fujitv.py8
-rw-r--r--yt_dlp/extractor/funimation.py32
-rw-r--r--yt_dlp/extractor/funker530.py6
-rw-r--r--yt_dlp/extractor/fuyintv.py2
-rw-r--r--yt_dlp/extractor/gab.py22
-rw-r--r--yt_dlp/extractor/gaia.py14
-rw-r--r--yt_dlp/extractor/gamejolt.py34
-rw-r--r--yt_dlp/extractor/gamespot.py5
-rw-r--r--yt_dlp/extractor/gamestar.py6
-rw-r--r--yt_dlp/extractor/gaskrank.py4
-rw-r--r--yt_dlp/extractor/gazeta.py4
-rw-r--r--yt_dlp/extractor/gbnews.py14
-rw-r--r--yt_dlp/extractor/gdcvault.py10
-rw-r--r--yt_dlp/extractor/gedidigital.py4
-rw-r--r--yt_dlp/extractor/generic.py145
-rw-r--r--yt_dlp/extractor/genericembeds.py10
-rw-r--r--yt_dlp/extractor/getcourseru.py22
-rw-r--r--yt_dlp/extractor/gettr.py14
-rw-r--r--yt_dlp/extractor/giantbomb.py2
-rw-r--r--yt_dlp/extractor/gigya.py2
-rw-r--r--yt_dlp/extractor/glide.py2
-rw-r--r--yt_dlp/extractor/globalplayer.py4
-rw-r--r--yt_dlp/extractor/globo.py29
-rw-r--r--yt_dlp/extractor/glomex.py6
-rw-r--r--yt_dlp/extractor/gmanetwork.py4
-rw-r--r--yt_dlp/extractor/go.py19
-rw-r--r--yt_dlp/extractor/godresource.py10
-rw-r--r--yt_dlp/extractor/godtube.py4
-rw-r--r--yt_dlp/extractor/gofile.py8
-rw-r--r--yt_dlp/extractor/golem.py16
-rw-r--r--yt_dlp/extractor/googledrive.py20
-rw-r--r--yt_dlp/extractor/googlepodcasts.py2
-rw-r--r--yt_dlp/extractor/goplay.py143
-rw-r--r--yt_dlp/extractor/gopro.py8
-rw-r--r--yt_dlp/extractor/goshgay.py9
-rw-r--r--yt_dlp/extractor/gotostage.py19
-rw-r--r--yt_dlp/extractor/gputechconf.py4
-rw-r--r--yt_dlp/extractor/gronkh.py14
-rw-r--r--yt_dlp/extractor/groupon.py3
-rw-r--r--yt_dlp/extractor/harpodeon.py6
-rw-r--r--yt_dlp/extractor/hbo.py8
-rw-r--r--yt_dlp/extractor/heise.py6
-rw-r--r--yt_dlp/extractor/hidive.py6
-rw-r--r--yt_dlp/extractor/historicfilms.py2
-rw-r--r--yt_dlp/extractor/hitrecord.py11
-rw-r--r--yt_dlp/extractor/hketv.py3
-rw-r--r--yt_dlp/extractor/hollywoodreporter.py2
-rw-r--r--yt_dlp/extractor/holodex.py2
-rw-r--r--yt_dlp/extractor/hotnewhiphop.py9
-rw-r--r--yt_dlp/extractor/hotstar.py11
-rw-r--r--yt_dlp/extractor/hrfensehen.py12
-rw-r--r--yt_dlp/extractor/hrti.py20
-rw-r--r--yt_dlp/extractor/hse.py4
-rw-r--r--yt_dlp/extractor/huajiao.py2
-rw-r--r--yt_dlp/extractor/huffpost.py2
-rw-r--r--yt_dlp/extractor/hungama.py6
-rw-r--r--yt_dlp/extractor/huya.py13
-rw-r--r--yt_dlp/extractor/hypem.py6
-rw-r--r--yt_dlp/extractor/hypergryph.py4
-rw-r--r--yt_dlp/extractor/hytale.py4
-rw-r--r--yt_dlp/extractor/icareus.py12
-rw-r--r--yt_dlp/extractor/ichinanalive.py15
-rw-r--r--yt_dlp/extractor/ign.py21
-rw-r--r--yt_dlp/extractor/iheart.py2
-rw-r--r--yt_dlp/extractor/ilpost.py2
-rw-r--r--yt_dlp/extractor/iltalehti.py2
-rw-r--r--yt_dlp/extractor/imdb.py8
-rw-r--r--yt_dlp/extractor/imggaming.py2
-rw-r--r--yt_dlp/extractor/imgur.py21
-rw-r--r--yt_dlp/extractor/ina.py2
-rw-r--r--yt_dlp/extractor/inc.py2
-rw-r--r--yt_dlp/extractor/indavideo.py4
-rw-r--r--yt_dlp/extractor/infoq.py12
-rw-r--r--yt_dlp/extractor/instagram.py54
-rw-r--r--yt_dlp/extractor/internazionale.py4
-rw-r--r--yt_dlp/extractor/iprima.py4
-rw-r--r--yt_dlp/extractor/iqiyi.py77
-rw-r--r--yt_dlp/extractor/islamchannel.py2
-rw-r--r--yt_dlp/extractor/israelnationalnews.py6
-rw-r--r--yt_dlp/extractor/itprotv.py14
-rw-r--r--yt_dlp/extractor/itv.py33
-rw-r--r--yt_dlp/extractor/ivi.py26
-rw-r--r--yt_dlp/extractor/ivideon.py16
-rw-r--r--yt_dlp/extractor/iwara.py8
-rw-r--r--yt_dlp/extractor/ixigua.py2
-rw-r--r--yt_dlp/extractor/izlesene.py18
-rw-r--r--yt_dlp/extractor/jamendo.py32
-rw-r--r--yt_dlp/extractor/japandiet.py8
-rw-r--r--yt_dlp/extractor/jiocinema.py14
-rw-r--r--yt_dlp/extractor/jiosaavn.py2
-rw-r--r--yt_dlp/extractor/joj.py15
-rw-r--r--yt_dlp/extractor/jove.py4
-rw-r--r--yt_dlp/extractor/jwplatform.py4
-rw-r--r--yt_dlp/extractor/kakao.py10
-rw-r--r--yt_dlp/extractor/kaltura.py73
-rw-r--r--yt_dlp/extractor/kankanews.py2
-rw-r--r--yt_dlp/extractor/karaoketv.py4
-rw-r--r--yt_dlp/extractor/kelbyone.py2
-rw-r--r--yt_dlp/extractor/kicker.py6
-rw-r--r--yt_dlp/extractor/kinja.py18
-rw-r--r--yt_dlp/extractor/kommunetv.py10
-rw-r--r--yt_dlp/extractor/kompas.py2
-rw-r--r--yt_dlp/extractor/koo.py27
-rw-r--r--yt_dlp/extractor/kth.py7
-rw-r--r--yt_dlp/extractor/ku6.py10
-rw-r--r--yt_dlp/extractor/kuwo.py42
-rw-r--r--yt_dlp/extractor/la7.py2
-rw-r--r--yt_dlp/extractor/laxarxames.py2
-rw-r--r--yt_dlp/extractor/lbry.py16
-rw-r--r--yt_dlp/extractor/lcp.py2
-rw-r--r--yt_dlp/extractor/lecture2go.py2
-rw-r--r--yt_dlp/extractor/lecturio.py6
-rw-r--r--yt_dlp/extractor/leeco.py41
-rw-r--r--yt_dlp/extractor/lego.py6
-rw-r--r--yt_dlp/extractor/lenta.py2
-rw-r--r--yt_dlp/extractor/libraryofcongress.py2
-rw-r--r--yt_dlp/extractor/libsyn.py6
-rw-r--r--yt_dlp/extractor/lifenews.py27
-rw-r--r--yt_dlp/extractor/likee.py2
-rw-r--r--yt_dlp/extractor/limelight.py16
-rw-r--r--yt_dlp/extractor/linkedin.py27
-rw-r--r--yt_dlp/extractor/liputan6.py6
-rw-r--r--yt_dlp/extractor/listennotes.py6
-rw-r--r--yt_dlp/extractor/litv.py6
-rw-r--r--yt_dlp/extractor/livejournal.py5
-rw-r--r--yt_dlp/extractor/livestream.py42
-rw-r--r--yt_dlp/extractor/livestreamfails.py4
-rw-r--r--yt_dlp/extractor/lnkgo.py27
-rw-r--r--yt_dlp/extractor/lovehomeporn.py6
-rw-r--r--yt_dlp/extractor/lrt.py8
-rw-r--r--yt_dlp/extractor/lsm.py12
-rw-r--r--yt_dlp/extractor/lumni.py2
-rw-r--r--yt_dlp/extractor/lynda.py54
-rw-r--r--yt_dlp/extractor/magentamusik.py2
-rw-r--r--yt_dlp/extractor/mailru.py15
-rw-r--r--yt_dlp/extractor/mainstreaming.py28
-rw-r--r--yt_dlp/extractor/mangomolo.py13
-rw-r--r--yt_dlp/extractor/manoto.py12
-rw-r--r--yt_dlp/extractor/manyvids.py8
-rw-r--r--yt_dlp/extractor/markiza.py9
-rw-r--r--yt_dlp/extractor/massengeschmacktv.py2
-rw-r--r--yt_dlp/extractor/masters.py2
-rw-r--r--yt_dlp/extractor/mdr.py7
-rw-r--r--yt_dlp/extractor/medaltv.py13
-rw-r--r--yt_dlp/extractor/mediaite.py14
-rw-r--r--yt_dlp/extractor/mediaklikk.py39
-rw-r--r--yt_dlp/extractor/mediaset.py6
-rw-r--r--yt_dlp/extractor/mediasite.py87
-rw-r--r--yt_dlp/extractor/mediaworksnz.py10
-rw-r--r--yt_dlp/extractor/meipai.py4
-rw-r--r--yt_dlp/extractor/melonvod.py4
-rw-r--r--yt_dlp/extractor/metacritic.py6
-rw-r--r--yt_dlp/extractor/mgtv.py6
-rw-r--r--yt_dlp/extractor/microsoftembed.py4
-rw-r--r--yt_dlp/extractor/microsoftstream.py6
-rw-r--r--yt_dlp/extractor/microsoftvirtualacademy.py21
-rw-r--r--yt_dlp/extractor/mildom.py10
-rw-r--r--yt_dlp/extractor/minds.py11
-rw-r--r--yt_dlp/extractor/minoto.py2
-rw-r--r--yt_dlp/extractor/mirrativ.py6
-rw-r--r--yt_dlp/extractor/mit.py8
-rw-r--r--yt_dlp/extractor/mixch.py4
-rw-r--r--yt_dlp/extractor/mixcloud.py37
-rw-r--r--yt_dlp/extractor/mlb.py16
-rw-r--r--yt_dlp/extractor/mlssoccer.py69
-rw-r--r--yt_dlp/extractor/mocha.py4
-rw-r--r--yt_dlp/extractor/mojvideo.py6
-rw-r--r--yt_dlp/extractor/monstercat.py6
-rw-r--r--yt_dlp/extractor/motherless.py6
-rw-r--r--yt_dlp/extractor/motorsport.py11
-rw-r--r--yt_dlp/extractor/moview.py6
-rw-r--r--yt_dlp/extractor/moviezine.py2
-rw-r--r--yt_dlp/extractor/movingimage.py2
-rw-r--r--yt_dlp/extractor/msn.py5
-rw-r--r--yt_dlp/extractor/mtv.py50
-rw-r--r--yt_dlp/extractor/muenchentv.py8
-rw-r--r--yt_dlp/extractor/murrtube.py6
-rw-r--r--yt_dlp/extractor/musescore.py12
-rw-r--r--yt_dlp/extractor/musicdex.py50
-rw-r--r--yt_dlp/extractor/mx3.py10
-rw-r--r--yt_dlp/extractor/mxplayer.py25
-rw-r--r--yt_dlp/extractor/myspace.py14
-rw-r--r--yt_dlp/extractor/myspass.py3
-rw-r--r--yt_dlp/extractor/mzaalo.py6
-rw-r--r--yt_dlp/extractor/n1.py6
-rw-r--r--yt_dlp/extractor/nate.py24
-rw-r--r--yt_dlp/extractor/nationalgeographic.py2
-rw-r--r--yt_dlp/extractor/naver.py12
-rw-r--r--yt_dlp/extractor/nba.py23
-rw-r--r--yt_dlp/extractor/nbc.py26
-rw-r--r--yt_dlp/extractor/ndr.py18
-rw-r--r--yt_dlp/extractor/ndtv.py26
-rw-r--r--yt_dlp/extractor/nekohacker.py32
-rw-r--r--yt_dlp/extractor/neteasemusic.py10
-rw-r--r--yt_dlp/extractor/netverse.py14
-rw-r--r--yt_dlp/extractor/netzkino.py6
-rw-r--r--yt_dlp/extractor/newgrounds.py10
-rw-r--r--yt_dlp/extractor/newsy.py4
-rw-r--r--yt_dlp/extractor/nextmedia.py17
-rw-r--r--yt_dlp/extractor/nexx.py92
-rw-r--r--yt_dlp/extractor/nfhsnetwork.py52
-rw-r--r--yt_dlp/extractor/nfl.py4
-rw-r--r--yt_dlp/extractor/nhk.py24
-rw-r--r--yt_dlp/extractor/nhl.py9
-rw-r--r--yt_dlp/extractor/nick.py18
-rw-r--r--yt_dlp/extractor/niconico.py74
-rw-r--r--yt_dlp/extractor/niconicochannelplus.py4
-rw-r--r--yt_dlp/extractor/ninaprotocol.py10
-rw-r--r--yt_dlp/extractor/ninecninemedia.py10
-rw-r--r--yt_dlp/extractor/ninegag.py6
-rw-r--r--yt_dlp/extractor/ninenews.py4
-rw-r--r--yt_dlp/extractor/ninenow.py21
-rw-r--r--yt_dlp/extractor/nintendo.py2
-rw-r--r--yt_dlp/extractor/nitter.py18
-rw-r--r--yt_dlp/extractor/nobelprize.py2
-rw-r--r--yt_dlp/extractor/noice.py6
-rw-r--r--yt_dlp/extractor/nonktube.py2
-rw-r--r--yt_dlp/extractor/noodlemagazine.py6
-rw-r--r--yt_dlp/extractor/noovo.py7
-rw-r--r--yt_dlp/extractor/nosnl.py6
-rw-r--r--yt_dlp/extractor/nova.py8
-rw-r--r--yt_dlp/extractor/novaplay.py4
-rw-r--r--yt_dlp/extractor/nowness.py7
-rw-r--r--yt_dlp/extractor/noz.py9
-rw-r--r--yt_dlp/extractor/npo.py40
-rw-r--r--yt_dlp/extractor/npr.py4
-rw-r--r--yt_dlp/extractor/nrk.py72
-rw-r--r--yt_dlp/extractor/ntvru.py6
-rw-r--r--yt_dlp/extractor/nubilesporn.py6
-rw-r--r--yt_dlp/extractor/nuevo.py2
-rw-r--r--yt_dlp/extractor/nuvid.py8
-rw-r--r--yt_dlp/extractor/nytimes.py2
-rw-r--r--yt_dlp/extractor/nzherald.py21
-rw-r--r--yt_dlp/extractor/nzonscreen.py2
-rw-r--r--yt_dlp/extractor/odkmedia.py4
-rw-r--r--yt_dlp/extractor/odnoklassniki.py19
-rw-r--r--yt_dlp/extractor/oftv.py8
-rw-r--r--yt_dlp/extractor/oktoberfesttv.py2
-rw-r--r--yt_dlp/extractor/olympics.py8
-rw-r--r--yt_dlp/extractor/on24.py6
-rw-r--r--yt_dlp/extractor/onefootball.py2
-rw-r--r--yt_dlp/extractor/onenewsnz.py10
-rw-r--r--yt_dlp/extractor/oneplace.py4
-rw-r--r--yt_dlp/extractor/onet.py6
-rw-r--r--yt_dlp/extractor/onionstudios.py3
-rw-r--r--yt_dlp/extractor/opencast.py2
-rw-r--r--yt_dlp/extractor/openload.py10
-rw-r--r--yt_dlp/extractor/openrec.py7
-rw-r--r--yt_dlp/extractor/ora.py8
-rw-r--r--yt_dlp/extractor/orf.py18
-rw-r--r--yt_dlp/extractor/outsidetv.py2
-rw-r--r--yt_dlp/extractor/packtpub.py9
-rw-r--r--yt_dlp/extractor/palcomp3.py11
-rw-r--r--yt_dlp/extractor/panopto.py66
-rw-r--r--yt_dlp/extractor/paramountplus.py8
-rw-r--r--yt_dlp/extractor/parler.py2
-rw-r--r--yt_dlp/extractor/parlview.py7
-rw-r--r--yt_dlp/extractor/patreon.py22
-rw-r--r--yt_dlp/extractor/pbs.py37
-rw-r--r--yt_dlp/extractor/pearvideo.py4
-rw-r--r--yt_dlp/extractor/peertube.py71
-rw-r--r--yt_dlp/extractor/peertv.py2
-rw-r--r--yt_dlp/extractor/peloton.py26
-rw-r--r--yt_dlp/extractor/performgroup.py6
-rw-r--r--yt_dlp/extractor/periscope.py12
-rw-r--r--yt_dlp/extractor/philharmoniedeparis.py7
-rw-r--r--yt_dlp/extractor/phoenix.py9
-rw-r--r--yt_dlp/extractor/photobucket.py6
-rw-r--r--yt_dlp/extractor/piapro.py15
-rw-r--r--yt_dlp/extractor/picarto.py14
-rw-r--r--yt_dlp/extractor/piksel.py8
-rw-r--r--yt_dlp/extractor/pinkbike.py8
-rw-r--r--yt_dlp/extractor/pinterest.py17
-rw-r--r--yt_dlp/extractor/pixivsketch.py4
-rw-r--r--yt_dlp/extractor/pladform.py12
-rw-r--r--yt_dlp/extractor/planetmarathi.py15
-rw-r--r--yt_dlp/extractor/platzi.py22
-rw-r--r--yt_dlp/extractor/playsuisse.py20
-rw-r--r--yt_dlp/extractor/playtvak.py24
-rw-r--r--yt_dlp/extractor/playwire.py2
-rw-r--r--yt_dlp/extractor/pluralsight.py69
-rw-r--r--yt_dlp/extractor/plutotv.py25
-rw-r--r--yt_dlp/extractor/podchaser.py14
-rw-r--r--yt_dlp/extractor/podomatic.py11
-rw-r--r--yt_dlp/extractor/pokemon.py14
-rw-r--r--yt_dlp/extractor/pokergo.py28
-rw-r--r--yt_dlp/extractor/polsatgo.py6
-rw-r--r--yt_dlp/extractor/polskieradio.py17
-rw-r--r--yt_dlp/extractor/popcorntimes.py5
-rw-r--r--yt_dlp/extractor/popcorntv.py2
-rw-r--r--yt_dlp/extractor/pornbox.py12
-rw-r--r--yt_dlp/extractor/pornflip.py2
-rw-r--r--yt_dlp/extractor/pornhub.py66
-rw-r--r--yt_dlp/extractor/pornotube.py11
-rw-r--r--yt_dlp/extractor/pornovoisines.py6
-rw-r--r--yt_dlp/extractor/pornoxo.py2
-rw-r--r--yt_dlp/extractor/pr0gramm.py2
-rw-r--r--yt_dlp/extractor/prankcast.py24
-rw-r--r--yt_dlp/extractor/premiershiprugby.py2
-rw-r--r--yt_dlp/extractor/presstv.py10
-rw-r--r--yt_dlp/extractor/projectveritas.py10
-rw-r--r--yt_dlp/extractor/prosiebensat1.py19
-rw-r--r--yt_dlp/extractor/prx.py68
-rw-r--r--yt_dlp/extractor/puhutv.py41
-rw-r--r--yt_dlp/extractor/puls4.py3
-rw-r--r--yt_dlp/extractor/pyvideo.py7
-rw-r--r--yt_dlp/extractor/qingting.py4
-rw-r--r--yt_dlp/extractor/qqmusic.py33
-rw-r--r--yt_dlp/extractor/r7.py6
-rw-r--r--yt_dlp/extractor/radiko.py8
-rw-r--r--yt_dlp/extractor/radiocanada.py8
-rw-r--r--yt_dlp/extractor/radiocomercial.py14
-rw-r--r--yt_dlp/extractor/radiode.py4
-rw-r--r--yt_dlp/extractor/radiofrance.py4
-rw-r--r--yt_dlp/extractor/radiojavan.py2
-rw-r--r--yt_dlp/extractor/radiokapital.py4
-rw-r--r--yt_dlp/extractor/radiozet.py2
-rw-r--r--yt_dlp/extractor/radlive.py10
-rw-r--r--yt_dlp/extractor/rai.py20
-rw-r--r--yt_dlp/extractor/raywenderlich.py16
-rw-r--r--yt_dlp/extractor/rbgtum.py10
-rw-r--r--yt_dlp/extractor/rcs.py30
-rw-r--r--yt_dlp/extractor/rcti.py30
-rw-r--r--yt_dlp/extractor/rds.py7
-rw-r--r--yt_dlp/extractor/redbee.py30
-rw-r--r--yt_dlp/extractor/redbulltv.py19
-rw-r--r--yt_dlp/extractor/redge.py4
-rw-r--r--yt_dlp/extractor/redgifs.py30
-rw-r--r--yt_dlp/extractor/redtube.py4
-rw-r--r--yt_dlp/extractor/rentv.py7
-rw-r--r--yt_dlp/extractor/restudy.py4
-rw-r--r--yt_dlp/extractor/reuters.py8
-rw-r--r--yt_dlp/extractor/reverbnation.py6
-rw-r--r--yt_dlp/extractor/ridehome.py4
-rw-r--r--yt_dlp/extractor/rinsefm.py14
-rw-r--r--yt_dlp/extractor/rmcdecouverte.py8
-rw-r--r--yt_dlp/extractor/rockstargames.py2
-rw-r--r--yt_dlp/extractor/rokfin.py18
-rw-r--r--yt_dlp/extractor/roosterteeth.py6
-rw-r--r--yt_dlp/extractor/rottentomatoes.py4
-rw-r--r--yt_dlp/extractor/rozhlas.py16
-rw-r--r--yt_dlp/extractor/rte.py2
-rw-r--r--yt_dlp/extractor/rtl2.py4
-rw-r--r--yt_dlp/extractor/rtlnl.py26
-rw-r--r--yt_dlp/extractor/rtnews.py60
-rw-r--r--yt_dlp/extractor/rtp.py2
-rw-r--r--yt_dlp/extractor/rtrfm.py4
-rw-r--r--yt_dlp/extractor/rts.py11
-rw-r--r--yt_dlp/extractor/rtvcplay.py6
-rw-r--r--yt_dlp/extractor/rtve.py20
-rw-r--r--yt_dlp/extractor/rtvs.py8
-rw-r--r--yt_dlp/extractor/rtvslo.py6
-rw-r--r--yt_dlp/extractor/rule34video.py8
-rw-r--r--yt_dlp/extractor/rumble.py26
-rw-r--r--yt_dlp/extractor/rutube.py15
-rw-r--r--yt_dlp/extractor/rutv.py10
-rw-r--r--yt_dlp/extractor/ruutu.py16
-rw-r--r--yt_dlp/extractor/ruv.py8
-rw-r--r--yt_dlp/extractor/s4c.py6
-rw-r--r--yt_dlp/extractor/safari.py29
-rw-r--r--yt_dlp/extractor/saitosan.py8
-rw-r--r--yt_dlp/extractor/samplefocus.py8
-rw-r--r--yt_dlp/extractor/sapo.py2
-rw-r--r--yt_dlp/extractor/sbscokr.py4
-rw-r--r--yt_dlp/extractor/screencast.py15
-rw-r--r--yt_dlp/extractor/screencastomatic.py2
-rw-r--r--yt_dlp/extractor/scrippsnetworks.py12
-rw-r--r--yt_dlp/extractor/scrolller.py14
-rw-r--r--yt_dlp/extractor/scte.py6
-rw-r--r--yt_dlp/extractor/senategov.py19
-rw-r--r--yt_dlp/extractor/sendtonews.py6
-rw-r--r--yt_dlp/extractor/servus.py2
-rw-r--r--yt_dlp/extractor/sevenplus.py7
-rw-r--r--yt_dlp/extractor/sexu.py2
-rw-r--r--yt_dlp/extractor/seznamzpravy.py12
-rw-r--r--yt_dlp/extractor/shahid.py18
-rw-r--r--yt_dlp/extractor/shemaroome.py21
-rw-r--r--yt_dlp/extractor/showroomlive.py9
-rw-r--r--yt_dlp/extractor/sibnet.py4
-rw-r--r--yt_dlp/extractor/simplecast.py6
-rw-r--r--yt_dlp/extractor/sina.py7
-rw-r--r--yt_dlp/extractor/sixplay.py11
-rw-r--r--yt_dlp/extractor/skeb.py16
-rw-r--r--yt_dlp/extractor/sky.py2
-rw-r--r--yt_dlp/extractor/skyit.py10
-rw-r--r--yt_dlp/extractor/skylinewebcams.py2
-rw-r--r--yt_dlp/extractor/skynewsarabia.py11
-rw-r--r--yt_dlp/extractor/skynewsau.py12
-rw-r--r--yt_dlp/extractor/slideshare.py8
-rw-r--r--yt_dlp/extractor/slideslive.py2
-rw-r--r--yt_dlp/extractor/slutload.py12
-rw-r--r--yt_dlp/extractor/snotr.py2
-rw-r--r--yt_dlp/extractor/sohu.py48
-rw-r--r--yt_dlp/extractor/sonyliv.py6
-rw-r--r--yt_dlp/extractor/soundcloud.py54
-rw-r--r--yt_dlp/extractor/soundgasm.py4
-rw-r--r--yt_dlp/extractor/southpark.py4
-rw-r--r--yt_dlp/extractor/spankbang.py11
-rw-r--r--yt_dlp/extractor/spiegel.py4
-rw-r--r--yt_dlp/extractor/sport5.py6
-rw-r--r--yt_dlp/extractor/sportdeutschland.py12
-rw-r--r--yt_dlp/extractor/spotify.py6
-rw-r--r--yt_dlp/extractor/spreaker.py23
-rw-r--r--yt_dlp/extractor/springboardplatform.py5
-rw-r--r--yt_dlp/extractor/srgssr.py15
-rw-r--r--yt_dlp/extractor/srmediathek.py2
-rw-r--r--yt_dlp/extractor/stageplus.py2
-rw-r--r--yt_dlp/extractor/stanfordoc.py18
-rw-r--r--yt_dlp/extractor/startrek.py4
-rw-r--r--yt_dlp/extractor/startv.py31
-rw-r--r--yt_dlp/extractor/steam.py24
-rw-r--r--yt_dlp/extractor/stitcher.py5
-rw-r--r--yt_dlp/extractor/storyfire.py8
-rw-r--r--yt_dlp/extractor/streamable.py10
-rw-r--r--yt_dlp/extractor/streamcz.py14
-rw-r--r--yt_dlp/extractor/streetvoice.py8
-rw-r--r--yt_dlp/extractor/stretchinternet.py2
-rw-r--r--yt_dlp/extractor/stripchat.py2
-rw-r--r--yt_dlp/extractor/stv.py9
-rw-r--r--yt_dlp/extractor/substack.py8
-rw-r--r--yt_dlp/extractor/sunporno.py4
-rw-r--r--yt_dlp/extractor/sverigesradio.py2
-rw-r--r--yt_dlp/extractor/svt.py43
-rw-r--r--yt_dlp/extractor/swearnet.py8
-rw-r--r--yt_dlp/extractor/syfy.py4
-rw-r--r--yt_dlp/extractor/syvdk.py4
-rw-r--r--yt_dlp/extractor/tagesschau.py4
-rw-r--r--yt_dlp/extractor/taptap.py24
-rw-r--r--yt_dlp/extractor/tbs.py19
-rw-r--r--yt_dlp/extractor/tbsjp.py6
-rw-r--r--yt_dlp/extractor/teachable.py35
-rw-r--r--yt_dlp/extractor/teachertube.py12
-rw-r--r--yt_dlp/extractor/ted.py22
-rw-r--r--yt_dlp/extractor/tele13.py2
-rw-r--r--yt_dlp/extractor/telecaribe.py2
-rw-r--r--yt_dlp/extractor/telecinco.py2
-rw-r--r--yt_dlp/extractor/telegraaf.py8
-rw-r--r--yt_dlp/extractor/telegram.py2
-rw-r--r--yt_dlp/extractor/telemb.py6
-rw-r--r--yt_dlp/extractor/telemundo.py4
-rw-r--r--yt_dlp/extractor/telequebec.py7
-rw-r--r--yt_dlp/extractor/teletask.py8
-rw-r--r--yt_dlp/extractor/telewebion.py2
-rw-r--r--yt_dlp/extractor/tempo.py18
-rw-r--r--yt_dlp/extractor/tencent.py2
-rw-r--r--yt_dlp/extractor/tennistv.py14
-rw-r--r--yt_dlp/extractor/tenplay.py6
-rw-r--r--yt_dlp/extractor/testurl.py2
-rw-r--r--yt_dlp/extractor/tf1.py4
-rw-r--r--yt_dlp/extractor/tfo.py4
-rw-r--r--yt_dlp/extractor/theguardian.py31
-rw-r--r--yt_dlp/extractor/theholetv.py6
-rw-r--r--yt_dlp/extractor/theintercept.py7
-rw-r--r--yt_dlp/extractor/theplatform.py32
-rw-r--r--yt_dlp/extractor/thestar.py2
-rw-r--r--yt_dlp/extractor/theweatherchannel.py8
-rw-r--r--yt_dlp/extractor/thisamericanlife.py4
-rw-r--r--yt_dlp/extractor/thisvid.py4
-rw-r--r--yt_dlp/extractor/threeqsdn.py6
-rw-r--r--yt_dlp/extractor/threespeak.py28
-rw-r--r--yt_dlp/extractor/tiktok.py42
-rw-r--r--yt_dlp/extractor/tmz.py6
-rw-r--r--yt_dlp/extractor/tnaflix.py13
-rw-r--r--yt_dlp/extractor/toggle.py24
-rw-r--r--yt_dlp/extractor/tonline.py4
-rw-r--r--yt_dlp/extractor/toongoggles.py2
-rw-r--r--yt_dlp/extractor/toutv.py2
-rw-r--r--yt_dlp/extractor/toypics.py8
-rw-r--r--yt_dlp/extractor/traileraddict.py4
-rw-r--r--yt_dlp/extractor/trovo.py4
-rw-r--r--yt_dlp/extractor/trtcocuk.py6
-rw-r--r--yt_dlp/extractor/trtworld.py10
-rw-r--r--yt_dlp/extractor/trueid.py6
-rw-r--r--yt_dlp/extractor/trutv.py2
-rw-r--r--yt_dlp/extractor/tube8.py10
-rw-r--r--yt_dlp/extractor/tubetugraz.py69
-rw-r--r--yt_dlp/extractor/tubitv.py8
-rw-r--r--yt_dlp/extractor/tumblr.py14
-rw-r--r--yt_dlp/extractor/tunein.py4
-rw-r--r--yt_dlp/extractor/turner.py9
-rw-r--r--yt_dlp/extractor/tv2.py18
-rw-r--r--yt_dlp/extractor/tv24ua.py6
-rw-r--r--yt_dlp/extractor/tv2dk.py2
-rw-r--r--yt_dlp/extractor/tv2hu.py19
-rw-r--r--yt_dlp/extractor/tv4.py2
-rw-r--r--yt_dlp/extractor/tv5unis.py12
-rw-r--r--yt_dlp/extractor/tvanouvelles.py4
-rw-r--r--yt_dlp/extractor/tvc.py2
-rw-r--r--yt_dlp/extractor/tver.py2
-rw-r--r--yt_dlp/extractor/tvigle.py8
-rw-r--r--yt_dlp/extractor/tviplayer.py8
-rw-r--r--yt_dlp/extractor/tvn24.py4
-rw-r--r--yt_dlp/extractor/tvnoe.py4
-rw-r--r--yt_dlp/extractor/tvp.py13
-rw-r--r--yt_dlp/extractor/tvplay.py14
-rw-r--r--yt_dlp/extractor/tvplayer.py7
-rw-r--r--yt_dlp/extractor/tweakers.py4
-rw-r--r--yt_dlp/extractor/twentymin.py6
-rw-r--r--yt_dlp/extractor/twentythreevideo.py4
-rw-r--r--yt_dlp/extractor/twitcasting.py14
-rw-r--r--yt_dlp/extractor/twitch.py114
-rw-r--r--yt_dlp/extractor/twitter.py78
-rw-r--r--yt_dlp/extractor/txxx.py38
-rw-r--r--yt_dlp/extractor/udemy.py44
-rw-r--r--yt_dlp/extractor/udn.py8
-rw-r--r--yt_dlp/extractor/uktvplay.py2
-rw-r--r--yt_dlp/extractor/umg.py4
-rw-r--r--yt_dlp/extractor/unistra.py8
-rw-r--r--yt_dlp/extractor/unity.py2
-rw-r--r--yt_dlp/extractor/uol.py16
-rw-r--r--yt_dlp/extractor/urort.py12
-rw-r--r--yt_dlp/extractor/urplay.py8
-rw-r--r--yt_dlp/extractor/usatoday.py7
-rw-r--r--yt_dlp/extractor/ustream.py33
-rw-r--r--yt_dlp/extractor/ustudio.py12
-rw-r--r--yt_dlp/extractor/utreon.py10
-rw-r--r--yt_dlp/extractor/veo.py6
-rw-r--r--yt_dlp/extractor/veoh.py16
-rw-r--r--yt_dlp/extractor/vesti.py4
-rw-r--r--yt_dlp/extractor/vevo.py43
-rw-r--r--yt_dlp/extractor/vgtv.py19
-rw-r--r--yt_dlp/extractor/vh1.py2
-rw-r--r--yt_dlp/extractor/vice.py16
-rw-r--r--yt_dlp/extractor/viddler.py6
-rw-r--r--yt_dlp/extractor/videa.py7
-rw-r--r--yt_dlp/extractor/videocampus_sachsen.py34
-rw-r--r--yt_dlp/extractor/videofyme.py2
-rw-r--r--yt_dlp/extractor/videoken.py2
-rw-r--r--yt_dlp/extractor/videomore.py13
-rw-r--r--yt_dlp/extractor/videopress.py6
-rw-r--r--yt_dlp/extractor/vidio.py22
-rw-r--r--yt_dlp/extractor/vidlii.py4
-rw-r--r--yt_dlp/extractor/vidly.py2
-rw-r--r--yt_dlp/extractor/viewlift.py28
-rw-r--r--yt_dlp/extractor/viidea.py23
-rw-r--r--yt_dlp/extractor/viki.py28
-rw-r--r--yt_dlp/extractor/vimeo.py52
-rw-r--r--yt_dlp/extractor/vine.py15
-rw-r--r--yt_dlp/extractor/viously.py2
-rw-r--r--yt_dlp/extractor/viqeo.py2
-rw-r--r--yt_dlp/extractor/viu.py39
-rw-r--r--yt_dlp/extractor/vk.py10
-rw-r--r--yt_dlp/extractor/vodplatform.py2
-rw-r--r--yt_dlp/extractor/voicy.py19
-rw-r--r--yt_dlp/extractor/volejtv.py4
-rw-r--r--yt_dlp/extractor/voxmedia.py9
-rw-r--r--yt_dlp/extractor/vrt.py22
-rw-r--r--yt_dlp/extractor/vtm.py4
-rw-r--r--yt_dlp/extractor/vuclip.py10
-rw-r--r--yt_dlp/extractor/vvvvid.py22
-rw-r--r--yt_dlp/extractor/walla.py4
-rw-r--r--yt_dlp/extractor/washingtonpost.py6
-rw-r--r--yt_dlp/extractor/wat.py5
-rw-r--r--yt_dlp/extractor/wdr.py29
-rw-r--r--yt_dlp/extractor/webcamerapl.py4
-rw-r--r--yt_dlp/extractor/webcaster.py2
-rw-r--r--yt_dlp/extractor/webofstories.py18
-rw-r--r--yt_dlp/extractor/weibo.py10
-rw-r--r--yt_dlp/extractor/wevidi.py12
-rw-r--r--yt_dlp/extractor/whowatch.py21
-rw-r--r--yt_dlp/extractor/wikimedia.py4
-rw-r--r--yt_dlp/extractor/wimtv.py26
-rw-r--r--yt_dlp/extractor/wistia.py24
-rw-r--r--yt_dlp/extractor/wordpress.py14
-rw-r--r--yt_dlp/extractor/worldstarhiphop.py4
-rw-r--r--yt_dlp/extractor/wppilot.py4
-rw-r--r--yt_dlp/extractor/wsj.py6
-rw-r--r--yt_dlp/extractor/wwe.py7
-rw-r--r--yt_dlp/extractor/wykop.py2
-rw-r--r--yt_dlp/extractor/xanimu.py19
-rw-r--r--yt_dlp/extractor/xboxclips.py4
-rw-r--r--yt_dlp/extractor/xhamster.py27
-rw-r--r--yt_dlp/extractor/xiaohongshu.py4
-rw-r--r--yt_dlp/extractor/ximalaya.py30
-rw-r--r--yt_dlp/extractor/xinpianchang.py4
-rw-r--r--yt_dlp/extractor/xminus.py2
-rw-r--r--yt_dlp/extractor/xnxx.py2
-rw-r--r--yt_dlp/extractor/xstream.py5
-rw-r--r--yt_dlp/extractor/xvideos.py48
-rw-r--r--yt_dlp/extractor/xxxymovies.py2
-rw-r--r--yt_dlp/extractor/yahoo.py16
-rw-r--r--yt_dlp/extractor/yandexdisk.py4
-rw-r--r--yt_dlp/extractor/yandexmusic.py73
-rw-r--r--yt_dlp/extractor/yandexvideo.py12
-rw-r--r--yt_dlp/extractor/yapfiles.py6
-rw-r--r--yt_dlp/extractor/yappy.py12
-rw-r--r--yt_dlp/extractor/yle_areena.py10
-rw-r--r--yt_dlp/extractor/youjizz.py2
-rw-r--r--yt_dlp/extractor/youku.py4
-rw-r--r--yt_dlp/extractor/younow.py45
-rw-r--r--yt_dlp/extractor/youporn.py6
-rw-r--r--yt_dlp/extractor/youtube.py337
-rw-r--r--yt_dlp/extractor/zaiko.py2
-rw-r--r--yt_dlp/extractor/zapiks.py4
-rw-r--r--yt_dlp/extractor/zattoo.py49
-rw-r--r--yt_dlp/extractor/zdf.py31
-rw-r--r--yt_dlp/extractor/zee5.py33
-rw-r--r--yt_dlp/extractor/zeenews.py6
-rw-r--r--yt_dlp/extractor/zenporn.py8
-rw-r--r--yt_dlp/extractor/zetland.py4
-rw-r--r--yt_dlp/extractor/zhihu.py2
-rw-r--r--yt_dlp/extractor/zingmp3.py12
-rw-r--r--yt_dlp/extractor/zoom.py10
-rw-r--r--yt_dlp/extractor/zype.py6
-rw-r--r--yt_dlp/jsinterp.py20
-rw-r--r--yt_dlp/networking/__init__.py2
-rw-r--r--yt_dlp/networking/_curlcffi.py2
-rw-r--r--yt_dlp/networking/_helper.py4
-rw-r--r--yt_dlp/networking/_requests.py20
-rw-r--r--yt_dlp/networking/_urllib.py8
-rw-r--r--yt_dlp/networking/_websockets.py6
-rw-r--r--yt_dlp/networking/common.py28
-rw-r--r--yt_dlp/networking/exceptions.py2
-rw-r--r--yt_dlp/networking/impersonate.py6
-rw-r--r--yt_dlp/options.py42
-rw-r--r--yt_dlp/postprocessor/__init__.py2
-rw-r--r--yt_dlp/postprocessor/common.py6
-rw-r--r--yt_dlp/postprocessor/embedthumbnail.py19
-rw-r--r--yt_dlp/postprocessor/exec.py5
-rw-r--r--yt_dlp/postprocessor/ffmpeg.py57
-rw-r--r--yt_dlp/postprocessor/modify_chapters.py2
-rw-r--r--yt_dlp/postprocessor/movefilesafterdownload.py7
-rw-r--r--yt_dlp/postprocessor/sponskrub.py4
-rw-r--r--yt_dlp/postprocessor/sponsorblock.py10
-rw-r--r--yt_dlp/socks.py8
-rw-r--r--yt_dlp/update.py10
-rw-r--r--yt_dlp/utils/_legacy.py10
-rw-r--r--yt_dlp/utils/_utils.py199
-rw-r--r--yt_dlp/utils/networking.py4
-rw-r--r--yt_dlp/webvtt.py17
915 files changed, 7028 insertions, 7247 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 837b600e3..aeba3c44d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -266,7 +266,7 @@ After you have ensured this site is distributing its content legally, you can fo
$ hatch fmt --check
```
- You can use `hatch fmt` to automatically fix problems.
+ You can use `hatch fmt` to automatically fix problems. Rules that the linter/formatter enforces should not be disabled with `# noqa` unless a maintainer requests it. The only exception allowed is for old/printf-style string formatting in GraphQL query templates (use `# noqa: UP031`).
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:
diff --git a/bundle/py2exe.py b/bundle/py2exe.py
index 5fbe55e46..5b7f4883b 100755
--- a/bundle/py2exe.py
+++ b/bundle/py2exe.py
@@ -44,7 +44,7 @@ def main():
'Cryptodome',
# requests >=2.32.0 breaks py2exe builds due to certifi dependency
'requests',
- 'urllib3'
+ 'urllib3',
],
'dll_excludes': ['w9xpopen.exe', 'crypt32.dll'],
# Modules that are only imported dynamically must be added here
diff --git a/bundle/pyinstaller.py b/bundle/pyinstaller.py
index db9dbfde5..4184c4bc9 100755
--- a/bundle/pyinstaller.py
+++ b/bundle/pyinstaller.py
@@ -68,7 +68,7 @@ def exe(onedir):
'dist/',
onedir and f'{name}/',
name,
- OS_NAME == 'win32' and '.exe'
+ OS_NAME == 'win32' and '.exe',
)))
@@ -113,7 +113,7 @@ def windows_set_version(exe, version):
),
kids=[
StringFileInfo([StringTable('040904B0', [
- StringStruct('Comments', 'yt-dlp%s Command Line Interface' % suffix),
+ StringStruct('Comments', f'yt-dlp{suffix} Command Line Interface'),
StringStruct('CompanyName', 'https://github.com/yt-dlp'),
StringStruct('FileDescription', 'yt-dlp%s' % (MACHINE and f' ({MACHINE})')),
StringStruct('FileVersion', version),
@@ -123,8 +123,8 @@ def windows_set_version(exe, version):
StringStruct('ProductName', f'yt-dlp{suffix}'),
StringStruct(
'ProductVersion', f'{version}{suffix} on Python {platform.python_version()}'),
- ])]), VarFileInfo([VarStruct('Translation', [0, 1200])])
- ]
+ ])]), VarFileInfo([VarStruct('Translation', [0, 1200])]),
+ ],
))
diff --git a/devscripts/bash-completion.py b/devscripts/bash-completion.py
index 9b4a9d4e2..3918ebde8 100755
--- a/devscripts/bash-completion.py
+++ b/devscripts/bash-completion.py
@@ -9,8 +9,8 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import yt_dlp
-BASH_COMPLETION_FILE = "completions/bash/yt-dlp"
-BASH_COMPLETION_TEMPLATE = "devscripts/bash-completion.in"
+BASH_COMPLETION_FILE = 'completions/bash/yt-dlp'
+BASH_COMPLETION_TEMPLATE = 'devscripts/bash-completion.in'
def build_completion(opt_parser):
@@ -21,9 +21,9 @@ def build_completion(opt_parser):
opts_flag.append(option.get_opt_string())
with open(BASH_COMPLETION_TEMPLATE) as f:
template = f.read()
- with open(BASH_COMPLETION_FILE, "w") as f:
+ with open(BASH_COMPLETION_FILE, 'w') as f:
# just using the special char
- filled_template = template.replace("{{flags}}", " ".join(opts_flag))
+ filled_template = template.replace('{{flags}}', ' '.join(opts_flag))
f.write(filled_template)
diff --git a/devscripts/make_changelog.py b/devscripts/make_changelog.py
index 8e199e7d0..00634fb91 100644
--- a/devscripts/make_changelog.py
+++ b/devscripts/make_changelog.py
@@ -223,10 +223,10 @@ class Changelog:
return message if not sep else f'{message}{sep}{rest}'
- def _format_message_link(self, message, hash):
- assert message or hash, 'Improperly defined commit message or override'
- message = message if message else hash[:HASH_LENGTH]
- return f'[{message}]({self.repo_url}/commit/{hash})' if hash else message
+ def _format_message_link(self, message, commit_hash):
+ assert message or commit_hash, 'Improperly defined commit message or override'
+ message = message if message else commit_hash[:HASH_LENGTH]
+ return f'[{message}]({self.repo_url}/commit/{commit_hash})' if commit_hash else message
def _format_issues(self, issues):
return ', '.join(f'[#{issue}]({self.repo_url}/issues/{issue})' for issue in issues)
@@ -356,7 +356,7 @@ class CommitRange:
logger.info(f'CHANGE {self._commits[commit.hash]} -> {commit}')
self._commits[commit.hash] = commit
- self._commits = {key: value for key, value in reversed(self._commits.items())}
+ self._commits = dict(reversed(self._commits.items()))
def groups(self):
group_dict = defaultdict(list)
diff --git a/devscripts/make_readme.py b/devscripts/make_readme.py
index 2270b31d3..cbb5859aa 100755
--- a/devscripts/make_readme.py
+++ b/devscripts/make_readme.py
@@ -51,7 +51,7 @@ PATCHES = (
),
( # Headings
r'(?m)^ (\w.+\n)( (?=\w))?',
- r'## \1'
+ r'## \1',
),
( # Fixup `--date` formatting
rf'(?m)( --date DATE.+({delim}[^\[]+)*)\[.+({delim}.+)*$',
@@ -61,26 +61,26 @@ PATCHES = (
),
( # Do not split URLs
rf'({delim[:-1]})? (?P<label>\[\S+\] )?(?P<url>https?({delim})?:({delim})?/({delim})?/(({delim})?\S+)+)\s',
- lambda mobj: ''.join((delim, mobj.group('label') or '', re.sub(r'\s+', '', mobj.group('url')), '\n'))
+ lambda mobj: ''.join((delim, mobj.group('label') or '', re.sub(r'\s+', '', mobj.group('url')), '\n')),
),
( # Do not split "words"
rf'(?m)({delim}\S+)+$',
- lambda mobj: ''.join((delim, mobj.group(0).replace(delim, '')))
+ lambda mobj: ''.join((delim, mobj.group(0).replace(delim, ''))),
),
( # Allow overshooting last line
rf'(?m)^(?P<prev>.+)${delim}(?P<current>.+)$(?!{delim})',
lambda mobj: (mobj.group().replace(delim, ' ')
if len(mobj.group()) - len(delim) + 1 <= max_width + ALLOWED_OVERSHOOT
- else mobj.group())
+ else mobj.group()),
),
( # Avoid newline when a space is available b/w switch and description
DISABLE_PATCH, # This creates issues with prepare_manpage
r'(?m)^(\s{4}-.{%d})(%s)' % (switch_col_width - 6, delim),
- r'\1 '
+ r'\1 ',
),
( # Replace brackets with a Markdown link
r'SponsorBlock API \((http.+)\)',
- r'[SponsorBlock API](\1)'
+ r'[SponsorBlock API](\1)',
),
)
diff --git a/devscripts/set-variant.py b/devscripts/set-variant.py
index 10341e744..24ce4552d 100644
--- a/devscripts/set-variant.py
+++ b/devscripts/set-variant.py
@@ -30,7 +30,7 @@ def property_setter(name, value):
opts = parse_options()
transform = compose_functions(
property_setter('VARIANT', opts.variant),
- property_setter('UPDATE_HINT', opts.update_message)
+ property_setter('UPDATE_HINT', opts.update_message),
)
write_file(VERSION_FILE, transform(read_file(VERSION_FILE)))
diff --git a/devscripts/update-version.py b/devscripts/update-version.py
index 07a071745..2018ba844 100644
--- a/devscripts/update-version.py
+++ b/devscripts/update-version.py
@@ -24,7 +24,7 @@ def get_new_version(version, revision):
else:
old_version = read_version().split('.')
if version.split('.') == old_version[:3]:
- revision = str(int((old_version + [0])[3]) + 1)
+ revision = str(int(([*old_version, 0])[3]) + 1)
return f'{version}.{revision}' if revision else version
diff --git a/devscripts/zsh-completion.py b/devscripts/zsh-completion.py
index 267af5f6e..8e190c00c 100755
--- a/devscripts/zsh-completion.py
+++ b/devscripts/zsh-completion.py
@@ -9,15 +9,15 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import yt_dlp
-ZSH_COMPLETION_FILE = "completions/zsh/_yt-dlp"
-ZSH_COMPLETION_TEMPLATE = "devscripts/zsh-completion.in"
+ZSH_COMPLETION_FILE = 'completions/zsh/_yt-dlp'
+ZSH_COMPLETION_TEMPLATE = 'devscripts/zsh-completion.in'
def build_completion(opt_parser):
opts = [opt for group in opt_parser.option_groups
for opt in group.option_list]
- opts_file = [opt for opt in opts if opt.metavar == "FILE"]
- opts_dir = [opt for opt in opts if opt.metavar == "DIR"]
+ opts_file = [opt for opt in opts if opt.metavar == 'FILE']
+ opts_dir = [opt for opt in opts if opt.metavar == 'DIR']
fileopts = []
for opt in opts_file:
@@ -38,11 +38,11 @@ def build_completion(opt_parser):
with open(ZSH_COMPLETION_TEMPLATE) as f:
template = f.read()
- template = template.replace("{{fileopts}}", "|".join(fileopts))
- template = template.replace("{{diropts}}", "|".join(diropts))
- template = template.replace("{{flags}}", " ".join(flags))
+ template = template.replace('{{fileopts}}', '|'.join(fileopts))
+ template = template.replace('{{diropts}}', '|'.join(diropts))
+ template = template.replace('{{flags}}', ' '.join(flags))
- with open(ZSH_COMPLETION_FILE, "w") as f:
+ with open(ZSH_COMPLETION_FILE, 'w') as f:
f.write(template)
diff --git a/pyproject.toml b/pyproject.toml
index da6403ec7..01162b794 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -183,21 +183,84 @@ line-length = 120
[tool.ruff.lint]
ignore = [
- "E402", # module level import not at top of file
- "E501", # line too long
- "E731", # do not assign a lambda expression, use a def
- "E741", # ambiguous variable name
+ "E402", # module-import-not-at-top-of-file
+ "E501", # line-too-long
+ "E731", # lambda-assignment
+ "E741", # ambiguous-variable-name
+ "UP036", # outdated-version-block
+ "B006", # mutable-argument-default
+ "B008", # function-call-in-default-argument
+ "B011", # assert-false
+ "B017", # assert-raises-exception
+ "B023", # function-uses-loop-variable (false positives)
+ "B028", # no-explicit-stacklevel
+ "B904", # raise-without-from-inside-except
+ "C401", # unnecessary-generator-set
+ "C402", # unnecessary-generator-dict
+ "PIE790", # unnecessary-placeholder
+ "SIM102", # collapsible-if
+ "SIM108", # if-else-block-instead-of-if-exp
+ "SIM112", # uncapitalized-environment-variables
+ "SIM113", # enumerate-for-loop
+ "SIM114", # if-with-same-arms
+ "SIM115", # open-file-with-context-handler
+ "SIM117", # multiple-with-statements
+ "SIM223", # expr-and-false
+ "SIM300", # yoda-conditions
+ "TD001", # invalid-todo-tag
+ "TD002", # missing-todo-author
+ "TD003", # missing-todo-link
+ "PLE0604", # invalid-all-object (false positives)
+ "PLW0603", # global-statement
+ "PLW1510", # subprocess-run-without-check
+ "PLW2901", # redefined-loop-name
+ "RUF001", # ambiguous-unicode-character-string
+ "RUF012", # mutable-class-default
+ "RUF100", # unused-noqa (flake8 has slightly different behavior)
]
select = [
- "E", # pycodestyle errors
- "W", # pycodestyle warnings
- "F", # pyflakes
- "I", # import order
+ "E", # pycodestyle Error
+ "W", # pycodestyle Warning
+ "F", # Pyflakes
+ "I", # isort
+ "Q", # flake8-quotes
+ "N803", # invalid-argument-name
+ "N804", # invalid-first-argument-name-for-class-method
+ "UP", # pyupgrade
+ "B", # flake8-bugbear
+ "A", # flake8-builtins
+ "COM", # flake8-commas
+ "C4", # flake8-comprehensions
+ "FA", # flake8-future-annotations
+ "ISC", # flake8-implicit-str-concat
+ "ICN003", # banned-import-from
+ "PIE", # flake8-pie
+ "T20", # flake8-print
+ "RSE", # flake8-raise
+ "RET504", # unnecessary-assign
+ "SIM", # flake8-simplify
+ "TID251", # banned-api
+ "TD", # flake8-todos
+ "PLC", # Pylint Convention
+ "PLE", # Pylint Error
+ "PLW", # Pylint Warning
+ "RUF", # Ruff-specific rules
]
[tool.ruff.lint.per-file-ignores]
-"devscripts/lazy_load_template.py" = ["F401"]
-"!yt_dlp/extractor/**.py" = ["I"]
+"devscripts/lazy_load_template.py" = [
+ "F401", # unused-import
+]
+"!yt_dlp/extractor/**.py" = [
+ "I", # isort
+ "ICN003", # banned-import-from
+ "T20", # flake8-print
+ "A002", # builtin-argument-shadowing
+ "C408", # unnecessary-collection-call
+]
+"yt_dlp/jsinterp.py" = [
+ "UP031", # printf-string-formatting
+]
[tool.ruff.lint.isort]
known-first-party = [
@@ -207,6 +270,50 @@ known-first-party = [
]
relative-imports-order = "closest-to-furthest"
+[tool.ruff.lint.flake8-quotes]
+docstring-quotes = "double"
+multiline-quotes = "single"
+inline-quotes = "single"
+avoid-escape = false
+
+[tool.ruff.lint.pep8-naming]
+classmethod-decorators = [
+ "yt_dlp.utils.classproperty",
+]
+
+[tool.ruff.lint.flake8-import-conventions]
+banned-from = [
+ "base64",
+ "datetime",
+ "functools",
+ "glob",
+ "hashlib",
+ "itertools",
+ "json",
+ "math",
+ "os",
+ "pathlib",
+ "random",
+ "re",
+ "string",
+ "sys",
+ "time",
+ "urllib",
+ "uuid",
+ "xml",
+]
+
+[tool.ruff.lint.flake8-tidy-imports.banned-api]
+"yt_dlp.compat.compat_str".msg = "Use `str` instead."
+"yt_dlp.compat.compat_b64decode".msg = "Use `base64.b64decode` instead."
+"yt_dlp.compat.compat_urlparse".msg = "Use `urllib.parse` instead."
+"yt_dlp.compat.compat_parse_qs".msg = "Use `urllib.parse.parse_qs` instead."
+"yt_dlp.compat.compat_urllib_parse_unquote".msg = "Use `urllib.parse.unquote` instead."
+"yt_dlp.compat.compat_urllib_parse_urlencode".msg = "Use `urllib.parse.urlencode` instead."
+"yt_dlp.compat.compat_urllib_parse_urlparse".msg = "Use `urllib.parse.urlparse` instead."
+"yt_dlp.compat.compat_shlex_quote".msg = "Use `yt_dlp.utils.shell_quote` instead."
+"yt_dlp.utils.error_to_compat_str".msg = "Use `str` instead."
+
[tool.autopep8]
max_line_length = 120
recursive = true
diff --git a/test/conftest.py b/test/conftest.py
index decd2c85c..a8b92f811 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -22,8 +22,8 @@ def handler(request):
class HandlerWrapper(handler):
RH_KEY = handler.RH_KEY
- def __init__(self, *args, **kwargs):
- super().__init__(logger=FakeLogger, *args, **kwargs)
+ def __init__(self, **kwargs):
+ super().__init__(logger=FakeLogger, **kwargs)
return HandlerWrapper
@@ -54,11 +54,11 @@ def skip_handlers_if(request, handler):
def pytest_configure(config):
config.addinivalue_line(
- "markers", "skip_handler(handler): skip test for the given handler",
+ 'markers', 'skip_handler(handler): skip test for the given handler',
)
config.addinivalue_line(
- "markers", "skip_handler_if(handler): skip test for the given handler if condition is true"
+ 'markers', 'skip_handler_if(handler): skip test for the given handler if condition is true',
)
config.addinivalue_line(
- "markers", "skip_handlers_if(handler): skip test for handlers when the condition is true"
+ 'markers', 'skip_handlers_if(handler): skip test for handlers when the condition is true',
)
diff --git a/test/helper.py b/test/helper.py
index e7473120d..3b550d192 100644
--- a/test/helper.py
+++ b/test/helper.py
@@ -16,8 +16,8 @@ if 'pytest' in sys.modules:
import pytest
is_download_test = pytest.mark.download
else:
- def is_download_test(testClass):
- return testClass
+ def is_download_test(test_class):
+ return test_class
def get_params(override=None):
@@ -45,10 +45,10 @@ def try_rm(filename):
def report_warning(message, *args, **kwargs):
- '''
+ """
Print the message to stderr, it will be prefixed with 'WARNING:'
If stderr is a tty file the 'WARNING:' will be colored
- '''
+ """
if sys.stderr.isatty() and compat_os_name != 'nt':
_msg_header = '\033[0;33mWARNING:\033[0m'
else:
@@ -138,15 +138,14 @@ def expect_value(self, got, expected, field):
elif isinstance(expected, list) and isinstance(got, list):
self.assertEqual(
len(expected), len(got),
- 'Expect a list of length %d, but got a list of length %d for field %s' % (
- len(expected), len(got), field))
+ f'Expect a list of length {len(expected)}, but got a list of length {len(got)} for field {field}')
for index, (item_got, item_expected) in enumerate(zip(got, expected)):
type_got = type(item_got)
type_expected = type(item_expected)
self.assertEqual(
type_expected, type_got,
- 'Type mismatch for list item at index %d for field %s, expected %r, got %r' % (
- index, field, type_expected, type_got))
+ f'Type mismatch for list item at index {index} for field {field}, '
+ f'expected {type_expected!r}, got {type_got!r}')
expect_value(self, item_got, item_expected, field)
else:
if isinstance(expected, str) and expected.startswith('md5:'):
@@ -224,7 +223,7 @@ def sanitize_got_info_dict(got_dict):
test_info_dict.pop('display_id')
# Remove deprecated fields
- for old in YoutubeDL._deprecated_multivalue_fields.keys():
+ for old in YoutubeDL._deprecated_multivalue_fields:
test_info_dict.pop(old, None)
# release_year may be generated from release_date
@@ -246,11 +245,11 @@ def expect_info_dict(self, got_dict, expected_dict):
if expected_dict.get('ext'):
mandatory_fields.extend(('url', 'ext'))
for key in mandatory_fields:
- self.assertTrue(got_dict.get(key), 'Missing mandatory field %s' % key)
+ self.assertTrue(got_dict.get(key), f'Missing mandatory field {key}')
# Check for mandatory fields that are automatically set by YoutubeDL
if got_dict.get('_type', 'video') == 'video':
for key in ['webpage_url', 'extractor', 'extractor_key']:
- self.assertTrue(got_dict.get(key), 'Missing field: %s' % key)
+ self.assertTrue(got_dict.get(key), f'Missing field: {key}')
test_info_dict = sanitize_got_info_dict(got_dict)
@@ -258,7 +257,7 @@ def expect_info_dict(self, got_dict, expected_dict):
if missing_keys:
def _repr(v):
if isinstance(v, str):
- return "'%s'" % v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n')
+ return "'{}'".format(v.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n'))
elif isinstance(v, type):
return v.__name__
else:
@@ -275,8 +274,7 @@ def expect_info_dict(self, got_dict, expected_dict):
write_string(info_dict_str.replace('\n', '\n '), out=sys.stderr)
self.assertFalse(
missing_keys,
- 'Missing keys in test definition: %s' % (
- ', '.join(sorted(missing_keys))))
+ 'Missing keys in test definition: {}'.format(', '.join(sorted(missing_keys))))
def assertRegexpMatches(self, text, regexp, msg=None):
@@ -285,9 +283,9 @@ def assertRegexpMatches(self, text, regexp, msg=None):
else:
m = re.match(regexp, text)
if not m:
- note = 'Regexp didn\'t match: %r not found' % (regexp)
+ note = f'Regexp didn\'t match: {regexp!r} not found'
if len(text) < 1000:
- note += ' in %r' % text
+ note += f' in {text!r}'
if msg is None:
msg = note
else:
@@ -310,7 +308,7 @@ def assertLessEqual(self, got, expected, msg=None):
def assertEqual(self, got, expected, msg=None):
- if not (got == expected):
+ if got != expected:
if msg is None:
msg = f'{got!r} not equal to {expected!r}'
self.assertTrue(got == expected, msg)
diff --git a/test/test_InfoExtractor.py b/test/test_InfoExtractor.py
index 744587e45..31e8f8244 100644
--- a/test/test_InfoExtractor.py
+++ b/test/test_InfoExtractor.py
@@ -262,19 +262,19 @@ class TestInfoExtractor(unittest.TestCase):
''',
{
'chapters': [
- {"title": "Explosie Turnhout", "start_time": 70, "end_time": 440},
- {"title": "Jaarwisseling", "start_time": 440, "end_time": 1179},
- {"title": "Natuurbranden Colorado", "start_time": 1179, "end_time": 1263},
- {"title": "Klimaatverandering", "start_time": 1263, "end_time": 1367},
- {"title": "Zacht weer", "start_time": 1367, "end_time": 1383},
- {"title": "Financiële balans", "start_time": 1383, "end_time": 1484},
- {"title": "Club Brugge", "start_time": 1484, "end_time": 1575},
- {"title": "Mentale gezondheid bij topsporters", "start_time": 1575, "end_time": 1728},
- {"title": "Olympische Winterspelen", "start_time": 1728, "end_time": 1873},
- {"title": "Sober oudjaar in Nederland", "start_time": 1873, "end_time": 2079.23}
+ {'title': 'Explosie Turnhout', 'start_time': 70, 'end_time': 440},
+ {'title': 'Jaarwisseling', 'start_time': 440, 'end_time': 1179},
+ {'title': 'Natuurbranden Colorado', 'start_time': 1179, 'end_time': 1263},
+ {'title': 'Klimaatverandering', 'start_time': 1263, 'end_time': 1367},
+ {'title': 'Zacht weer', 'start_time': 1367, 'end_time': 1383},
+ {'title': 'Financiële balans', 'start_time': 1383, 'end_time': 1484},
+ {'title': 'Club Brugge', 'start_time': 1484, 'end_time': 1575},
+ {'title': 'Mentale gezondheid bij topsporters', 'start_time': 1575, 'end_time': 1728},
+ {'title': 'Olympische Winterspelen', 'start_time': 1728, 'end_time': 1873},
+ {'title': 'Sober oudjaar in Nederland', 'start_time': 1873, 'end_time': 2079.23},
],
- 'title': 'Het journaal - Aflevering 365 (Seizoen 2021)'
- }, {}
+ 'title': 'Het journaal - Aflevering 365 (Seizoen 2021)',
+ }, {},
),
(
# test multiple thumbnails in a list
@@ -301,13 +301,13 @@ class TestInfoExtractor(unittest.TestCase):
'thumbnails': [{'url': 'https://www.rainews.it/cropgd/640x360/dl/img/2021/12/30/1640886376927_GettyImages.jpg'}],
},
{},
- )
+ ),
]
for html, expected_dict, search_json_ld_kwargs in _TESTS:
expect_dict(
self,
self.ie._search_json_ld(html, None, **search_json_ld_kwargs),
- expected_dict
+ expected_dict,
)
def test_download_json(self):
@@ -366,7 +366,7 @@ class TestInfoExtractor(unittest.TestCase):
'height': 740,
'tbr': 1500,
}],
- 'thumbnail': '//pics.r18.com/digital/amateur/mgmr105/mgmr105jp.jpg'
+ 'thumbnail': '//pics.r18.com/digital/amateur/mgmr105/mgmr105jp.jpg',
})
# from https://www.csfd.cz/
@@ -419,9 +419,9 @@ class TestInfoExtractor(unittest.TestCase):
'height': 1080,
}],
'subtitles': {
- 'cs': [{'url': 'https://video.csfd.cz/files/subtitles/163/344/163344115_4c388b.srt'}]
+ 'cs': [{'url': 'https://video.csfd.cz/files/subtitles/163/344/163344115_4c388b.srt'}],
},
- 'thumbnail': 'https://img.csfd.cz/files/images/film/video/preview/163/344/163344118_748d20.png?h360'
+ 'thumbnail': 'https://img.csfd.cz/files/images/film/video/preview/163/344/163344118_748d20.png?h360',
})
# from https://tamasha.com/v/Kkdjw
@@ -452,7 +452,7 @@ class TestInfoExtractor(unittest.TestCase):
'ext': 'mp4',
'format_id': '144p',
'height': 144,
- }]
+ }],
})
# from https://www.directvnow.com
@@ -470,7 +470,7 @@ class TestInfoExtractor(unittest.TestCase):
'formats': [{
'ext': 'mp4',
'url': 'https://cdn.directv.com/content/dam/dtv/prod/website_directvnow-international/videos/DTVN_hdr_HBO_v3.mp4',
- }]
+ }],
})
# from https://www.directvnow.com
@@ -488,7 +488,7 @@ class TestInfoExtractor(unittest.TestCase):
'formats': [{
'url': 'https://cdn.directv.com/content/dam/dtv/prod/website_directvnow-international/videos/DTVN_hdr_HBO_v3.mp4',
'ext': 'mp4',
- }]
+ }],
})
# from https://www.klarna.com/uk/
@@ -547,8 +547,8 @@ class TestInfoExtractor(unittest.TestCase):
'id': 'XEgvuql4',
'formats': [{
'url': 'rtmp://192.138.214.154/live/sjclive',
- 'ext': 'flv'
- }]
+ 'ext': 'flv',
+ }],
})
# from https://www.pornoxo.com/videos/7564/striptease-from-sexy-secretary/
@@ -588,8 +588,8 @@ class TestInfoExtractor(unittest.TestCase):
'thumbnail': 'https://t03.vipstreamservice.com/thumbs/pxo-full/2009-12/14/a4b2157147afe5efa93ce1978e0265289c193874e02597.flv-full-13.jpg',
'formats': [{
'url': 'https://cdn.pornoxo.com/key=MF+oEbaxqTKb50P-w9G3nA,end=1489689259,ip=104.199.146.27/ip=104.199.146.27/speed=6573765/buffer=3.0/2009-12/4b2157147afe5efa93ce1978e0265289c193874e02597.flv',
- 'ext': 'flv'
- }]
+ 'ext': 'flv',
+ }],
})
# from http://www.indiedb.com/games/king-machine/videos
@@ -610,12 +610,12 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'formats': [{
'url': 'http://cdn.dbolical.com/cache/videos/games/1/50/49678/encode_mp4/king-machine-trailer.mp4',
'height': 360,
- 'ext': 'mp4'
+ 'ext': 'mp4',
}, {
'url': 'http://cdn.dbolical.com/cache/videos/games/1/50/49678/encode720p_mp4/king-machine-trailer.mp4',
'height': 720,
- 'ext': 'mp4'
- }]
+ 'ext': 'mp4',
+ }],
})
def test_parse_m3u8_formats(self):
@@ -866,7 +866,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'height': 1080,
'vcodec': 'avc1.64002a',
}],
- {}
+ {},
),
(
'bipbop_16x9',
@@ -990,45 +990,45 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'en': [{
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/eng/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}, {
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/eng_forced/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}],
'fr': [{
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/fra/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}, {
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/fra_forced/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}],
'es': [{
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/spa/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}, {
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/spa_forced/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}],
'ja': [{
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/jpn/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}, {
'url': 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/subtitles/jpn_forced/prog_index.m3u8',
'ext': 'vtt',
- 'protocol': 'm3u8_native'
+ 'protocol': 'm3u8_native',
}],
- }
+ },
),
]
for m3u8_file, m3u8_url, expected_formats, expected_subs in _TEST_CASES:
- with open('./test/testdata/m3u8/%s.m3u8' % m3u8_file, encoding='utf-8') as f:
+ with open(f'./test/testdata/m3u8/{m3u8_file}.m3u8', encoding='utf-8') as f:
formats, subs = self.ie._parse_m3u8_formats_and_subtitles(
f.read(), m3u8_url, ext='mp4')
self.ie._sort_formats(formats)
@@ -1366,14 +1366,14 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/manifest.mpd',
'fragment_base_url': 'https://sdn-global-streaming-cache-3qsdn.akamaized.net/stream/3144/files/17/07/672975/3144-kZT4LWMQw6Rh7Kpd.ism/dash/',
'protocol': 'http_dash_segments',
- }
- ]
+ },
+ ],
},
- )
+ ),
]
for mpd_file, mpd_url, mpd_base_url, expected_formats, expected_subtitles in _TEST_CASES:
- with open('./test/testdata/mpd/%s.mpd' % mpd_file, encoding='utf-8') as f:
+ with open(f'./test/testdata/mpd/{mpd_file}.mpd', encoding='utf-8') as f:
formats, subtitles = self.ie._parse_mpd_formats_and_subtitles(
compat_etree_fromstring(f.read().encode()),
mpd_base_url=mpd_base_url, mpd_url=mpd_url)
@@ -1408,7 +1408,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'sampling_rate': 48000,
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video-100',
@@ -1431,7 +1431,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401FDA0544EFFC2D002CBC40000003004000000C03C60CA80000000168EF32C8',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video-326',
@@ -1454,7 +1454,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401FDA0241FE23FFC3BC83BA44000003000400000300C03C60CA800000000168EF32C8',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video-698',
@@ -1477,7 +1477,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401FDA0350BFB97FF06AF06AD1000003000100000300300F1832A00000000168EF32C8',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video-1493',
@@ -1500,7 +1500,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401FDA011C3DE6FFF0D890D871000003000100000300300F1832A00000000168EF32C8',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video-4482',
@@ -1523,7 +1523,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401FDA01A816F97FFC1ABC1AB440000003004000000C03C60CA80000000168EF32C8',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}],
{
@@ -1538,10 +1538,10 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'duration': 8880746666,
'timescale': 10000000,
'fourcc': 'TTML',
- 'codec_private_data': ''
- }
- }
- ]
+ 'codec_private_data': '',
+ },
+ },
+ ],
},
),
(
@@ -1571,7 +1571,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'sampling_rate': 48000,
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'audio_deu_1-224',
@@ -1597,7 +1597,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'sampling_rate': 48000,
'channels': 6,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-23',
@@ -1622,7 +1622,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '000000016742C00CDB06077E5C05A808080A00000300020000030009C0C02EE0177CC6300F142AE00000000168CA8DC8',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-403',
@@ -1647,7 +1647,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D4014E98323B602D4040405000003000100000300320F1429380000000168EAECF2',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-680',
@@ -1672,7 +1672,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401EE981405FF2E02D4040405000000300100000030320F162D3800000000168EAECF2',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-1253',
@@ -1698,7 +1698,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401EE981405FF2E02D4040405000000300100000030320F162D3800000000168EAECF2',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-2121',
@@ -1723,7 +1723,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D401EECA0601BD80B50101014000003000400000300C83C58B6580000000168E93B3C80',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-3275',
@@ -1748,7 +1748,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D4020ECA02802DD80B501010140000003004000000C83C60C65800000000168E93B3C80',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-5300',
@@ -1773,7 +1773,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D4028ECA03C0113F2E02D4040405000000300100000030320F18319600000000168E93B3C80',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}, {
'format_id': 'video_deu-8079',
@@ -1798,7 +1798,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'codec_private_data': '00000001674D4028ECA03C0113F2E02D4040405000000300100000030320F18319600000000168E93B3C80',
'channels': 2,
'bits_per_sample': 16,
- 'nal_unit_length_field': 4
+ 'nal_unit_length_field': 4,
},
}],
{},
@@ -1806,7 +1806,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
]
for ism_file, ism_url, expected_formats, expected_subtitles in _TEST_CASES:
- with open('./test/testdata/ism/%s.Manifest' % ism_file, encoding='utf-8') as f:
+ with open(f'./test/testdata/ism/{ism_file}.Manifest', encoding='utf-8') as f:
formats, subtitles = self.ie._parse_ism_formats_and_subtitles(
compat_etree_fromstring(f.read().encode()), ism_url=ism_url)
self.ie._sort_formats(formats)
@@ -1827,12 +1827,12 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
'tbr': 2148,
'width': 1280,
'height': 720,
- }]
+ }],
),
]
for f4m_file, f4m_url, expected_formats in _TEST_CASES:
- with open('./test/testdata/f4m/%s.f4m' % f4m_file, encoding='utf-8') as f:
+ with open(f'./test/testdata/f4m/{f4m_file}.f4m', encoding='utf-8') as f:
formats = self.ie._parse_f4m_formats(
compat_etree_fromstring(f.read().encode()),
f4m_url, None)
@@ -1873,13 +1873,13 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
}, {
'manifest_url': 'https://example.org/src/foo_xspf.xspf',
'url': 'https://example.com/track3.mp3',
- }]
- }]
+ }],
+ }],
),
]
for xspf_file, xspf_url, expected_entries in _TEST_CASES:
- with open('./test/testdata/xspf/%s.xspf' % xspf_file, encoding='utf-8') as f:
+ with open(f'./test/testdata/xspf/{xspf_file}.xspf', encoding='utf-8') as f:
entries = self.ie._parse_xspf(
compat_etree_fromstring(f.read().encode()),
xspf_file, xspf_url=xspf_url, xspf_base_url=xspf_url)
@@ -1902,7 +1902,7 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
server_thread.start()
(content, urlh) = self.ie._download_webpage_handle(
- 'http://127.0.0.1:%d/teapot' % port, None,
+ f'http://127.0.0.1:{port}/teapot', None,
expected_status=TEAPOT_RESPONSE_STATUS)
self.assertEqual(content, TEAPOT_RESPONSE_BODY)
diff --git a/test/test_YoutubeDL.py b/test/test_YoutubeDL.py
index 5242cf88f..841ce1af3 100644
--- a/test/test_YoutubeDL.py
+++ b/test/test_YoutubeDL.py
@@ -8,6 +8,7 @@ import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+import contextlib
import copy
import json
@@ -129,8 +130,8 @@ class TestFormatSelection(unittest.TestCase):
'allow_multiple_audio_streams': multi,
})
ydl.process_ie_result(info_dict.copy())
- downloaded = map(lambda x: x['format_id'], ydl.downloaded_info_dicts)
- self.assertEqual(list(downloaded), list(expected))
+ downloaded = [x['format_id'] for x in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded, list(expected))
test('20/47', '47')
test('20/71/worst', '35')
@@ -515,10 +516,8 @@ class TestFormatSelection(unittest.TestCase):
self.assertEqual(downloaded_ids, ['D', 'C', 'B'])
ydl = YDL({'format': 'best[height<40]'})
- try:
+ with contextlib.suppress(ExtractorError):
ydl.process_ie_result(info_dict)
- except ExtractorError:
- pass
self.assertEqual(ydl.downloaded_info_dicts, [])
def test_default_format_spec(self):
@@ -652,8 +651,8 @@ class TestYoutubeDL(unittest.TestCase):
'formats': [
{'id': 'id 1', 'height': 1080, 'width': 1920},
{'id': 'id 2', 'height': 720},
- {'id': 'id 3'}
- ]
+ {'id': 'id 3'},
+ ],
}
def test_prepare_outtmpl_and_filename(self):
@@ -773,7 +772,7 @@ class TestYoutubeDL(unittest.TestCase):
test('%(formats)j', (json.dumps(FORMATS), None))
test('%(formats)#j', (
json.dumps(FORMATS, indent=4),
- json.dumps(FORMATS, indent=4).replace(':', ':').replace('"', """).replace('\n', ' ')
+ json.dumps(FORMATS, indent=4).replace(':', ':').replace('"', '"').replace('\n', ' '),
))
test('%(title5).3B', 'á')
test('%(title5)U', 'áéí 𝐀')
@@ -843,8 +842,8 @@ class TestYoutubeDL(unittest.TestCase):
# Empty filename
test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
- # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
- # test('%(foo|)s', ('', '_')) # fixme
+ # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # FIXME: ?
+ # test('%(foo|)s', ('', '_')) # FIXME: ?
# Environment variable expansion for prepare_filename
os.environ['__yt_dlp_var'] = 'expanded'
@@ -861,7 +860,7 @@ class TestYoutubeDL(unittest.TestCase):
test('Hello %(title1)s', 'Hello $PATH')
test('Hello %(title2)s', 'Hello %PATH%')
test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
- test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os.path.sep))
+ test('folder/%(title3)s', ('folder/foo/bar\\test', f'folder{os.path.sep}foo⧸bar⧹test'))
def test_format_note(self):
ydl = YoutubeDL()
@@ -883,22 +882,22 @@ class TestYoutubeDL(unittest.TestCase):
f.write('EXAMPLE')
return [info['filepath']], info
- def run_pp(params, PP):
+ def run_pp(params, pp):
with open(filename, 'w') as f:
f.write('EXAMPLE')
ydl = YoutubeDL(params)
- ydl.add_post_processor(PP())
+ 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)
+ self.assertTrue(os.path.exists(filename), f'{filename} doesn\'t exist')
+ self.assertTrue(os.path.exists(audiofile), f'{audiofile} doesn\'t exist')
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)
+ self.assertFalse(os.path.exists(filename), f'{filename} exists')
+ self.assertTrue(os.path.exists(audiofile), f'{audiofile} doesn\'t exist')
os.unlink(audiofile)
class ModifierPP(PostProcessor):
@@ -908,7 +907,7 @@ class TestYoutubeDL(unittest.TestCase):
return [], info
run_pp({'keepvideo': False}, ModifierPP)
- self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
+ self.assertTrue(os.path.exists(filename), f'{filename} doesn\'t exist')
os.unlink(filename)
def test_match_filter(self):
@@ -920,7 +919,7 @@ class TestYoutubeDL(unittest.TestCase):
'duration': 30,
'filesize': 10 * 1024,
'playlist_id': '42',
- 'uploader': "變態妍字幕版 太妍 тест",
+ 'uploader': '變態妍字幕版 太妍 тест',
'creator': "тест ' 123 ' тест--",
'webpage_url': 'http://example.com/watch?v=shenanigans',
}
@@ -933,7 +932,7 @@ class TestYoutubeDL(unittest.TestCase):
'description': 'foo',
'filesize': 5 * 1024,
'playlist_id': '43',
- 'uploader': "тест 123",
+ 'uploader': 'тест 123',
'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
}
videos = [first, second]
@@ -1180,7 +1179,7 @@ class TestYoutubeDL(unittest.TestCase):
})
return {
'id': video_id,
- 'title': 'Video %s' % video_id,
+ 'title': f'Video {video_id}',
'formats': formats,
}
@@ -1194,8 +1193,8 @@ class TestYoutubeDL(unittest.TestCase):
'_type': 'url_transparent',
'ie_key': VideoIE.ie_key(),
'id': video_id,
- 'url': 'video:%s' % video_id,
- 'title': 'Video Transparent %s' % video_id,
+ 'url': f'video:{video_id}',
+ 'title': f'Video Transparent {video_id}',
}
def _real_extract(self, url):
diff --git a/test/test_aes.py b/test/test_aes.py
index a26abfd7d..5f975efec 100644
--- a/test/test_aes.py
+++ b/test/test_aes.py
@@ -87,7 +87,7 @@ class TestAES(unittest.TestCase):
password = intlist_to_bytes(self.key).decode()
encrypted = base64.b64encode(
intlist_to_bytes(self.iv[:8])
- + b'\x17\x15\x93\xab\x8d\x80V\xcdV\xe0\t\xcdo\xc2\xa5\xd8ksM\r\xe27N\xae'
+ + b'\x17\x15\x93\xab\x8d\x80V\xcdV\xe0\t\xcdo\xc2\xa5\xd8ksM\r\xe27N\xae',
).decode()
decrypted = (aes_decrypt_text(encrypted, password, 16))
self.assertEqual(decrypted, self.secret_msg)
@@ -95,7 +95,7 @@ class TestAES(unittest.TestCase):
password = intlist_to_bytes(self.key).decode()
encrypted = base64.b64encode(
intlist_to_bytes(self.iv[:8])
- + b'\x0b\xe6\xa4\xd9z\x0e\xb8\xb9\xd0\xd4i_\x85\x1d\x99\x98_\xe5\x80\xe7.\xbf\xa5\x83'
+ + b'\x0b\xe6\xa4\xd9z\x0e\xb8\xb9\xd0\xd4i_\x85\x1d\x99\x98_\xe5\x80\xe7.\xbf\xa5\x83',
).decode()
decrypted = (aes_decrypt_text(encrypted, password, 32))
self.assertEqual(decrypted, self.secret_msg)
@@ -132,16 +132,16 @@ class TestAES(unittest.TestCase):
block = [0x21, 0xA0, 0x43, 0xFF]
self.assertEqual(pad_block(block, 'pkcs7'),
- block + [0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C])
+ [*block, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C])
self.assertEqual(pad_block(block, 'iso7816'),
- block + [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+ [*block, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
self.assertEqual(pad_block(block, 'whitespace'),
- block + [0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20])
+ [*block, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20])
self.assertEqual(pad_block(block, 'zero'),
- block + [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+ [*block, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
block = list(range(16))
for mode in ('pkcs7', 'iso7816', 'whitespace', 'zero'):
diff --git a/test/test_compat.py b/test/test_compat.py
index 71ca7f99f..e7d97e3e9 100644
--- a/test/test_compat.py
+++ b/test/test_compat.py
@@ -15,8 +15,8 @@ from yt_dlp.compat import urllib # isort: split
from yt_dlp.compat import (
compat_etree_fromstring,
compat_expanduser,
- compat_urllib_parse_unquote,
- compat_urllib_parse_urlencode,
+ compat_urllib_parse_unquote, # noqa: TID251
+ compat_urllib_parse_urlencode, # noqa: TID251
)
from yt_dlp.compat.urllib.request import getproxies
@@ -24,15 +24,15 @@ from yt_dlp.compat.urllib.request import getproxies
class TestCompat(unittest.TestCase):
def test_compat_passthrough(self):
with self.assertWarns(DeprecationWarning):
- compat.compat_basestring
+ _ = compat.compat_basestring
with self.assertWarns(DeprecationWarning):
- compat.WINDOWS_VT_MODE
+ _ = compat.WINDOWS_VT_MODE
self.assertEqual(urllib.request.getproxies, getproxies)
with self.assertWarns(DeprecationWarning):
- compat.compat_pycrypto_AES # Must not raise error
+ _ = compat.compat_pycrypto_AES # Must not raise error
def test_compat_expanduser(self):
old_home = os.environ.get('HOME')
diff --git a/test/test_config.py b/test/test_config.py
index a393b6534..238ca66d0 100644
--- a/test/test_config.py
+++ b/test/test_config.py
@@ -71,7 +71,7 @@ def _generate_expected_groups():
Path('/etc/yt-dlp.conf'),
Path('/etc/yt-dlp/config'),
Path('/etc/yt-dlp/config.txt'),
- ]
+ ],
}
diff --git a/test/test_cookies.py b/test/test_cookies.py
index bd61f30a6..a682fee1d 100644
--- a/test/test_cookies.py
+++ b/test/test_cookies.py
@@ -106,7 +106,7 @@ class TestCookies(unittest.TestCase):
def test_chrome_cookie_decryptor_windows_v10(self):
with MonkeyPatch(cookies, {
- '_get_windows_v10_key': lambda *args, **kwargs: b'Y\xef\xad\xad\xeerp\xf0Y\xe6\x9b\x12\xc2<z\x16]\n\xbb\xb8\xcb\xd7\x9bA\xc3\x14e\x99{\xd6\xf4&'
+ '_get_windows_v10_key': lambda *args, **kwargs: b'Y\xef\xad\xad\xeerp\xf0Y\xe6\x9b\x12\xc2<z\x16]\n\xbb\xb8\xcb\xd7\x9bA\xc3\x14e\x99{\xd6\xf4&',
}):
encrypted_value = b'v10T\xb8\xf3\xb8\x01\xa7TtcV\xfc\x88\xb8\xb8\xef\x05\xb5\xfd\x18\xc90\x009\xab\xb1\x893\x85)\x87\xe1\xa9-\xa3\xad='
value = '32101439'
@@ -121,17 +121,17 @@ class TestCookies(unittest.TestCase):
self.assertEqual(decryptor.decrypt(encrypted_value), value)
def test_safari_cookie_parsing(self):
- cookies = \
- b'cook\x00\x00\x00\x01\x00\x00\x00i\x00\x00\x01\x00\x01\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00Y' \
- b'\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00B\x00\x00\x00F\x00\x00\x00H' \
- b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03\xa5>\xc3A\x00\x00\x80\xc3\x07:\xc3A' \
- b'localhost\x00foo\x00/\x00test%20%3Bcookie\x00\x00\x00\x054\x07\x17 \x05\x00\x00\x00Kbplist00\xd1\x01' \
- b'\x02_\x10\x18NSHTTPCookieAcceptPolicy\x10\x02\x08\x0b&\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00' \
- b'\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00('
+ cookies = (
+ b'cook\x00\x00\x00\x01\x00\x00\x00i\x00\x00\x01\x00\x01\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00Y'
+ b'\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x008\x00\x00\x00B\x00\x00\x00F\x00\x00\x00H'
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03\xa5>\xc3A\x00\x00\x80\xc3\x07:\xc3A'
+ b'localhost\x00foo\x00/\x00test%20%3Bcookie\x00\x00\x00\x054\x07\x17 \x05\x00\x00\x00Kbplist00\xd1\x01'
+ b'\x02_\x10\x18NSHTTPCookieAcceptPolicy\x10\x02\x08\x0b&\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00'
+ b'\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(')
jar = parse_safari_cookies(cookies)
self.assertEqual(len(jar), 1)
- cookie = list(jar)[0]
+ cookie = next(iter(jar))
self.assertEqual(cookie.domain, 'localhost')
self.assertEqual(cookie.port, None)
self.assertEqual(cookie.path, '/')
@@ -164,7 +164,7 @@ class TestLenientSimpleCookie(unittest.TestCase):
attributes = {
key: value
for key, value in dict(morsel).items()
- if value != ""
+ if value != ''
}
self.assertEqual(attributes, expected_attributes, message)
@@ -174,133 +174,133 @@ class TestLenientSimpleCookie(unittest.TestCase):
self._run_tests(
# Copied from https://github.com/python/cpython/blob/v3.10.7/Lib/test/test_http_cookies.py
(
- "Test basic cookie",
- "chips=ahoy; vienna=finger",
- {"chips": "ahoy", "vienna": "finger"},
+ 'Test basic cookie',
+ 'chips=ahoy; vienna=finger',
+ {'chips': 'ahoy', 'vienna': 'finger'},
),
(
- "Test quoted cookie",
+ 'Test quoted cookie',
'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
- {"keebler": 'E=mc2; L="Loves"; fudge=\012;'},
+ {'keebler': 'E=mc2; L="Loves"; fudge=\012;'},
),
(
"Allow '=' in an unquoted value",
- "keebler=E=mc2",
- {"keebler": "E=mc2"},
+ 'keebler=E=mc2',
+ {'keebler': 'E=mc2'},
),
(
"Allow cookies with ':' in their name",
- "key:term=value:term",
- {"key:term": "value:term"},
+ 'key:term=value:term',
+ {'key:term': 'value:term'},
),
(
"Allow '[' and ']' in cookie values",
- "a=b; c=[; d=r; f=h",
- {"a": "b", "c": "[", "d": "r", "f": "h"},
+ 'a=b; c=[; d=r; f=h',
+ {'a': 'b', 'c': '[', 'd': 'r', 'f': 'h'},
),
(
- "Test basic cookie attributes",
+ 'Test basic cookie attributes',
'Customer="WILE_E_COYOTE"; Version=1; Path=/acme',
- {"Customer": ("WILE_E_COYOTE", {"version": "1", "path": "/acme"})},
+ {'Customer': ('WILE_E_COYOTE', {'version': '1', 'path': '/acme'})},
),
(
- "Test flag only cookie attributes",
+ 'Test flag only cookie attributes',
'Customer="WILE_E_COYOTE"; HttpOnly; Secure',
- {"Customer": ("WILE_E_COYOTE", {"httponly": True, "secure": True})},
+ {'Customer': ('WILE_E_COYOTE', {'httponly': True, 'secure': True})},
),
(
- "Test flag only attribute with values",
- "eggs=scrambled; httponly=foo; secure=bar; Path=/bacon",
- {"eggs": ("scrambled", {"httponly": "foo", "secure": "bar", "path": "/bacon"})},
+ 'Test flag only attribute with values',
+ 'eggs=scrambled; httponly=foo; secure=bar; Path=/bacon',
+ {'eggs': ('scrambled', {'httponly': 'foo', 'secure': 'bar', 'path': '/bacon'})},
),
(
"Test special case for 'expires' attribute, 4 digit year",
'Customer="W"; expires=Wed, 01 Jan 2010 00:00:00 GMT',
- {"Customer": ("W", {"expires": "Wed, 01 Jan 2010 00:00:00 GMT"})},
+ {'Customer': ('W', {'expires': 'Wed, 01 Jan 2010 00:00:00 GMT'})},
),
(
"Test special case for 'expires' attribute, 2 digit year",
'Customer="W"; expires=Wed, 01 Jan 98 00:00:00 GMT',
- {"Customer": ("W", {"expires": "Wed, 01 Jan 98 00:00:00 GMT"})},
+ {'Customer': ('W', {'expires': 'Wed, 01 Jan 98 00:00:00 GMT'})},
),
(
- "Test extra spaces in keys and values",
- "eggs = scrambled ; secure ; path = bar ; foo=foo ",
- {"eggs": ("scrambled", {"secure": True, "path": "bar"}), "foo": "foo"},
+ 'Test extra spaces in keys and values',
+ 'eggs = scrambled ; secure ; path = bar ; foo=foo ',
+ {'eggs': ('scrambled', {'secure': True, 'path': 'bar'}), 'foo': 'foo'},
),
(
- "Test quoted attributes",
+ 'Test quoted attributes',
'Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"',
- {"Customer": ("WILE_E_COYOTE", {"version": "1", "path": "/acme"})}
+ {'Customer': ('WILE_E_COYOTE', {'version': '1', 'path': '/acme'})},
),
# Our own tests that CPython passes
(
"Allow ';' in quoted value",
'chips="a;hoy"; vienna=finger',
- {"chips": "a;hoy", "vienna": "finger"},
+ {'chips': 'a;hoy', 'vienna': 'finger'},
),
(
- "Keep only the last set value",
- "a=c; a=b",
- {"a": "b"},
+ 'Keep only the last set value',
+ 'a=c; a=b',
+ {'a': 'b'},
),
)
def test_lenient_parsing(self):
self._run_tests(
(
- "Ignore and try to skip invalid cookies",
+ 'Ignore and try to skip invalid cookies',
'chips={"ahoy;": 1}; vienna="finger;"',
- {"vienna": "finger;"},
+ {'vienna': 'finger;'},
),
(
- "Ignore cookies without a name",
- "a=b; unnamed; c=d",
- {"a": "b", "c": "d"},
+ 'Ignore cookies without a name',
+ 'a=b; unnamed; c=d',
+ {'a': 'b', 'c': 'd'},
),
(
"Ignore '\"' cookie without name",
'a=b; "; c=d',
- {"a": "b", "c": "d"},
+ {'a': 'b', 'c': 'd'},
),
(
- "Skip all space separated values",
- "x a=b c=d x; e=f",
- {"a": "b", "c": "d", "e": "f"},
+ 'Skip all space separated values',
+ 'x a=b c=d x; e=f',
+ {'a': 'b', 'c': 'd', 'e': 'f'},
),
(
- "Skip all space separated values",
+ 'Skip all space separated values',
'x a=b; data={"complex": "json", "with": "key=value"}; x c=d x',
- {"a": "b", "c": "d"},
+ {'a': 'b', 'c': 'd'},
),
(
- "Expect quote mending",
+ 'Expect quote mending',
'a=b; invalid="; c=d',
- {"a": "b", "c": "d"},
+ {'a': 'b', 'c': 'd'},
),
(
- "Reset morsel after invalid to not capture attributes",
- "a=b; invalid; Version=1; c=d",
- {"a": "b", "c": "d"},
+ 'Reset morsel after invalid to not capture attributes',
+ 'a=b; invalid; Version=1; c=d',
+ {'a': 'b', 'c': 'd'},
),
(
- "Reset morsel after invalid to not capture attributes",
- "a=b; $invalid; $Version=1; c=d",
- {"a": "b", "c": "d"},
+ 'Reset morsel after invalid to not capture attributes',
+ 'a=b; $invalid; $Version=1; c=d',
+ {'a': 'b', 'c': 'd'},
),
(
- "Continue after non-flag attribute without value",
- "a=b; path; Version=1; c=d",
- {"a": "b", "c": "d"},
+ 'Continue after non-flag attribute without value',
+ 'a=b; path; Version=1; c=d',
+ {'a': 'b', 'c': 'd'},
),
(
- "Allow cookie attributes with `$` prefix",
+ 'Allow cookie attributes with `$` prefix',
'Customer="WILE_E_COYOTE"; $Version=1; $Secure; $Path=/acme',
- {"Customer": ("WILE_E_COYOTE", {"version": "1", "secure": True, "path": "/acme"})},
+ {'Customer': ('WILE_E_COYOTE', {'version': '1', 'secure': True, 'path': '/acme'})},
),
(
- "Invalid Morsel keys should not result in an error",
- "Key=Value; [Invalid]=Value; Another=Value",
- {"Key": "Value", "Another": "Value"},
+ 'Invalid Morsel keys should not result in an error',
+ 'Key=Value; [Invalid]=Value; Another=Value',
+ {'Key': 'Value', 'Another': 'Value'},
),
)
diff --git a/test/test_download.py b/test/test_download.py
index 253079249..882d54565 100755
--- a/test/test_download.py
+++ b/test/test_download.py
@@ -94,7 +94,7 @@ def generator(test_case, tname):
'playlist', [] if is_playlist else [test_case])
def print_skipping(reason):
- print('Skipping %s: %s' % (test_case['name'], reason))
+ print('Skipping {}: {}'.format(test_case['name'], reason))
self.skipTest(reason)
if not ie.working():
@@ -117,7 +117,7 @@ def generator(test_case, tname):
for other_ie in other_ies:
if not other_ie.working():
- print_skipping('test depends on %sIE, marked as not WORKING' % other_ie.ie_key())
+ print_skipping(f'test depends on {other_ie.ie_key()}IE, marked as not WORKING')
params = get_params(test_case.get('params', {}))
params['outtmpl'] = tname + '_' + params['outtmpl']
@@ -148,10 +148,7 @@ def generator(test_case, tname):
return False
if err.__class__.__name__ == expected_exception:
return True
- for exc in err.exc_info:
- if exc.__class__.__name__ == expected_exception:
- return True
- return False
+ return any(exc.__class__.__name__ == expected_exception for exc in err.exc_info)
def try_rm_tcs_files(tcs=None):
if tcs is None:
@@ -181,7 +178,7 @@ def generator(test_case, tname):
raise
if try_num == RETRIES:
- report_warning('%s failed due to network errors, skipping...' % tname)
+ report_warning(f'{tname} failed due to network errors, skipping...')
return
print(f'Retrying: {try_num} failed tries\n\n##########\n\n')
@@ -244,9 +241,8 @@ def generator(test_case, tname):
got_fsize = os.path.getsize(tc_filename)
assertGreaterEqual(
self, got_fsize, expected_minsize,
- 'Expected %s to be at least %s, but it\'s only %s ' %
- (tc_filename, format_bytes(expected_minsize),
- format_bytes(got_fsize)))
+ f'Expected {tc_filename} to be at least {format_bytes(expected_minsize)}, '
+ f'but it\'s only {format_bytes(got_fsize)} ')
if 'md5' in tc:
md5_for_file = _file_md5(tc_filename)
self.assertEqual(tc['md5'], md5_for_file)
@@ -255,7 +251,7 @@ def generator(test_case, tname):
info_json_fn = os.path.splitext(tc_filename)[0] + '.info.json'
self.assertTrue(
os.path.exists(info_json_fn),
- 'Missing info file %s' % info_json_fn)
+ f'Missing info file {info_json_fn}')
with open(info_json_fn, encoding='utf-8') as infof:
info_dict = json.load(infof)
expect_info_dict(self, info_dict, tc.get('info_dict', {}))
diff --git a/test/test_downloader_http.py b/test/test_downloader_http.py
index 099ec2fff..faba0bc9c 100644
--- a/test/test_downloader_http.py
+++ b/test/test_downloader_http.py
@@ -38,9 +38,9 @@ class HTTPTestRequestHandler(http.server.BaseHTTPRequestHandler):
end = int(mobj.group(2))
valid_range = start is not None and end is not None
if valid_range:
- content_range = 'bytes %d-%d' % (start, end)
+ content_range = f'bytes {start}-{end}'
if total:
- content_range += '/%d' % total
+ content_range += f'/{total}'
self.send_header('Content-Range', content_range)
return (end - start + 1) if valid_range else total
@@ -84,7 +84,7 @@ class TestHttpFD(unittest.TestCase):
filename = 'testfile.mp4'
try_rm(encodeFilename(filename))
self.assertTrue(downloader.real_download(filename, {
- 'url': 'http://127.0.0.1:%d/%s' % (self.port, ep),
+ 'url': f'http://127.0.0.1:{self.port}/{ep}',
}), ep)
self.assertEqual(os.path.getsize(encodeFilename(filename)), TEST_SIZE, ep)
try_rm(encodeFilename(filename))
diff --git a/test/test_http_proxy.py b/test/test_http_proxy.py
index 1b21fe78e..2435c878a 100644
--- a/test/test_http_proxy.py
+++ b/test/test_http_proxy.py
@@ -105,7 +105,7 @@ if urllib3:
self.incoming,
self.outgoing,
server_hostname=server_hostname,
- server_side=server_side
+ server_side=server_side,
)
self._ssl_io_loop(self.sslobj.do_handshake)
@@ -333,7 +333,7 @@ class TestHTTPConnectProxy:
@pytest.mark.skip_handler(
'Requests',
- 'bug in urllib3 causes unclosed socket: https://github.com/urllib3/urllib3/issues/3374'
+ 'bug in urllib3 causes unclosed socket: https://github.com/urllib3/urllib3/issues/3374',
)
def test_http_connect_bad_auth(self, handler, ctx):
with ctx.http_server(HTTPConnectProxyHandler, username='test', password='test') as server_address:
diff --git a/test/test_iqiyi_sdk_interpreter.py b/test/test_iqiyi_sdk_interpreter.py
index 47c632a4e..4e41007c8 100644
--- a/test/test_iqiyi_sdk_interpreter.py
+++ b/test/test_iqiyi_sdk_interpreter.py
@@ -29,11 +29,11 @@ class WarningLogger:
@is_download_test
class TestIqiyiSDKInterpreter(unittest.TestCase):
def test_iqiyi_sdk_interpreter(self):
- '''
+ """
Test the functionality of IqiyiSDKInterpreter by trying to log in
If `sign` is incorrect, /validate call throws an HTTP 556 error
- '''
+ """
logger = WarningLogger()
ie = IqiyiIE(FakeYDL({'logger': logger}))
ie._perform_login('foo', 'bar')
diff --git a/test/test_netrc.py b/test/test_netrc.py
index dc708d974..1e0f4ee3b 100644
--- a/test/test_netrc.py
+++ b/test/test_netrc.py
@@ -21,7 +21,7 @@ class TestNetRc(unittest.TestCase):
continue
self.assertTrue(
ie._NETRC_MACHINE,
- 'Extractor %s supports login, but is missing a _NETRC_MACHINE property' % ie.IE_NAME)
+ f'Extractor {ie.IE_NAME} supports login, but is missing a _NETRC_MACHINE property')
if __name__ == '__main__':
diff --git a/test/test_networking.py b/test/test_networking.py
index d127cbb94..af3ece3b4 100644
--- a/test/test_networking.py
+++ b/test/test_networking.py
@@ -375,10 +375,10 @@ class TestHTTPRequestHandler(TestRequestHandlerBase):
with handler() as rh:
for bad_status in (400, 500, 599, 302):
with pytest.raises(HTTPError):
- validate_and_send(rh, Request('http://127.0.0.1:%d/gen_%d' % (self.http_port, bad_status)))
+ validate_and_send(rh, Request(f'http://127.0.0.1:{self.http_port}/gen_{bad_status}'))
# Should not raise an error
- validate_and_send(rh, Request('http://127.0.0.1:%d/gen_200' % self.http_port)).close()
+ validate_and_send(rh, Request(f'http://127.0.0.1:{self.http_port}/gen_200')).close()
def test_response_url(self, handler):
with handler() as rh:
@@ -472,7 +472,7 @@ class TestHTTPRequestHandler(TestRequestHandlerBase):
def test_incompleteread(self, handler):
with handler(timeout=2) as rh:
with pytest.raises(IncompleteRead, match='13 bytes read, 234221 more expected'):
- validate_and_send(rh, Request('http://127.0.0.1:%d/incompleteread' % self.http_port)).read()
+ validate_and_send(rh, Request(f'http://127.0.0.1:{self.http_port}/incompleteread')).read()
def test_cookies(self, handler):
cookiejar = YoutubeDLCookieJar()
@@ -740,7 +740,7 @@ class TestRequestHandlerMisc:
@pytest.mark.parametrize('handler,logger_name', [
('Requests', 'urllib3'),
('Websockets', 'websockets.client'),
- ('Websockets', 'websockets.server')
+ ('Websockets', 'websockets.server'),
], indirect=['handler'])
def test_remove_logging_handler(self, handler, logger_name):
# Ensure any logging handlers, which may contain a YoutubeDL instance,
@@ -794,7 +794,7 @@ class TestUrllibRequestHandler(TestRequestHandlerBase):
with handler() as rh:
with pytest.raises(
CertificateVerifyError,
- match=r'\[SSL: CERTIFICATE_VERIFY_FAILED\] certificate verify failed: self.signed certificate'
+ match=r'\[SSL: CERTIFICATE_VERIFY_FAILED\] certificate verify failed: self.signed certificate',
):
validate_and_send(rh, Request(f'https://127.0.0.1:{self.https_port}/headers'))
@@ -804,14 +804,14 @@ class TestUrllibRequestHandler(TestRequestHandlerBase):
(
Request('http://127.0.0.1', method='GET\n'),
'method can\'t contain control characters',
- lambda v: v < (3, 7, 9) or (3, 8, 0) <= v < (3, 8, 5)
+ lambda v: v < (3, 7, 9) or (3, 8, 0) <= v < (3, 8, 5),
),
# https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1265
# bpo-38576: Check implemented in 3.7.8+, 3.8.3+
(
Request('http://127.0.0. 1', method='GET'),
'URL can\'t contain control characters',
- lambda v: v < (3, 7, 8) or (3, 8, 0) <= v < (3, 8, 3)
+ lambda v: v < (3, 7, 8) or (3, 8, 0) <= v < (3, 8, 3),
),
# https://github.com/python/cpython/blob/987b712b4aeeece336eed24fcc87a950a756c3e2/Lib/http/client.py#L1288C31-L1288C50
(Request('http://127.0.0.1', headers={'foo\n': 'bar'}), 'Invalid header name', None),
@@ -840,7 +840,7 @@ class TestRequestsRequestHandler(TestRequestHandlerBase):
(lambda: requests.exceptions.InvalidHeader(), RequestError),
# catch-all: https://github.com/psf/requests/blob/main/src/requests/adapters.py#L535
(lambda: urllib3.exceptions.HTTPError(), TransportError),
- (lambda: requests.exceptions.RequestException(), RequestError)
+ (lambda: requests.exceptions.RequestException(), RequestError),
# (lambda: requests.exceptions.TooManyRedirects(), HTTPError) - Needs a response object
])
def test_request_error_mapping(self, handler, monkeypatch, raised, expected):
@@ -868,12 +868,12 @@ class TestRequestsRequestHandler(TestRequestHandlerBase):
(
lambda: urllib3.exceptions.ProtocolError('error', http.client.IncompleteRead(partial=b'abc', expected=4)),
IncompleteRead,
- '3 bytes read, 4 more expected'
+ '3 bytes read, 4 more expected',
),
(
lambda: urllib3.exceptions.ProtocolError('error', urllib3.exceptions.IncompleteRead(partial=3, expected=5)),
IncompleteRead,
- '3 bytes read, 5 more expected'
+ '3 bytes read, 5 more expected',
),
])
def test_response_error_mapping(self, handler, monkeypatch, raised, expected, match):
@@ -1125,7 +1125,7 @@ class TestRequestHandlerValidation:
('https', False, {}),
]),
(NoCheckRH, [('http', False, {})]),
- (ValidationRH, [('http', UnsupportedRequest, {})])
+ (ValidationRH, [('http', UnsupportedRequest, {})]),
]
PROXY_SCHEME_TESTS = [
@@ -1219,7 +1219,7 @@ class TestRequestHandlerValidation:
({'impersonate': ImpersonateTarget('chrome', None, None, None)}, False),
({'impersonate': ImpersonateTarget(None, None, None, None)}, False),
({'impersonate': ImpersonateTarget()}, False),
- ({'impersonate': 'chrome'}, AssertionError)
+ ({'impersonate': 'chrome'}, AssertionError),
]),
(NoCheckRH, 'http', [
({'cookiejar': 'notacookiejar'}, False),
@@ -1235,7 +1235,7 @@ class TestRequestHandlerValidation:
('Urllib', False, 'http'),
('Requests', False, 'http'),
('CurlCFFI', False, 'http'),
- ('Websockets', False, 'ws')
+ ('Websockets', False, 'ws'),
], indirect=['handler'])
def test_no_proxy(self, handler, fail, scheme):
run_validation(handler, fail, Request(f'{scheme}://', proxies={'no': '127.0.0.1,github.com'}))
@@ -1246,7 +1246,7 @@ class TestRequestHandlerValidation:
(HTTPSupportedRH, 'http'),
('Requests', 'http'),
('CurlCFFI', 'http'),
- ('Websockets', 'ws')
+ ('Websockets', 'ws'),
], indirect=['handler'])
def test_empty_proxy(self, handler, scheme):
run_validation(handler, False, Request(f'{scheme}://', proxies={scheme: None}))
@@ -1258,7 +1258,7 @@ class TestRequestHandlerValidation:
(HTTPSupportedRH, 'http'),
('Requests', 'http'),
('CurlCFFI', 'http'),
- ('Websockets', 'ws')
+ ('Websockets', 'ws'),
], indirect=['handler'])
def test_invalid_proxy_url(self, handler, scheme, proxy_url):
run_validation(handler, UnsupportedRequest, Request(f'{scheme}://', proxies={scheme: proxy_url}))
@@ -1474,7 +1474,7 @@ class TestYoutubeDLNetworking:
@pytest.mark.parametrize('proxy,expected', [
('http://127.0.0.1:8080', {'all': 'http://127.0.0.1:8080'}),
('', {'all': '__noproxy__'}),
- (None, {'http': 'http://127.0.0.1:8081', 'https': 'http://127.0.0.1:8081'}) # env, set https
+ (None, {'http': 'http://127.0.0.1:8081', 'https': 'http://127.0.0.1:8081'}), # env, set https
])
def test_proxy(self, proxy, expected, monkeypatch):
monkeypatch.setenv('HTTP_PROXY', 'http://127.0.0.1:8081')
@@ -1546,7 +1546,7 @@ class TestYoutubeDLNetworking:
with FakeImpersonationRHYDL() as ydl:
with pytest.raises(
RequestError,
- match=r'Impersonate target "test" is not available'
+ match=r'Impersonate target "test" is not available',
):
ydl.urlopen(Request('http://', extensions={'impersonate': ImpersonateTarget('test', None, None, None)}))
@@ -1558,7 +1558,7 @@ class TestYoutubeDLNetworking:
pass
_SUPPORTED_URL_SCHEMES = ('http',)
- _SUPPORTED_IMPERSONATE_TARGET_MAP = {ImpersonateTarget('abc',): 'test'}
+ _SUPPORTED_IMPERSONATE_TARGET_MAP = {ImpersonateTarget('abc'): 'test'}
_SUPPORTED_PROXY_SCHEMES = None
super().__init__(*args, **kwargs)
@@ -1567,14 +1567,14 @@ class TestYoutubeDLNetworking:
with FakeHTTPRHYDL() as ydl:
with pytest.raises(
RequestError,
- match=r'Impersonate target "test" is not available'
+ match=r'Impersonate target "test" is not available',
):
ydl.urlopen(Request('http://', extensions={'impersonate': ImpersonateTarget('test', None, None, None)}))
def test_raise_impersonate_error(self):
with pytest.raises(
YoutubeDLError,
- match=r'Impersonate target "test" is not available'
+ match=r'Impersonate target "test" is not available',
):
FakeYDL({'impersonate': ImpersonateTarget('test', None, None, None)})
@@ -1592,7 +1592,7 @@ class TestYoutubeDLNetworking:
monkeypatch.setattr(FakeYDL, 'build_request_director', lambda cls, handlers, preferences=None: brh(cls, handlers=[IRH]))
with FakeYDL({
- 'impersonate': ImpersonateTarget('abc', None, None, None)
+ 'impersonate': ImpersonateTarget('abc', None, None, None),
}) as ydl:
rh = self.build_handler(ydl, IRH)
assert rh.impersonate == ImpersonateTarget('abc', None, None, None)
@@ -1604,7 +1604,7 @@ class TestYoutubeDLNetworking:
def _send(self, request: Request):
pass
_SUPPORTED_URL_SCHEMES = ('http',)
- _SUPPORTED_IMPERSONATE_TARGET_MAP = {ImpersonateTarget(target_client,): 'test'}
+ _SUPPORTED_IMPERSONATE_TARGET_MAP = {ImpersonateTarget(target_client): 'test'}
RH_KEY = target_client
RH_NAME = target_client
handlers.append(TestRH)
@@ -1614,7 +1614,7 @@ class TestYoutubeDLNetworking:
assert set(ydl._get_available_impersonate_targets()) == {
(ImpersonateTarget('xyz'), 'xyz'),
(ImpersonateTarget('abc'), 'abc'),
- (ImpersonateTarget('asd'), 'asd')
+ (ImpersonateTarget('asd'), 'asd'),
}
assert ydl._impersonate_target_available(ImpersonateTarget('abc'))
assert ydl._impersonate_target_available(ImpersonateTarget())
@@ -1837,7 +1837,7 @@ class TestRequest:
extensions={'cookiejar': CookieJar()},
headers={'Accept-Encoding': 'br'},
proxies={'http': 'http://127.0.0.1'},
- data=[b'123']
+ data=[b'123'],
)
req_copy = req.copy()
assert req_copy is not req
@@ -1863,7 +1863,7 @@ class TestRequest:
assert isinstance(req.copy(), AnotherRequest)
def test_url(self):
- req = Request(url='https://фtest.example.com/ some spaceв?ä=c',)
+ req = Request(url='https://фtest.example.com/ some spaceв?ä=c')
assert req.url == 'https://xn--test-z6d.example.com/%20some%20space%D0%B2?%C3%A4=c'
assert Request(url='//example.com').url == 'http://example.com'
@@ -1878,7 +1878,7 @@ class TestResponse:
('custom', 200, 'custom'),
(None, 404, 'Not Found'), # fallback status
('', 403, 'Forbidden'),
- (None, 999, None)
+ (None, 999, None),
])
def test_reason(self, reason, status, expected):
res = Response(io.BytesIO(b''), url='test://', headers={}, status=status, reason=reason)
@@ -1933,7 +1933,7 @@ class TestImpersonateTarget:
@pytest.mark.parametrize('target_str', [
'-120', ':-12.0', '-12:-12', '-:-',
- '::', 'a-c-d:', 'a-c-d:e-f-g', 'a:b:'
+ '::', 'a-c-d:', 'a-c-d:e-f-g', 'a:b:',
])
def test_target_from_invalid_str(self, target_str):
with pytest.raises(ValueError):
@@ -1949,7 +1949,7 @@ class TestImpersonateTarget:
(ImpersonateTarget('abc', '120', 'xyz', None), 'abc-120:xyz'),
(ImpersonateTarget('abc', None, 'xyz'), 'abc:xyz'),
(ImpersonateTarget(None, None, 'xyz', '6.5'), ':xyz-6.5'),
- (ImpersonateTarget('abc', ), 'abc'),
+ (ImpersonateTarget('abc'), 'abc'),
(ImpersonateTarget(None, None, None, None), ''),
])
def test_str(self, target, expected):
diff --git a/test/test_networking_utils.py b/test/test_networking_utils.py
index b7b71430e..204fe87bd 100644
--- a/test/test_networking_utils.py
+++ b/test/test_networking_utils.py
@@ -39,7 +39,7 @@ class TestNetworkingUtils:
proxies = {
'all': 'socks5://example.com',
'http': 'http://example.com:1080',
- 'no': 'bypass.example.com,yt-dl.org'
+ 'no': 'bypass.example.com,yt-dl.org',
}
assert select_proxy('https://example.com', proxies) == proxies['all']
@@ -54,7 +54,7 @@ class TestNetworkingUtils:
'port': 1080,
'rdns': True,
'username': None,
- 'password': None
+ 'password': None,
}),
('socks5://user:@example.com:5555', {
'proxytype': ProxyType.SOCKS5,
@@ -62,7 +62,7 @@ class TestNetworkingUtils:
'port': 5555,
'rdns': False,
'username': 'user',
- 'password': ''
+ 'password': '',
}),
('socks4://u%40ser:pa%20ss@127.0.0.1:1080', {
'proxytype': ProxyType.SOCKS4,
@@ -70,7 +70,7 @@ class TestNetworkingUtils:
'port': 1080,
'rdns': False,
'username': 'u@ser',
- 'password': 'pa ss'
+ 'password': 'pa ss',
}),
('socks4a://:pa%20ss@127.0.0.1', {
'proxytype': ProxyType.SOCKS4A,
@@ -78,8 +78,8 @@ class TestNetworkingUtils:
'port': 1080,
'rdns': True,
'username': '',
- 'password': 'pa ss'
- })
+ 'password': 'pa ss',
+ }),
])
def test_make_socks_proxy_opts(self, socks_proxy, expected):
assert make_socks_proxy_opts(socks_proxy) == expected
diff --git a/test/test_overwrites.py b/test/test_overwrites.py
index 6954c07f9..0beafdf12 100644
--- a/test/test_overwrites.py
+++ b/test/test_overwrites.py
@@ -27,7 +27,7 @@ class TestOverwrites(unittest.TestCase):
[
sys.executable, 'yt_dlp/__main__.py',
'-o', 'test.webm',
- 'https://www.youtube.com/watch?v=jNQXAC9IVRw'
+ 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
], cwd=root_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sout, serr = outp.communicate()
self.assertTrue(b'has already been downloaded' in sout)
@@ -39,7 +39,7 @@ class TestOverwrites(unittest.TestCase):
[
sys.executable, 'yt_dlp/__main__.py', '--yes-overwrites',
'-o', 'test.webm',
- 'https://www.youtube.com/watch?v=jNQXAC9IVRw'
+ 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
], cwd=root_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sout, serr = outp.communicate()
self.assertTrue(b'has already been downloaded' not in sout)
diff --git a/test/test_plugins.py b/test/test_plugins.py
index 6cde579e1..c82158e9f 100644
--- a/test/test_plugins.py
+++ b/test/test_plugins.py
@@ -31,7 +31,7 @@ class TestPlugins(unittest.TestCase):
# don't load modules with underscore prefix
self.assertFalse(
- f'{PACKAGE_NAME}.extractor._ignore' in sys.modules.keys(),
+ f'{PACKAGE_NAME}.extractor._ignore' in sys.modules,
'loaded module beginning with underscore')
self.assertNotIn('IgnorePluginIE', plugins_ie.keys())
diff --git a/test/test_post_hooks.py b/test/test_post_hooks.py
index 3778d1794..6500dd386 100644
--- a/test/test_post_hooks.py
+++ b/test/test_post_hooks.py
@@ -59,7 +59,7 @@ class TestPostHooks(unittest.TestCase):
def hook_three(self, filename):
self.files.append(filename)
- raise Exception('Test exception for \'%s\'' % filename)
+ raise Exception(f'Test exception for \'{filename}\'')
def tearDown(self):
for f in self.files:
diff --git a/test/test_postprocessors.py b/test/test_postprocessors.py
index 52e558772..603f85c65 100644
--- a/test/test_postprocessors.py
+++ b/test/test_postprocessors.py
@@ -9,7 +9,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from yt_dlp import YoutubeDL
-from yt_dlp.compat import compat_shlex_quote
+from yt_dlp.utils import shell_quote
from yt_dlp.postprocessor import (
ExecPP,
FFmpegThumbnailsConvertorPP,
@@ -65,7 +65,7 @@ class TestExec(unittest.TestCase):
def test_parse_cmd(self):
pp = ExecPP(YoutubeDL(), '')
info = {'filepath': 'file name'}
- cmd = 'echo %s' % compat_shlex_quote(info['filepath'])
+ cmd = 'echo {}'.format(shell_quote(info['filepath']))
self.assertEqual(pp.parse_cmd('echo', info), cmd)
self.assertEqual(pp.parse_cmd('echo {}', info), cmd)
@@ -125,7 +125,8 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, chapters, [])
def test_remove_marked_arrange_sponsors_ChapterWithSponsors(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 20, 'sponsor'),
self._sponsor_chapter(30, 40, 'preview'),
self._sponsor_chapter(50, 60, 'filler')]
@@ -136,7 +137,8 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
def test_remove_marked_arrange_sponsors_SponsorBlockChapters(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 20, 'chapter', title='sb c1'),
self._sponsor_chapter(15, 16, 'chapter', title='sb c2'),
self._sponsor_chapter(30, 40, 'preview'),
@@ -149,10 +151,14 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
def test_remove_marked_arrange_sponsors_UniqueNamesForOverlappingSponsors(self):
- chapters = self._chapters([120], ['c']) + [
- self._sponsor_chapter(10, 45, 'sponsor'), self._sponsor_chapter(20, 40, 'selfpromo'),
- self._sponsor_chapter(50, 70, 'sponsor'), self._sponsor_chapter(60, 85, 'selfpromo'),
- self._sponsor_chapter(90, 120, 'selfpromo'), self._sponsor_chapter(100, 110, 'sponsor')]
+ chapters = [
+ *self._chapters([120], ['c']),
+ self._sponsor_chapter(10, 45, 'sponsor'),
+ self._sponsor_chapter(20, 40, 'selfpromo'),
+ self._sponsor_chapter(50, 70, 'sponsor'),
+ self._sponsor_chapter(60, 85, 'selfpromo'),
+ self._sponsor_chapter(90, 120, 'selfpromo'),
+ self._sponsor_chapter(100, 110, 'sponsor')]
expected = self._chapters(
[10, 20, 40, 45, 50, 60, 70, 85, 90, 100, 110, 120],
['c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Sponsor, Unpaid/Self Promotion',
@@ -172,7 +178,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, self._chapters([40], ['c']), cuts)
def test_remove_marked_arrange_sponsors_ChapterWithSponsorsAndCuts(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 20, 'sponsor'),
self._sponsor_chapter(30, 40, 'selfpromo', remove=True),
self._sponsor_chapter(50, 60, 'interaction')]
@@ -185,24 +192,29 @@ class TestModifyChaptersPP(unittest.TestCase):
def test_remove_marked_arrange_sponsors_ChapterWithSponsorCutInTheMiddle(self):
cuts = [self._sponsor_chapter(20, 30, 'selfpromo', remove=True),
self._chapter(40, 50, remove=True)]
- chapters = self._chapters([70], ['c']) + [self._sponsor_chapter(10, 60, 'sponsor')] + cuts
+ chapters = [
+ *self._chapters([70], ['c']),
+ self._sponsor_chapter(10, 60, 'sponsor'),
+ *cuts]
expected = self._chapters(
[10, 40, 50], ['c', '[SponsorBlock]: Sponsor', 'c'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_ChapterWithCutHidingSponsor(self):
cuts = [self._sponsor_chapter(20, 50, 'selfpromo', remove=True)]
- chapters = self._chapters([60], ['c']) + [
+ chapters = [
+ *self._chapters([60], ['c']),
self._sponsor_chapter(10, 20, 'intro'),
self._sponsor_chapter(30, 40, 'sponsor'),
self._sponsor_chapter(50, 60, 'outro'),
- ] + cuts
+ *cuts]
expected = self._chapters(
[10, 20, 30], ['c', '[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_ChapterWithAdjacentSponsors(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 20, 'sponsor'),
self._sponsor_chapter(20, 30, 'selfpromo'),
self._sponsor_chapter(30, 40, 'interaction')]
@@ -213,7 +225,8 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
def test_remove_marked_arrange_sponsors_ChapterWithAdjacentCuts(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 20, 'sponsor'),
self._sponsor_chapter(20, 30, 'interaction', remove=True),
self._chapter(30, 40, remove=True),
@@ -226,7 +239,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, expected, [self._chapter(20, 50, remove=True)])
def test_remove_marked_arrange_sponsors_ChapterWithOverlappingSponsors(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 30, 'sponsor'),
self._sponsor_chapter(20, 50, 'selfpromo'),
self._sponsor_chapter(40, 60, 'interaction')]
@@ -238,7 +252,8 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
def test_remove_marked_arrange_sponsors_ChapterWithOverlappingCuts(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 30, 'sponsor', remove=True),
self._sponsor_chapter(20, 50, 'selfpromo', remove=True),
self._sponsor_chapter(40, 60, 'interaction', remove=True)]
@@ -246,7 +261,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, self._chapters([20], ['c']), [self._chapter(10, 60, remove=True)])
def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsors(self):
- chapters = self._chapters([170], ['c']) + [
+ chapters = [
+ *self._chapters([170], ['c']),
self._sponsor_chapter(0, 30, 'intro'),
self._sponsor_chapter(20, 50, 'sponsor'),
self._sponsor_chapter(40, 60, 'selfpromo'),
@@ -267,7 +283,8 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingCuts(self):
- chapters = self._chapters([170], ['c']) + [
+ chapters = [
+ *self._chapters([170], ['c']),
self._chapter(0, 30, remove=True),
self._sponsor_chapter(20, 50, 'sponsor', remove=True),
self._chapter(40, 60, remove=True),
@@ -284,7 +301,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, self._chapters([20], ['c']), expected_cuts)
def test_remove_marked_arrange_sponsors_OverlappingSponsorsDifferentTitlesAfterCut(self):
- chapters = self._chapters([60], ['c']) + [
+ chapters = [
+ *self._chapters([60], ['c']),
self._sponsor_chapter(10, 60, 'sponsor'),
self._sponsor_chapter(10, 40, 'intro'),
self._sponsor_chapter(30, 50, 'interaction'),
@@ -297,7 +315,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, expected, [self._chapter(30, 50, remove=True)])
def test_remove_marked_arrange_sponsors_SponsorsNoLongerOverlapAfterCut(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 30, 'sponsor'),
self._sponsor_chapter(20, 50, 'interaction'),
self._sponsor_chapter(30, 50, 'selfpromo', remove=True),
@@ -310,7 +329,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, expected, [self._chapter(30, 50, remove=True)])
def test_remove_marked_arrange_sponsors_SponsorsStillOverlapAfterCut(self):
- chapters = self._chapters([70], ['c']) + [
+ chapters = [
+ *self._chapters([70], ['c']),
self._sponsor_chapter(10, 60, 'sponsor'),
self._sponsor_chapter(20, 60, 'interaction'),
self._sponsor_chapter(30, 50, 'selfpromo', remove=True)]
@@ -321,7 +341,8 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, expected, [self._chapter(30, 50, remove=True)])
def test_remove_marked_arrange_sponsors_ChapterWithRunsOfOverlappingSponsorsAndCuts(self):
- chapters = self._chapters([200], ['c']) + [
+ chapters = [
+ *self._chapters([200], ['c']),
self._sponsor_chapter(10, 40, 'sponsor'),
self._sponsor_chapter(10, 30, 'intro'),
self._chapter(20, 30, remove=True),
@@ -347,8 +368,9 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, expected_cuts)
def test_remove_marked_arrange_sponsors_SponsorOverlapsMultipleChapters(self):
- chapters = (self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5'])
- + [self._sponsor_chapter(10, 90, 'sponsor')])
+ chapters = [
+ *self._chapters([20, 40, 60, 80, 100], ['c1', 'c2', 'c3', 'c4', 'c5']),
+ self._sponsor_chapter(10, 90, 'sponsor')]
expected = self._chapters([10, 90, 100], ['c1', '[SponsorBlock]: Sponsor', 'c5'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -359,9 +381,10 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_SponsorsWithinSomeChaptersAndOverlappingOthers(self):
- chapters = (self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4'])
- + [self._sponsor_chapter(20, 30, 'sponsor'),
- self._sponsor_chapter(50, 70, 'selfpromo')])
+ chapters = [
+ *self._chapters([10, 40, 60, 80], ['c1', 'c2', 'c3', 'c4']),
+ self._sponsor_chapter(20, 30, 'sponsor'),
+ self._sponsor_chapter(50, 70, 'selfpromo')]
expected = self._chapters([10, 20, 30, 40, 50, 70, 80],
['c1', 'c2', '[SponsorBlock]: Sponsor', 'c2', 'c3',
'[SponsorBlock]: Unpaid/Self Promotion', 'c4'])
@@ -374,8 +397,9 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_ChaptersAfterLastSponsor(self):
- chapters = (self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4'])
- + [self._sponsor_chapter(10, 30, 'music_offtopic')])
+ chapters = [
+ *self._chapters([20, 40, 50, 60], ['c1', 'c2', 'c3', 'c4']),
+ self._sponsor_chapter(10, 30, 'music_offtopic')]
expected = self._chapters(
[10, 30, 40, 50, 60],
['c1', '[SponsorBlock]: Non-Music Section', 'c2', 'c3', 'c4'])
@@ -388,8 +412,9 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_SponsorStartsAtChapterStart(self):
- chapters = (self._chapters([10, 20, 40], ['c1', 'c2', 'c3'])
- + [self._sponsor_chapter(20, 30, 'sponsor')])
+ chapters = [
+ *self._chapters([10, 20, 40], ['c1', 'c2', 'c3']),
+ self._sponsor_chapter(20, 30, 'sponsor')]
expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -400,8 +425,9 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_SponsorEndsAtChapterEnd(self):
- chapters = (self._chapters([10, 30, 40], ['c1', 'c2', 'c3'])
- + [self._sponsor_chapter(20, 30, 'sponsor')])
+ chapters = [
+ *self._chapters([10, 30, 40], ['c1', 'c2', 'c3']),
+ self._sponsor_chapter(20, 30, 'sponsor')]
expected = self._chapters([10, 20, 30, 40], ['c1', 'c2', '[SponsorBlock]: Sponsor', 'c3'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -412,8 +438,9 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_SponsorCoincidesWithChapters(self):
- chapters = (self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
- + [self._sponsor_chapter(10, 30, 'sponsor')])
+ chapters = [
+ *self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']),
+ self._sponsor_chapter(10, 30, 'sponsor')]
expected = self._chapters([10, 30, 40], ['c1', '[SponsorBlock]: Sponsor', 'c4'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -424,8 +451,9 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_SponsorsAtVideoBoundaries(self):
- chapters = (self._chapters([20, 40, 60], ['c1', 'c2', 'c3'])
- + [self._sponsor_chapter(0, 10, 'intro'), self._sponsor_chapter(50, 60, 'outro')])
+ chapters = [
+ *self._chapters([20, 40, 60], ['c1', 'c2', 'c3']),
+ self._sponsor_chapter(0, 10, 'intro'), self._sponsor_chapter(50, 60, 'outro')]
expected = self._chapters(
[10, 20, 40, 50, 60], ['[SponsorBlock]: Intermission/Intro Animation', 'c1', 'c2', 'c3', '[SponsorBlock]: Endcards/Credits'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -437,8 +465,10 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_SponsorsOverlapChaptersAtVideoBoundaries(self):
- chapters = (self._chapters([10, 40, 50], ['c1', 'c2', 'c3'])
- + [self._sponsor_chapter(0, 20, 'intro'), self._sponsor_chapter(30, 50, 'outro')])
+ chapters = [
+ *self._chapters([10, 40, 50], ['c1', 'c2', 'c3']),
+ self._sponsor_chapter(0, 20, 'intro'),
+ self._sponsor_chapter(30, 50, 'outro')]
expected = self._chapters(
[20, 30, 50], ['[SponsorBlock]: Intermission/Intro Animation', 'c2', '[SponsorBlock]: Endcards/Credits'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -450,8 +480,10 @@ class TestModifyChaptersPP(unittest.TestCase):
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, cuts)
def test_remove_marked_arrange_sponsors_EverythingSponsored(self):
- chapters = (self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4'])
- + [self._sponsor_chapter(0, 20, 'intro'), self._sponsor_chapter(20, 40, 'outro')])
+ chapters = [
+ *self._chapters([10, 20, 30, 40], ['c1', 'c2', 'c3', 'c4']),
+ self._sponsor_chapter(0, 20, 'intro'),
+ self._sponsor_chapter(20, 40, 'outro')]
expected = self._chapters([20, 40], ['[SponsorBlock]: Intermission/Intro Animation', '[SponsorBlock]: Endcards/Credits'])
self._remove_marked_arrange_sponsors_test_impl(chapters, expected, [])
@@ -491,38 +523,39 @@ class TestModifyChaptersPP(unittest.TestCase):
chapters, self._chapters([2.5], ['c2']), cuts)
def test_remove_marked_arrange_sponsors_TinyChaptersResultingFromSponsorOverlapAreIgnored(self):
- chapters = self._chapters([1, 3, 4], ['c1', 'c2', 'c3']) + [
+ chapters = [
+ *self._chapters([1, 3, 4], ['c1', 'c2', 'c3']),
self._sponsor_chapter(1.5, 2.5, 'sponsor')]
self._remove_marked_arrange_sponsors_test_impl(
chapters, self._chapters([1.5, 2.5, 4], ['c1', '[SponsorBlock]: Sponsor', 'c3']), [])
def test_remove_marked_arrange_sponsors_TinySponsorsOverlapsAreIgnored(self):
- chapters = self._chapters([2, 3, 5], ['c1', 'c2', 'c3']) + [
+ chapters = [
+ *self._chapters([2, 3, 5], ['c1', 'c2', 'c3']),
self._sponsor_chapter(1, 3, 'sponsor'),
- self._sponsor_chapter(2.5, 4, 'selfpromo')
- ]
+ self._sponsor_chapter(2.5, 4, 'selfpromo')]
self._remove_marked_arrange_sponsors_test_impl(
chapters, self._chapters([1, 3, 4, 5], [
'c1', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion', 'c3']), [])
def test_remove_marked_arrange_sponsors_TinySponsorsPrependedToTheNextSponsor(self):
- chapters = self._chapters([4], ['c']) + [
+ chapters = [
+ *self._chapters([4], ['c']),
self._sponsor_chapter(1.5, 2, 'sponsor'),
- self._sponsor_chapter(2, 4, 'selfpromo')
- ]
+ self._sponsor_chapter(2, 4, 'selfpromo')]
self._remove_marked_arrange_sponsors_test_impl(
chapters, self._chapters([1.5, 4], ['c', '[SponsorBlock]: Unpaid/Self Promotion']), [])
def test_remove_marked_arrange_sponsors_SmallestSponsorInTheOverlapGetsNamed(self):
self._pp._sponsorblock_chapter_title = '[SponsorBlock]: %(name)s'
- chapters = self._chapters([10], ['c']) + [
+ chapters = [
+ *self._chapters([10], ['c']),
self._sponsor_chapter(2, 8, 'sponsor'),
- self._sponsor_chapter(4, 6, 'selfpromo')
- ]
+ self._sponsor_chapter(4, 6, 'selfpromo')]
self._remove_marked_arrange_sponsors_test_impl(
chapters, self._chapters([2, 4, 6, 8, 10], [
'c', '[SponsorBlock]: Sponsor', '[SponsorBlock]: Unpaid/Self Promotion',
- '[SponsorBlock]: Sponsor', 'c'
+ '[SponsorBlock]: Sponsor', 'c',
]), [])
def test_make_concat_opts_CommonCase(self):
diff --git a/test/test_socks.py b/test/test_socks.py
index 43d612d85..68af19d0c 100644
--- a/test/test_socks.py
+++ b/test/test_socks.py
@@ -95,7 +95,7 @@ class Socks5ProxyHandler(StreamRequestHandler, SocksProxyHandler):
return
elif Socks5Auth.AUTH_USER_PASS in methods:
- self.connection.sendall(struct.pack("!BB", SOCKS5_VERSION, Socks5Auth.AUTH_USER_PASS))
+ self.connection.sendall(struct.pack('!BB', SOCKS5_VERSION, Socks5Auth.AUTH_USER_PASS))
_, user_len = struct.unpack('!BB', self.connection.recv(2))
username = self.connection.recv(user_len).decode()
@@ -174,7 +174,7 @@ class Socks4ProxyHandler(StreamRequestHandler, SocksProxyHandler):
if 0x0 < dest_ip <= 0xFF:
use_remote_dns = True
else:
- socks_info['ipv4_address'] = socket.inet_ntoa(struct.pack("!I", dest_ip))
+ socks_info['ipv4_address'] = socket.inet_ntoa(struct.pack('!I', dest_ip))
user_id = self._read_until_null().decode()
if user_id != (self.socks_kwargs.get('user_id') or ''):
@@ -291,7 +291,7 @@ def ctx(request):
('Urllib', 'http'),
('Requests', 'http'),
('Websockets', 'ws'),
- ('CurlCFFI', 'http')
+ ('CurlCFFI', 'http'),
], indirect=True)
class TestSocks4Proxy:
def test_socks4_no_auth(self, handler, ctx):
@@ -366,7 +366,7 @@ class TestSocks4Proxy:
('Urllib', 'http'),
('Requests', 'http'),
('Websockets', 'ws'),
- ('CurlCFFI', 'http')
+ ('CurlCFFI', 'http'),
], indirect=True)
class TestSocks5Proxy:
diff --git a/test/test_subtitles.py b/test/test_subtitles.py
index 57362895f..f3b005617 100644
--- a/test/test_subtitles.py
+++ b/test/test_subtitles.py
@@ -40,12 +40,11 @@ class BaseTestSubtitles(unittest.TestCase):
self.ie = self.IE()
self.DL.add_info_extractor(self.ie)
if not self.IE.working():
- print('Skipping: %s marked as not _WORKING' % self.IE.ie_key())
+ print(f'Skipping: {self.IE.ie_key()} marked as not _WORKING')
self.skipTest('IE marked as not _WORKING')
def getInfoDict(self):
- info_dict = self.DL.extract_info(self.url, download=False)
- return info_dict
+ return self.DL.extract_info(self.url, download=False)
def getSubtitles(self):
info_dict = self.getInfoDict()
@@ -87,7 +86,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
self.assertEqual(md5(subtitles['en']), 'ae1bd34126571a77aabd4d276b28044d')
self.assertEqual(md5(subtitles['it']), '0e0b667ba68411d88fd1c5f4f4eab2f9')
for lang in ['fr', 'de']:
- self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
+ self.assertTrue(subtitles.get(lang) is not None, f'Subtitles for \'{lang}\' not extracted')
def _test_subtitles_format(self, fmt, md5_hash, lang='en'):
self.DL.params['writesubtitles'] = True
@@ -157,7 +156,7 @@ class TestDailymotionSubtitles(BaseTestSubtitles):
self.assertEqual(md5(subtitles['en']), '976553874490cba125086bbfea3ff76f')
self.assertEqual(md5(subtitles['fr']), '594564ec7d588942e384e920e5341792')
for lang in ['es', 'fr', 'de']:
- self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
+ self.assertTrue(subtitles.get(lang) is not None, f'Subtitles for \'{lang}\' not extracted')
def test_nosubtitles(self):
self.DL.expect_warning('video doesn\'t have subtitles')
@@ -182,7 +181,7 @@ class TestTedSubtitles(BaseTestSubtitles):
self.assertEqual(md5(subtitles['en']), '4262c1665ff928a2dada178f62cb8d14')
self.assertEqual(md5(subtitles['fr']), '66a63f7f42c97a50f8c0e90bc7797bb5')
for lang in ['es', 'fr', 'de']:
- self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
+ self.assertTrue(subtitles.get(lang) is not None, f'Subtitles for \'{lang}\' not extracted')
@is_download_test
diff --git a/test/test_traversal.py b/test/test_traversal.py
index 9b2a27b08..5d9fbe1d1 100644
--- a/test/test_traversal.py
+++ b/test/test_traversal.py
@@ -31,7 +31,7 @@ class TestTraversal:
'allow tuple path'
assert traverse_obj(_TEST_DATA, ['str']) == 'str', \
'allow list path'
- assert traverse_obj(_TEST_DATA, (value for value in ("str",))) == 'str', \
+ assert traverse_obj(_TEST_DATA, (value for value in ('str',))) == 'str', \
'allow iterable path'
assert traverse_obj(_TEST_DATA, 'str') == 'str', \
'single items should be treated as a path'
@@ -70,7 +70,7 @@ class TestTraversal:
def test_traversal_set(self):
# transformation/type, like `expected_type`
- assert traverse_obj(_TEST_DATA, (..., {str.upper}, )) == ['STR'], \
+ assert traverse_obj(_TEST_DATA, (..., {str.upper})) == ['STR'], \
'Function in set should be a transformation'
assert traverse_obj(_TEST_DATA, (..., {str})) == ['str'], \
'Type in set should be a type filter'
@@ -276,7 +276,7 @@ class TestTraversal:
'`...` should result in string (same value) if `traverse_string`'
assert traverse_obj(_TRAVERSE_STRING_DATA, ('str', slice(0, None, 2)), traverse_string=True) == 'sr', \
'`slice` should result in string if `traverse_string`'
- assert traverse_obj(_TRAVERSE_STRING_DATA, ('str', lambda i, v: i or v == "s"), traverse_string=True) == 'str', \
+ assert traverse_obj(_TRAVERSE_STRING_DATA, ('str', lambda i, v: i or v == 's'), traverse_string=True) == 'str', \
'function should result in string if `traverse_string`'
assert traverse_obj(_TRAVERSE_STRING_DATA, ('str', (0, 2)), traverse_string=True) == ['s', 'r'], \
'branching should result in list if `traverse_string`'
diff --git a/test/test_update.py b/test/test_update.py
index bc139562f..63a21e445 100644
--- a/test/test_update.py
+++ b/test/test_update.py
@@ -78,11 +78,11 @@ TEST_API_DATA = {
TEST_LOCKFILE_COMMENT = '# This file is used for regulating self-update'
-TEST_LOCKFILE_V1 = r'''%s
+TEST_LOCKFILE_V1 = rf'''{TEST_LOCKFILE_COMMENT}
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
@@ -98,12 +98,12 @@ TEST_LOCKFILE_V2 = TEST_LOCKFILE_V2_TMPL % TEST_LOCKFILE_COMMENT
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
+TEST_LOCKFILE_FORK = rf'''{TEST_LOCKFILE_ACTUAL}# Test if a fork blocks updates to non-numeric tags
lockV2 fork/yt-dlp pr0000 .+ Python 3.6
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):
diff --git a/test/test_utils.py b/test/test_utils.py
index 77fadbbea..251739686 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -276,8 +276,8 @@ class TestUtil(unittest.TestCase):
self.assertEqual(expand_path(env('HOME')), os.getenv('HOME'))
self.assertEqual(expand_path('~'), os.getenv('HOME'))
self.assertEqual(
- expand_path('~/%s' % env('yt_dlp_EXPATH_PATH')),
- '%s/expanded' % os.getenv('HOME'))
+ expand_path('~/{}'.format(env('yt_dlp_EXPATH_PATH'))),
+ '{}/expanded'.format(os.getenv('HOME')))
finally:
os.environ['HOME'] = old_home or ''
@@ -356,12 +356,12 @@ class TestUtil(unittest.TestCase):
self.assertEqual(datetime_from_str('now+23hours', precision='hour'), datetime_from_str('now+23hours', precision='auto'))
def test_daterange(self):
- _20century = DateRange("19000101", "20000101")
- self.assertFalse("17890714" in _20century)
- _ac = DateRange("00010101")
- self.assertTrue("19690721" in _ac)
- _firstmilenium = DateRange(end="10000101")
- self.assertTrue("07110427" in _firstmilenium)
+ _20century = DateRange('19000101', '20000101')
+ self.assertFalse('17890714' in _20century)
+ _ac = DateRange('00010101')
+ self.assertTrue('19690721' in _ac)
+ _firstmilenium = DateRange(end='10000101')
+ self.assertTrue('07110427' in _firstmilenium)
def test_unified_dates(self):
self.assertEqual(unified_strdate('December 21, 2010'), '20101221')
@@ -506,7 +506,7 @@ class TestUtil(unittest.TestCase):
self.assertRaises(ExtractorError, xpath_attr, doc, 'div/p', 'y', fatal=True)
def test_smuggle_url(self):
- data = {"ö": "ö", "abc": [3]}
+ data = {'ö': 'ö', 'abc': [3]}
url = 'https://foo.bar/baz?x=y#a'
smug_url = smuggle_url(url, data)
unsmug_url, unsmug_data = unsmuggle_url(smug_url)
@@ -784,7 +784,7 @@ class TestUtil(unittest.TestCase):
def test_strip_jsonp(self):
stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);')
d = json.loads(stripped)
- self.assertEqual(d, [{"id": "532cb", "x": 3}])
+ self.assertEqual(d, [{'id': '532cb', 'x': 3}])
stripped = strip_jsonp('parseMetadata({"STATUS":"OK"})\n\n\n//epc')
d = json.loads(stripped)
@@ -922,19 +922,19 @@ class TestUtil(unittest.TestCase):
def test_normalize_url(self):
self.assertEqual(
normalize_url('http://wowza.imust.org/srv/vod/telemb/new/UPLOAD/UPLOAD/20224_IncendieHavré_FD.mp4'),
- 'http://wowza.imust.org/srv/vod/telemb/new/UPLOAD/UPLOAD/20224_IncendieHavre%CC%81_FD.mp4'
+ 'http://wowza.imust.org/srv/vod/telemb/new/UPLOAD/UPLOAD/20224_IncendieHavre%CC%81_FD.mp4',
)
self.assertEqual(
normalize_url('http://www.ardmediathek.de/tv/Sturm-der-Liebe/Folge-2036-Zu-Mann-und-Frau-erklärt/Das-Erste/Video?documentId=22673108&bcastId=5290'),
- 'http://www.ardmediathek.de/tv/Sturm-der-Liebe/Folge-2036-Zu-Mann-und-Frau-erkl%C3%A4rt/Das-Erste/Video?documentId=22673108&bcastId=5290'
+ 'http://www.ardmediathek.de/tv/Sturm-der-Liebe/Folge-2036-Zu-Mann-und-Frau-erkl%C3%A4rt/Das-Erste/Video?documentId=22673108&bcastId=5290',
)
self.assertEqual(
normalize_url('http://тест.рф/фрагмент'),
- 'http://xn--e1aybc.xn--p1ai/%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82'
+ 'http://xn--e1aybc.xn--p1ai/%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82',
)
self.assertEqual(
normalize_url('http://тест.рф/абв?абв=абв#абв'),
- 'http://xn--e1aybc.xn--p1ai/%D0%B0%D0%B1%D0%B2?%D0%B0%D0%B1%D0%B2=%D0%B0%D0%B1%D0%B2#%D0%B0%D0%B1%D0%B2'
+ 'http://xn--e1aybc.xn--p1ai/%D0%B0%D0%B1%D0%B2?%D0%B0%D0%B1%D0%B2=%D0%B0%D0%B1%D0%B2#%D0%B0%D0%B1%D0%B2',
)
self.assertEqual(normalize_url('http://vimeo.com/56015672#at=0'), 'http://vimeo.com/56015672#at=0')
@@ -979,7 +979,7 @@ class TestUtil(unittest.TestCase):
'e': 'false',
'f': '"false"',
'g': 'var',
- }
+ },
)),
{
'null': None,
@@ -988,8 +988,8 @@ class TestUtil(unittest.TestCase):
'trueStr': 'true',
'false': False,
'falseStr': 'false',
- 'unresolvedVar': 'var'
- }
+ 'unresolvedVar': 'var',
+ },
)
self.assertDictEqual(
@@ -1005,14 +1005,14 @@ class TestUtil(unittest.TestCase):
'b': '"123"',
'c': '1.23',
'd': '"1.23"',
- }
+ },
)),
{
'int': 123,
'intStr': '123',
'float': 1.23,
'floatStr': '1.23',
- }
+ },
)
self.assertDictEqual(
@@ -1028,14 +1028,14 @@ class TestUtil(unittest.TestCase):
'b': '"{}"',
'c': '[]',
'd': '"[]"',
- }
+ },
)),
{
'object': {},
'objectStr': '{}',
'array': [],
'arrayStr': '[]',
- }
+ },
)
def test_js_to_json_realworld(self):
@@ -1081,7 +1081,7 @@ class TestUtil(unittest.TestCase):
def test_js_to_json_edgecases(self):
on = js_to_json("{abc_def:'1\\'\\\\2\\\\\\'3\"4'}")
- self.assertEqual(json.loads(on), {"abc_def": "1'\\2\\'3\"4"})
+ self.assertEqual(json.loads(on), {'abc_def': "1'\\2\\'3\"4"})
on = js_to_json('{"abc": true}')
self.assertEqual(json.loads(on), {'abc': True})
@@ -1113,9 +1113,9 @@ class TestUtil(unittest.TestCase):
'c': 0,
'd': 42.42,
'e': [],
- 'f': "abc",
- 'g': "",
- '42': 42
+ 'f': 'abc',
+ 'g': '',
+ '42': 42,
})
on = js_to_json('["abc", "def",]')
@@ -1209,8 +1209,8 @@ class TestUtil(unittest.TestCase):
self.assertEqual(json.loads(js_to_json('Array(5, 10)')), [5, 10])
self.assertEqual(json.loads(js_to_json('new Array(15,5)')), [15, 5])
self.assertEqual(json.loads(js_to_json('new Map([Array(5, 10),new Array(15,5)])')), {'5': 10, '15': 5})
- self.assertEqual(json.loads(js_to_json('new Date("123")')), "123")
- self.assertEqual(json.loads(js_to_json('new Date(\'2023-10-19\')')), "2023-10-19")
+ self.assertEqual(json.loads(js_to_json('new Date("123")')), '123')
+ self.assertEqual(json.loads(js_to_json('new Date(\'2023-10-19\')')), '2023-10-19')
def test_extract_attributes(self):
self.assertEqual(extract_attributes('<e x="y">'), {'x': 'y'})
@@ -1265,7 +1265,7 @@ class TestUtil(unittest.TestCase):
def test_args_to_str(self):
self.assertEqual(
args_to_str(['foo', 'ba/r', '-baz', '2 be', '']),
- 'foo ba/r -baz \'2 be\' \'\'' if compat_os_name != 'nt' else 'foo ba/r -baz "2 be" ""'
+ 'foo ba/r -baz \'2 be\' \'\'' if compat_os_name != 'nt' else 'foo ba/r -baz "2 be" ""',
)
def test_parse_filesize(self):
@@ -1348,10 +1348,10 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
self.assertTrue(is_html( # UTF-8 with BOM
b'\xef\xbb\xbf<!DOCTYPE foo>\xaaa'))
self.assertTrue(is_html( # UTF-16-LE
- b'\xff\xfe<\x00h\x00t\x00m\x00l\x00>\x00\xe4\x00'
+ b'\xff\xfe<\x00h\x00t\x00m\x00l\x00>\x00\xe4\x00',
))
self.assertTrue(is_html( # UTF-16-BE
- b'\xfe\xff\x00<\x00h\x00t\x00m\x00l\x00>\x00\xe4'
+ b'\xfe\xff\x00<\x00h\x00t\x00m\x00l\x00>\x00\xe4',
))
self.assertTrue(is_html( # UTF-32-BE
b'\x00\x00\xFE\xFF\x00\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4'))
@@ -1935,7 +1935,7 @@ Line 1
with locked_file(FILE, test_mode, False):
pass
except (BlockingIOError, PermissionError):
- if not testing_write: # FIXME
+ if not testing_write: # FIXME: blocked read access
print(f'Known issue: Exclusive lock ({lock_mode}) blocks read access ({test_mode})')
continue
self.assertTrue(testing_write, f'{test_mode} is blocked by {lock_mode}')
@@ -2003,7 +2003,7 @@ Line 1
msg='int fn with expected_type int should give int')
self.assertEqual(try_call(lambda: 1, expected_type=dict), None,
msg='int fn with wrong expected_type should give None')
- self.assertEqual(try_call(total, args=(0, 1, 0, ), expected_type=int), 1,
+ self.assertEqual(try_call(total, args=(0, 1, 0), expected_type=int), 1,
msg='fn should accept arglist')
self.assertEqual(try_call(total, kwargs={'a': 0, 'b': 1, 'c': 0}, expected_type=int), 1,
msg='fn should accept kwargs')
diff --git a/test/test_websockets.py b/test/test_websockets.py
index aa0dfa2d5..5f101abcc 100644
--- a/test/test_websockets.py
+++ b/test/test_websockets.py
@@ -297,14 +297,14 @@ class TestWebsSocketRequestHandlerConformance:
'client_certificate': os.path.join(MTLS_CERT_DIR, 'client.crt'),
'client_certificate_key': os.path.join(MTLS_CERT_DIR, 'clientencrypted.key'),
'client_certificate_password': 'foobar',
- }
+ },
))
def test_mtls(self, handler, client_cert):
with handler(
# Disable client-side validation of unacceptable self-signed testcert.pem
# The test is of a check on the server side, so unaffected
verify=False,
- client_cert=client_cert
+ client_cert=client_cert,
) as rh:
ws_validate_and_send(rh, Request(self.mtls_wss_base_url)).close()
diff --git a/test/test_youtube_misc.py b/test/test_youtube_misc.py
index 81be5d3c9..81b116217 100644
--- a/test/test_youtube_misc.py
+++ b/test/test_youtube_misc.py
@@ -13,7 +13,7 @@ from yt_dlp.extractor import YoutubeIE
class TestYoutubeMisc(unittest.TestCase):
def test_youtube_extract(self):
- assertExtractId = lambda url, id: self.assertEqual(YoutubeIE.extract_id(url), id)
+ assertExtractId = lambda url, video_id: self.assertEqual(YoutubeIE.extract_id(url), video_id)
assertExtractId('http://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc')
assertExtractId('https://www.youtube.com/watch?&v=BaW_jenozKc', 'BaW_jenozKc')
assertExtractId('https://www.youtube.com/watch?feature=player_embedded&v=BaW_jenozKc', 'BaW_jenozKc')
diff --git a/test/test_youtube_signature.py b/test/test_youtube_signature.py
index c5592845b..bfaff83a0 100644
--- a/test/test_youtube_signature.py
+++ b/test/test_youtube_signature.py
@@ -46,17 +46,17 @@ _SIG_TESTS = [
(
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflBb0OQx.js',
84,
- '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ0STUVWXYZ!"#$%&\'()*+,@./:;<=>'
+ '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ0STUVWXYZ!"#$%&\'()*+,@./:;<=>',
),
(
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vfl9FYC6l.js',
83,
- '123456789abcdefghijklmnopqr0tuvwxyzABCDETGHIJKLMNOPQRS>UVWXYZ!"#$%&\'()*+,-./:;<=F'
+ '123456789abcdefghijklmnopqr0tuvwxyzABCDETGHIJKLMNOPQRS>UVWXYZ!"#$%&\'()*+,-./:;<=F',
),
(
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflCGk6yw/html5player.js',
'4646B5181C6C3020DF1D9C7FCFEA.AD80ABF70C39BD369CCCAE780AFBB98FA6B6CB42766249D9488C288',
- '82C8849D94266724DC6B6AF89BBFA087EACCD963.B93C07FBA084ACAEFCF7C9D1FD0203C6C1815B6B'
+ '82C8849D94266724DC6B6AF89BBFA087EACCD963.B93C07FBA084ACAEFCF7C9D1FD0203C6C1815B6B',
),
(
'https://s.ytimg.com/yts/jsbin/html5player-en_US-vflKjOTVq/html5player.js',
@@ -207,7 +207,7 @@ class TestSignature(unittest.TestCase):
def t_factory(name, sig_func, url_pattern):
def make_tfunc(url, sig_input, expected_sig):
m = url_pattern.match(url)
- assert m, '%r should follow URL format' % url
+ assert m, f'{url!r} should follow URL format'
test_id = m.group('id')
def test_func(self):
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py
index 2c6f695d0..5abcb4635 100644
--- a/yt_dlp/YoutubeDL.py
+++ b/yt_dlp/YoutubeDL.py
@@ -109,7 +109,6 @@ from .utils import (
determine_protocol,
encode_compat_str,
encodeFilename,
- error_to_compat_str,
escapeHTML,
expand_path,
extract_basic_auth,
@@ -583,7 +582,7 @@ class YoutubeDL:
'player_url', 'protocol', 'fragment_base_url', 'fragments', 'is_from_start', 'is_dash_periods', 'request_data',
'preference', 'language', 'language_preference', 'quality', 'source_preference', 'cookies',
'http_headers', 'stretched_ratio', 'no_resume', 'has_drm', 'extra_param_to_segment_url', 'hls_aes', 'downloader_options',
- 'page_url', 'app', 'play_path', 'tc_url', 'flash_version', 'rtmp_live', 'rtmp_conn', 'rtmp_protocol', 'rtmp_real_time'
+ 'page_url', 'app', 'play_path', 'tc_url', 'flash_version', 'rtmp_live', 'rtmp_conn', 'rtmp_protocol', 'rtmp_real_time',
}
_deprecated_multivalue_fields = {
'album_artist': 'album_artists',
@@ -594,7 +593,7 @@ class YoutubeDL:
}
_format_selection_exts = {
'audio': set(MEDIA_EXTENSIONS.common_audio),
- 'video': set(MEDIA_EXTENSIONS.common_video + ('3gp', )),
+ 'video': {*MEDIA_EXTENSIONS.common_video, '3gp'},
'storyboards': set(MEDIA_EXTENSIONS.storyboards),
}
@@ -628,7 +627,7 @@ class YoutubeDL:
error=sys.stderr,
screen=sys.stderr if self.params.get('quiet') else stdout,
console=None if compat_os_name == 'nt' else next(
- filter(supports_terminal_sequences, (sys.stderr, sys.stdout)), None)
+ filter(supports_terminal_sequences, (sys.stderr, sys.stdout)), None),
)
try:
@@ -679,9 +678,9 @@ class YoutubeDL:
width_args = [] if width is None else ['-w', str(width)]
sp_kwargs = {'stdin': subprocess.PIPE, 'stdout': slave, 'stderr': self._out_files.error}
try:
- self._output_process = Popen(['bidiv'] + width_args, **sp_kwargs)
+ self._output_process = Popen(['bidiv', *width_args], **sp_kwargs)
except OSError:
- self._output_process = Popen(['fribidi', '-c', 'UTF-8'] + width_args, **sp_kwargs)
+ self._output_process = Popen(['fribidi', '-c', 'UTF-8', *width_args], **sp_kwargs)
self._output_channel = os.fdopen(master, 'rb')
except OSError as ose:
if ose.errno == errno.ENOENT:
@@ -822,8 +821,7 @@ class YoutubeDL:
)
self.report_warning(
'Long argument string detected. '
- 'Use -- to separate parameters and URLs, like this:\n%s' %
- shell_quote(correct_argv))
+ f'Use -- to separate parameters and URLs, like this:\n{shell_quote(correct_argv)}')
def add_info_extractor(self, ie):
"""Add an InfoExtractor object to the end of the list."""
@@ -922,7 +920,7 @@ class YoutubeDL:
if (self.params.get('quiet') if quiet is None else quiet) and not self.params.get('verbose'):
return
self._write_string(
- '%s%s' % (self._bidi_workaround(message), ('' if skip_eol else '\n')),
+ '{}{}'.format(self._bidi_workaround(message), ('' if skip_eol else '\n')),
self._out_files.screen, only_once=only_once)
def to_stderr(self, message, only_once=False):
@@ -1045,10 +1043,10 @@ class YoutubeDL:
return self._format_text(self._out_files.error, self._allow_colors.error, *args, **kwargs)
def report_warning(self, message, only_once=False):
- '''
+ """
Print the message to stderr, it will be prefixed with 'WARNING:'
If stderr is a tty file the 'WARNING:' will be colored
- '''
+ """
if self.params.get('logger') is not None:
self.params['logger'].warning(message)
else:
@@ -1066,14 +1064,14 @@ class YoutubeDL:
self.to_stderr(f'{self._format_err("Deprecated Feature:", self.Styles.ERROR)} {message}', True)
def report_error(self, message, *args, **kwargs):
- '''
+ """
Do the same as trouble, but prefixes the message with 'ERROR:', colored
in red if stderr is a tty file.
- '''
+ """
self.trouble(f'{self._format_err("ERROR:", self.Styles.ERROR)} {message}', *args, **kwargs)
def write_debug(self, message, only_once=False):
- '''Log debug message or Print message to stderr'''
+ """Log debug message or Print message to stderr"""
if not self.params.get('verbose', False):
return
message = f'[debug] {message}'
@@ -1085,14 +1083,14 @@ class YoutubeDL:
def report_file_already_downloaded(self, file_name):
"""Report file has already been fully downloaded."""
try:
- self.to_screen('[download] %s has already been downloaded' % file_name)
+ self.to_screen(f'[download] {file_name} has already been downloaded')
except UnicodeEncodeError:
self.to_screen('[download] The file has already been downloaded')
def report_file_delete(self, file_name):
"""Report that existing file will be deleted."""
try:
- self.to_screen('Deleting existing file %s' % file_name)
+ self.to_screen(f'Deleting existing file {file_name}')
except UnicodeEncodeError:
self.to_screen('Deleting existing file')
@@ -1147,7 +1145,7 @@ class YoutubeDL:
@staticmethod
def escape_outtmpl(outtmpl):
- ''' Escape any remaining strings like %s, %abc% etc. '''
+ """ Escape any remaining strings like %s, %abc% etc. """
return re.sub(
STR_FORMAT_RE_TMPL.format('', '(?![%(\0])'),
lambda mobj: ('' if mobj.group('has_key') else '%') + mobj.group(0),
@@ -1155,7 +1153,7 @@ class YoutubeDL:
@classmethod
def validate_outtmpl(cls, outtmpl):
- ''' @return None or Exception object '''
+ """ @return None or Exception object """
outtmpl = re.sub(
STR_FORMAT_RE_TMPL.format('[^)]*', '[ljhqBUDS]'),
lambda mobj: f'{mobj.group(0)[:-1]}s',
@@ -1208,13 +1206,13 @@ class YoutubeDL:
}
# Field is of the form key1.key2...
# where keys (except first) can be string, int, slice or "{field, ...}"
- FIELD_INNER_RE = r'(?:\w+|%(num)s|%(num)s?(?::%(num)s?){1,2})' % {'num': r'(?:-?\d+)'}
- FIELD_RE = r'\w*(?:\.(?:%(inner)s|{%(field)s(?:,%(field)s)*}))*' % {
+ FIELD_INNER_RE = r'(?:\w+|%(num)s|%(num)s?(?::%(num)s?){1,2})' % {'num': r'(?:-?\d+)'} # noqa: UP031
+ FIELD_RE = r'\w*(?:\.(?:%(inner)s|{%(field)s(?:,%(field)s)*}))*' % { # noqa: UP031
'inner': FIELD_INNER_RE,
- 'field': rf'\w*(?:\.{FIELD_INNER_RE})*'
+ 'field': rf'\w*(?:\.{FIELD_INNER_RE})*',
}
MATH_FIELD_RE = rf'(?:{FIELD_RE}|-?{NUMBER_RE})'
- MATH_OPERATORS_RE = r'(?:%s)' % '|'.join(map(re.escape, MATH_FUNCTIONS.keys()))
+ MATH_OPERATORS_RE = r'(?:{})'.format('|'.join(map(re.escape, MATH_FUNCTIONS.keys())))
INTERNAL_FORMAT_RE = re.compile(rf'''(?xs)
(?P<negate>-)?
(?P<fields>{FIELD_RE})
@@ -1337,7 +1335,7 @@ class YoutubeDL:
value, default = None, na
fmt = outer_mobj.group('format')
- if fmt == 's' and last_field in field_size_compat_map.keys() and isinstance(value, int):
+ if fmt == 's' and last_field in field_size_compat_map and isinstance(value, int):
fmt = f'0{field_size_compat_map[last_field]:d}d'
flags = outer_mobj.group('conversion') or ''
@@ -1362,7 +1360,7 @@ class YoutubeDL:
elif fmt[-1] == 'U': # unicode normalized
value, fmt = unicodedata.normalize(
# "+" = compatibility equivalence, "#" = NFD
- 'NF%s%s' % ('K' if '+' in flags else '', 'D' if '#' in flags else 'C'),
+ 'NF{}{}'.format('K' if '+' in flags else '', 'D' if '#' in flags else 'C'),
value), str_fmt
elif fmt[-1] == 'D': # decimal suffix
num_fmt, fmt = fmt[:-1].replace('#', ''), 's'
@@ -1390,7 +1388,7 @@ class YoutubeDL:
if fmt[-1] in 'csra':
value = sanitizer(last_field, value)
- key = '%s\0%s' % (key.replace('%', '%\0'), outer_mobj.group('format'))
+ key = '{}\0{}'.format(key.replace('%', '%\0'), outer_mobj.group('format'))
TMPL_DICT[key] = value
return '{prefix}%({key}){fmt}'.format(key=key, fmt=fmt, prefix=outer_mobj.group('prefix'))
@@ -1479,9 +1477,9 @@ class YoutubeDL:
date = info_dict.get('upload_date')
if date is not None:
- dateRange = self.params.get('daterange', DateRange())
- if date not in dateRange:
- return f'{date_from_str(date).isoformat()} upload date is not in range {dateRange}'
+ date_range = self.params.get('daterange', DateRange())
+ if date not in date_range:
+ return f'{date_from_str(date).isoformat()} upload date is not in range {date_range}'
view_count = info_dict.get('view_count')
if view_count is not None:
min_views = self.params.get('min_views')
@@ -1491,7 +1489,7 @@ class YoutubeDL:
if max_views is not None and view_count > max_views:
return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
if age_restricted(info_dict.get('age_limit'), self.params.get('age_limit')):
- return 'Skipping "%s" because it is age restricted' % video_title
+ return f'Skipping "{video_title}" because it is age restricted'
match_filter = self.params.get('match_filter')
if match_filter is None:
@@ -1544,7 +1542,7 @@ class YoutubeDL:
@staticmethod
def add_extra_info(info_dict, extra_info):
- '''Set the keys from extra_info in info dict if they are missing'''
+ """Set the keys from extra_info in info dict if they are missing"""
for key, value in extra_info.items():
info_dict.setdefault(key, value)
@@ -1590,7 +1588,7 @@ class YoutubeDL:
self.to_screen(f'[download] {self._format_screen(temp_id, self.Styles.ID)}: '
'has already been recorded in the archive')
if self.params.get('break_on_existing', False):
- raise ExistingVideoReached()
+ raise ExistingVideoReached
break
return self.__extract_info(url, self.get_info_extractor(key), download, extra_info, process)
else:
@@ -1616,8 +1614,8 @@ class YoutubeDL:
except GeoRestrictedError as e:
msg = e.msg
if e.countries:
- msg += '\nThis video is available in %s.' % ', '.join(
- map(ISO3166Utils.short2full, e.countries))
+ msg += '\nThis video is available in {}.'.format(', '.join(
+ map(ISO3166Utils.short2full, e.countries)))
msg += '\nYou might want to use a VPN or a proxy server (with --proxy) to workaround.'
self.report_error(msg)
except ExtractorError as e: # An error we somewhat expected
@@ -1826,8 +1824,8 @@ class YoutubeDL:
if isinstance(additional_urls, str):
additional_urls = [additional_urls]
self.to_screen(
- '[info] %s: %d additional URL(s) requested' % (ie_result['id'], len(additional_urls)))
- self.write_debug('Additional URLs: "%s"' % '", "'.join(additional_urls))
+ '[info] {}: {} additional URL(s) requested'.format(ie_result['id'], len(additional_urls)))
+ self.write_debug('Additional URLs: "{}"'.format('", "'.join(additional_urls)))
ie_result['additional_entries'] = [
self.extract_info(
url, download, extra_info=extra_info,
@@ -1879,8 +1877,8 @@ class YoutubeDL:
webpage_url = ie_result.get('webpage_url') # Playlists maynot have webpage_url
if webpage_url and webpage_url in self._playlist_urls:
self.to_screen(
- '[download] Skipping already downloaded playlist: %s'
- % ie_result.get('title') or ie_result.get('id'))
+ '[download] Skipping already downloaded playlist: {}'.format(
+ ie_result.get('title')) or ie_result.get('id'))
return
self._playlist_level += 1
@@ -1895,8 +1893,8 @@ class YoutubeDL:
self._playlist_urls.clear()
elif result_type == 'compat_list':
self.report_warning(
- 'Extractor %s returned a compat_list result. '
- 'It needs to be updated.' % ie_result.get('extractor'))
+ 'Extractor {} returned a compat_list result. '
+ 'It needs to be updated.'.format(ie_result.get('extractor')))
def _fixup(r):
self.add_extra_info(r, {
@@ -1913,7 +1911,7 @@ class YoutubeDL:
]
return ie_result
else:
- raise Exception('Invalid result type: %s' % result_type)
+ raise Exception(f'Invalid result type: {result_type}')
def _ensure_dir_exists(self, path):
return make_dir(path, self.report_error)
@@ -2029,8 +2027,9 @@ class YoutubeDL:
resolved_entries[i] = (playlist_index, NO_DEFAULT)
continue
- self.to_screen('[download] Downloading item %s of %s' % (
- self._format_screen(i + 1, self.Styles.ID), self._format_screen(n_entries, self.Styles.EMPHASIS)))
+ self.to_screen(
+ f'[download] Downloading item {self._format_screen(i + 1, self.Styles.ID)} '
+ f'of {self._format_screen(n_entries, self.Styles.EMPHASIS)}')
entry_result = self.__process_iterable_entry(entry, download, collections.ChainMap({
'playlist_index': playlist_index,
@@ -2080,9 +2079,9 @@ class YoutubeDL:
}
operator_rex = re.compile(r'''(?x)\s*
(?P<key>[\w.-]+)\s*
- (?P<op>%s)(?P<none_inclusive>\s*\?)?\s*
+ (?P<op>{})(?P<none_inclusive>\s*\?)?\s*
(?P<value>[0-9.]+(?:[kKmMgGtTpPeEzZyY]i?[Bb]?)?)\s*
- ''' % '|'.join(map(re.escape, OPERATORS.keys())))
+ '''.format('|'.join(map(re.escape, OPERATORS.keys()))))
m = operator_rex.fullmatch(filter_spec)
if m:
try:
@@ -2093,7 +2092,7 @@ class YoutubeDL:
comparison_value = parse_filesize(m.group('value') + 'B')
if comparison_value is None:
raise ValueError(
- 'Invalid value %r in format specification %r' % (
+ 'Invalid value {!r} in format specification {!r}'.format(
m.group('value'), filter_spec))
op = OPERATORS[m.group('op')]
@@ -2103,15 +2102,15 @@ class YoutubeDL:
'^=': lambda attr, value: attr.startswith(value),
'$=': lambda attr, value: attr.endswith(value),
'*=': lambda attr, value: value in attr,
- '~=': lambda attr, value: value.search(attr) is not None
+ '~=': lambda attr, value: value.search(attr) is not None,
}
str_operator_rex = re.compile(r'''(?x)\s*
(?P<key>[a-zA-Z0-9._-]+)\s*
- (?P<negation>!\s*)?(?P<op>%s)\s*(?P<none_inclusive>\?\s*)?
+ (?P<negation>!\s*)?(?P<op>{})\s*(?P<none_inclusive>\?\s*)?
(?P<quote>["'])?
(?P<value>(?(quote)(?:(?!(?P=quote))[^\\]|\\.)+|[\w.-]+))
(?(quote)(?P=quote))\s*
- ''' % '|'.join(map(re.escape, STR_OPERATORS.keys())))
+ '''.format('|'.join(map(re.escape, STR_OPERATORS.keys()))))
m = str_operator_rex.fullmatch(filter_spec)
if m:
if m.group('op') == '~=':
@@ -2125,7 +2124,7 @@ class YoutubeDL:
op = str_op
if not m:
- raise SyntaxError('Invalid filter specification %r' % filter_spec)
+ raise SyntaxError(f'Invalid filter specification {filter_spec!r}')
def _filter(f):
actual_value = f.get(m.group('key'))
@@ -2141,7 +2140,7 @@ class YoutubeDL:
if working:
yield f
continue
- self.to_screen('[info] Testing format %s' % f['format_id'])
+ self.to_screen('[info] Testing format {}'.format(f['format_id']))
path = self.get_output_path('temp')
if not self._ensure_dir_exists(f'{path}/'):
continue
@@ -2149,19 +2148,19 @@ class YoutubeDL:
temp_file.close()
try:
success, _ = self.dl(temp_file.name, f, test=True)
- except (DownloadError, OSError, ValueError) + network_exceptions:
+ except (DownloadError, OSError, ValueError, *network_exceptions):
success = False
finally:
if os.path.exists(temp_file.name):
try:
os.remove(temp_file.name)
except OSError:
- self.report_warning('Unable to delete temporary file "%s"' % temp_file.name)
+ self.report_warning(f'Unable to delete temporary file "{temp_file.name}"')
f['__working'] = success
if success:
yield f
else:
- self.to_screen('[info] Unable to download format %s. Skipping...' % f['format_id'])
+ self.to_screen('[info] Unable to download format {}. Skipping...'.format(f['format_id']))
def _select_formats(self, formats, selector):
return list(selector({
@@ -2214,8 +2213,8 @@ class YoutubeDL:
def _parse_filter(tokens):
filter_parts = []
- for type, string_, start, _, _ in tokens:
- if type == tokenize.OP and string_ == ']':
+ for type_, string_, _start, _, _ in tokens:
+ if type_ == tokenize.OP and string_ == ']':
return ''.join(filter_parts)
else:
filter_parts.append(string_)
@@ -2225,23 +2224,23 @@ class YoutubeDL:
# E.g. 'mp4' '-' 'baseline' '-' '16x9' is converted to 'mp4-baseline-16x9'
ALLOWED_OPS = ('/', '+', ',', '(', ')')
last_string, last_start, last_end, last_line = None, None, None, None
- for type, string_, start, end, line in tokens:
- if type == tokenize.OP and string_ == '[':
+ for type_, string_, start, end, line in tokens:
+ if type_ == tokenize.OP and string_ == '[':
if last_string:
yield tokenize.NAME, last_string, last_start, last_end, last_line
last_string = None
- yield type, string_, start, end, line
+ yield type_, string_, start, end, line
# everything inside brackets will be handled by _parse_filter
- for type, string_, start, end, line in tokens:
- yield type, string_, start, end, line
- if type == tokenize.OP and string_ == ']':
+ for type_, string_, start, end, line in tokens:
+ yield type_, string_, start, end, line
+ if type_ == tokenize.OP and string_ == ']':
break
- elif type == tokenize.OP and string_ in ALLOWED_OPS:
+ elif type_ == tokenize.OP and string_ in ALLOWED_OPS:
if last_string:
yield tokenize.NAME, last_string, last_start, last_end, last_line
last_string = None
- yield type, string_, start, end, line
- elif type in [tokenize.NAME, tokenize.NUMBER, tokenize.OP]:
+ yield type_, string_, start, end, line
+ elif type_ in [tokenize.NAME, tokenize.NUMBER, tokenize.OP]:
if not last_string:
last_string = string_
last_start = start
@@ -2254,13 +2253,13 @@ class YoutubeDL:
def _parse_format_selection(tokens, inside_merge=False, inside_choice=False, inside_group=False):
selectors = []
current_selector = None
- for type, string_, start, _, _ in tokens:
+ for type_, string_, start, _, _ in tokens:
# ENCODING is only defined in Python 3.x
- if type == getattr(tokenize, 'ENCODING', None):
+ if type_ == getattr(tokenize, 'ENCODING', None):
continue
- elif type in [tokenize.NAME, tokenize.NUMBER]:
+ elif type_ in [tokenize.NAME, tokenize.NUMBER]:
current_selector = FormatSelector(SINGLE, string_, [])
- elif type == tokenize.OP:
+ elif type_ == tokenize.OP:
if string_ == ')':
if not inside_group:
# ')' will be handled by the parentheses group
@@ -2303,7 +2302,7 @@ class YoutubeDL:
current_selector = FormatSelector(MERGE, (selector_1, selector_2), [])
else:
raise syntax_error(f'Operator not recognized: "{string_}"', start)
- elif type == tokenize.ENDMARKER:
+ elif type_ == tokenize.ENDMARKER:
break
if current_selector:
selectors.append(current_selector)
@@ -2378,7 +2377,7 @@ class YoutubeDL:
'acodec': the_only_audio.get('acodec'),
'abr': the_only_audio.get('abr'),
'asr': the_only_audio.get('asr'),
- 'audio_channels': the_only_audio.get('audio_channels')
+ 'audio_channels': the_only_audio.get('audio_channels'),
})
return new_dict
@@ -2459,9 +2458,9 @@ class YoutubeDL:
format_fallback = not format_type and not format_modified # for b, w
_filter_f = (
- (lambda f: f.get('%scodec' % format_type) != 'none')
+ (lambda f: f.get(f'{format_type}codec') != 'none')
if format_type and format_modified # bv*, ba*, wv*, wa*
- else (lambda f: f.get('%scodec' % not_format_type) == 'none')
+ else (lambda f: f.get(f'{not_format_type}codec') == 'none')
if format_type # bv, ba, wv, wa
else (lambda f: f.get('vcodec') != 'none' and f.get('acodec') != 'none')
if not format_modified # b, w
@@ -2529,7 +2528,7 @@ class YoutubeDL:
def __next__(self):
if self.counter >= len(self.tokens):
- raise StopIteration()
+ raise StopIteration
value = self.tokens[self.counter]
self.counter += 1
return value
@@ -2612,7 +2611,7 @@ class YoutubeDL:
self._sort_thumbnails(thumbnails)
for i, t in enumerate(thumbnails):
if t.get('id') is None:
- t['id'] = '%d' % i
+ t['id'] = str(i)
if t.get('width') and t.get('height'):
t['resolution'] = '%dx%d' % (t['width'], t['height'])
t['url'] = sanitize_url(t['url'])
@@ -2673,8 +2672,8 @@ class YoutubeDL:
# Auto generate title fields corresponding to the *_number fields when missing
# in order to always have clean titles. This is very common for TV series.
for field in ('chapter', 'season', 'episode'):
- if final and info_dict.get('%s_number' % field) is not None and not info_dict.get(field):
- info_dict[field] = '%s %d' % (field.capitalize(), info_dict['%s_number' % field])
+ if final and info_dict.get(f'{field}_number') is not None and not info_dict.get(field):
+ info_dict[field] = '%s %d' % (field.capitalize(), info_dict[f'{field}_number'])
for old_key, new_key in self._deprecated_multivalue_fields.items():
if new_key in info_dict and old_key in info_dict:
@@ -2706,8 +2705,8 @@ class YoutubeDL:
def report_force_conversion(field, field_not, conversion):
self.report_warning(
- '"%s" field is not %s - forcing %s conversion, there is an error in extractor'
- % (field, field_not, conversion))
+ f'"{field}" field is not {field_not} - forcing {conversion} conversion, '
+ 'there is an error in extractor')
def sanitize_string_field(info, string_field):
field = info.get(string_field)
@@ -2824,28 +2823,28 @@ class YoutubeDL:
if not formats:
self.raise_no_formats(info_dict)
- for format in formats:
- sanitize_string_field(format, 'format_id')
- sanitize_numeric_fields(format)
- format['url'] = sanitize_url(format['url'])
- if format.get('ext') is None:
- format['ext'] = determine_ext(format['url']).lower()
- if format['ext'] in ('aac', 'opus', 'mp3', 'flac', 'vorbis'):
- if format.get('acodec') is None:
- format['acodec'] = format['ext']
- if format.get('protocol') is None:
- format['protocol'] = determine_protocol(format)
- if format.get('resolution') is None:
- format['resolution'] = self.format_resolution(format, default=None)
- if format.get('dynamic_range') is None and format.get('vcodec') != 'none':
- format['dynamic_range'] = 'SDR'
- if format.get('aspect_ratio') is None:
- format['aspect_ratio'] = try_call(lambda: round(format['width'] / format['height'], 2))
+ for fmt in formats:
+ sanitize_string_field(fmt, 'format_id')
+ sanitize_numeric_fields(fmt)
+ fmt['url'] = sanitize_url(fmt['url'])
+ if fmt.get('ext') is None:
+ fmt['ext'] = determine_ext(fmt['url']).lower()
+ if fmt['ext'] in ('aac', 'opus', 'mp3', 'flac', 'vorbis'):
+ if fmt.get('acodec') is None:
+ fmt['acodec'] = fmt['ext']
+ if fmt.get('protocol') is None:
+ fmt['protocol'] = determine_protocol(fmt)
+ if fmt.get('resolution') is None:
+ fmt['resolution'] = self.format_resolution(fmt, default=None)
+ if fmt.get('dynamic_range') is None and fmt.get('vcodec') != 'none':
+ fmt['dynamic_range'] = 'SDR'
+ if fmt.get('aspect_ratio') is None:
+ fmt['aspect_ratio'] = try_call(lambda: round(fmt['width'] / fmt['height'], 2))
# For fragmented formats, "tbr" is often max bitrate and not average
- if (('manifest-filesize-approx' in self.params['compat_opts'] or not format.get('manifest_url'))
- and not format.get('filesize') and not format.get('filesize_approx')):
- format['filesize_approx'] = filesize_from_tbr(format.get('tbr'), info_dict.get('duration'))
- format['http_headers'] = self._calc_headers(collections.ChainMap(format, info_dict), load_cookies=True)
+ if (('manifest-filesize-approx' in self.params['compat_opts'] or not fmt.get('manifest_url'))
+ and not fmt.get('filesize') and not fmt.get('filesize_approx')):
+ fmt['filesize_approx'] = filesize_from_tbr(fmt.get('tbr'), info_dict.get('duration'))
+ fmt['http_headers'] = self._calc_headers(collections.ChainMap(fmt, info_dict), load_cookies=True)
# Safeguard against old/insecure infojson when using --load-info-json
if info_dict.get('http_headers'):
@@ -2858,36 +2857,36 @@ class YoutubeDL:
self.sort_formats({
'formats': formats,
- '_format_sort_fields': info_dict.get('_format_sort_fields')
+ '_format_sort_fields': info_dict.get('_format_sort_fields'),
})
# Sanitize and group by format_id
formats_dict = {}
- for i, format in enumerate(formats):
- if not format.get('format_id'):
- format['format_id'] = str(i)
+ for i, fmt in enumerate(formats):
+ if not fmt.get('format_id'):
+ fmt['format_id'] = str(i)
else:
# Sanitize format_id from characters used in format selector expression
- format['format_id'] = re.sub(r'[\s,/+\[\]()]', '_', format['format_id'])
- formats_dict.setdefault(format['format_id'], []).append(format)
+ fmt['format_id'] = re.sub(r'[\s,/+\[\]()]', '_', fmt['format_id'])
+ formats_dict.setdefault(fmt['format_id'], []).append(fmt)
# Make sure all formats have unique format_id
common_exts = set(itertools.chain(*self._format_selection_exts.values()))
for format_id, ambiguous_formats in formats_dict.items():
ambigious_id = len(ambiguous_formats) > 1
- for i, format in enumerate(ambiguous_formats):
+ for i, fmt in enumerate(ambiguous_formats):
if ambigious_id:
- format['format_id'] = '%s-%d' % (format_id, i)
+ fmt['format_id'] = f'{format_id}-{i}'
# Ensure there is no conflict between id and ext in format selection
# See https://github.com/yt-dlp/yt-dlp/issues/1282
- if format['format_id'] != format['ext'] and format['format_id'] in common_exts:
- format['format_id'] = 'f%s' % format['format_id']
-
- if format.get('format') is None:
- format['format'] = '{id} - {res}{note}'.format(
- id=format['format_id'],
- res=self.format_resolution(format),
- note=format_field(format, 'format_note', ' (%s)'),
+ if fmt['format_id'] != fmt['ext'] and fmt['format_id'] in common_exts:
+ fmt['format_id'] = 'f{}'.format(fmt['format_id'])
+
+ if fmt.get('format') is None:
+ fmt['format'] = '{id} - {res}{note}'.format(
+ id=fmt['format_id'],
+ res=self.format_resolution(fmt),
+ note=format_field(fmt, 'format_note', ' (%s)'),
)
if self.params.get('check_formats') is True:
@@ -3009,7 +3008,7 @@ class YoutubeDL:
info_dict['requested_downloads'] = downloaded_formats
info_dict = self.run_all_pps('after_video', info_dict)
if max_downloads_reached:
- raise MaxDownloadsReached()
+ raise MaxDownloadsReached
# We update the info dict with the selected best quality format (backwards compatibility)
info_dict.update(best_format)
@@ -3070,8 +3069,8 @@ class YoutubeDL:
else:
f = formats[-1]
self.report_warning(
- 'No subtitle format found matching "%s" for language %s, '
- 'using %s. Use --list-subs for a list of available subtitles' % (formats_query, lang, f['ext']))
+ 'No subtitle format found matching "{}" for language {}, '
+ 'using {}. Use --list-subs for a list of available subtitles'.format(formats_query, lang, f['ext']))
subs[lang] = f
return subs
@@ -3226,7 +3225,7 @@ class YoutubeDL:
def check_max_downloads():
if self._num_downloads >= float(self.params.get('max_downloads') or 'inf'):
- raise MaxDownloadsReached()
+ raise MaxDownloadsReached
if self.params.get('simulate'):
info_dict['__write_download_archive'] = self.params.get('force_write_download_archive')
@@ -3400,7 +3399,7 @@ class YoutubeDL:
for f in info_dict['requested_formats'] if fd != FFmpegFD else []:
f['filepath'] = fname = prepend_extension(
correct_ext(temp_filename, info_dict['ext']),
- 'f%s' % f['format_id'], info_dict['ext'])
+ 'f{}'.format(f['format_id']), info_dict['ext'])
downloaded.append(fname)
info_dict['url'] = '\n'.join(f['url'] for f in info_dict['requested_formats'])
success, real_download = self.dl(temp_filename, info_dict)
@@ -3433,7 +3432,7 @@ class YoutubeDL:
if temp_filename != '-':
fname = prepend_extension(
correct_ext(temp_filename, new_info['ext']),
- 'f%s' % f['format_id'], new_info['ext'])
+ 'f{}'.format(f['format_id']), new_info['ext'])
if not self._ensure_dir_exists(fname):
return
f['filepath'] = fname
@@ -3465,11 +3464,11 @@ class YoutubeDL:
info_dict['__finaldir'] = os.path.dirname(os.path.abspath(encodeFilename(full_filename)))
except network_exceptions as err:
- self.report_error('unable to download video data: %s' % error_to_compat_str(err))
+ self.report_error(f'unable to download video data: {err}')
return
except OSError as err:
raise UnavailableVideoError(err)
- except (ContentTooShortError, ) as err:
+ except ContentTooShortError as err:
self.report_error(f'content too short (expected {err.expected} bytes and served {err.downloaded})')
return
@@ -3536,13 +3535,13 @@ class YoutubeDL:
try:
replace_info_dict(self.post_process(dl_filename, info_dict, files_to_move))
except PostProcessingError as err:
- self.report_error('Postprocessing: %s' % str(err))
+ self.report_error(f'Postprocessing: {err}')
return
try:
for ph in self._post_hooks:
ph(info_dict['filepath'])
except Exception as err:
- self.report_error('post hooks: %s' % str(err))
+ self.report_error(f'post hooks: {err}')
return
info_dict['__write_download_archive'] = True
@@ -3609,7 +3608,7 @@ class YoutubeDL:
@staticmethod
def sanitize_info(info_dict, remove_private_keys=False):
- ''' Sanitize the infodict for converting to json '''
+ """ Sanitize the infodict for converting to json """
if info_dict is None:
return info_dict
info_dict.setdefault('epoch', int(time.time()))
@@ -3644,7 +3643,7 @@ class YoutubeDL:
@staticmethod
def filter_requested_info(info_dict, actually_filter=True):
- ''' Alias of sanitize_info for backward compatibility '''
+ """ Alias of sanitize_info for backward compatibility """
return YoutubeDL.sanitize_info(info_dict, actually_filter)
def _delete_downloaded_files(self, *files_to_delete, info={}, msg=None):
@@ -3666,7 +3665,7 @@ class YoutubeDL:
actual_post_extract(video_dict or {})
return
- post_extractor = info_dict.pop('__post_extractor', None) or (lambda: {})
+ post_extractor = info_dict.pop('__post_extractor', None) or dict
info_dict.update(post_extractor())
actual_post_extract(info_dict or {})
@@ -3771,7 +3770,7 @@ class YoutubeDL:
if format.get('width') and format.get('height'):
return '%dx%d' % (format['width'], format['height'])
elif format.get('height'):
- return '%sp' % format['height']
+ return '{}p'.format(format['height'])
elif format.get('width'):
return '%dx?' % format['width']
return default
@@ -3788,7 +3787,7 @@ class YoutubeDL:
if fdict.get('language'):
if res:
res += ' '
- res += '[%s]' % fdict['language']
+ res += '[{}]'.format(fdict['language'])
if fdict.get('format_note') is not None:
if res:
res += ' '
@@ -3800,7 +3799,7 @@ class YoutubeDL:
if fdict.get('container') is not None:
if res:
res += ', '
- res += '%s container' % fdict['container']
+ res += '{} container'.format(fdict['container'])
if (fdict.get('vcodec') is not None
and fdict.get('vcodec') != 'none'):
if res:
@@ -3815,7 +3814,7 @@ class YoutubeDL:
if fdict.get('fps') is not None:
if res:
res += ', '
- res += '%sfps' % fdict['fps']
+ res += '{}fps'.format(fdict['fps'])
if fdict.get('acodec') is not None:
if res:
res += ', '
@@ -3858,7 +3857,7 @@ class YoutubeDL:
format_field(f, 'format_id'),
format_field(f, 'ext'),
self.format_resolution(f),
- self._format_note(f)
+ self._format_note(f),
] for f in formats if (f.get('preference') or 0) >= -1000]
return render_table(['format code', 'extension', 'resolution', 'note'], table, extra_gap=1)
@@ -3964,11 +3963,11 @@ class YoutubeDL:
from .extractor.extractors import _LAZY_LOADER
from .extractor.extractors import (
_PLUGIN_CLASSES as plugin_ies,
- _PLUGIN_OVERRIDES as plugin_ie_overrides
+ _PLUGIN_OVERRIDES as plugin_ie_overrides,
)
def get_encoding(stream):
- ret = str(getattr(stream, 'encoding', 'missing (%s)' % type(stream).__name__))
+ ret = str(getattr(stream, 'encoding', f'missing ({type(stream).__name__})'))
additional_info = []
if os.environ.get('TERM', '').lower() == 'dumb':
additional_info.append('dumb')
@@ -3979,13 +3978,13 @@ class YoutubeDL:
ret = f'{ret} ({",".join(additional_info)})'
return ret
- encoding_str = 'Encodings: locale %s, fs %s, pref %s, %s' % (
+ encoding_str = 'Encodings: locale {}, fs {}, pref {}, {}'.format(
locale.getpreferredencoding(),
sys.getfilesystemencoding(),
self.get_encoding(),
', '.join(
f'{key} {get_encoding(stream)}' for key, stream in self._out_files.items_
- if stream is not None and key != 'console')
+ if stream is not None and key != 'console'),
)
logger = self.params.get('logger')
@@ -4017,7 +4016,7 @@ class YoutubeDL:
else:
write_debug('Lazy loading extractors is disabled')
if self.params['compat_opts']:
- write_debug('Compatibility options: %s' % ', '.join(self.params['compat_opts']))
+ write_debug('Compatibility options: {}'.format(', '.join(self.params['compat_opts'])))
if current_git_head():
write_debug(f'Git HEAD: {current_git_head()}')
@@ -4026,14 +4025,14 @@ class YoutubeDL:
exe_versions, ffmpeg_features = FFmpegPostProcessor.get_versions_and_features(self)
ffmpeg_features = {key for key, val in ffmpeg_features.items() if val}
if ffmpeg_features:
- exe_versions['ffmpeg'] += ' (%s)' % ','.join(sorted(ffmpeg_features))
+ exe_versions['ffmpeg'] += ' ({})'.format(','.join(sorted(ffmpeg_features)))
exe_versions['rtmpdump'] = rtmpdump_version()
exe_versions['phantomjs'] = PhantomJSwrapper._version()
exe_str = ', '.join(
f'{exe} {v}' for exe, v in sorted(exe_versions.items()) if v
) or 'none'
- write_debug('exe versions: %s' % exe_str)
+ write_debug(f'exe versions: {exe_str}')
from .compat.compat_utils import get_package_info
from .dependencies import available_dependencies
@@ -4045,7 +4044,7 @@ class YoutubeDL:
write_debug(f'Proxy map: {self.proxies}')
write_debug(f'Request Handlers: {", ".join(rh.RH_NAME for rh in self._request_director.handlers.values())}')
for plugin_type, plugins in {'Extractor': plugin_ies, 'Post-Processor': plugin_pps}.items():
- display_list = ['%s%s' % (
+ display_list = ['{}{}'.format(
klass.__name__, '' if klass.__name__ == name else f' as {name}')
for name, klass in plugins.items()]
if plugin_type == 'Extractor':
@@ -4062,14 +4061,13 @@ class YoutubeDL:
# Not implemented
if False and self.params.get('call_home'):
ipaddr = self.urlopen('https://yt-dl.org/ip').read().decode()
- write_debug('Public IP address: %s' % ipaddr)
+ write_debug(f'Public IP address: {ipaddr}')
latest_version = self.urlopen(
'https://yt-dl.org/latest/version').read().decode()
if version_tuple(latest_version) > version_tuple(__version__):
self.report_warning(
- 'You are using an outdated version (newest version: %s)! '
- 'See https://yt-dl.org/update if you need help updating.' %
- latest_version)
+ f'You are using an outdated version (newest version: {latest_version})! '
+ 'See https://yt-dl.org/update if you need help updating.')
@functools.cached_property
def proxies(self):
@@ -4103,7 +4101,7 @@ class YoutubeDL:
return handler._get_instance(cookiejar=self.cookiejar, proxies=self.proxies)
def _get_available_impersonate_targets(self):
- # todo(future): make available as public API
+ # TODO(future): make available as public API
return [
(target, rh.RH_NAME)
for rh in self._request_director.handlers.values()
@@ -4112,7 +4110,7 @@ class YoutubeDL:
]
def _impersonate_target_available(self, target):
- # todo(future): make available as public API
+ # TODO(future): make available as public API
return any(
rh.is_supported_target(target)
for rh in self._request_director.handlers.values()
@@ -4238,7 +4236,7 @@ class YoutubeDL:
return encoding
def _write_info_json(self, label, ie_result, infofn, overwrite=None):
- ''' Write infojson and returns True = written, 'exists' = Already exists, False = skip, None = error '''
+ """ Write infojson and returns True = written, 'exists' = Already exists, False = skip, None = error """
if overwrite is None:
overwrite = self.params.get('overwrites', True)
if not self.params.get('writeinfojson'):
@@ -4261,7 +4259,7 @@ class YoutubeDL:
return None
def _write_description(self, label, ie_result, descfn):
- ''' Write description and returns True = written, False = skip, None = error '''
+ """ Write description and returns True = written, False = skip, None = error """
if not self.params.get('writedescription'):
return False
elif not descfn:
@@ -4285,7 +4283,7 @@ class YoutubeDL:
return True
def _write_subtitles(self, info_dict, filename):
- ''' Write subtitles to file and return list of (sub_filename, final_sub_filename); or None if error'''
+ """ Write subtitles to file and return list of (sub_filename, final_sub_filename); or None if error"""
ret = []
subtitles = info_dict.get('requested_subtitles')
if not (self.params.get('writesubtitles') or self.params.get('writeautomaticsub')):
@@ -4331,7 +4329,7 @@ class YoutubeDL:
self.dl(sub_filename, sub_copy, subtitle=True)
sub_info['filepath'] = sub_filename
ret.append((sub_filename, sub_filename_final))
- except (DownloadError, ExtractorError, IOError, OSError, ValueError) + network_exceptions as err:
+ except (DownloadError, ExtractorError, OSError, ValueError, *network_exceptions) as err:
msg = f'Unable to download video subtitles for {sub_lang!r}: {err}'
if self.params.get('ignoreerrors') is not True: # False or 'only_download'
if not self.params.get('ignoreerrors'):
@@ -4341,7 +4339,7 @@ class YoutubeDL:
return ret
def _write_thumbnails(self, label, info_dict, filename, thumb_filename_base=None):
- ''' Write thumbnails to file and return list of (thumb_filename, final_thumb_filename); or None if error '''
+ """ Write thumbnails to file and return list of (thumb_filename, final_thumb_filename); or None if error """
write_all = self.params.get('write_all_thumbnails', False)
thumbnails, ret = [], []
if write_all or self.params.get('writethumbnail', False):
@@ -4368,8 +4366,8 @@ class YoutubeDL:
existing_thumb = self.existing_file((thumb_filename_final, thumb_filename))
if existing_thumb:
- self.to_screen('[info] %s is already present' % (
- thumb_display_id if multiple else f'{label} thumbnail').capitalize())
+ self.to_screen('[info] {} is already present'.format((
+ thumb_display_id if multiple else f'{label} thumbnail').capitalize()))
t['filepath'] = existing_thumb
ret.append((existing_thumb, thumb_filename_final))
else:
diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py
index 3d606bcba..c18af7589 100644
--- a/yt_dlp/__init__.py
+++ b/yt_dlp/__init__.py
@@ -14,7 +14,7 @@ import os
import re
import traceback
-from .compat import compat_os_name, compat_shlex_quote
+from .compat import compat_os_name
from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS
from .downloader.external import get_external_downloader
from .extractor import list_extractor_classes
@@ -58,6 +58,7 @@ from .utils import (
read_stdin,
render_table,
setproctitle,
+ shell_quote,
traverse_obj,
variadic,
write_string,
@@ -115,9 +116,9 @@ def print_extractor_information(opts, urls):
ie.description(markdown=False, search_examples=_SEARCHES)
for ie in list_extractor_classes(opts.age_limit) if ie.working() and ie.IE_DESC is not False)
elif opts.ap_list_mso:
- out = 'Supported TV Providers:\n%s\n' % render_table(
+ out = 'Supported TV Providers:\n{}\n'.format(render_table(
['mso', 'mso name'],
- [[mso_id, mso_info['name']] for mso_id, mso_info in MSO_INFO.items()])
+ [[mso_id, mso_info['name']] for mso_id, mso_info in MSO_INFO.items()]))
else:
return False
write_string(out, out=sys.stdout)
@@ -129,7 +130,7 @@ def set_compat_opts(opts):
if name not in opts.compat_opts:
return False
opts.compat_opts.discard(name)
- opts.compat_opts.update(['*%s' % name])
+ opts.compat_opts.update([f'*{name}'])
return True
def set_default_compat(compat_name, opt_name, default=True, remove_compat=True):
@@ -222,7 +223,7 @@ def validate_options(opts):
validate_minmax(opts.sleep_interval, opts.max_sleep_interval, 'sleep interval')
if opts.wait_for_video is not None:
- min_wait, max_wait, *_ = map(parse_duration, opts.wait_for_video.split('-', 1) + [None])
+ min_wait, max_wait, *_ = map(parse_duration, [*opts.wait_for_video.split('-', 1), None])
validate(min_wait is not None and not (max_wait is None and '-' in opts.wait_for_video),
'time range to wait for video', opts.wait_for_video)
validate_minmax(min_wait, max_wait, 'time range to wait for video')
@@ -264,9 +265,9 @@ def validate_options(opts):
# Retry sleep function
def parse_sleep_func(expr):
NUMBER_RE = r'\d+(?:\.\d+)?'
- op, start, limit, step, *_ = tuple(re.fullmatch(
+ op, start, limit, step, *_ = (*tuple(re.fullmatch(
rf'(?:(linear|exp)=)?({NUMBER_RE})(?::({NUMBER_RE})?)?(?::({NUMBER_RE}))?',
- expr.strip()).groups()) + (None, None)
+ expr.strip()).groups()), None, None)
if op == 'exp':
return lambda n: min(float(start) * (float(step or 2) ** n), float(limit or 'inf'))
@@ -396,13 +397,13 @@ def validate_options(opts):
# MetadataParser
def metadataparser_actions(f):
if isinstance(f, str):
- cmd = '--parse-metadata %s' % compat_shlex_quote(f)
+ cmd = f'--parse-metadata {shell_quote(f)}'
try:
actions = [MetadataFromFieldPP.to_action(f)]
except Exception as err:
raise ValueError(f'{cmd} is invalid; {err}')
else:
- cmd = '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote, f))
+ cmd = f'--replace-in-metadata {shell_quote(f)}'
actions = ((MetadataParserPP.Actions.REPLACE, x, *f[1:]) for x in f[0].split(','))
for action in actions:
@@ -413,7 +414,7 @@ def validate_options(opts):
yield action
if opts.metafromtitle is not None:
- opts.parse_metadata.setdefault('pre_process', []).append('title:%s' % opts.metafromtitle)
+ opts.parse_metadata.setdefault('pre_process', []).append(f'title:{opts.metafromtitle}')
opts.parse_metadata = {
k: list(itertools.chain(*map(metadataparser_actions, v)))
for k, v in opts.parse_metadata.items()
@@ -602,7 +603,7 @@ def get_postprocessors(opts):
yield {
'key': 'MetadataParser',
'actions': actions,
- 'when': when
+ 'when': when,
}
sponsorblock_query = opts.sponsorblock_mark | opts.sponsorblock_remove
if sponsorblock_query:
@@ -610,19 +611,19 @@ def get_postprocessors(opts):
'key': 'SponsorBlock',
'categories': sponsorblock_query,
'api': opts.sponsorblock_api,
- 'when': 'after_filter'
+ 'when': 'after_filter',
}
if opts.convertsubtitles:
yield {
'key': 'FFmpegSubtitlesConvertor',
'format': opts.convertsubtitles,
- 'when': 'before_dl'
+ 'when': 'before_dl',
}
if opts.convertthumbnails:
yield {
'key': 'FFmpegThumbnailsConvertor',
'format': opts.convertthumbnails,
- 'when': 'before_dl'
+ 'when': 'before_dl',
}
if opts.extractaudio:
yield {
@@ -647,7 +648,7 @@ def get_postprocessors(opts):
yield {
'key': 'FFmpegEmbedSubtitle',
# already_have_subtitle = True prevents the file from being deleted after embedding
- 'already_have_subtitle': opts.writesubtitles and keep_subs
+ 'already_have_subtitle': opts.writesubtitles and keep_subs,
}
if not opts.writeautomaticsub and keep_subs:
opts.writesubtitles = True
@@ -660,7 +661,7 @@ def get_postprocessors(opts):
'remove_sponsor_segments': opts.sponsorblock_remove,
'remove_ranges': opts.remove_ranges,
'sponsorblock_chapter_title': opts.sponsorblock_chapter_title,
- 'force_keyframes': opts.force_keyframes_at_cuts
+ 'force_keyframes': opts.force_keyframes_at_cuts,
}
# FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
# FFmpegExtractAudioPP as containers before conversion may not support
@@ -694,7 +695,7 @@ def get_postprocessors(opts):
yield {
'key': 'EmbedThumbnail',
# already_have_thumbnail = True prevents the file from being deleted after embedding
- 'already_have_thumbnail': opts.writethumbnail
+ 'already_have_thumbnail': opts.writethumbnail,
}
if not opts.writethumbnail:
opts.writethumbnail = True
@@ -741,7 +742,7 @@ def parse_options(argv=None):
print_only = bool(opts.forceprint) and all(k not in opts.forceprint for k in POSTPROCESS_WHEN[3:])
any_getting = any(getattr(opts, k) for k in (
'dumpjson', 'dump_single_json', 'getdescription', 'getduration', 'getfilename',
- 'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl'
+ 'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl',
))
if opts.quiet is None:
opts.quiet = any_getting or opts.print_json or bool(opts.forceprint)
@@ -1002,7 +1003,7 @@ def _real_main(argv=None):
def make_row(target, handler):
return [
join_nonempty(target.client.title(), target.version, delim='-') or '-',
- join_nonempty((target.os or "").title(), target.os_version, delim='-') or '-',
+ join_nonempty((target.os or '').title(), target.os_version, delim='-') or '-',
handler,
]
diff --git a/yt_dlp/aes.py b/yt_dlp/aes.py
index b3a383cd9..abf54a998 100644
--- a/yt_dlp/aes.py
+++ b/yt_dlp/aes.py
@@ -68,7 +68,7 @@ def pad_block(block, padding_mode):
raise NotImplementedError(f'Padding mode {padding_mode} is not implemented')
if padding_mode == 'iso7816' and padding_size:
- block = block + [0x80] # NB: += mutates list
+ block = [*block, 0x80] # NB: += mutates list
padding_size -= 1
return block + [PADDING_BYTE[padding_mode]] * padding_size
@@ -110,9 +110,7 @@ def aes_ecb_decrypt(data, key, iv=None):
for i in range(block_count):
block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
encrypted_data += aes_decrypt(block, expanded_key)
- encrypted_data = encrypted_data[:len(data)]
-
- return encrypted_data
+ return encrypted_data[:len(data)]
def aes_ctr_decrypt(data, key, iv):
@@ -148,9 +146,7 @@ def aes_ctr_encrypt(data, key, iv):
cipher_counter_block = aes_encrypt(counter_block, expanded_key)
encrypted_data += xor(block, cipher_counter_block)
- encrypted_data = encrypted_data[:len(data)]
-
- return encrypted_data
+ return encrypted_data[:len(data)]
def aes_cbc_decrypt(data, key, iv):
@@ -174,9 +170,7 @@ def aes_cbc_decrypt(data, key, iv):
decrypted_block = aes_decrypt(block, expanded_key)
decrypted_data += xor(decrypted_block, previous_cipher_block)
previous_cipher_block = block
- decrypted_data = decrypted_data[:len(data)]
-
- return decrypted_data
+ return decrypted_data[:len(data)]
def aes_cbc_encrypt(data, key, iv, *, padding_mode='pkcs7'):
@@ -224,7 +218,7 @@ def aes_gcm_decrypt_and_verify(data, key, tag, nonce):
hash_subkey = aes_encrypt([0] * BLOCK_SIZE_BYTES, key_expansion(key))
if len(nonce) == 12:
- j0 = nonce + [0, 0, 0, 1]
+ j0 = [*nonce, 0, 0, 0, 1]
else:
fill = (BLOCK_SIZE_BYTES - (len(nonce) % BLOCK_SIZE_BYTES)) % BLOCK_SIZE_BYTES + 8
ghash_in = nonce + [0] * fill + bytes_to_intlist((8 * len(nonce)).to_bytes(8, 'big'))
@@ -242,11 +236,11 @@ def aes_gcm_decrypt_and_verify(data, key, tag, nonce):
data
+ [0] * (BLOCK_SIZE_BYTES - len(data) + pad_len) # pad
+ bytes_to_intlist((0 * 8).to_bytes(8, 'big') # length of associated data
- + ((len(data) * 8).to_bytes(8, 'big'))) # length of data
+ + ((len(data) * 8).to_bytes(8, 'big'))), # length of data
)
if tag != aes_ctr_encrypt(s_tag, key, j0):
- raise ValueError("Mismatching authentication tag")
+ raise ValueError('Mismatching authentication tag')
return decrypted_data
@@ -288,9 +282,7 @@ def aes_decrypt(data, expanded_key):
data = list(iter_mix_columns(data, MIX_COLUMN_MATRIX_INV))
data = shift_rows_inv(data)
data = sub_bytes_inv(data)
- data = xor(data, expanded_key[:BLOCK_SIZE_BYTES])
-
- return data
+ return xor(data, expanded_key[:BLOCK_SIZE_BYTES])
def aes_decrypt_text(data, password, key_size_bytes):
@@ -318,9 +310,7 @@ def aes_decrypt_text(data, password, key_size_bytes):
cipher = data[NONCE_LENGTH_BYTES:]
decrypted_data = aes_ctr_decrypt(cipher, key, nonce + [0] * (BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES))
- plaintext = intlist_to_bytes(decrypted_data)
-
- return plaintext
+ return intlist_to_bytes(decrypted_data)
RCON = (0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36)
@@ -428,9 +418,7 @@ def key_expansion(data):
for _ in range(3 if key_size_bytes == 32 else 2 if key_size_bytes == 24 else 0):
temp = data[-4:]
data += xor(temp, data[-key_size_bytes: 4 - key_size_bytes])
- data = data[:expanded_key_size_bytes]
-
- return data
+ return data[:expanded_key_size_bytes]
def iter_vector(iv):
@@ -511,7 +499,7 @@ def block_product(block_x, block_y):
# NIST SP 800-38D, Algorithm 1
if len(block_x) != BLOCK_SIZE_BYTES or len(block_y) != BLOCK_SIZE_BYTES:
- raise ValueError("Length of blocks need to be %d bytes" % BLOCK_SIZE_BYTES)
+ raise ValueError(f'Length of blocks need to be {BLOCK_SIZE_BYTES} bytes')
block_r = [0xE1] + [0] * (BLOCK_SIZE_BYTES - 1)
block_v = block_y[:]
@@ -534,7 +522,7 @@ def ghash(subkey, data):
# NIST SP 800-38D, Algorithm 2
if len(data) % BLOCK_SIZE_BYTES:
- raise ValueError("Length of data should be %d bytes" % BLOCK_SIZE_BYTES)
+ raise ValueError(f'Length of data should be {BLOCK_SIZE_BYTES} bytes')
last_y = [0] * BLOCK_SIZE_BYTES
for i in range(0, len(data), BLOCK_SIZE_BYTES):
diff --git a/yt_dlp/cache.py b/yt_dlp/cache.py
index 9dd4f2f25..71dca82b3 100644
--- a/yt_dlp/cache.py
+++ b/yt_dlp/cache.py
@@ -81,10 +81,10 @@ class Cache:
cachedir = self._get_root_dir()
if not any((term in cachedir) for term in ('cache', 'tmp')):
- raise Exception('Not removing directory %s - this does not look like a cache dir' % cachedir)
+ raise Exception(f'Not removing directory {cachedir} - this does not look like a cache dir')
self._ydl.to_screen(
- 'Removing cache dir %s .' % cachedir, skip_eol=True)
+ f'Removing cache dir {cachedir} .', skip_eol=True)
if os.path.exists(cachedir):
self._ydl.to_screen('.', skip_eol=True)
shutil.rmtree(cachedir)
diff --git a/yt_dlp/compat/_legacy.py b/yt_dlp/compat/_legacy.py
index 7ea5d0812..dfc792eae 100644
--- a/yt_dlp/compat/_legacy.py
+++ b/yt_dlp/compat/_legacy.py
@@ -35,7 +35,7 @@ from .compat_utils import passthrough_module
from ..dependencies import brotli as compat_brotli # noqa: F401
from ..dependencies import websockets as compat_websockets # noqa: F401
from ..dependencies.Cryptodome import AES as compat_pycrypto_AES # noqa: F401
-from ..networking.exceptions import HTTPError as compat_HTTPError # noqa: F401
+from ..networking.exceptions import HTTPError as compat_HTTPError
passthrough_module(__name__, '...utils', ('WINDOWS_VT_MODE', 'windows_enable_vt_mode'))
diff --git a/yt_dlp/compat/functools.py b/yt_dlp/compat/functools.py
index 36c983642..96689575f 100644
--- a/yt_dlp/compat/functools.py
+++ b/yt_dlp/compat/functools.py
@@ -7,6 +7,6 @@ passthrough_module(__name__, 'functools')
del passthrough_module
try:
- cache # >= 3.9
+ _ = cache # >= 3.9
except NameError:
cache = lru_cache(maxsize=None)
diff --git a/yt_dlp/cookies.py b/yt_dlp/cookies.py
index 815897d5a..0850ad260 100644
--- a/yt_dlp/cookies.py
+++ b/yt_dlp/cookies.py
@@ -146,7 +146,7 @@ def _extract_firefox_cookies(profile, container, logger):
identities = json.load(containers).get('identities', [])
container_id = next((context.get('userContextId') for context in identities if container in (
context.get('name'),
- try_call(lambda: re.fullmatch(r'userContext([^\.]+)\.label', context['l10nID']).group())
+ try_call(lambda: re.fullmatch(r'userContext([^\.]+)\.label', context['l10nID']).group()),
)), None)
if not isinstance(container_id, int):
raise ValueError(f'could not find firefox container "{container}" in containers.json')
@@ -263,7 +263,7 @@ def _get_chromium_based_browser_settings(browser_name):
return {
'browser_dir': browser_dir,
'keyring_name': keyring_name,
- 'supports_profiles': browser_name not in browsers_without_profiles
+ 'supports_profiles': browser_name not in browsers_without_profiles,
}
@@ -826,7 +826,7 @@ def _choose_linux_keyring(logger):
elif desktop_environment == _LinuxDesktopEnvironment.KDE6:
linux_keyring = _LinuxKeyring.KWALLET6
elif desktop_environment in (
- _LinuxDesktopEnvironment.KDE3, _LinuxDesktopEnvironment.LXQT, _LinuxDesktopEnvironment.OTHER
+ _LinuxDesktopEnvironment.KDE3, _LinuxDesktopEnvironment.LXQT, _LinuxDesktopEnvironment.OTHER,
):
linux_keyring = _LinuxKeyring.BASICTEXT
else:
@@ -861,7 +861,7 @@ def _get_kwallet_network_wallet(keyring, logger):
'dbus-send', '--session', '--print-reply=literal',
f'--dest={service_name}',
wallet_path,
- 'org.kde.KWallet.networkWallet'
+ 'org.kde.KWallet.networkWallet',
], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
if returncode:
@@ -891,7 +891,7 @@ def _get_kwallet_password(browser_keyring_name, keyring, logger):
'kwallet-query',
'--read-password', f'{browser_keyring_name} Safe Storage',
'--folder', f'{browser_keyring_name} Keys',
- network_wallet
+ network_wallet,
], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
if returncode:
@@ -931,9 +931,8 @@ def _get_gnome_keyring_password(browser_keyring_name, logger):
for item in col.get_all_items():
if item.get_label() == f'{browser_keyring_name} Safe Storage':
return item.get_secret()
- else:
- logger.error('failed to read from keyring')
- return b''
+ logger.error('failed to read from keyring')
+ return b''
def _get_linux_keyring_password(browser_keyring_name, keyring, logger):
@@ -1053,7 +1052,7 @@ def _decrypt_windows_dpapi(ciphertext, logger):
None, # pvReserved: must be NULL
None, # pPromptStruct: information about prompts to display
0, # dwFlags
- ctypes.byref(blob_out) # pDataOut
+ ctypes.byref(blob_out), # pDataOut
)
if not ret:
logger.warning('failed to decrypt with DPAPI', only_once=True)
@@ -1129,24 +1128,24 @@ class LenientSimpleCookie(http.cookies.SimpleCookie):
_LEGAL_VALUE_CHARS = _LEGAL_KEY_CHARS + re.escape('(),/<=>?@[]{}')
_RESERVED = {
- "expires",
- "path",
- "comment",
- "domain",
- "max-age",
- "secure",
- "httponly",
- "version",
- "samesite",
+ 'expires',
+ 'path',
+ 'comment',
+ 'domain',
+ 'max-age',
+ 'secure',
+ 'httponly',
+ 'version',
+ 'samesite',
}
- _FLAGS = {"secure", "httponly"}
+ _FLAGS = {'secure', 'httponly'}
# Added 'bad' group to catch the remaining value
- _COOKIE_PATTERN = re.compile(r"""
+ _COOKIE_PATTERN = re.compile(r'''
\s* # Optional whitespace at start of cookie
(?P<key> # Start of group 'key'
- [""" + _LEGAL_KEY_CHARS + r"""]+?# Any word of at least one letter
+ [''' + _LEGAL_KEY_CHARS + r''']+?# Any word of at least one letter
) # End of group 'key'
( # Optional group: there may not be a value.
\s*=\s* # Equal Sign
@@ -1156,7 +1155,7 @@ class LenientSimpleCookie(http.cookies.SimpleCookie):
| # or
\w{3},\s[\w\d\s-]{9,11}\s[\d:]{8}\sGMT # Special case for "expires" attr
| # or
- [""" + _LEGAL_VALUE_CHARS + r"""]* # Any word or empty string
+ [''' + _LEGAL_VALUE_CHARS + r''']* # Any word or empty string
) # End of group 'val'
| # or
(?P<bad>(?:\\;|[^;])*?) # 'bad' group fallback for invalid values
@@ -1164,7 +1163,7 @@ class LenientSimpleCookie(http.cookies.SimpleCookie):
)? # End of optional value group
\s* # Any number of spaces.
(\s+|;|$) # Ending either at space, semicolon, or EOS.
- """, re.ASCII | re.VERBOSE)
+ ''', re.ASCII | re.VERBOSE)
def load(self, data):
# Workaround for https://github.com/yt-dlp/yt-dlp/issues/4776
@@ -1260,14 +1259,14 @@ class YoutubeDLCookieJar(http.cookiejar.MozillaCookieJar):
# with no name, whereas http.cookiejar regards it as a
# cookie with no value.
name, value = '', name
- f.write('%s\n' % '\t'.join((
+ f.write('{}\n'.format('\t'.join((
cookie.domain,
self._true_or_false(cookie.domain.startswith('.')),
cookie.path,
self._true_or_false(cookie.secure),
str_or_none(cookie.expires, default=''),
- name, value
- )))
+ name, value,
+ ))))
def save(self, filename=None, ignore_discard=True, ignore_expires=True):
"""
@@ -1306,10 +1305,10 @@ class YoutubeDLCookieJar(http.cookiejar.MozillaCookieJar):
return line
cookie_list = line.split('\t')
if len(cookie_list) != self._ENTRY_LEN:
- raise http.cookiejar.LoadError('invalid length %d' % len(cookie_list))
+ raise http.cookiejar.LoadError(f'invalid length {len(cookie_list)}')
cookie = self._CookieFileEntry(*cookie_list)
if cookie.expires_at and not cookie.expires_at.isdigit():
- raise http.cookiejar.LoadError('invalid expires at %s' % cookie.expires_at)
+ raise http.cookiejar.LoadError(f'invalid expires at {cookie.expires_at}')
return line
cf = io.StringIO()
diff --git a/yt_dlp/downloader/common.py b/yt_dlp/downloader/common.py
index 65a0d6f23..2e3ea2fc4 100644
--- a/yt_dlp/downloader/common.py
+++ b/yt_dlp/downloader/common.py
@@ -404,7 +404,7 @@ class FileDownloader:
def report_resuming_byte(self, resume_len):
"""Report attempt to resume at given byte."""
- self.to_screen('[download] Resuming download at byte %s' % resume_len)
+ self.to_screen(f'[download] Resuming download at byte {resume_len}')
def report_retry(self, err, count, retries, frag_index=NO_DEFAULT, fatal=True):
"""Report retry"""
diff --git a/yt_dlp/downloader/external.py b/yt_dlp/downloader/external.py
index 8b0b94e72..8b45c671a 100644
--- a/yt_dlp/downloader/external.py
+++ b/yt_dlp/downloader/external.py
@@ -55,7 +55,7 @@ class ExternalFD(FragmentFD):
# correct and expected termination thus all postprocessing
# should take place
retval = 0
- self.to_screen('[%s] Interrupted by user' % self.get_basename())
+ self.to_screen(f'[{self.get_basename()}] Interrupted by user')
finally:
if self._cookies_tempfile:
self.try_remove(self._cookies_tempfile)
@@ -172,7 +172,7 @@ class ExternalFD(FragmentFD):
decrypt_fragment = self.decrypter(info_dict)
dest, _ = self.sanitize_open(tmpfilename, 'wb')
for frag_index, fragment in enumerate(info_dict['fragments']):
- fragment_filename = '%s-Frag%d' % (tmpfilename, frag_index)
+ fragment_filename = f'{tmpfilename}-Frag{frag_index}'
try:
src, _ = self.sanitize_open(fragment_filename, 'rb')
except OSError as err:
@@ -186,7 +186,7 @@ class ExternalFD(FragmentFD):
if not self.params.get('keep_fragments', False):
self.try_remove(encodeFilename(fragment_filename))
dest.close()
- self.try_remove(encodeFilename('%s.frag.urls' % tmpfilename))
+ self.try_remove(encodeFilename(f'{tmpfilename}.frag.urls'))
return 0
def _call_process(self, cmd, info_dict):
@@ -336,11 +336,11 @@ class Aria2cFD(ExternalFD):
if 'fragments' in info_dict:
cmd += ['--uri-selector=inorder']
- url_list_file = '%s.frag.urls' % tmpfilename
+ url_list_file = f'{tmpfilename}.frag.urls'
url_list = []
for frag_index, fragment in enumerate(info_dict['fragments']):
- fragment_filename = '%s-Frag%d' % (os.path.basename(tmpfilename), frag_index)
- url_list.append('%s\n\tout=%s' % (fragment['url'], self._aria2c_filename(fragment_filename)))
+ fragment_filename = f'{os.path.basename(tmpfilename)}-Frag{frag_index}'
+ url_list.append('{}\n\tout={}'.format(fragment['url'], self._aria2c_filename(fragment_filename)))
stream, _ = self.sanitize_open(url_list_file, 'wb')
stream.write('\n'.join(url_list).encode())
stream.close()
@@ -357,7 +357,7 @@ class Aria2cFD(ExternalFD):
'id': sanitycheck,
'method': method,
'params': [f'token:{rpc_secret}', *params],
- }).encode('utf-8')
+ }).encode()
request = Request(
f'http://localhost:{rpc_port}/jsonrpc',
data=d, headers={
@@ -416,7 +416,7 @@ class Aria2cFD(ExternalFD):
'total_bytes_estimate': total,
'eta': (total - downloaded) / (speed or 1),
'fragment_index': min(frag_count, len(completed) + 1) if fragmented else None,
- 'elapsed': time.time() - started
+ 'elapsed': time.time() - started,
})
self._hook_progress(status, info_dict)
@@ -509,12 +509,12 @@ class FFmpegFD(ExternalFD):
proxy = self.params.get('proxy')
if proxy:
if not re.match(r'^[\da-zA-Z]+://', proxy):
- proxy = 'http://%s' % proxy
+ proxy = f'http://{proxy}'
if proxy.startswith('socks'):
self.report_warning(
- '%s does not support SOCKS proxies. Downloading is likely to fail. '
- 'Consider adding --hls-prefer-native to your command.' % self.get_basename())
+ f'{self.get_basename()} does not support SOCKS proxies. Downloading is likely to fail. '
+ 'Consider adding --hls-prefer-native to your command.')
# Since December 2015 ffmpeg supports -http_proxy option (see
# http://git.videolan.org/?p=ffmpeg.git;a=commit;h=b4eb1f29ebddd60c41a2eb39f5af701e38e0d3fd)
@@ -575,7 +575,7 @@ class FFmpegFD(ExternalFD):
if end_time:
args += ['-t', str(end_time - start_time)]
- args += self._configuration_args((f'_i{i + 1}', '_i')) + ['-i', fmt['url']]
+ args += [*self._configuration_args((f'_i{i + 1}', '_i')), '-i', fmt['url']]
if not (start_time or end_time) or not self.params.get('force_keyframes_at_cuts'):
args += ['-c', 'copy']
diff --git a/yt_dlp/downloader/f4m.py b/yt_dlp/downloader/f4m.py
index 28cbba016..22d0ebd26 100644
--- a/yt_dlp/downloader/f4m.py
+++ b/yt_dlp/downloader/f4m.py
@@ -67,12 +67,12 @@ class FlvReader(io.BytesIO):
self.read_bytes(3)
quality_entry_count = self.read_unsigned_char()
# QualityEntryCount
- for i in range(quality_entry_count):
+ for _ in range(quality_entry_count):
self.read_string()
segment_run_count = self.read_unsigned_int()
segments = []
- for i in range(segment_run_count):
+ for _ in range(segment_run_count):
first_segment = self.read_unsigned_int()
fragments_per_segment = self.read_unsigned_int()
segments.append((first_segment, fragments_per_segment))
@@ -91,12 +91,12 @@ class FlvReader(io.BytesIO):
quality_entry_count = self.read_unsigned_char()
# QualitySegmentUrlModifiers
- for i in range(quality_entry_count):
+ for _ in range(quality_entry_count):
self.read_string()
fragments_count = self.read_unsigned_int()
fragments = []
- for i in range(fragments_count):
+ for _ in range(fragments_count):
first = self.read_unsigned_int()
first_ts = self.read_unsigned_long_long()
duration = self.read_unsigned_int()
@@ -135,11 +135,11 @@ class FlvReader(io.BytesIO):
self.read_string() # MovieIdentifier
server_count = self.read_unsigned_char()
# ServerEntryTable
- for i in range(server_count):
+ for _ in range(server_count):
self.read_string()
quality_count = self.read_unsigned_char()
# QualityEntryTable
- for i in range(quality_count):
+ for _ in range(quality_count):
self.read_string()
# DrmData
self.read_string()
@@ -148,14 +148,14 @@ class FlvReader(io.BytesIO):
segments_count = self.read_unsigned_char()
segments = []
- for i in range(segments_count):
+ for _ in range(segments_count):
box_size, box_type, box_data = self.read_box_info()
assert box_type == b'asrt'
segment = FlvReader(box_data).read_asrt()
segments.append(segment)
fragments_run_count = self.read_unsigned_char()
fragments = []
- for i in range(fragments_run_count):
+ for _ in range(fragments_run_count):
box_size, box_type, box_data = self.read_box_info()
assert box_type == b'afrt'
fragments.append(FlvReader(box_data).read_afrt())
@@ -309,7 +309,7 @@ class F4mFD(FragmentFD):
def real_download(self, filename, info_dict):
man_url = info_dict['url']
requested_bitrate = info_dict.get('tbr')
- self.to_screen('[%s] Downloading f4m manifest' % self.FD_NAME)
+ self.to_screen(f'[{self.FD_NAME}] Downloading f4m manifest')
urlh = self.ydl.urlopen(self._prepare_url(info_dict, man_url))
man_url = urlh.url
@@ -326,8 +326,8 @@ class F4mFD(FragmentFD):
formats = sorted(formats, key=lambda f: f[0])
rate, media = formats[-1]
else:
- rate, media = list(filter(
- lambda f: int(f[0]) == requested_bitrate, formats))[0]
+ rate, media = next(filter(
+ lambda f: int(f[0]) == requested_bitrate, formats))
# Prefer baseURL for relative URLs as per 11.2 of F4M 3.0 spec.
man_base_url = get_base_url(doc) or man_url
diff --git a/yt_dlp/downloader/fragment.py b/yt_dlp/downloader/fragment.py
index b4f003d37..0d00196e2 100644
--- a/yt_dlp/downloader/fragment.py
+++ b/yt_dlp/downloader/fragment.py
@@ -199,7 +199,7 @@ class FragmentFD(FileDownloader):
'.ytdl file is corrupt' if is_corrupt else
'Inconsistent state of incomplete fragment download')
self.report_warning(
- '%s. Restarting from the beginning ...' % message)
+ f'{message}. Restarting from the beginning ...')
ctx['fragment_index'] = resume_len = 0
if 'ytdl_corrupt' in ctx:
del ctx['ytdl_corrupt']
@@ -366,10 +366,10 @@ class FragmentFD(FileDownloader):
return decrypt_fragment
def download_and_append_fragments_multiple(self, *args, **kwargs):
- '''
+ """
@params (ctx1, fragments1, info_dict1), (ctx2, fragments2, info_dict2), ...
all args must be either tuple or list
- '''
+ """
interrupt_trigger = [True]
max_progress = len(args)
if max_progress == 1:
@@ -424,7 +424,7 @@ class FragmentFD(FileDownloader):
finally:
tpe.shutdown(wait=True)
if not interrupt_trigger[0] and not is_live:
- raise KeyboardInterrupt()
+ raise KeyboardInterrupt
# we expect the user wants to stop and DO WANT the preceding postprocessors to run;
# so returning a intermediate result here instead of KeyboardInterrupt on live
return result
diff --git a/yt_dlp/downloader/hls.py b/yt_dlp/downloader/hls.py
index 4ac5d99dc..9cb4f014c 100644
--- a/yt_dlp/downloader/hls.py
+++ b/yt_dlp/downloader/hls.py
@@ -72,7 +72,7 @@ class HlsFD(FragmentFD):
def real_download(self, filename, info_dict):
man_url = info_dict['url']
- self.to_screen('[%s] Downloading m3u8 manifest' % self.FD_NAME)
+ self.to_screen(f'[{self.FD_NAME}] Downloading m3u8 manifest')
urlh = self.ydl.urlopen(self._prepare_url(info_dict, man_url))
man_url = urlh.url
@@ -228,7 +228,7 @@ class HlsFD(FragmentFD):
'url': frag_url,
'decrypt_info': decrypt_info,
'byte_range': byte_range,
- 'media_sequence': media_sequence
+ 'media_sequence': media_sequence,
})
media_sequence += 1
@@ -350,9 +350,8 @@ class HlsFD(FragmentFD):
# XXX: this should probably be silent as well
# or verify that all segments contain the same data
self.report_warning(bug_reports_message(
- 'Discarding a %s block found in the middle of the stream; '
- 'if the subtitles display incorrectly,'
- % (type(block).__name__)))
+ f'Discarding a {type(block).__name__} block found in the middle of the stream; '
+ 'if the subtitles display incorrectly,'))
continue
block.write_into(output)
diff --git a/yt_dlp/downloader/http.py b/yt_dlp/downloader/http.py
index 693828b6e..c0165790d 100644
--- a/yt_dlp/downloader/http.py
+++ b/yt_dlp/downloader/http.py
@@ -176,7 +176,7 @@ class HttpFD(FileDownloader):
'downloaded_bytes': ctx.resume_len,
'total_bytes': ctx.resume_len,
}, info_dict)
- raise SucceedDownload()
+ raise SucceedDownload
else:
# The length does not match, we start the download over
self.report_unable_to_resume()
@@ -194,7 +194,7 @@ class HttpFD(FileDownloader):
def close_stream():
if ctx.stream is not None:
- if not ctx.tmpfilename == '-':
+ if ctx.tmpfilename != '-':
ctx.stream.close()
ctx.stream = None
@@ -268,20 +268,20 @@ class HttpFD(FileDownloader):
ctx.filename = self.undo_temp_name(ctx.tmpfilename)
self.report_destination(ctx.filename)
except OSError as err:
- self.report_error('unable to open for writing: %s' % str(err))
+ self.report_error(f'unable to open for writing: {err}')
return False
if self.params.get('xattr_set_filesize', False) and data_len is not None:
try:
write_xattr(ctx.tmpfilename, 'user.ytdl.filesize', str(data_len).encode())
except (XAttrUnavailableError, XAttrMetadataError) as err:
- self.report_error('unable to set filesize xattr: %s' % str(err))
+ self.report_error(f'unable to set filesize xattr: {err}')
try:
ctx.stream.write(data_block)
except OSError as err:
self.to_stderr('\n')
- self.report_error('unable to write data: %s' % str(err))
+ self.report_error(f'unable to write data: {err}')
return False
# Apply rate limit
@@ -327,7 +327,7 @@ class HttpFD(FileDownloader):
elif now - ctx.throttle_start > 3:
if ctx.stream is not None and ctx.tmpfilename != '-':
ctx.stream.close()
- raise ThrottledDownload()
+ raise ThrottledDownload
elif speed:
ctx.throttle_start = None
@@ -338,7 +338,7 @@ class HttpFD(FileDownloader):
if not is_test and ctx.chunk_size and ctx.content_len is not None and byte_counter < ctx.content_len:
ctx.resume_len = byte_counter
- raise NextFragment()
+ raise NextFragment
if ctx.tmpfilename != '-':
ctx.stream.close()
diff --git a/yt_dlp/downloader/ism.py b/yt_dlp/downloader/ism.py
index dd688f586..62c3a3b7f 100644
--- a/yt_dlp/downloader/ism.py
+++ b/yt_dlp/downloader/ism.py
@@ -251,7 +251,7 @@ class IsmFD(FragmentFD):
skip_unavailable_fragments = self.params.get('skip_unavailable_fragments', True)
frag_index = 0
- for i, segment in enumerate(segments):
+ for segment in segments:
frag_index += 1
if frag_index <= ctx['fragment_index']:
continue
diff --git a/yt_dlp/downloader/mhtml.py b/yt_dlp/downloader/mhtml.py
index d977dcec3..3d4f2d763 100644
--- a/yt_dlp/downloader/mhtml.py
+++ b/yt_dlp/downloader/mhtml.py
@@ -10,7 +10,7 @@ from ..version import __version__ as YT_DLP_VERSION
class MhtmlFD(FragmentFD):
- _STYLESHEET = """\
+ _STYLESHEET = '''\
html, body {
margin: 0;
padding: 0;
@@ -45,7 +45,7 @@ body > figure > img {
max-width: 100%;
max-height: calc(100vh - 5em);
}
-"""
+'''
_STYLESHEET = re.sub(r'\s+', ' ', _STYLESHEET)
_STYLESHEET = re.sub(r'\B \B|(?<=[\w\-]) (?=[^\w\-])|(?<=[^\w\-]) (?=[\w\-])', '', _STYLESHEET)
@@ -57,24 +57,19 @@ body > figure > img {
)).decode('us-ascii') + '?='
def _gen_cid(self, i, fragment, frag_boundary):
- return '%u.%s@yt-dlp.github.io.invalid' % (i, frag_boundary)
+ return f'{i}.{frag_boundary}@yt-dlp.github.io.invalid'
def _gen_stub(self, *, fragments, frag_boundary, title):
output = io.StringIO()
- output.write((
+ output.write(
'<!DOCTYPE html>'
'<html>'
'<head>'
- '' '<meta name="generator" content="yt-dlp {version}">'
- '' '<title>{title}</title>'
- '' '<style>{styles}</style>'
- '<body>'
- ).format(
- version=escapeHTML(YT_DLP_VERSION),
- styles=self._STYLESHEET,
- title=escapeHTML(title)
- ))
+ f'<meta name="generator" content="yt-dlp {escapeHTML(YT_DLP_VERSION)}">'
+ f'<title>{escapeHTML(title)}</title>'
+ f'<style>{self._STYLESHEET}</style>'
+ '<body>')
t0 = 0
for i, frag in enumerate(fragments):
@@ -87,15 +82,12 @@ body > figure > img {
num=i + 1,
t0=srt_subtitles_timecode(t0),
t1=srt_subtitles_timecode(t1),
- duration=formatSeconds(frag['duration'], msec=True)
+ duration=formatSeconds(frag['duration'], msec=True),
))
except (KeyError, ValueError, TypeError):
t1 = None
- output.write((
- '<figcaption>Slide #{num}</figcaption>'
- ).format(num=i + 1))
- output.write('<img src="cid:{cid}">'.format(
- cid=self._gen_cid(i, frag, frag_boundary)))
+ output.write(f'<figcaption>Slide #{i + 1}</figcaption>')
+ output.write(f'<img src="cid:{self._gen_cid(i, frag, frag_boundary)}">')
output.write('</figure>')
t0 = t1
@@ -126,31 +118,24 @@ body > figure > img {
stub = self._gen_stub(
fragments=fragments,
frag_boundary=frag_boundary,
- title=title
+ title=title,
)
ctx['dest_stream'].write((
'MIME-Version: 1.0\r\n'
'From: <nowhere@yt-dlp.github.io.invalid>\r\n'
'To: <nowhere@yt-dlp.github.io.invalid>\r\n'
- 'Subject: {title}\r\n'
+ f'Subject: {self._escape_mime(title)}\r\n'
'Content-type: multipart/related; '
- '' 'boundary="{boundary}"; '
- '' 'type="text/html"\r\n'
- 'X.yt-dlp.Origin: {origin}\r\n'
+ f'boundary="{frag_boundary}"; '
+ 'type="text/html"\r\n'
+ f'X.yt-dlp.Origin: {origin}\r\n'
'\r\n'
- '--{boundary}\r\n'
+ f'--{frag_boundary}\r\n'
'Content-Type: text/html; charset=utf-8\r\n'
- 'Content-Length: {length}\r\n'
+ f'Content-Length: {len(stub)}\r\n'
'\r\n'
- '{stub}\r\n'
- ).format(
- origin=origin,
- boundary=frag_boundary,
- length=len(stub),
- title=self._escape_mime(title),
- stub=stub
- ).encode())
+ f'{stub}\r\n').encode())
extra_state['header_written'] = True
for i, fragment in enumerate(fragments):
diff --git a/yt_dlp/downloader/niconico.py b/yt_dlp/downloader/niconico.py
index fef8bff73..462c6e2d6 100644
--- a/yt_dlp/downloader/niconico.py
+++ b/yt_dlp/downloader/niconico.py
@@ -15,7 +15,7 @@ class NiconicoDmcFD(FileDownloader):
def real_download(self, filename, info_dict):
from ..extractor.niconico import NiconicoIE
- self.to_screen('[%s] Downloading from DMC' % self.FD_NAME)
+ self.to_screen(f'[{self.FD_NAME}] Downloading from DMC')
ie = NiconicoIE(self.ydl)
info_dict, heartbeat_info_dict = ie._get_heartbeat_info(info_dict)
@@ -34,7 +34,7 @@ class NiconicoDmcFD(FileDownloader):
try:
self.ydl.urlopen(request).read()
except Exception:
- self.to_screen('[%s] Heartbeat failed' % self.FD_NAME)
+ self.to_screen(f'[{self.FD_NAME}] Heartbeat failed')
with heartbeat_lock:
if not download_complete:
@@ -85,14 +85,14 @@ class NiconicoLiveFD(FileDownloader):
'quality': live_quality,
'protocol': 'hls+fmp4',
'latency': live_latency,
- 'chasePlay': False
+ 'chasePlay': False,
},
'room': {
'protocol': 'webSocket',
- 'commentable': True
+ 'commentable': True,
},
'reconnect': True,
- }
+ },
}))
else:
ws = ws_extractor
@@ -118,7 +118,7 @@ class NiconicoLiveFD(FileDownloader):
elif self.ydl.params.get('verbose', False):
if len(recv) > 100:
recv = recv[:100] + '...'
- self.to_screen('[debug] Server said: %s' % recv)
+ self.to_screen(f'[debug] Server said: {recv}')
def ws_main():
reconnect = False
@@ -128,7 +128,7 @@ class NiconicoLiveFD(FileDownloader):
if ret is True:
return
except BaseException as e:
- self.to_screen('[%s] %s: Connection error occured, reconnecting after 10 seconds: %s' % ('niconico:live', video_id, str_or_none(e)))
+ self.to_screen('[{}] {}: Connection error occured, reconnecting after 10 seconds: {}'.format('niconico:live', video_id, str_or_none(e)))
time.sleep(10)
continue
finally:
diff --git a/yt_dlp/downloader/rtmp.py b/yt_dlp/downloader/rtmp.py
index 0e0952599..d7ffb3b34 100644
--- a/yt_dlp/downloader/rtmp.py
+++ b/yt_dlp/downloader/rtmp.py
@@ -180,9 +180,9 @@ class RtmpFD(FileDownloader):
while retval in (RD_INCOMPLETE, RD_FAILED) and not test and not live:
prevsize = os.path.getsize(encodeFilename(tmpfilename))
- self.to_screen('[rtmpdump] Downloaded %s bytes' % prevsize)
+ self.to_screen(f'[rtmpdump] Downloaded {prevsize} bytes')
time.sleep(5.0) # This seems to be needed
- args = basic_args + ['--resume']
+ args = [*basic_args, '--resume']
if retval == RD_FAILED:
args += ['--skip', '1']
args = [encodeArgument(a) for a in args]
@@ -197,7 +197,7 @@ class RtmpFD(FileDownloader):
break
if retval == RD_SUCCESS or (test and retval == RD_INCOMPLETE):
fsize = os.path.getsize(encodeFilename(tmpfilename))
- self.to_screen('[rtmpdump] Downloaded %s bytes' % fsize)
+ self.to_screen(f'[rtmpdump] Downloaded {fsize} bytes')
self.try_rename(tmpfilename, filename)
self._hook_progress({
'downloaded_bytes': fsize,
diff --git a/yt_dlp/downloader/youtube_live_chat.py b/yt_dlp/downloader/youtube_live_chat.py
index c7a86374a..961938d44 100644
--- a/yt_dlp/downloader/youtube_live_chat.py
+++ b/yt_dlp/downloader/youtube_live_chat.py
@@ -18,7 +18,7 @@ class YoutubeLiveChatFD(FragmentFD):
def real_download(self, filename, info_dict):
video_id = info_dict['video_id']
- self.to_screen('[%s] Downloading live chat' % self.FD_NAME)
+ self.to_screen(f'[{self.FD_NAME}] Downloading live chat')
if not self.params.get('skip_download') and info_dict['protocol'] == 'youtube_live_chat':
self.report_warning('Live chat download runs until the livestream ends. '
'If you wish to download the video simultaneously, run a separate yt-dlp instance')
diff --git a/yt_dlp/extractor/abc.py b/yt_dlp/extractor/abc.py
index 2c0d296fd..7518ba6f0 100644
--- a/yt_dlp/extractor/abc.py
+++ b/yt_dlp/extractor/abc.py
@@ -4,7 +4,6 @@ import re
import time
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
dict_get,
@@ -67,7 +66,7 @@ class ABCIE(InfoExtractor):
'ext': 'mp4',
'title': 'WWI Centenary',
'description': 'md5:c2379ec0ca84072e86b446e536954546',
- }
+ },
}, {
'url': 'https://www.abc.net.au/news/programs/the-world/2020-06-10/black-lives-matter-protests-spawn-support-for/12342074',
'info_dict': {
@@ -75,7 +74,7 @@ class ABCIE(InfoExtractor):
'ext': 'mp4',
'title': 'Black Lives Matter protests spawn support for Papuans in Indonesia',
'description': 'md5:2961a17dc53abc558589ccd0fb8edd6f',
- }
+ },
}, {
'url': 'https://www.abc.net.au/btn/newsbreak/btn-newsbreak-20200814/12560476',
'info_dict': {
@@ -86,7 +85,7 @@ class ABCIE(InfoExtractor):
'upload_date': '20200813',
'uploader': 'Behind the News',
'uploader_id': 'behindthenews',
- }
+ },
}, {
'url': 'https://www.abc.net.au/news/2023-06-25/wagner-boss-orders-troops-back-to-bases-to-avoid-bloodshed/102520540',
'info_dict': {
@@ -95,7 +94,7 @@ class ABCIE(InfoExtractor):
'ext': 'mp4',
'description': 'Wagner troops leave Rostov-on-Don and\xa0Yevgeny Prigozhin will move to Belarus under a deal brokered by Belarusian President Alexander Lukashenko to end the mutiny.',
'thumbnail': 'https://live-production.wcms.abc-cdn.net.au/0c170f5b57f0105c432f366c0e8e267b?impolicy=wcms_crop_resize&cropH=2813&cropW=5000&xPos=0&yPos=249&width=862&height=485',
- }
+ },
}]
def _real_extract(self, url):
@@ -126,7 +125,7 @@ class ABCIE(InfoExtractor):
if mobj is None:
expired = self._html_search_regex(r'(?s)class="expired-(?:video|audio)".+?<span>(.+?)</span>', webpage, 'expired', None)
if expired:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, expired), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {expired}', expected=True)
raise ExtractorError('Unable to extract video urls')
urls_info = self._parse_json(
@@ -164,7 +163,7 @@ class ABCIE(InfoExtractor):
'height': height,
'tbr': bitrate,
'filesize': int_or_none(url_info.get('filesize')),
- 'format_id': format_id
+ 'format_id': format_id,
})
return {
@@ -288,13 +287,12 @@ class ABCIViewIE(InfoExtractor):
stream = next(s for s in video_params['playlist'] if s.get('type') in ('program', 'livestream'))
house_number = video_params.get('episodeHouseNumber') or video_id
- path = '/auth/hls/sign?ts={0}&hn={1}&d=android-tablet'.format(
- int(time.time()), house_number)
+ path = f'/auth/hls/sign?ts={int(time.time())}&hn={house_number}&d=android-tablet'
sig = hmac.new(
b'android.content.res.Resources',
- path.encode('utf-8'), hashlib.sha256).hexdigest()
+ path.encode(), hashlib.sha256).hexdigest()
token = self._download_webpage(
- 'http://iview.abc.net.au{0}&sig={1}'.format(path, sig), video_id)
+ f'http://iview.abc.net.au{path}&sig={sig}', video_id)
def tokenize_url(url, token):
return update_url_query(url, {
@@ -303,7 +301,7 @@ class ABCIViewIE(InfoExtractor):
for sd in ('1080', '720', 'sd', 'sd-low'):
sd_url = try_get(
- stream, lambda x: x['streams']['hls'][sd], compat_str)
+ stream, lambda x: x['streams']['hls'][sd], str)
if not sd_url:
continue
formats = self._extract_m3u8_formats(
@@ -358,7 +356,7 @@ class ABCIViewShowSeriesIE(InfoExtractor):
'description': 'md5:93119346c24a7c322d446d8eece430ff',
'series': 'Upper Middle Bogan',
'season': 'Series 1',
- 'thumbnail': r're:^https?://cdn\.iview\.abc\.net\.au/thumbs/.*\.jpg$'
+ 'thumbnail': r're:^https?://cdn\.iview\.abc\.net\.au/thumbs/.*\.jpg$',
},
'playlist_count': 8,
}, {
@@ -386,7 +384,7 @@ class ABCIViewShowSeriesIE(InfoExtractor):
'description': 'Satirist Mark Humphries brings his unique perspective on current political events for 7.30.',
'series': '7.30 Mark Humphries Satire',
'season': 'Episodes',
- 'thumbnail': r're:^https?://cdn\.iview\.abc\.net\.au/thumbs/.*\.jpg$'
+ 'thumbnail': r're:^https?://cdn\.iview\.abc\.net\.au/thumbs/.*\.jpg$',
},
'playlist_count': 15,
}]
@@ -398,7 +396,7 @@ class ABCIViewShowSeriesIE(InfoExtractor):
r'window\.__INITIAL_STATE__\s*=\s*[\'"](.+?)[\'"]\s*;',
webpage, 'initial state')
video_data = self._parse_json(
- unescapeHTML(webpage_data).encode('utf-8').decode('unicode_escape'), show_id)
+ unescapeHTML(webpage_data).encode().decode('unicode_escape'), show_id)
video_data = video_data['route']['pageData']['_embedded']
highlight = try_get(video_data, lambda x: x['highlightVideo']['shareUrl'])
diff --git a/yt_dlp/extractor/abcnews.py b/yt_dlp/extractor/abcnews.py
index a57295b13..7215500b9 100644
--- a/yt_dlp/extractor/abcnews.py
+++ b/yt_dlp/extractor/abcnews.py
@@ -58,7 +58,7 @@ class AbcNewsVideoIE(AMPIE):
display_id = mobj.group('display_id')
video_id = mobj.group('id')
info_dict = self._extract_feed_info(
- 'http://abcnews.go.com/video/itemfeed?id=%s' % video_id)
+ f'http://abcnews.go.com/video/itemfeed?id={video_id}')
info_dict.update({
'id': video_id,
'display_id': display_id,
diff --git a/yt_dlp/extractor/abcotvs.py b/yt_dlp/extractor/abcotvs.py
index 6dca19de4..ea5882b26 100644
--- a/yt_dlp/extractor/abcotvs.py
+++ b/yt_dlp/extractor/abcotvs.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
dict_get,
int_or_none,
@@ -57,11 +56,11 @@ class ABCOTVSIE(InfoExtractor):
data = self._download_json(
'https://api.abcotvs.com/v2/content', display_id, query={
'id': video_id,
- 'key': 'otv.web.%s.story' % station,
+ 'key': f'otv.web.{station}.story',
'station': station,
})['data']
video = try_get(data, lambda x: x['featuredMedia']['video'], dict) or data
- video_id = compat_str(dict_get(video, ('id', 'publishedKey'), video_id))
+ video_id = str(dict_get(video, ('id', 'publishedKey'), video_id))
title = video.get('title') or video['linkText']
formats = []
diff --git a/yt_dlp/extractor/abematv.py b/yt_dlp/extractor/abematv.py
index b8c79b912..293a6c40e 100644
--- a/yt_dlp/extractor/abematv.py
+++ b/yt_dlp/extractor/abematv.py
@@ -66,8 +66,8 @@ class AbemaLicenseHandler(urllib.request.BaseHandler):
query={'t': media_token},
data=json.dumps({
'kv': 'a',
- 'lt': ticket
- }).encode('utf-8'),
+ 'lt': ticket,
+ }).encode(),
headers={
'Content-Type': 'application/json',
})
@@ -77,7 +77,7 @@ class AbemaLicenseHandler(urllib.request.BaseHandler):
h = hmac.new(
binascii.unhexlify(self.HKEY),
- (license_response['cid'] + self.ie._DEVICE_ID).encode('utf-8'),
+ (license_response['cid'] + self.ie._DEVICE_ID).encode(),
digestmod=hashlib.sha256)
enckey = bytes_to_intlist(h.digest())
@@ -103,11 +103,11 @@ class AbemaTVBaseIE(InfoExtractor):
@classmethod
def _generate_aks(cls, deviceid):
- deviceid = deviceid.encode('utf-8')
+ deviceid = deviceid.encode()
# add 1 hour and then drop minute and secs
ts_1hour = int((time_seconds() // 3600 + 1) * 3600)
time_struct = time.gmtime(ts_1hour)
- ts_1hour_str = str(ts_1hour).encode('utf-8')
+ ts_1hour_str = str(ts_1hour).encode()
tmp = None
@@ -119,7 +119,7 @@ class AbemaTVBaseIE(InfoExtractor):
def mix_tmp(count):
nonlocal tmp
- for i in range(count):
+ for _ in range(count):
mix_once(tmp)
def mix_twist(nonce):
@@ -160,7 +160,7 @@ class AbemaTVBaseIE(InfoExtractor):
data=json.dumps({
'deviceId': self._DEVICE_ID,
'applicationKeySecret': aks,
- }).encode('utf-8'),
+ }).encode(),
headers={
'Content-Type': 'application/json',
})
@@ -180,7 +180,7 @@ class AbemaTVBaseIE(InfoExtractor):
'osLang': 'ja_JP',
'osTimezone': 'Asia/Tokyo',
'appId': 'tv.abema',
- 'appVersion': '3.27.1'
+ 'appVersion': '3.27.1',
}, headers={
'Authorization': f'bearer {self._get_device_token()}',
})['token']
@@ -202,8 +202,8 @@ class AbemaTVBaseIE(InfoExtractor):
f'https://api.abema.io/v1/auth/{ep}', None, note='Logging in',
data=json.dumps({
method: username,
- 'password': password
- }).encode('utf-8'), headers={
+ 'password': password,
+ }).encode(), headers={
'Authorization': f'bearer {self._get_device_token()}',
'Origin': 'https://abema.tv',
'Referer': 'https://abema.tv/',
@@ -344,7 +344,7 @@ class AbemaTVIE(AbemaTVBaseIE):
description = self._html_search_regex(
(r'<p\s+class="com-video-EpisodeDetailsBlock__content"><span\s+class=".+?">(.+?)</span></p><div',
- r'<span\s+class=".+?SlotSummary.+?">(.+?)</span></div><div',),
+ r'<span\s+class=".+?SlotSummary.+?">(.+?)</span></div><div'),
webpage, 'description', default=None, group=1)
if not description:
og_desc = self._html_search_meta(
diff --git a/yt_dlp/extractor/acast.py b/yt_dlp/extractor/acast.py
index 427d04c31..8f4a2cf0f 100644
--- a/yt_dlp/extractor/acast.py
+++ b/yt_dlp/extractor/acast.py
@@ -67,7 +67,7 @@ class ACastIE(ACastBaseIE):
'display_id': '2.raggarmordet-rosterurdetforflutna',
'season_number': 4,
'season': 'Season 4',
- }
+ },
}, {
'url': 'http://embed.acast.com/adambuxton/ep.12-adam-joeschristmaspodcast2015',
'only_matching': True,
@@ -93,13 +93,13 @@ class ACastIE(ACastBaseIE):
'series': 'Democracy Sausage with Mark Kenny',
'timestamp': 1684826362,
'description': 'md5:feabe1fc5004c78ee59c84a46bf4ba16',
- }
+ },
}]
def _real_extract(self, url):
channel, display_id = self._match_valid_url(url).groups()
episode = self._call_api(
- '%s/episodes/%s' % (channel, display_id),
+ f'{channel}/episodes/{display_id}',
display_id, {'showInfo': 'true'})
return self._extract_episode(
episode, self._extract_show_info(episode.get('show') or {}))
@@ -130,7 +130,7 @@ class ACastChannelIE(ACastBaseIE):
@classmethod
def suitable(cls, url):
- return False if ACastIE.suitable(url) else super(ACastChannelIE, cls).suitable(url)
+ return False if ACastIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
show_slug = self._match_id(url)
diff --git a/yt_dlp/extractor/acfun.py b/yt_dlp/extractor/acfun.py
index 07933192f..28559baec 100644
--- a/yt_dlp/extractor/acfun.py
+++ b/yt_dlp/extractor/acfun.py
@@ -25,7 +25,7 @@ class AcFunVideoBaseIE(InfoExtractor):
'width': int_or_none(video.get('width')),
'height': int_or_none(video.get('height')),
'tbr': float_or_none(video.get('avgBitrate')),
- **parse_codecs(video.get('codecs', ''))
+ **parse_codecs(video.get('codecs', '')),
})
return {
@@ -77,7 +77,7 @@ class AcFunVideoIE(AcFunVideoBaseIE):
'comment_count': int,
'thumbnail': r're:^https?://.*\.(jpg|jpeg)',
'description': 'md5:67583aaf3a0f933bd606bc8a2d3ebb17',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/adn.py b/yt_dlp/extractor/adn.py
index 2f3b67dad..7be990b9c 100644
--- a/yt_dlp/extractor/adn.py
+++ b/yt_dlp/extractor/adn.py
@@ -7,7 +7,6 @@ import time
from .common import InfoExtractor
from ..aes import aes_cbc_decrypt_bytes, unpad_pkcs7
-from ..compat import compat_b64decode
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -111,9 +110,9 @@ class ADNIE(ADNBaseIE):
# http://animationdigitalnetwork.fr/components/com_vodvideo/videojs/adn-vjs.min.js
dec_subtitles = unpad_pkcs7(aes_cbc_decrypt_bytes(
- compat_b64decode(enc_subtitles[24:]),
+ base64.b64decode(enc_subtitles[24:]),
binascii.unhexlify(self._K + '7fac1178830cfe0c'),
- compat_b64decode(enc_subtitles[:24])))
+ base64.b64decode(enc_subtitles[:24])))
subtitles_json = self._parse_json(dec_subtitles.decode(), None, fatal=False)
if not subtitles_json:
return None
@@ -136,7 +135,7 @@ Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'''
if start is None or end is None or text is None:
continue
alignment = self._POS_ALIGN_MAP.get(position_align, 2) + self._LINE_ALIGN_MAP.get(line_align, 0)
- ssa += os.linesep + 'Dialogue: Marked=0,%s,%s,Default,,0,0,0,,%s%s' % (
+ ssa += os.linesep + 'Dialogue: Marked=0,{},{},Default,,0,0,0,,{}{}'.format(
ass_subtitles_timecode(start),
ass_subtitles_timecode(end),
'{\\a%d}' % alignment if alignment != 2 else '',
@@ -178,7 +177,7 @@ Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'''
def _real_extract(self, url):
lang, video_id = self._match_valid_url(url).group('lang', 'id')
- video_base_url = self._PLAYER_BASE_URL + 'video/%s/' % video_id
+ video_base_url = self._PLAYER_BASE_URL + f'video/{video_id}/'
player = self._download_json(
video_base_url + 'configuration', video_id,
'Downloading player config JSON metadata',
@@ -219,12 +218,12 @@ Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'''
links_url, video_id, 'Downloading links JSON metadata', headers={
'X-Player-Token': authorization,
'X-Target-Distribution': lang,
- **self._HEADERS
+ **self._HEADERS,
}, query={
'freeWithAds': 'true',
'adaptive': 'false',
'withMetadata': 'true',
- 'source': 'Web'
+ 'source': 'Web',
})
break
except ExtractorError as e:
@@ -256,7 +255,7 @@ Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'''
for quality, load_balancer_url in qualities.items():
load_balancer_data = self._download_json(
load_balancer_url, video_id,
- 'Downloading %s %s JSON metadata' % (format_id, quality),
+ f'Downloading {format_id} {quality} JSON metadata',
fatal=False) or {}
m3u8_url = load_balancer_data.get('location')
if not m3u8_url:
@@ -276,7 +275,7 @@ Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'''
self.raise_login_required('This video requires a subscription', method='password')
video = (self._download_json(
- self._API_BASE_URL + 'video/%s' % video_id, video_id,
+ self._API_BASE_URL + f'video/{video_id}', video_id,
'Downloading additional video metadata', fatal=False) or {}).get('video') or {}
show = video.get('show') or {}
@@ -320,7 +319,7 @@ class ADNSeasonIE(ADNBaseIE):
f'{self._API_BASE_URL}video/show/{show_id}', video_show_slug,
'Downloading episode list', headers={
'X-Target-Distribution': lang,
- **self._HEADERS
+ **self._HEADERS,
}, query={
'order': 'asc',
'limit': '-1',
diff --git a/yt_dlp/extractor/adobeconnect.py b/yt_dlp/extractor/adobeconnect.py
index 8963b128a..b2934d6f7 100644
--- a/yt_dlp/extractor/adobeconnect.py
+++ b/yt_dlp/extractor/adobeconnect.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urlparse,
-)
class AdobeConnectIE(InfoExtractor):
@@ -12,13 +10,13 @@ class AdobeConnectIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
title = self._html_extract_title(webpage)
- qs = compat_parse_qs(self._search_regex(r"swfUrl\s*=\s*'([^']+)'", webpage, 'swf url').split('?')[1])
+ qs = urllib.parse.parse_qs(self._search_regex(r"swfUrl\s*=\s*'([^']+)'", webpage, 'swf url').split('?')[1])
is_live = qs.get('isLive', ['false'])[0] == 'true'
formats = []
for con_string in qs['conStrings'][0].split(','):
formats.append({
'format_id': con_string.split('://')[0],
- 'app': compat_urlparse.quote('?' + con_string.split('?')[1] + 'flvplayerapp/' + qs['appInstance'][0]),
+ 'app': urllib.parse.quote('?' + con_string.split('?')[1] + 'flvplayerapp/' + qs['appInstance'][0]),
'ext': 'flv',
'play_path': 'mp4:' + qs['streamName'][0],
'rtmp_conn': 'S:' + qs['ticket'][0],
diff --git a/yt_dlp/extractor/adobepass.py b/yt_dlp/extractor/adobepass.py
index 5eed0ca22..eb7e597e5 100644
--- a/yt_dlp/extractor/adobepass.py
+++ b/yt_dlp/extractor/adobepass.py
@@ -2,10 +2,10 @@ import getpass
import json
import re
import time
+import urllib.parse
import xml.etree.ElementTree as etree
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..networking.exceptions import HTTPError
from ..utils import (
NO_DEFAULT,
@@ -68,7 +68,7 @@ MSO_INFO = {
},
'Philo': {
'name': 'Philo',
- 'username_field': 'ident'
+ 'username_field': 'ident',
},
'Verizon': {
'name': 'Verizon FiOS',
@@ -81,1258 +81,1258 @@ MSO_INFO = {
'password_field': 'j_password',
},
'thr030': {
- 'name': '3 Rivers Communications'
+ 'name': '3 Rivers Communications',
},
'com140': {
- 'name': 'Access Montana'
+ 'name': 'Access Montana',
},
'acecommunications': {
- 'name': 'AcenTek'
+ 'name': 'AcenTek',
},
'acm010': {
- 'name': 'Acme Communications'
+ 'name': 'Acme Communications',
},
'ada020': {
- 'name': 'Adams Cable Service'
+ 'name': 'Adams Cable Service',
},
'alb020': {
- 'name': 'Albany Mutual Telephone'
+ 'name': 'Albany Mutual Telephone',
},
'algona': {
- 'name': 'Algona Municipal Utilities'
+ 'name': 'Algona Municipal Utilities',
},
'allwest': {
- 'name': 'All West Communications'
+ 'name': 'All West Communications',
},
'all025': {
- 'name': 'Allen\'s Communications'
+ 'name': 'Allen\'s Communications',
},
'spl010': {
- 'name': 'Alliance Communications'
+ 'name': 'Alliance Communications',
},
'all070': {
- 'name': 'ALLO Communications'
+ 'name': 'ALLO Communications',
},
'alpine': {
- 'name': 'Alpine Communications'
+ 'name': 'Alpine Communications',
},
'hun015': {
- 'name': 'American Broadband'
+ 'name': 'American Broadband',
},
'nwc010': {
- 'name': 'American Broadband Missouri'
+ 'name': 'American Broadband Missouri',
},
'com130-02': {
- 'name': 'American Community Networks'
+ 'name': 'American Community Networks',
},
'com130-01': {
- 'name': 'American Warrior Networks'
+ 'name': 'American Warrior Networks',
},
'tom020': {
- 'name': 'Amherst Telephone/Tomorrow Valley'
+ 'name': 'Amherst Telephone/Tomorrow Valley',
},
'tvc020': {
- 'name': 'Andycable'
+ 'name': 'Andycable',
},
'arkwest': {
- 'name': 'Arkwest Communications'
+ 'name': 'Arkwest Communications',
},
'art030': {
- 'name': 'Arthur Mutual Telephone Company'
+ 'name': 'Arthur Mutual Telephone Company',
},
'arvig': {
- 'name': 'Arvig'
+ 'name': 'Arvig',
},
'nttcash010': {
- 'name': 'Ashland Home Net'
+ 'name': 'Ashland Home Net',
},
'astound': {
- 'name': 'Astound (now Wave)'
+ 'name': 'Astound (now Wave)',
},
'dix030': {
- 'name': 'ATC Broadband'
+ 'name': 'ATC Broadband',
},
'ara010': {
- 'name': 'ATC Communications'
+ 'name': 'ATC Communications',
},
'she030-02': {
- 'name': 'Ayersville Communications'
+ 'name': 'Ayersville Communications',
},
'baldwin': {
- 'name': 'Baldwin Lightstream'
+ 'name': 'Baldwin Lightstream',
},
'bal040': {
- 'name': 'Ballard TV'
+ 'name': 'Ballard TV',
},
'cit025': {
- 'name': 'Bardstown Cable TV'
+ 'name': 'Bardstown Cable TV',
},
'bay030': {
- 'name': 'Bay Country Communications'
+ 'name': 'Bay Country Communications',
},
'tel095': {
- 'name': 'Beaver Creek Cooperative Telephone'
+ 'name': 'Beaver Creek Cooperative Telephone',
},
'bea020': {
- 'name': 'Beaver Valley Cable'
+ 'name': 'Beaver Valley Cable',
},
'bee010': {
- 'name': 'Bee Line Cable'
+ 'name': 'Bee Line Cable',
},
'wir030': {
- 'name': 'Beehive Broadband'
+ 'name': 'Beehive Broadband',
},
'bra020': {
- 'name': 'BELD'
+ 'name': 'BELD',
},
'bel020': {
- 'name': 'Bellevue Municipal Cable'
+ 'name': 'Bellevue Municipal Cable',
},
'vol040-01': {
- 'name': 'Ben Lomand Connect / BLTV'
+ 'name': 'Ben Lomand Connect / BLTV',
},
'bev010': {
- 'name': 'BEVCOMM'
+ 'name': 'BEVCOMM',
},
'big020': {
- 'name': 'Big Sandy Broadband'
+ 'name': 'Big Sandy Broadband',
},
'ble020': {
- 'name': 'Bledsoe Telephone Cooperative'
+ 'name': 'Bledsoe Telephone Cooperative',
},
'bvt010': {
- 'name': 'Blue Valley Tele-Communications'
+ 'name': 'Blue Valley Tele-Communications',
},
'bra050': {
- 'name': 'Brandenburg Telephone Co.'
+ 'name': 'Brandenburg Telephone Co.',
},
'bte010': {
- 'name': 'Bristol Tennessee Essential Services'
+ 'name': 'Bristol Tennessee Essential Services',
},
'annearundel': {
- 'name': 'Broadstripe'
+ 'name': 'Broadstripe',
},
'btc010': {
- 'name': 'BTC Communications'
+ 'name': 'BTC Communications',
},
'btc040': {
- 'name': 'BTC Vision - Nahunta'
+ 'name': 'BTC Vision - Nahunta',
},
'bul010': {
- 'name': 'Bulloch Telephone Cooperative'
+ 'name': 'Bulloch Telephone Cooperative',
},
'but010': {
- 'name': 'Butler-Bremer Communications'
+ 'name': 'Butler-Bremer Communications',
},
'tel160-csp': {
- 'name': 'C Spire SNAP'
+ 'name': 'C Spire SNAP',
},
'csicable': {
- 'name': 'Cable Services Inc.'
+ 'name': 'Cable Services Inc.',
},
'cableamerica': {
- 'name': 'CableAmerica'
+ 'name': 'CableAmerica',
},
'cab038': {
- 'name': 'CableSouth Media 3'
+ 'name': 'CableSouth Media 3',
},
'weh010-camtel': {
- 'name': 'Cam-Tel Company'
+ 'name': 'Cam-Tel Company',
},
'car030': {
- 'name': 'Cameron Communications'
+ 'name': 'Cameron Communications',
},
'canbytel': {
- 'name': 'Canby Telcom'
+ 'name': 'Canby Telcom',
},
'crt020': {
- 'name': 'CapRock Tv'
+ 'name': 'CapRock Tv',
},
'car050': {
- 'name': 'Carnegie Cable'
+ 'name': 'Carnegie Cable',
},
'cas': {
- 'name': 'CAS Cable'
+ 'name': 'CAS Cable',
},
'casscomm': {
- 'name': 'CASSCOMM'
+ 'name': 'CASSCOMM',
},
'mid180-02': {
- 'name': 'Catalina Broadband Solutions'
+ 'name': 'Catalina Broadband Solutions',
},
'cccomm': {
- 'name': 'CC Communications'
+ 'name': 'CC Communications',
},
'nttccde010': {
- 'name': 'CDE Lightband'
+ 'name': 'CDE Lightband',
},
'cfunet': {
- 'name': 'Cedar Falls Utilities'
+ 'name': 'Cedar Falls Utilities',
},
'dem010-01': {
- 'name': 'Celect-Bloomer Telephone Area'
+ 'name': 'Celect-Bloomer Telephone Area',
},
'dem010-02': {
- 'name': 'Celect-Bruce Telephone Area'
+ 'name': 'Celect-Bruce Telephone Area',
},
'dem010-03': {
- 'name': 'Celect-Citizens Connected Area'
+ 'name': 'Celect-Citizens Connected Area',
},
'dem010-04': {
- 'name': 'Celect-Elmwood/Spring Valley Area'
+ 'name': 'Celect-Elmwood/Spring Valley Area',
},
'dem010-06': {
- 'name': 'Celect-Mosaic Telecom'
+ 'name': 'Celect-Mosaic Telecom',
},
'dem010-05': {
- 'name': 'Celect-West WI Telephone Area'
+ 'name': 'Celect-West WI Telephone Area',
},
'net010-02': {
- 'name': 'Cellcom/Nsight Telservices'
+ 'name': 'Cellcom/Nsight Telservices',
},
'cen100': {
- 'name': 'CentraCom'
+ 'name': 'CentraCom',
},
'nttccst010': {
- 'name': 'Central Scott / CSTV'
+ 'name': 'Central Scott / CSTV',
},
'cha035': {
- 'name': 'Chaparral CableVision'
+ 'name': 'Chaparral CableVision',
},
'cha050': {
- 'name': 'Chariton Valley Communication Corporation, Inc.'
+ 'name': 'Chariton Valley Communication Corporation, Inc.',
},
'cha060': {
- 'name': 'Chatmoss Cablevision'
+ 'name': 'Chatmoss Cablevision',
},
'nttcche010': {
- 'name': 'Cherokee Communications'
+ 'name': 'Cherokee Communications',
},
'che050': {
- 'name': 'Chesapeake Bay Communications'
+ 'name': 'Chesapeake Bay Communications',
},
'cimtel': {
- 'name': 'Cim-Tel Cable, LLC.'
+ 'name': 'Cim-Tel Cable, LLC.',
},
'cit180': {
- 'name': 'Citizens Cablevision - Floyd, VA'
+ 'name': 'Citizens Cablevision - Floyd, VA',
},
'cit210': {
- 'name': 'Citizens Cablevision, Inc.'
+ 'name': 'Citizens Cablevision, Inc.',
},
'cit040': {
- 'name': 'Citizens Fiber'
+ 'name': 'Citizens Fiber',
},
'cit250': {
- 'name': 'Citizens Mutual'
+ 'name': 'Citizens Mutual',
},
'war040': {
- 'name': 'Citizens Telephone Corporation'
+ 'name': 'Citizens Telephone Corporation',
},
'wat025': {
- 'name': 'City Of Monroe'
+ 'name': 'City Of Monroe',
},
'wadsworth': {
- 'name': 'CityLink'
+ 'name': 'CityLink',
},
'nor100': {
- 'name': 'CL Tel'
+ 'name': 'CL Tel',
},
'cla010': {
- 'name': 'Clarence Telephone and Cedar Communications'
+ 'name': 'Clarence Telephone and Cedar Communications',
},
'ser060': {
- 'name': 'Clear Choice Communications'
+ 'name': 'Clear Choice Communications',
},
'tac020': {
- 'name': 'Click! Cable TV'
+ 'name': 'Click! Cable TV',
},
'war020': {
- 'name': 'CLICK1.NET'
+ 'name': 'CLICK1.NET',
},
'cml010': {
- 'name': 'CML Telephone Cooperative Association'
+ 'name': 'CML Telephone Cooperative Association',
},
'cns': {
- 'name': 'CNS'
+ 'name': 'CNS',
},
'com160': {
- 'name': 'Co-Mo Connect'
+ 'name': 'Co-Mo Connect',
},
'coa020': {
- 'name': 'Coast Communications'
+ 'name': 'Coast Communications',
},
'coa030': {
- 'name': 'Coaxial Cable TV'
+ 'name': 'Coaxial Cable TV',
},
'mid055': {
- 'name': 'Cobalt TV (Mid-State Community TV)'
+ 'name': 'Cobalt TV (Mid-State Community TV)',
},
'col070': {
- 'name': 'Columbia Power & Water Systems'
+ 'name': 'Columbia Power & Water Systems',
},
'col080': {
- 'name': 'Columbus Telephone'
+ 'name': 'Columbus Telephone',
},
'nor105': {
- 'name': 'Communications 1 Cablevision, Inc.'
+ 'name': 'Communications 1 Cablevision, Inc.',
},
'com150': {
- 'name': 'Community Cable & Broadband'
+ 'name': 'Community Cable & Broadband',
},
'com020': {
- 'name': 'Community Communications Company'
+ 'name': 'Community Communications Company',
},
'coy010': {
- 'name': 'commZoom'
+ 'name': 'commZoom',
},
'com025': {
- 'name': 'Complete Communication Services'
+ 'name': 'Complete Communication Services',
},
'cat020': {
- 'name': 'Comporium'
+ 'name': 'Comporium',
},
'com071': {
- 'name': 'ComSouth Telesys'
+ 'name': 'ComSouth Telesys',
},
'consolidatedcable': {
- 'name': 'Consolidated'
+ 'name': 'Consolidated',
},
'conwaycorp': {
- 'name': 'Conway Corporation'
+ 'name': 'Conway Corporation',
},
'coo050': {
- 'name': 'Coon Valley Telecommunications Inc'
+ 'name': 'Coon Valley Telecommunications Inc',
},
'coo080': {
- 'name': 'Cooperative Telephone Company'
+ 'name': 'Cooperative Telephone Company',
},
'cpt010': {
- 'name': 'CP-TEL'
+ 'name': 'CP-TEL',
},
'cra010': {
- 'name': 'Craw-Kan Telephone'
+ 'name': 'Craw-Kan Telephone',
},
'crestview': {
- 'name': 'Crestview Cable Communications'
+ 'name': 'Crestview Cable Communications',
},
'cross': {
- 'name': 'Cross TV'
+ 'name': 'Cross TV',
},
'cro030': {
- 'name': 'Crosslake Communications'
+ 'name': 'Crosslake Communications',
},
'ctc040': {
- 'name': 'CTC - Brainerd MN'
+ 'name': 'CTC - Brainerd MN',
},
'phe030': {
- 'name': 'CTV-Beam - East Alabama'
+ 'name': 'CTV-Beam - East Alabama',
},
'cun010': {
- 'name': 'Cunningham Telephone & Cable'
+ 'name': 'Cunningham Telephone & Cable',
},
'dpc010': {
- 'name': 'D & P Communications'
+ 'name': 'D & P Communications',
},
'dak030': {
- 'name': 'Dakota Central Telecommunications'
+ 'name': 'Dakota Central Telecommunications',
},
'nttcdel010': {
- 'name': 'Delcambre Telephone LLC'
+ 'name': 'Delcambre Telephone LLC',
},
'tel160-del': {
- 'name': 'Delta Telephone Company'
+ 'name': 'Delta Telephone Company',
},
'sal040': {
- 'name': 'DiamondNet'
+ 'name': 'DiamondNet',
},
'ind060-dc': {
- 'name': 'Direct Communications'
+ 'name': 'Direct Communications',
},
'doy010': {
- 'name': 'Doylestown Cable TV'
+ 'name': 'Doylestown Cable TV',
},
'dic010': {
- 'name': 'DRN'
+ 'name': 'DRN',
},
'dtc020': {
- 'name': 'DTC'
+ 'name': 'DTC',
},
'dtc010': {
- 'name': 'DTC Cable (Delhi)'
+ 'name': 'DTC Cable (Delhi)',
},
'dum010': {
- 'name': 'Dumont Telephone Company'
+ 'name': 'Dumont Telephone Company',
},
'dun010': {
- 'name': 'Dunkerton Telephone Cooperative'
+ 'name': 'Dunkerton Telephone Cooperative',
},
'cci010': {
- 'name': 'Duo County Telecom'
+ 'name': 'Duo County Telecom',
},
'eagle': {
- 'name': 'Eagle Communications'
+ 'name': 'Eagle Communications',
},
'weh010-east': {
- 'name': 'East Arkansas Cable TV'
+ 'name': 'East Arkansas Cable TV',
},
'eatel': {
- 'name': 'EATEL Video, LLC'
+ 'name': 'EATEL Video, LLC',
},
'ell010': {
- 'name': 'ECTA'
+ 'name': 'ECTA',
},
'emerytelcom': {
- 'name': 'Emery Telcom Video LLC'
+ 'name': 'Emery Telcom Video LLC',
},
'nor200': {
- 'name': 'Empire Access'
+ 'name': 'Empire Access',
},
'endeavor': {
- 'name': 'Endeavor Communications'
+ 'name': 'Endeavor Communications',
},
'sun045': {
- 'name': 'Enhanced Telecommunications Corporation'
+ 'name': 'Enhanced Telecommunications Corporation',
},
'mid030': {
- 'name': 'enTouch'
+ 'name': 'enTouch',
},
'epb020': {
- 'name': 'EPB Smartnet'
+ 'name': 'EPB Smartnet',
},
'jea010': {
- 'name': 'EPlus Broadband'
+ 'name': 'EPlus Broadband',
},
'com065': {
- 'name': 'ETC'
+ 'name': 'ETC',
},
'ete010': {
- 'name': 'Etex Communications'
+ 'name': 'Etex Communications',
},
'fbc-tele': {
- 'name': 'F&B Communications'
+ 'name': 'F&B Communications',
},
'fal010': {
- 'name': 'Falcon Broadband'
+ 'name': 'Falcon Broadband',
},
'fam010': {
- 'name': 'FamilyView CableVision'
+ 'name': 'FamilyView CableVision',
},
'far020': {
- 'name': 'Farmers Mutual Telephone Company'
+ 'name': 'Farmers Mutual Telephone Company',
},
'fay010': {
- 'name': 'Fayetteville Public Utilities'
+ 'name': 'Fayetteville Public Utilities',
},
'sal060': {
- 'name': 'fibrant'
+ 'name': 'fibrant',
},
'fid010': {
- 'name': 'Fidelity Communications'
+ 'name': 'Fidelity Communications',
},
'for030': {
- 'name': 'FJ Communications'
+ 'name': 'FJ Communications',
},
'fli020': {
- 'name': 'Flint River Communications'
+ 'name': 'Flint River Communications',
},
'far030': {
- 'name': 'FMT - Jesup'
+ 'name': 'FMT - Jesup',
},
'foo010': {
- 'name': 'Foothills Communications'
+ 'name': 'Foothills Communications',
},
'for080': {
- 'name': 'Forsyth CableNet'
+ 'name': 'Forsyth CableNet',
},
'fbcomm': {
- 'name': 'Frankfort Plant Board'
+ 'name': 'Frankfort Plant Board',
},
'tel160-fra': {
- 'name': 'Franklin Telephone Company'
+ 'name': 'Franklin Telephone Company',
},
'nttcftc010': {
- 'name': 'FTC'
+ 'name': 'FTC',
},
'fullchannel': {
- 'name': 'Full Channel, Inc.'
+ 'name': 'Full Channel, Inc.',
},
'gar040': {
- 'name': 'Gardonville Cooperative Telephone Association'
+ 'name': 'Gardonville Cooperative Telephone Association',
},
'gbt010': {
- 'name': 'GBT Communications, Inc.'
+ 'name': 'GBT Communications, Inc.',
},
'tec010': {
- 'name': 'Genuine Telecom'
+ 'name': 'Genuine Telecom',
},
'clr010': {
- 'name': 'Giant Communications'
+ 'name': 'Giant Communications',
},
'gla010': {
- 'name': 'Glasgow EPB'
+ 'name': 'Glasgow EPB',
},
'gle010': {
- 'name': 'Glenwood Telecommunications'
+ 'name': 'Glenwood Telecommunications',
},
'gra060': {
- 'name': 'GLW Broadband Inc.'
+ 'name': 'GLW Broadband Inc.',
},
'goldenwest': {
- 'name': 'Golden West Cablevision'
+ 'name': 'Golden West Cablevision',
},
'vis030': {
- 'name': 'Grantsburg Telcom'
+ 'name': 'Grantsburg Telcom',
},
'gpcom': {
- 'name': 'Great Plains Communications'
+ 'name': 'Great Plains Communications',
},
'gri010': {
- 'name': 'Gridley Cable Inc'
+ 'name': 'Gridley Cable Inc',
},
'hbc010': {
- 'name': 'H&B Cable Services'
+ 'name': 'H&B Cable Services',
},
'hae010': {
- 'name': 'Haefele TV Inc.'
+ 'name': 'Haefele TV Inc.',
},
'htc010': {
- 'name': 'Halstad Telephone Company'
+ 'name': 'Halstad Telephone Company',
},
'har005': {
- 'name': 'Harlan Municipal Utilities'
+ 'name': 'Harlan Municipal Utilities',
},
'har020': {
- 'name': 'Hart Communications'
+ 'name': 'Hart Communications',
},
'ced010': {
- 'name': 'Hartelco TV'
+ 'name': 'Hartelco TV',
},
'hea040': {
- 'name': 'Heart of Iowa Communications Cooperative'
+ 'name': 'Heart of Iowa Communications Cooperative',
},
'htc020': {
- 'name': 'Hickory Telephone Company'
+ 'name': 'Hickory Telephone Company',
},
'nttchig010': {
- 'name': 'Highland Communication Services'
+ 'name': 'Highland Communication Services',
},
'hig030': {
- 'name': 'Highland Media'
+ 'name': 'Highland Media',
},
'spc010': {
- 'name': 'Hilliary Communications'
+ 'name': 'Hilliary Communications',
},
'hin020': {
- 'name': 'Hinton CATV Co.'
+ 'name': 'Hinton CATV Co.',
},
'hometel': {
- 'name': 'HomeTel Entertainment, Inc.'
+ 'name': 'HomeTel Entertainment, Inc.',
},
'hoodcanal': {
- 'name': 'Hood Canal Communications'
+ 'name': 'Hood Canal Communications',
},
'weh010-hope': {
- 'name': 'Hope - Prescott Cable TV'
+ 'name': 'Hope - Prescott Cable TV',
},
'horizoncable': {
- 'name': 'Horizon Cable TV, Inc.'
+ 'name': 'Horizon Cable TV, Inc.',
},
'hor040': {
- 'name': 'Horizon Chillicothe Telephone'
+ 'name': 'Horizon Chillicothe Telephone',
},
'htc030': {
- 'name': 'HTC Communications Co. - IL'
+ 'name': 'HTC Communications Co. - IL',
},
'htccomm': {
- 'name': 'HTC Communications, Inc. - IA'
+ 'name': 'HTC Communications, Inc. - IA',
},
'wal005': {
- 'name': 'Huxley Communications'
+ 'name': 'Huxley Communications',
},
'imon': {
- 'name': 'ImOn Communications'
+ 'name': 'ImOn Communications',
},
'ind040': {
- 'name': 'Independence Telecommunications'
+ 'name': 'Independence Telecommunications',
},
'rrc010': {
- 'name': 'Inland Networks'
+ 'name': 'Inland Networks',
},
'stc020': {
- 'name': 'Innovative Cable TV St Croix'
+ 'name': 'Innovative Cable TV St Croix',
},
'car100': {
- 'name': 'Innovative Cable TV St Thomas-St John'
+ 'name': 'Innovative Cable TV St Thomas-St John',
},
'icc010': {
- 'name': 'Inside Connect Cable'
+ 'name': 'Inside Connect Cable',
},
'int100': {
- 'name': 'Integra Telecom'
+ 'name': 'Integra Telecom',
},
'int050': {
- 'name': 'Interstate Telecommunications Coop'
+ 'name': 'Interstate Telecommunications Coop',
},
'irv010': {
- 'name': 'Irvine Cable'
+ 'name': 'Irvine Cable',
},
'k2c010': {
- 'name': 'K2 Communications'
+ 'name': 'K2 Communications',
},
'kal010': {
- 'name': 'Kalida Telephone Company, Inc.'
+ 'name': 'Kalida Telephone Company, Inc.',
},
'kal030': {
- 'name': 'Kalona Cooperative Telephone Company'
+ 'name': 'Kalona Cooperative Telephone Company',
},
'kmt010': {
- 'name': 'KMTelecom'
+ 'name': 'KMTelecom',
},
'kpu010': {
- 'name': 'KPU Telecommunications'
+ 'name': 'KPU Telecommunications',
},
'kuh010': {
- 'name': 'Kuhn Communications, Inc.'
+ 'name': 'Kuhn Communications, Inc.',
},
'lak130': {
- 'name': 'Lakeland Communications'
+ 'name': 'Lakeland Communications',
},
'lan010': {
- 'name': 'Langco'
+ 'name': 'Langco',
},
'lau020': {
- 'name': 'Laurel Highland Total Communications, Inc.'
+ 'name': 'Laurel Highland Total Communications, Inc.',
},
'leh010': {
- 'name': 'Lehigh Valley Cooperative Telephone'
+ 'name': 'Lehigh Valley Cooperative Telephone',
},
'bra010': {
- 'name': 'Limestone Cable/Bracken Cable'
+ 'name': 'Limestone Cable/Bracken Cable',
},
'loc020': {
- 'name': 'LISCO'
+ 'name': 'LISCO',
},
'lit020': {
- 'name': 'Litestream'
+ 'name': 'Litestream',
},
'tel140': {
- 'name': 'LivCom'
+ 'name': 'LivCom',
},
'loc010': {
- 'name': 'LocalTel Communications'
+ 'name': 'LocalTel Communications',
},
'weh010-longview': {
- 'name': 'Longview - Kilgore Cable TV'
+ 'name': 'Longview - Kilgore Cable TV',
},
'lon030': {
- 'name': 'Lonsdale Video Ventures, LLC'
+ 'name': 'Lonsdale Video Ventures, LLC',
},
'lns010': {
- 'name': 'Lost Nation-Elwood Telephone Co.'
+ 'name': 'Lost Nation-Elwood Telephone Co.',
},
'nttclpc010': {
- 'name': 'LPC Connect'
+ 'name': 'LPC Connect',
},
'lumos': {
- 'name': 'Lumos Networks'
+ 'name': 'Lumos Networks',
},
'madison': {
- 'name': 'Madison Communications'
+ 'name': 'Madison Communications',
},
'mad030': {
- 'name': 'Madison County Cable Inc.'
+ 'name': 'Madison County Cable Inc.',
},
'nttcmah010': {
- 'name': 'Mahaska Communication Group'
+ 'name': 'Mahaska Communication Group',
},
'mar010': {
- 'name': 'Marne & Elk Horn Telephone Company'
+ 'name': 'Marne & Elk Horn Telephone Company',
},
'mcc040': {
- 'name': 'McClure Telephone Co.'
+ 'name': 'McClure Telephone Co.',
},
'mctv': {
- 'name': 'MCTV'
+ 'name': 'MCTV',
},
'merrimac': {
- 'name': 'Merrimac Communications Ltd.'
+ 'name': 'Merrimac Communications Ltd.',
},
'metronet': {
- 'name': 'Metronet'
+ 'name': 'Metronet',
},
'mhtc': {
- 'name': 'MHTC'
+ 'name': 'MHTC',
},
'midhudson': {
- 'name': 'Mid-Hudson Cable'
+ 'name': 'Mid-Hudson Cable',
},
'midrivers': {
- 'name': 'Mid-Rivers Communications'
+ 'name': 'Mid-Rivers Communications',
},
'mid045': {
- 'name': 'Midstate Communications'
+ 'name': 'Midstate Communications',
},
'mil080': {
- 'name': 'Milford Communications'
+ 'name': 'Milford Communications',
},
'min030': {
- 'name': 'MINET'
+ 'name': 'MINET',
},
'nttcmin010': {
- 'name': 'Minford TV'
+ 'name': 'Minford TV',
},
'san040-02': {
- 'name': 'Mitchell Telecom'
+ 'name': 'Mitchell Telecom',
},
'mlg010': {
- 'name': 'MLGC'
+ 'name': 'MLGC',
},
'mon060': {
- 'name': 'Mon-Cre TVE'
+ 'name': 'Mon-Cre TVE',
},
'mou110': {
- 'name': 'Mountain Telephone'
+ 'name': 'Mountain Telephone',
},
'mou050': {
- 'name': 'Mountain Village Cable'
+ 'name': 'Mountain Village Cable',
},
'mtacomm': {
- 'name': 'MTA Communications, LLC'
+ 'name': 'MTA Communications, LLC',
},
'mtc010': {
- 'name': 'MTC Cable'
+ 'name': 'MTC Cable',
},
'med040': {
- 'name': 'MTC Technologies'
+ 'name': 'MTC Technologies',
},
'man060': {
- 'name': 'MTCC'
+ 'name': 'MTCC',
},
'mtc030': {
- 'name': 'MTCO Communications'
+ 'name': 'MTCO Communications',
},
'mul050': {
- 'name': 'Mulberry Telecommunications'
+ 'name': 'Mulberry Telecommunications',
},
'mur010': {
- 'name': 'Murray Electric System'
+ 'name': 'Murray Electric System',
},
'musfiber': {
- 'name': 'MUS FiberNET'
+ 'name': 'MUS FiberNET',
},
'mpw': {
- 'name': 'Muscatine Power & Water'
+ 'name': 'Muscatine Power & Water',
},
'nttcsli010': {
- 'name': 'myEVTV.com'
+ 'name': 'myEVTV.com',
},
'nor115': {
- 'name': 'NCC'
+ 'name': 'NCC',
},
'nor260': {
- 'name': 'NDTC'
+ 'name': 'NDTC',
},
'nctc': {
- 'name': 'Nebraska Central Telecom, Inc.'
+ 'name': 'Nebraska Central Telecom, Inc.',
},
'nel020': {
- 'name': 'Nelsonville TV Cable'
+ 'name': 'Nelsonville TV Cable',
},
'nem010': {
- 'name': 'Nemont'
+ 'name': 'Nemont',
},
'new075': {
- 'name': 'New Hope Telephone Cooperative'
+ 'name': 'New Hope Telephone Cooperative',
},
'nor240': {
- 'name': 'NICP'
+ 'name': 'NICP',
},
'cic010': {
- 'name': 'NineStar Connect'
+ 'name': 'NineStar Connect',
},
'nktelco': {
- 'name': 'NKTelco'
+ 'name': 'NKTelco',
},
'nortex': {
- 'name': 'Nortex Communications'
+ 'name': 'Nortex Communications',
},
'nor140': {
- 'name': 'North Central Telephone Cooperative'
+ 'name': 'North Central Telephone Cooperative',
},
'nor030': {
- 'name': 'Northland Communications'
+ 'name': 'Northland Communications',
},
'nor075': {
- 'name': 'Northwest Communications'
+ 'name': 'Northwest Communications',
},
'nor125': {
- 'name': 'Norwood Light Broadband'
+ 'name': 'Norwood Light Broadband',
},
'net010': {
- 'name': 'Nsight Telservices'
+ 'name': 'Nsight Telservices',
},
'dur010': {
- 'name': 'Ntec'
+ 'name': 'Ntec',
},
'nts010': {
- 'name': 'NTS Communications'
+ 'name': 'NTS Communications',
},
'new045': {
- 'name': 'NU-Telecom'
+ 'name': 'NU-Telecom',
},
'nulink': {
- 'name': 'NuLink'
+ 'name': 'NuLink',
},
'jam030': {
- 'name': 'NVC'
+ 'name': 'NVC',
},
'far035': {
- 'name': 'OmniTel Communications'
+ 'name': 'OmniTel Communications',
},
'onesource': {
- 'name': 'OneSource Communications'
+ 'name': 'OneSource Communications',
},
'cit230': {
- 'name': 'Opelika Power Services'
+ 'name': 'Opelika Power Services',
},
'daltonutilities': {
- 'name': 'OptiLink'
+ 'name': 'OptiLink',
},
'mid140': {
- 'name': 'OPTURA'
+ 'name': 'OPTURA',
},
'ote010': {
- 'name': 'OTEC Communication Company'
+ 'name': 'OTEC Communication Company',
},
'cci020': {
- 'name': 'Packerland Broadband'
+ 'name': 'Packerland Broadband',
},
'pan010': {
- 'name': 'Panora Telco/Guthrie Center Communications'
+ 'name': 'Panora Telco/Guthrie Center Communications',
},
'otter': {
- 'name': 'Park Region Telephone & Otter Tail Telcom'
+ 'name': 'Park Region Telephone & Otter Tail Telcom',
},
'mid050': {
- 'name': 'Partner Communications Cooperative'
+ 'name': 'Partner Communications Cooperative',
},
'fib010': {
- 'name': 'Pathway'
+ 'name': 'Pathway',
},
'paulbunyan': {
- 'name': 'Paul Bunyan Communications'
+ 'name': 'Paul Bunyan Communications',
},
'pem020': {
- 'name': 'Pembroke Telephone Company'
+ 'name': 'Pembroke Telephone Company',
},
'mck010': {
- 'name': 'Peoples Rural Telephone Cooperative'
+ 'name': 'Peoples Rural Telephone Cooperative',
},
'pul010': {
- 'name': 'PES Energize'
+ 'name': 'PES Energize',
},
'phi010': {
- 'name': 'Philippi Communications System'
+ 'name': 'Philippi Communications System',
},
'phonoscope': {
- 'name': 'Phonoscope Cable'
+ 'name': 'Phonoscope Cable',
},
'pin070': {
- 'name': 'Pine Belt Communications, Inc.'
+ 'name': 'Pine Belt Communications, Inc.',
},
'weh010-pine': {
- 'name': 'Pine Bluff Cable TV'
+ 'name': 'Pine Bluff Cable TV',
},
'pin060': {
- 'name': 'Pineland Telephone Cooperative'
+ 'name': 'Pineland Telephone Cooperative',
},
'cam010': {
- 'name': 'Pinpoint Communications'
+ 'name': 'Pinpoint Communications',
},
'pio060': {
- 'name': 'Pioneer Broadband'
+ 'name': 'Pioneer Broadband',
},
'pioncomm': {
- 'name': 'Pioneer Communications'
+ 'name': 'Pioneer Communications',
},
'pioneer': {
- 'name': 'Pioneer DTV'
+ 'name': 'Pioneer DTV',
},
'pla020': {
- 'name': 'Plant TiftNet, Inc.'
+ 'name': 'Plant TiftNet, Inc.',
},
'par010': {
- 'name': 'PLWC'
+ 'name': 'PLWC',
},
'pro035': {
- 'name': 'PMT'
+ 'name': 'PMT',
},
'vik011': {
- 'name': 'Polar Cablevision'
+ 'name': 'Polar Cablevision',
},
'pottawatomie': {
- 'name': 'Pottawatomie Telephone Co.'
+ 'name': 'Pottawatomie Telephone Co.',
},
'premiercomm': {
- 'name': 'Premier Communications'
+ 'name': 'Premier Communications',
},
'psc010': {
- 'name': 'PSC'
+ 'name': 'PSC',
},
'pan020': {
- 'name': 'PTCI'
+ 'name': 'PTCI',
},
'qco010': {
- 'name': 'QCOL'
+ 'name': 'QCOL',
},
'qua010': {
- 'name': 'Quality Cablevision'
+ 'name': 'Quality Cablevision',
},
'rad010': {
- 'name': 'Radcliffe Telephone Company'
+ 'name': 'Radcliffe Telephone Company',
},
'car040': {
- 'name': 'Rainbow Communications'
+ 'name': 'Rainbow Communications',
},
'rai030': {
- 'name': 'Rainier Connect'
+ 'name': 'Rainier Connect',
},
'ral010': {
- 'name': 'Ralls Technologies'
+ 'name': 'Ralls Technologies',
},
'rct010': {
- 'name': 'RC Technologies'
+ 'name': 'RC Technologies',
},
'red040': {
- 'name': 'Red River Communications'
+ 'name': 'Red River Communications',
},
'ree010': {
- 'name': 'Reedsburg Utility Commission'
+ 'name': 'Reedsburg Utility Commission',
},
'mol010': {
- 'name': 'Reliance Connects- Oregon'
+ 'name': 'Reliance Connects- Oregon',
},
'res020': {
- 'name': 'Reserve Telecommunications'
+ 'name': 'Reserve Telecommunications',
},
'weh010-resort': {
- 'name': 'Resort TV Cable'
+ 'name': 'Resort TV Cable',
},
'rld010': {
- 'name': 'Richland Grant Telephone Cooperative, Inc.'
+ 'name': 'Richland Grant Telephone Cooperative, Inc.',
},
'riv030': {
- 'name': 'River Valley Telecommunications Coop'
+ 'name': 'River Valley Telecommunications Coop',
},
'rockportcable': {
- 'name': 'Rock Port Cablevision'
+ 'name': 'Rock Port Cablevision',
},
'rsf010': {
- 'name': 'RS Fiber'
+ 'name': 'RS Fiber',
},
'rtc': {
- 'name': 'RTC Communication Corp'
+ 'name': 'RTC Communication Corp',
},
'res040': {
- 'name': 'RTC-Reservation Telephone Coop.'
+ 'name': 'RTC-Reservation Telephone Coop.',
},
'rte010': {
- 'name': 'RTEC Communications'
+ 'name': 'RTEC Communications',
},
'stc010': {
- 'name': 'S&T'
+ 'name': 'S&T',
},
'san020': {
- 'name': 'San Bruno Cable TV'
+ 'name': 'San Bruno Cable TV',
},
'san040-01': {
- 'name': 'Santel'
+ 'name': 'Santel',
},
'sav010': {
- 'name': 'SCI Broadband-Savage Communications Inc.'
+ 'name': 'SCI Broadband-Savage Communications Inc.',
},
'sco050': {
- 'name': 'Scottsboro Electric Power Board'
+ 'name': 'Scottsboro Electric Power Board',
},
'scr010': {
- 'name': 'Scranton Telephone Company'
+ 'name': 'Scranton Telephone Company',
},
'selco': {
- 'name': 'SELCO'
+ 'name': 'SELCO',
},
'she010': {
- 'name': 'Shentel'
+ 'name': 'Shentel',
},
'she030': {
- 'name': 'Sherwood Mutual Telephone Association, Inc.'
+ 'name': 'Sherwood Mutual Telephone Association, Inc.',
},
'ind060-ssc': {
- 'name': 'Silver Star Communications'
+ 'name': 'Silver Star Communications',
},
'sjoberg': {
- 'name': 'Sjoberg\'s Inc.'
+ 'name': 'Sjoberg\'s Inc.',
},
'sou025': {
- 'name': 'SKT'
+ 'name': 'SKT',
},
'sky050': {
- 'name': 'SkyBest TV'
+ 'name': 'SkyBest TV',
},
'nttcsmi010': {
- 'name': 'Smithville Communications'
+ 'name': 'Smithville Communications',
},
'woo010': {
- 'name': 'Solarus'
+ 'name': 'Solarus',
},
'sou075': {
- 'name': 'South Central Rural Telephone Cooperative'
+ 'name': 'South Central Rural Telephone Cooperative',
},
'sou065': {
- 'name': 'South Holt Cablevision, Inc.'
+ 'name': 'South Holt Cablevision, Inc.',
},
'sou035': {
- 'name': 'South Slope Cooperative Communications'
+ 'name': 'South Slope Cooperative Communications',
},
'spa020': {
- 'name': 'Spanish Fork Community Network'
+ 'name': 'Spanish Fork Community Network',
},
'spe010': {
- 'name': 'Spencer Municipal Utilities'
+ 'name': 'Spencer Municipal Utilities',
},
'spi005': {
- 'name': 'Spillway Communications, Inc.'
+ 'name': 'Spillway Communications, Inc.',
},
'srt010': {
- 'name': 'SRT'
+ 'name': 'SRT',
},
'cccsmc010': {
- 'name': 'St. Maarten Cable TV'
+ 'name': 'St. Maarten Cable TV',
},
'sta025': {
- 'name': 'Star Communications'
+ 'name': 'Star Communications',
},
'sco020': {
- 'name': 'STE'
+ 'name': 'STE',
},
'uin010': {
- 'name': 'STRATA Networks'
+ 'name': 'STRATA Networks',
},
'sum010': {
- 'name': 'Sumner Cable TV'
+ 'name': 'Sumner Cable TV',
},
'pie010': {
- 'name': 'Surry TV/PCSI TV'
+ 'name': 'Surry TV/PCSI TV',
},
'swa010': {
- 'name': 'Swayzee Communications'
+ 'name': 'Swayzee Communications',
},
'sweetwater': {
- 'name': 'Sweetwater Cable Television Co'
+ 'name': 'Sweetwater Cable Television Co',
},
'weh010-talequah': {
- 'name': 'Tahlequah Cable TV'
+ 'name': 'Tahlequah Cable TV',
},
'tct': {
- 'name': 'TCT'
+ 'name': 'TCT',
},
'tel050': {
- 'name': 'Tele-Media Company'
+ 'name': 'Tele-Media Company',
},
'com050': {
- 'name': 'The Community Agency'
+ 'name': 'The Community Agency',
},
'thr020': {
- 'name': 'Three River'
+ 'name': 'Three River',
},
'cab140': {
- 'name': 'Town & Country Technologies'
+ 'name': 'Town & Country Technologies',
},
'tra010': {
- 'name': 'Trans-Video'
+ 'name': 'Trans-Video',
},
'tre010': {
- 'name': 'Trenton TV Cable Company'
+ 'name': 'Trenton TV Cable Company',
},
'tcc': {
- 'name': 'Tri County Communications Cooperative'
+ 'name': 'Tri County Communications Cooperative',
},
'tri025': {
- 'name': 'TriCounty Telecom'
+ 'name': 'TriCounty Telecom',
},
'tri110': {
- 'name': 'TrioTel Communications, Inc.'
+ 'name': 'TrioTel Communications, Inc.',
},
'tro010': {
- 'name': 'Troy Cablevision, Inc.'
+ 'name': 'Troy Cablevision, Inc.',
},
'tsc': {
- 'name': 'TSC'
+ 'name': 'TSC',
},
'cit220': {
- 'name': 'Tullahoma Utilities Board'
+ 'name': 'Tullahoma Utilities Board',
},
'tvc030': {
- 'name': 'TV Cable of Rensselaer'
+ 'name': 'TV Cable of Rensselaer',
},
'tvc015': {
- 'name': 'TVC Cable'
+ 'name': 'TVC Cable',
},
'cab180': {
- 'name': 'TVision'
+ 'name': 'TVision',
},
'twi040': {
- 'name': 'Twin Lakes'
+ 'name': 'Twin Lakes',
},
'tvtinc': {
- 'name': 'Twin Valley'
+ 'name': 'Twin Valley',
},
'uis010': {
- 'name': 'Union Telephone Company'
+ 'name': 'Union Telephone Company',
},
'uni110': {
- 'name': 'United Communications - TN'
+ 'name': 'United Communications - TN',
},
'uni120': {
- 'name': 'United Services'
+ 'name': 'United Services',
},
'uss020': {
- 'name': 'US Sonet'
+ 'name': 'US Sonet',
},
'cab060': {
- 'name': 'USA Communications'
+ 'name': 'USA Communications',
},
'she005': {
- 'name': 'USA Communications/Shellsburg, IA'
+ 'name': 'USA Communications/Shellsburg, IA',
},
'val040': {
- 'name': 'Valley TeleCom Group'
+ 'name': 'Valley TeleCom Group',
},
'val025': {
- 'name': 'Valley Telecommunications'
+ 'name': 'Valley Telecommunications',
},
'val030': {
- 'name': 'Valparaiso Broadband'
+ 'name': 'Valparaiso Broadband',
},
'cla050': {
- 'name': 'Vast Broadband'
+ 'name': 'Vast Broadband',
},
'sul015': {
- 'name': 'Venture Communications Cooperative, Inc.'
+ 'name': 'Venture Communications Cooperative, Inc.',
},
'ver025': {
- 'name': 'Vernon Communications Co-op'
+ 'name': 'Vernon Communications Co-op',
},
'weh010-vicksburg': {
- 'name': 'Vicksburg Video'
+ 'name': 'Vicksburg Video',
},
'vis070': {
- 'name': 'Vision Communications'
+ 'name': 'Vision Communications',
},
'volcanotel': {
- 'name': 'Volcano Vision, Inc.'
+ 'name': 'Volcano Vision, Inc.',
},
'vol040-02': {
- 'name': 'VolFirst / BLTV'
+ 'name': 'VolFirst / BLTV',
},
'ver070': {
- 'name': 'VTel'
+ 'name': 'VTel',
},
'nttcvtx010': {
- 'name': 'VTX1'
+ 'name': 'VTX1',
},
'bci010-02': {
- 'name': 'Vyve Broadband'
+ 'name': 'Vyve Broadband',
},
'wab020': {
- 'name': 'Wabash Mutual Telephone'
+ 'name': 'Wabash Mutual Telephone',
},
'waitsfield': {
- 'name': 'Waitsfield Cable'
+ 'name': 'Waitsfield Cable',
},
'wal010': {
- 'name': 'Walnut Communications'
+ 'name': 'Walnut Communications',
},
'wavebroadband': {
- 'name': 'Wave'
+ 'name': 'Wave',
},
'wav030': {
- 'name': 'Waverly Communications Utility'
+ 'name': 'Waverly Communications Utility',
},
'wbi010': {
- 'name': 'WBI'
+ 'name': 'WBI',
},
'web020': {
- 'name': 'Webster-Calhoun Cooperative Telephone Association'
+ 'name': 'Webster-Calhoun Cooperative Telephone Association',
},
'wes005': {
- 'name': 'West Alabama TV Cable'
+ 'name': 'West Alabama TV Cable',
},
'carolinata': {
- 'name': 'West Carolina Communications'
+ 'name': 'West Carolina Communications',
},
'wct010': {
- 'name': 'West Central Telephone Association'
+ 'name': 'West Central Telephone Association',
},
'wes110': {
- 'name': 'West River Cooperative Telephone Company'
+ 'name': 'West River Cooperative Telephone Company',
},
'ani030': {
- 'name': 'WesTel Systems'
+ 'name': 'WesTel Systems',
},
'westianet': {
- 'name': 'Western Iowa Networks'
+ 'name': 'Western Iowa Networks',
},
'nttcwhi010': {
- 'name': 'Whidbey Telecom'
+ 'name': 'Whidbey Telecom',
},
'weh010-white': {
- 'name': 'White County Cable TV'
+ 'name': 'White County Cable TV',
},
'wes130': {
- 'name': 'Wiatel'
+ 'name': 'Wiatel',
},
'wik010': {
- 'name': 'Wiktel'
+ 'name': 'Wiktel',
},
'wil070': {
- 'name': 'Wilkes Communications, Inc./RiverStreet Networks'
+ 'name': 'Wilkes Communications, Inc./RiverStreet Networks',
},
'wil015': {
- 'name': 'Wilson Communications'
+ 'name': 'Wilson Communications',
},
'win010': {
- 'name': 'Windomnet/SMBS'
+ 'name': 'Windomnet/SMBS',
},
'win090': {
- 'name': 'Windstream Cable TV'
+ 'name': 'Windstream Cable TV',
},
'wcta': {
- 'name': 'Winnebago Cooperative Telecom Association'
+ 'name': 'Winnebago Cooperative Telecom Association',
},
'wtc010': {
- 'name': 'WTC'
+ 'name': 'WTC',
},
'wil040': {
- 'name': 'WTC Communications, Inc.'
+ 'name': 'WTC Communications, Inc.',
},
'wya010': {
- 'name': 'Wyandotte Cable'
+ 'name': 'Wyandotte Cable',
},
'hin020-02': {
- 'name': 'X-Stream Services'
+ 'name': 'X-Stream Services',
},
'xit010': {
- 'name': 'XIT Communications'
+ 'name': 'XIT Communications',
},
'yel010': {
- 'name': 'Yelcot Communications'
+ 'name': 'Yelcot Communications',
},
'mid180-01': {
- 'name': 'yondoo'
+ 'name': 'yondoo',
},
'cou060': {
- 'name': 'Zito Media'
+ 'name': 'Zito Media',
},
'slingtv': {
'name': 'Sling TV',
@@ -1363,7 +1363,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
headers = self.geo_verification_headers()
headers.update(kwargs.get('headers', {}))
kwargs['headers'] = headers
- return super(AdobePassIE, self)._download_webpage_handle(
+ return super()._download_webpage_handle(
*args, **kwargs)
@staticmethod
@@ -1384,7 +1384,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
def _extract_mvpd_auth(self, url, video_id, requestor_id, resource):
def xml_text(xml_str, tag):
return self._search_regex(
- '<%s>(.+?)</%s>' % (tag, tag), xml_str, tag)
+ f'<{tag}>(.+?)</{tag}>', xml_str, tag)
def is_expired(token, date_ele):
token_expires = unified_timestamp(re.sub(r'[_ ]GMT', '', xml_text(token, date_ele)))
@@ -1394,7 +1394,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
form_page, urlh = form_page_res
post_url = self._html_search_regex(r'<form[^>]+action=(["\'])(?P<url>.+?)\1', form_page, 'post url', group='url')
if not re.match(r'https?://', post_url):
- post_url = compat_urlparse.urljoin(urlh.url, post_url)
+ post_url = urllib.parse.urljoin(urlh.url, post_url)
form_data = self._hidden_inputs(form_page)
form_data.update(data)
return self._download_webpage_handle(
@@ -1414,13 +1414,13 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
redirect_url = self._search_regex(
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
- r'(?:[a-z-]+="[^"]+"\s+)*?content="%s' % REDIRECT_REGEX,
+ rf'(?:[a-z-]+="[^"]+"\s+)*?content="{REDIRECT_REGEX}',
html, 'meta refresh redirect',
default=NO_DEFAULT if fatal else None, fatal=fatal)
if not redirect_url:
return None
if url:
- redirect_url = compat_urlparse.urljoin(url, unescapeHTML(redirect_url))
+ redirect_url = urllib.parse.urljoin(url, unescapeHTML(redirect_url))
return redirect_url
mvpd_headers = {
@@ -1506,12 +1506,12 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
'ident': username,
'device': 'web',
'send_confirm_link': False,
- 'send_token': True
+ 'send_token': True,
}))
philo_code = getpass.getpass('Type auth code you have received [Return]: ')
self._download_webpage(
'https://idp.philo.com/auth/update/login_code', video_id, 'Submitting token', data=urlencode_postdata({
- 'token': philo_code
+ 'token': philo_code,
}))
mvpd_confirm_page_res = self._download_webpage_handle('https://idp.philo.com/idp/submit', video_id, 'Confirming Philo Login')
post_form(mvpd_confirm_page_res, 'Confirming Login')
@@ -1569,9 +1569,9 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
saml_response_json['targetValue'], video_id,
'Confirming Login', data=urlencode_postdata({
'SAMLResponse': saml_response_json['SAMLResponse'],
- 'RelayState': saml_response_json['RelayState']
+ 'RelayState': saml_response_json['RelayState'],
}), headers={
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
})
elif mso_id in ('Spectrum', 'Charter_Direct'):
# Spectrum's login for is dynamically loaded via JS so we need to hardcode the flow
@@ -1606,7 +1606,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
'SAMLResponse': saml_response_json['SAMLResponse'],
'RelayState': relay_state,
}), headers={
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
})
elif mso_id == 'slingtv':
# SlingTV has a meta-refresh based authentication, but also
@@ -1625,7 +1625,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
provider_association_redirect, urlh = post_form(
provider_login_page_res, 'Logging in', {
mso_info['username_field']: username,
- mso_info['password_field']: password
+ mso_info['password_field']: password,
})
provider_refresh_redirect_url = extract_redirect_url(
@@ -1676,7 +1676,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
provider_association_redirect, urlh = post_form(
provider_login_page_res, 'Logging in', {
mso_info['username_field']: username,
- mso_info['password_field']: password
+ mso_info['password_field']: password,
})
provider_refresh_redirect_url = extract_redirect_url(
@@ -1708,7 +1708,7 @@ class AdobePassIE(InfoExtractor): # XXX: Conventionally, base classes should en
provider_redirect_page_res, self._DOWNLOADING_LOGIN_PAGE)
form_data = {
mso_info.get('username_field', 'username'): username,
- mso_info.get('password_field', 'password'): password
+ mso_info.get('password_field', 'password'): password,
}
if mso_id in ('Cablevision', 'AlticeOne'):
form_data['_eventId_proceed'] = ''
diff --git a/yt_dlp/extractor/adobetv.py b/yt_dlp/extractor/adobetv.py
index 08e9e5182..4608e5c13 100644
--- a/yt_dlp/extractor/adobetv.py
+++ b/yt_dlp/extractor/adobetv.py
@@ -2,7 +2,6 @@ import functools
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ISO639Utils,
OnDemandPagedList,
@@ -36,7 +35,7 @@ class AdobeTVBaseIE(InfoExtractor):
return subtitles
def _parse_video_data(self, video_data):
- video_id = compat_str(video_data['id'])
+ video_id = str(video_data['id'])
title = video_data['title']
s3_extracted = False
@@ -151,7 +150,7 @@ class AdobeTVPlaylistBaseIE(AdobeTVBaseIE):
page += 1
query['page'] = page
for element_data in self._call_api(
- self._RESOURCE, display_id, query, 'Download Page %d' % page):
+ self._RESOURCE, display_id, query, f'Download Page {page}'):
yield self._process_data(element_data)
def _extract_playlist_entries(self, display_id, query):
diff --git a/yt_dlp/extractor/adultswim.py b/yt_dlp/extractor/adultswim.py
index d807c4181..2c83701e7 100644
--- a/yt_dlp/extractor/adultswim.py
+++ b/yt_dlp/extractor/adultswim.py
@@ -91,7 +91,7 @@ class AdultSwimIE(TurnerBaseIE):
getShowBySlug(slug:"%s") {
%%s
}
-}''' % show_path
+}''' % show_path # noqa: UP031
if episode_path:
query = query % '''title
getVideoBySlug(slug:"%s") {
@@ -128,7 +128,7 @@ class AdultSwimIE(TurnerBaseIE):
episode_title = title = video_data['title']
series = show_data.get('title')
if series:
- title = '%s - %s' % (series, title)
+ title = f'{series} - {title}'
info = {
'id': video_id,
'title': title,
@@ -191,7 +191,7 @@ class AdultSwimIE(TurnerBaseIE):
if not slug:
continue
entries.append(self.url_result(
- 'http://adultswim.com/videos/%s/%s' % (show_path, slug),
+ f'http://adultswim.com/videos/{show_path}/{slug}',
'AdultSwim', video.get('_id')))
return self.playlist_result(
entries, show_path, show_data.get('title'),
diff --git a/yt_dlp/extractor/aenetworks.py b/yt_dlp/extractor/aenetworks.py
index ab4b6c0eb..8e257865f 100644
--- a/yt_dlp/extractor/aenetworks.py
+++ b/yt_dlp/extractor/aenetworks.py
@@ -73,8 +73,8 @@ class AENetworksBaseIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
def _extract_aetn_info(self, domain, filter_key, filter_value, url):
requestor_id, brand = self._DOMAIN_MAP[domain]
result = self._download_json(
- 'https://feeds.video.aetnd.com/api/v2/%s/videos' % brand,
- filter_value, query={'filter[%s]' % filter_key: filter_value})
+ f'https://feeds.video.aetnd.com/api/v2/{brand}/videos',
+ filter_value, query={f'filter[{filter_key}]': filter_value})
result = traverse_obj(
result, ('results',
lambda k, v: k == 0 and v[filter_key] == filter_value),
@@ -142,7 +142,7 @@ class AENetworksIE(AENetworksBaseIE):
'skip_download': True,
},
'add_ie': ['ThePlatform'],
- 'skip': 'Geo-restricted - This content is not available in your location.'
+ 'skip': 'Geo-restricted - This content is not available in your location.',
}, {
'url': 'http://www.aetv.com/shows/duck-dynasty/season-9/episode-1',
'info_dict': {
@@ -171,28 +171,28 @@ class AENetworksIE(AENetworksBaseIE):
'skip': 'This video is only available for users of participating TV providers.',
}, {
'url': 'http://www.fyi.tv/shows/tiny-house-nation/season-1/episode-8',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://www.mylifetime.com/shows/project-runway-junior/season-1/episode-6',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://www.mylifetime.com/movies/center-stage-on-pointe/full-movie',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://watch.lifetimemovieclub.com/movies/10-year-reunion/full-movie',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://www.history.com/specials/sniper-into-the-kill-zone/full-special',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.aetv.com/specials/hunting-jonbenets-killer-the-untold-story/preview-hunting-jonbenets-killer-the-untold-story',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://www.history.com/videos/history-of-valentines-day',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://play.aetv.com/shows/duck-dynasty/videos/best-of-duck-dynasty-getting-quack-in-shape',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -209,14 +209,14 @@ class AENetworksListBaseIE(AENetworksBaseIE):
%s(slug: "%s") {
%s
}
-}''' % (resource, slug, fields),
+}''' % (resource, slug, fields), # noqa: UP031
}))['data'][resource]
def _real_extract(self, url):
domain, slug = self._match_valid_url(url).groups()
_, brand = self._DOMAIN_MAP[domain]
playlist = self._call_api(self._RESOURCE, slug, brand, self._FIELDS)
- base_url = 'http://watch.%s' % domain
+ base_url = f'http://watch.{domain}'
entries = []
for item in (playlist.get(self._ITEMS_KEY) or []):
@@ -248,10 +248,10 @@ class AENetworksCollectionIE(AENetworksListBaseIE):
'playlist_mincount': 12,
}, {
'url': 'https://watch.historyvault.com/shows/america-the-story-of-us-2/season-1/list/america-the-story-of-us',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.historyvault.com/collections/mysteryquest',
- 'only_matching': True
+ 'only_matching': True,
}]
_RESOURCE = 'list'
_ITEMS_KEY = 'items'
@@ -309,7 +309,7 @@ class HistoryTopicIE(AENetworksBaseIE):
'info_dict': {
'id': '40700995724',
'ext': 'mp4',
- 'title': "History of Valentine’s Day",
+ 'title': 'History of Valentine’s Day',
'description': 'md5:7b57ea4829b391995b405fa60bd7b5f7',
'timestamp': 1375819729,
'upload_date': '20130806',
@@ -364,6 +364,6 @@ class BiographyIE(AENetworksBaseIE):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id)
player_url = self._search_regex(
- r'<phoenix-iframe[^>]+src="(%s)' % HistoryPlayerIE._VALID_URL,
+ rf'<phoenix-iframe[^>]+src="({HistoryPlayerIE._VALID_URL})',
webpage, 'player URL')
return self.url_result(player_url, HistoryPlayerIE.ie_key())
diff --git a/yt_dlp/extractor/aeonco.py b/yt_dlp/extractor/aeonco.py
index 390eae32b..22d0266ba 100644
--- a/yt_dlp/extractor/aeonco.py
+++ b/yt_dlp/extractor/aeonco.py
@@ -16,8 +16,8 @@ class AeonCoIE(InfoExtractor):
'uploader': 'Semiconductor',
'uploader_id': 'semiconductor',
'uploader_url': 'https://vimeo.com/semiconductor',
- 'duration': 348
- }
+ 'duration': 348,
+ },
}, {
'url': 'https://aeon.co/videos/dazzling-timelapse-shows-how-microbes-spoil-our-food-and-sometimes-enrich-it',
'md5': '03582d795382e49f2fd0b427b55de409',
@@ -29,8 +29,8 @@ class AeonCoIE(InfoExtractor):
'uploader': 'Aeon Video',
'uploader_id': 'aeonvideo',
'uploader_url': 'https://vimeo.com/aeonvideo',
- 'duration': 1344
- }
+ 'duration': 1344,
+ },
}, {
'url': 'https://aeon.co/videos/chew-over-the-prisoners-dilemma-and-see-if-you-can-find-the-rational-path-out',
'md5': '1cfda0bf3ae24df17d00f2c0cb6cc21b',
diff --git a/yt_dlp/extractor/afreecatv.py b/yt_dlp/extractor/afreecatv.py
index 3e5738f6a..bcfb02cb9 100644
--- a/yt_dlp/extractor/afreecatv.py
+++ b/yt_dlp/extractor/afreecatv.py
@@ -55,7 +55,7 @@ class AfreecaTVBaseIE(InfoExtractor):
if result != 1:
error = _ERRORS.get(result, 'You have failed to log in.')
raise ExtractorError(
- 'Unable to login: %s said: %s' % (self.IE_NAME, error),
+ f'Unable to login: {self.IE_NAME} said: {error}',
expected=True)
@@ -227,7 +227,7 @@ class AfreecaTVIE(AfreecaTVBaseIE):
**traverse_obj(file_element, {
'duration': ('duration', {functools.partial(int_or_none, scale=1000)}),
'timestamp': ('file_start', {unified_timestamp}),
- })
+ }),
})
if traverse_obj(data, ('adult_status', {str})) == 'notLogin':
diff --git a/yt_dlp/extractor/agora.py b/yt_dlp/extractor/agora.py
index abb2d3ff2..983558425 100644
--- a/yt_dlp/extractor/agora.py
+++ b/yt_dlp/extractor/agora.py
@@ -168,7 +168,7 @@ class TokFMPodcastIE(InfoExtractor):
for ext in ('aac', 'mp3'):
url_data = self._download_json(
f'https://api.podcast.radioagora.pl/api4/getSongUrl?podcast_id={media_id}&device_id={uuid.uuid4()}&ppre=false&audio={ext}',
- media_id, 'Downloading podcast %s URL' % ext)
+ media_id, f'Downloading podcast {ext} URL')
# prevents inserting the mp3 (default) multiple times
if 'link_ssl' in url_data and f'.{ext}' in url_data['link_ssl']:
formats.append({
@@ -206,8 +206,8 @@ class TokFMAuditionIE(InfoExtractor):
}
@staticmethod
- def _create_url(id):
- return f'https://audycje.tokfm.pl/audycja/{id}'
+ def _create_url(video_id):
+ return f'https://audycje.tokfm.pl/audycja/{video_id}'
def _real_extract(self, url):
audition_id = self._match_id(url)
diff --git a/yt_dlp/extractor/airtv.py b/yt_dlp/extractor/airtv.py
index 6cc63cd7f..cee660dfc 100644
--- a/yt_dlp/extractor/airtv.py
+++ b/yt_dlp/extractor/airtv.py
@@ -26,7 +26,7 @@ class AirTVIE(InfoExtractor):
'view_count': int,
'thumbnail': 'https://cdn-sp-gcs.air.tv/videos/W/8/W87jcWleSn2hXZN47zJZsQ/b13fc56464f47d9d62a36d110b9b5a72-4096x2160_9.jpg',
'timestamp': 1664792603,
- }
+ },
}, {
# with youtube_id
'url': 'https://www.air.tv/watch?v=sv57EC8tRXG6h8dNXFUU1Q',
@@ -54,7 +54,7 @@ class AirTVIE(InfoExtractor):
'channel': 'Newsflare',
'duration': 37,
'upload_date': '20180511',
- }
+ },
}]
def _get_formats_and_subtitle(self, json_data, video_id):
diff --git a/yt_dlp/extractor/aitube.py b/yt_dlp/extractor/aitube.py
index 89a64503f..5179b72e9 100644
--- a/yt_dlp/extractor/aitube.py
+++ b/yt_dlp/extractor/aitube.py
@@ -22,7 +22,7 @@ class AitubeKZVideoIE(InfoExtractor):
'timestamp': 1667370519,
'title': 'Ангел хранитель 1 серия',
'channel_follower_count': int,
- }
+ },
}, {
# embed url
'url': 'https://aitube.kz/embed/?id=9291d29b-c038-49a1-ad42-3da2051d353c',
diff --git a/yt_dlp/extractor/aliexpress.py b/yt_dlp/extractor/aliexpress.py
index 2e83f2eb6..e8f8618fa 100644
--- a/yt_dlp/extractor/aliexpress.py
+++ b/yt_dlp/extractor/aliexpress.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
float_or_none,
try_get,
@@ -44,7 +43,7 @@ class AliExpressLiveIE(InfoExtractor):
'title': title,
'thumbnail': data.get('coverUrl'),
'uploader': try_get(
- data, lambda x: x['followBar']['name'], compat_str),
+ data, lambda x: x['followBar']['name'], str),
'timestamp': float_or_none(data.get('startTimeLong'), scale=1000),
'formats': formats,
}
diff --git a/yt_dlp/extractor/aljazeera.py b/yt_dlp/extractor/aljazeera.py
index 124bab0d9..9715b497e 100644
--- a/yt_dlp/extractor/aljazeera.py
+++ b/yt_dlp/extractor/aljazeera.py
@@ -18,7 +18,7 @@ class AlJazeeraIE(InfoExtractor):
'timestamp': 1636219149,
'description': 'U sarajevskim naseljima Rajlovac i Reljevo stambeni objekti, ali i industrijska postrojenja i dalje su pod vodom.',
'upload_date': '20211106',
- }
+ },
}, {
'url': 'https://balkans.aljazeera.net/videos/2021/11/6/djokovic-usao-u-finale-mastersa-u-parizu',
'info_dict': {
@@ -33,7 +33,7 @@ class AlJazeeraIE(InfoExtractor):
BRIGHTCOVE_URL_RE = r'https?://players.brightcove.net/(?P<account>\d+)/(?P<player_id>[a-zA-Z0-9]+)_(?P<embed>[^/]+)/index.html\?videoId=(?P<id>\d+)'
def _real_extract(self, url):
- base, post_type, id = self._match_valid_url(url).groups()
+ base, post_type, display_id = self._match_valid_url(url).groups()
wp = {
'balkans.aljazeera.net': 'ajb',
'chinese.aljazeera.net': 'chinese',
@@ -47,11 +47,11 @@ class AlJazeeraIE(InfoExtractor):
'news': 'news',
}[post_type.split('/')[0]]
video = self._download_json(
- f'https://{base}/graphql', id, query={
+ f'https://{base}/graphql', display_id, query={
'wp-site': wp,
'operationName': 'ArchipelagoSingleArticleQuery',
'variables': json.dumps({
- 'name': id,
+ 'name': display_id,
'postType': post_type,
}),
}, headers={
@@ -64,7 +64,7 @@ class AlJazeeraIE(InfoExtractor):
embed = 'default'
if video_id is None:
- webpage = self._download_webpage(url, id)
+ webpage = self._download_webpage(url, display_id)
account, player_id, embed, video_id = self._search_regex(self.BRIGHTCOVE_URL_RE, webpage, 'video id',
group=(1, 2, 3, 4), default=(None, None, None, None))
@@ -73,11 +73,11 @@ class AlJazeeraIE(InfoExtractor):
return {
'_type': 'url_transparent',
'url': url,
- 'ie_key': 'Generic'
+ 'ie_key': 'Generic',
}
return {
'_type': 'url_transparent',
'url': f'https://players.brightcove.net/{account}/{player_id}_{embed}/index.html?videoId={video_id}',
- 'ie_key': 'BrightcoveNew'
+ 'ie_key': 'BrightcoveNew',
}
diff --git a/yt_dlp/extractor/allocine.py b/yt_dlp/extractor/allocine.py
index 2d342cf03..e0859d451 100644
--- a/yt_dlp/extractor/allocine.py
+++ b/yt_dlp/extractor/allocine.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
qualities,
@@ -95,11 +94,11 @@ class AllocineIE(InfoExtractor):
duration = int_or_none(video.get('duration'))
view_count = int_or_none(video.get('view_count'))
timestamp = unified_timestamp(try_get(
- video, lambda x: x['added_at']['date'], compat_str))
+ video, lambda x: x['added_at']['date'], str))
else:
video_id = display_id
media_data = self._download_json(
- 'http://www.allocine.fr/ws/AcVisiondataV5.ashx?media=%s' % video_id, display_id)
+ f'http://www.allocine.fr/ws/AcVisiondataV5.ashx?media={video_id}', display_id)
title = remove_end(strip_or_none(self._html_extract_title(webpage), ' - AlloCiné'))
for key, value in media_data['video'].items():
if not key.endswith('Path'):
diff --git a/yt_dlp/extractor/allstar.py b/yt_dlp/extractor/allstar.py
index 49df4bf3a..5ea1c30e3 100644
--- a/yt_dlp/extractor/allstar.py
+++ b/yt_dlp/extractor/allstar.py
@@ -33,27 +33,27 @@ _QUERIES = {
video: getClip(clipIdentifier: $id) {
%s %s
}
- }''' % (_FIELDS, _EXTRA_FIELDS),
+ }''' % (_FIELDS, _EXTRA_FIELDS), # noqa: UP031
'montage': '''query ($id: String!) {
video: getMontage(clipIdentifier: $id) {
%s
}
- }''' % _FIELDS,
+ }''' % _FIELDS, # noqa: UP031
'Clips': '''query ($page: Int!, $user: String!, $game: Int) {
videos: clips(search: createdDate, page: $page, user: $user, mobile: false, game: $game) {
data { %s %s }
}
- }''' % (_FIELDS, _EXTRA_FIELDS),
+ }''' % (_FIELDS, _EXTRA_FIELDS), # noqa: UP031
'Montages': '''query ($page: Int!, $user: String!) {
videos: montages(search: createdDate, page: $page, user: $user) {
data { %s }
}
- }''' % _FIELDS,
+ }''' % _FIELDS, # noqa: UP031
'Mobile Clips': '''query ($page: Int!, $user: String!) {
videos: clips(search: createdDate, page: $page, user: $user, mobile: true) {
data { %s %s }
}
- }''' % (_FIELDS, _EXTRA_FIELDS),
+ }''' % (_FIELDS, _EXTRA_FIELDS), # noqa: UP031
}
@@ -121,7 +121,7 @@ class AllstarIE(AllstarBaseIE):
'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
'upload_date': '20230425',
'view_count': int,
- }
+ },
}, {
'url': 'https://allstar.gg/clip?clip=8LJLY4JKB',
'info_dict': {
@@ -139,7 +139,7 @@ class AllstarIE(AllstarBaseIE):
'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
'upload_date': '20230702',
'view_count': int,
- }
+ },
}, {
'url': 'https://allstar.gg/montage?montage=643e64089da7e9363e1fa66c',
'info_dict': {
@@ -155,7 +155,7 @@ class AllstarIE(AllstarBaseIE):
'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
'upload_date': '20230418',
'view_count': int,
- }
+ },
}, {
'url': 'https://allstar.gg/montage?montage=RILJMH6QOS',
'info_dict': {
@@ -171,7 +171,7 @@ class AllstarIE(AllstarBaseIE):
'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
'upload_date': '20230703',
'view_count': int,
- }
+ },
}]
def _real_extract(self, url):
@@ -191,28 +191,28 @@ class AllstarProfileIE(AllstarBaseIE):
'id': '62b8bdfc9021052f7905882d-clips',
'title': 'cherokee - Clips',
},
- 'playlist_mincount': 15
+ 'playlist_mincount': 15,
}, {
'url': 'https://allstar.gg/u/cherokee?game=730&view=Clips',
'info_dict': {
'id': '62b8bdfc9021052f7905882d-clips-730',
'title': 'cherokee - Clips - 730',
},
- 'playlist_mincount': 15
+ 'playlist_mincount': 15,
}, {
'url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d?view=Montages',
'info_dict': {
'id': '62b8bdfc9021052f7905882d-montages',
'title': 'cherokee - Montages',
},
- 'playlist_mincount': 4
+ 'playlist_mincount': 4,
}, {
'url': 'https://allstar.gg/profile?user=cherokee&view=Mobile Clips',
'info_dict': {
'id': '62b8bdfc9021052f7905882d-mobile',
'title': 'cherokee - Mobile Clips',
},
- 'playlist_mincount': 1
+ 'playlist_mincount': 1,
}]
_PAGE_SIZE = 10
diff --git a/yt_dlp/extractor/alphaporno.py b/yt_dlp/extractor/alphaporno.py
index f927965de..7b74d5524 100644
--- a/yt_dlp/extractor/alphaporno.py
+++ b/yt_dlp/extractor/alphaporno.py
@@ -25,7 +25,7 @@ class AlphaPornoIE(InfoExtractor):
'tbr': 1145,
'categories': list,
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/alsace20tv.py b/yt_dlp/extractor/alsace20tv.py
index ea3332e3d..c315e4f21 100644
--- a/yt_dlp/extractor/alsace20tv.py
+++ b/yt_dlp/extractor/alsace20tv.py
@@ -12,7 +12,7 @@ from ..utils import (
class Alsace20TVBaseIE(InfoExtractor):
def _extract_video(self, video_id, url=None):
info = self._download_json(
- 'https://www.alsace20.tv/visionneuse/visio_v9_js.php?key=%s&habillage=0&mode=html' % (video_id, ),
+ f'https://www.alsace20.tv/visionneuse/visio_v9_js.php?key={video_id}&habillage=0&mode=html',
video_id) or {}
title = info.get('titre')
@@ -24,9 +24,9 @@ class Alsace20TVBaseIE(InfoExtractor):
else self._extract_mpd_formats(fmt_url, video_id, mpd_id=res, fatal=False))
webpage = (url and self._download_webpage(url, video_id, fatal=False)) or ''
- thumbnail = url_or_none(dict_get(info, ('image', 'preview', )) or self._og_search_thumbnail(webpage))
+ thumbnail = url_or_none(dict_get(info, ('image', 'preview')) or self._og_search_thumbnail(webpage))
upload_date = self._search_regex(r'/(\d{6})_', thumbnail, 'upload_date', default=None)
- upload_date = unified_strdate('20%s-%s-%s' % (upload_date[:2], upload_date[2:4], upload_date[4:])) if upload_date else None
+ upload_date = unified_strdate(f'20{upload_date[:2]}-{upload_date[2:4]}-{upload_date[4:]}') if upload_date else None
return {
'id': video_id,
'title': title,
diff --git a/yt_dlp/extractor/altcensored.py b/yt_dlp/extractor/altcensored.py
index 6878918a0..bfbf6b6af 100644
--- a/yt_dlp/extractor/altcensored.py
+++ b/yt_dlp/extractor/altcensored.py
@@ -34,7 +34,7 @@ class AltCensoredIE(InfoExtractor):
'thumbnail': 'https://archive.org/download/youtube-k0srjLSkga8/youtube-k0srjLSkga8.thumbs/k0srjLSkga8_000925.jpg',
'view_count': int,
'categories': ['News & Politics'],
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/alura.py b/yt_dlp/extractor/alura.py
index cb2b9891e..ce03a4265 100644
--- a/yt_dlp/extractor/alura.py
+++ b/yt_dlp/extractor/alura.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
ExtractorError,
clean_html,
@@ -21,7 +21,7 @@ class AluraIE(InfoExtractor):
'info_dict': {
'id': '60095',
'ext': 'mp4',
- 'title': 'Referências, ref-set e alter'
+ 'title': 'Referências, ref-set e alter',
},
'skip': 'Requires alura account credentials'},
{
@@ -30,7 +30,7 @@ class AluraIE(InfoExtractor):
'only_matching': True},
{
'url': 'https://cursos.alura.com.br/course/fundamentos-market-digital/task/55219',
- 'only_matching': True}
+ 'only_matching': True},
]
def _real_extract(self, url):
@@ -62,7 +62,7 @@ class AluraIE(InfoExtractor):
return {
'id': video_id,
'title': video_title,
- "formats": formats
+ 'formats': formats,
}
def _perform_login(self, username, password):
@@ -91,7 +91,7 @@ class AluraIE(InfoExtractor):
'post url', default=self._LOGIN_URL, group='url')
if not post_url.startswith('http'):
- post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
+ post_url = urllib.parse.urljoin(self._LOGIN_URL, post_url)
response = self._download_webpage(
post_url, None, 'Logging in',
@@ -103,7 +103,7 @@ class AluraIE(InfoExtractor):
r'(?s)<p[^>]+class="alert-message[^"]*">(.+?)</p>',
response, 'error message', default=None)
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
raise ExtractorError('Unable to log in')
@@ -119,7 +119,7 @@ class AluraCourseIE(AluraIE): # XXX: Do not subclass from concrete IE
@classmethod
def suitable(cls, url):
- return False if AluraIE.suitable(url) else super(AluraCourseIE, cls).suitable(url)
+ return False if AluraIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
@@ -157,7 +157,7 @@ class AluraCourseIE(AluraIE): # XXX: Do not subclass from concrete IE
'url': video_url,
'id_key': self.ie_key(),
'chapter': chapter,
- 'chapter_number': chapter_number
+ 'chapter_number': chapter_number,
}
entries.append(entry)
return self.playlist_result(entries, course_path, course_title)
diff --git a/yt_dlp/extractor/amadeustv.py b/yt_dlp/extractor/amadeustv.py
index 2f5ca9137..f4ea04efd 100644
--- a/yt_dlp/extractor/amadeustv.py
+++ b/yt_dlp/extractor/amadeustv.py
@@ -24,7 +24,7 @@ class AmadeusTVIE(InfoExtractor):
'display_id': '65091a87ff85af59d9fc54c3',
'view_count': int,
'description': 'md5:a0357b9c215489e2067cbae0b777bb95',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/amara.py b/yt_dlp/extractor/amara.py
index 509b21a53..ed0f0cd35 100644
--- a/yt_dlp/extractor/amara.py
+++ b/yt_dlp/extractor/amara.py
@@ -25,7 +25,7 @@ class AmaraIE(InfoExtractor):
'uploader': 'PBS NewsHour',
'uploader_id': 'PBSNewsHour',
'timestamp': 1549639570,
- }
+ },
}, {
# Vimeo
'url': 'https://amara.org/en/videos/kYkK1VUTWW5I/info/vimeo-at-ces-2011',
@@ -40,8 +40,8 @@ class AmaraIE(InfoExtractor):
'timestamp': 1294763658,
'upload_date': '20110111',
'uploader': 'Sam Morrill',
- 'uploader_id': 'sammorrill'
- }
+ 'uploader_id': 'sammorrill',
+ },
}, {
# Direct Link
'url': 'https://amara.org/en/videos/s8KL7I3jLmh6/info/the-danger-of-a-single-story/',
@@ -55,13 +55,13 @@ class AmaraIE(InfoExtractor):
'subtitles': dict,
'upload_date': '20091007',
'timestamp': 1254942511,
- }
+ },
}]
def _real_extract(self, url):
video_id = self._match_id(url)
meta = self._download_json(
- 'https://amara.org/api/videos/%s/' % video_id,
+ f'https://amara.org/api/videos/{video_id}/',
video_id, query={'format': 'json'})
title = meta['title']
video_url = meta['all_urls'][0]
diff --git a/yt_dlp/extractor/amazon.py b/yt_dlp/extractor/amazon.py
index a03f983e0..d1b91665c 100644
--- a/yt_dlp/extractor/amazon.py
+++ b/yt_dlp/extractor/amazon.py
@@ -61,13 +61,13 @@ class AmazonStoreIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
+ playlist_id = self._match_id(url)
for retry in self.RetryManager():
- webpage = self._download_webpage(url, id)
+ webpage = self._download_webpage(url, playlist_id)
try:
data_json = self._search_json(
- r'var\s?obj\s?=\s?jQuery\.parseJSON\(\'', webpage, 'data', id,
+ r'var\s?obj\s?=\s?jQuery\.parseJSON\(\'', webpage, 'data', playlist_id,
transform_source=js_to_json)
except ExtractorError as e:
retry.error = e
@@ -81,7 +81,7 @@ class AmazonStoreIE(InfoExtractor):
'height': int_or_none(video.get('videoHeight')),
'width': int_or_none(video.get('videoWidth')),
} for video in (data_json.get('videos') or []) if video.get('isVideo') and video.get('url')]
- return self.playlist_result(entries, playlist_id=id, playlist_title=data_json.get('title'))
+ return self.playlist_result(entries, playlist_id=playlist_id, playlist_title=data_json.get('title'))
class AmazonReviewsIE(InfoExtractor):
diff --git a/yt_dlp/extractor/amazonminitv.py b/yt_dlp/extractor/amazonminitv.py
index 2c71c5ef5..0590a344a 100644
--- a/yt_dlp/extractor/amazonminitv.py
+++ b/yt_dlp/extractor/amazonminitv.py
@@ -25,7 +25,7 @@ class AmazonMiniTVBaseIE(InfoExtractor):
asin, note=note, headers={
'Content-Type': 'application/json',
'currentpageurl': '/',
- 'currentplatform': 'dWeb'
+ 'currentplatform': 'dWeb',
}, data=json.dumps(data).encode() if data else None,
query=None if data else {
'deviceType': 'A1WMMUXPCUJL4N',
diff --git a/yt_dlp/extractor/amcnetworks.py b/yt_dlp/extractor/amcnetworks.py
index 10bd021c5..15a86e245 100644
--- a/yt_dlp/extractor/amcnetworks.py
+++ b/yt_dlp/extractor/amcnetworks.py
@@ -64,8 +64,8 @@ class AMCNetworksIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
site, display_id = self._match_valid_url(url).groups()
requestor_id = self._REQUESTOR_ID_MAP[site]
page_data = self._download_json(
- 'https://content-delivery-gw.svc.ds.amcn.com/api/v2/content/amcn/%s/url/%s'
- % (requestor_id.lower(), display_id), display_id)['data']
+ f'https://content-delivery-gw.svc.ds.amcn.com/api/v2/content/amcn/{requestor_id.lower()}/url/{display_id}',
+ display_id)['data']
properties = page_data.get('properties') or {}
query = {
'mbr': 'true',
@@ -76,15 +76,15 @@ class AMCNetworksIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
try:
for v in page_data['children']:
if v.get('type') == 'video-player':
- releasePid = v['properties']['currentVideo']['meta']['releasePid']
- tp_path = 'M_UwQC/' + releasePid
+ release_pid = v['properties']['currentVideo']['meta']['releasePid']
+ tp_path = 'M_UwQC/' + release_pid
media_url = 'https://link.theplatform.com/s/' + tp_path
video_player_count += 1
except KeyError:
pass
if video_player_count > 1:
self.report_warning(
- 'The JSON data has %d video players. Only one will be extracted' % video_player_count)
+ f'The JSON data has {video_player_count} video players. Only one will be extracted')
# Fall back to videoPid if releasePid not found.
# TODO: Fall back to videoPid if releasePid manifest uses DRM.
@@ -131,7 +131,7 @@ class AMCNetworksIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
})
ns_keys = theplatform_metadata.get('$xmlns', {}).keys()
if ns_keys:
- ns = list(ns_keys)[0]
+ ns = next(iter(ns_keys))
episode = theplatform_metadata.get(ns + '$episodeTitle') or None
episode_number = int_or_none(
theplatform_metadata.get(ns + '$episode'))
diff --git a/yt_dlp/extractor/americastestkitchen.py b/yt_dlp/extractor/americastestkitchen.py
index e889458a2..a6337e482 100644
--- a/yt_dlp/extractor/americastestkitchen.py
+++ b/yt_dlp/extractor/americastestkitchen.py
@@ -87,13 +87,13 @@ class AmericasTestKitchenIE(InfoExtractor):
resource_type = 'episodes'
resource = self._download_json(
- 'https://www.americastestkitchen.com/api/v6/%s/%s' % (resource_type, video_id), video_id)
+ f'https://www.americastestkitchen.com/api/v6/{resource_type}/{video_id}', video_id)
video = resource['video'] if is_episode else resource
episode = resource if is_episode else resource.get('episode') or {}
return {
'_type': 'url_transparent',
- 'url': 'https://player.zype.com/embed/%s.js?api_key=jZ9GUhRmxcPvX7M3SlfejB6Hle9jyHTdk2jVxG7wOHPLODgncEKVdPYBhuz9iWXQ' % video['zypeId'],
+ 'url': 'https://player.zype.com/embed/{}.js?api_key=jZ9GUhRmxcPvX7M3SlfejB6Hle9jyHTdk2jVxG7wOHPLODgncEKVdPYBhuz9iWXQ'.format(video['zypeId']),
'ie_key': 'Zype',
'description': clean_html(video.get('description')),
'timestamp': unified_timestamp(video.get('publishDate')),
@@ -174,22 +174,22 @@ class AmericasTestKitchenSeasonIE(InfoExtractor):
]
if season_number:
- playlist_id = 'season_%d' % season_number
- playlist_title = 'Season %d' % season_number
+ playlist_id = f'season_{season_number}'
+ playlist_title = f'Season {season_number}'
facet_filters.append('search_season_list:' + playlist_title)
else:
playlist_id = show
playlist_title = title
season_search = self._download_json(
- 'https://y1fnzxui30-dsn.algolia.net/1/indexes/everest_search_%s_season_desc_production' % slug,
+ f'https://y1fnzxui30-dsn.algolia.net/1/indexes/everest_search_{slug}_season_desc_production',
playlist_id, headers={
'Origin': 'https://www.americastestkitchen.com',
'X-Algolia-API-Key': '8d504d0099ed27c1b73708d22871d805',
'X-Algolia-Application-Id': 'Y1FNZXUI30',
}, query={
'facetFilters': json.dumps(facet_filters),
- 'attributesToRetrieve': 'description,search_%s_episode_number,search_document_date,search_url,title,search_atk_episode_season' % slug,
+ 'attributesToRetrieve': f'description,search_{slug}_episode_number,search_document_date,search_url,title,search_atk_episode_season',
'attributesToHighlight': '',
'hitsPerPage': 1000,
})
@@ -207,7 +207,7 @@ class AmericasTestKitchenSeasonIE(InfoExtractor):
'description': episode.get('description'),
'timestamp': unified_timestamp(episode.get('search_document_date')),
'season_number': season_number,
- 'episode_number': int_or_none(episode.get('search_%s_episode_number' % slug)),
+ 'episode_number': int_or_none(episode.get(f'search_{slug}_episode_number')),
'ie_key': AmericasTestKitchenIE.ie_key(),
}
diff --git a/yt_dlp/extractor/amp.py b/yt_dlp/extractor/amp.py
index 6b2bf2db2..adf473374 100644
--- a/yt_dlp/extractor/amp.py
+++ b/yt_dlp/extractor/amp.py
@@ -19,12 +19,12 @@ class AMPIE(InfoExtractor): # XXX: Conventionally, base classes should end with
'Unable to download Akamai AMP feed', transform_source=strip_jsonp)
item = feed.get('channel', {}).get('item')
if not item:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, feed['error']))
+ raise ExtractorError('{} said: {}'.format(self.IE_NAME, feed['error']))
video_id = item['guid']
def get_media_node(name, default=None):
- media_name = 'media-%s' % name
+ media_name = f'media-{name}'
media_group = item.get('media-group') or item
return media_group.get(media_name) or item.get(media_name) or item.get(name, default)
diff --git a/yt_dlp/extractor/anchorfm.py b/yt_dlp/extractor/anchorfm.py
index 5e78f372e..652154a4a 100644
--- a/yt_dlp/extractor/anchorfm.py
+++ b/yt_dlp/extractor/anchorfm.py
@@ -29,7 +29,7 @@ class AnchorFMEpisodeIE(InfoExtractor):
'release_date': '20230121',
'release_timestamp': 1674285179,
'episode_id': 'e1tpt3d',
- }
+ },
}, {
# embed url
'url': 'https://anchor.fm/apakatatempo/embed/episodes/S2E75-Perang-Bintang-di-Balik-Kasus-Ferdy-Sambo-dan-Ismail-Bolong-e1shjqd',
@@ -50,7 +50,7 @@ class AnchorFMEpisodeIE(InfoExtractor):
'season': 'Season 2',
'season_number': 2,
'episode_id': 'e1shjqd',
- }
+ },
}]
_WEBPAGE_TESTS = [{
@@ -72,7 +72,7 @@ class AnchorFMEpisodeIE(InfoExtractor):
'thumbnail': 'https://s3-us-west-2.amazonaws.com/anchor-generated-image-bank/production/podcast_uploaded_episode400/2627805/2627805-1671590688729-4db3882ac9e4b.jpg',
'uploader': 'Podcast Tempo',
'channel': 'apakatatempo',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/angel.py b/yt_dlp/extractor/angel.py
index 9f5b9b523..6800fe3d7 100644
--- a/yt_dlp/extractor/angel.py
+++ b/yt_dlp/extractor/angel.py
@@ -15,8 +15,8 @@ class AngelIE(InfoExtractor):
'title': 'Tuttle Twins Season 1, Episode 1: When Laws Give You Lemons',
'description': 'md5:73b704897c20ab59c433a9c0a8202d5e',
'thumbnail': r're:^https?://images.angelstudios.com/image/upload/angel-app/.*$',
- 'duration': 1359.0
- }
+ 'duration': 1359.0,
+ },
}, {
'url': 'https://www.angel.com/watch/the-chosen/episode/8dfb714d-bca5-4812-8125-24fb9514cd10/season-1/episode-1/i-have-called-you-by-name',
'md5': 'e4774bad0a5f0ad2e90d175cafdb797d',
@@ -26,8 +26,8 @@ class AngelIE(InfoExtractor):
'title': 'The Chosen Season 1, Episode 1: I Have Called You By Name',
'description': 'md5:aadfb4827a94415de5ff6426e6dee3be',
'thumbnail': r're:^https?://images.angelstudios.com/image/upload/angel-app/.*$',
- 'duration': 3276.0
- }
+ 'duration': 3276.0,
+ },
}]
def _real_extract(self, url):
@@ -44,7 +44,7 @@ class AngelIE(InfoExtractor):
'title': self._og_search_title(webpage),
'description': self._og_search_description(webpage),
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
# Angel uses cloudinary in the background and supports image transformations.
diff --git a/yt_dlp/extractor/antenna.py b/yt_dlp/extractor/antenna.py
index 2929d6550..b1a01791f 100644
--- a/yt_dlp/extractor/antenna.py
+++ b/yt_dlp/extractor/antenna.py
@@ -105,7 +105,7 @@ class Ant1NewsGrArticleIE(AntennaBaseIE):
info = self._search_json_ld(webpage, video_id, expected_type='NewsArticle')
embed_urls = list(Ant1NewsGrEmbedIE._extract_embed_urls(url, webpage))
if not embed_urls:
- raise ExtractorError('no videos found for %s' % video_id, expected=True)
+ raise ExtractorError(f'no videos found for {video_id}', expected=True)
return self.playlist_from_matches(
embed_urls, video_id, info.get('title'), ie=Ant1NewsGrEmbedIE.ie_key(),
video_kwargs={'url_transparent': True, 'timestamp': info.get('timestamp')})
diff --git a/yt_dlp/extractor/anvato.py b/yt_dlp/extractor/anvato.py
index 0df50333c..bf3d60b5e 100644
--- a/yt_dlp/extractor/anvato.py
+++ b/yt_dlp/extractor/anvato.py
@@ -238,7 +238,7 @@ class AnvatoIE(InfoExtractor):
'gray': 'anvato_mcp_gray_web_prod_4c10f067c393ed8fc453d3930f8ab2b159973900',
'hearst': 'anvato_mcp_hearst_web_prod_5356c3de0fc7c90a3727b4863ca7fec3a4524a99',
'cbs': 'anvato_mcp_cbs_web_prod_02f26581ff80e5bda7aad28226a8d369037f2cbe',
- 'telemundo': 'anvato_mcp_telemundo_web_prod_c5278d51ad46fda4b6ca3d0ea44a7846a054f582'
+ 'telemundo': 'anvato_mcp_telemundo_web_prod_c5278d51ad46fda4b6ca3d0ea44a7846a054f582',
}
def _generate_nfl_token(self, anvack, mcp_id):
@@ -255,7 +255,7 @@ class AnvatoIE(InfoExtractor):
token
}
}
-}''' % (anvack, mcp_id),
+}''' % (anvack, mcp_id), # noqa: UP031
}).encode(), headers={
'Authorization': auth_token,
'Content-Type': 'application/json',
@@ -299,7 +299,7 @@ class AnvatoIE(InfoExtractor):
return self._download_json(
video_data_url, video_id, transform_source=strip_jsonp, query=query,
- data=json.dumps({'api': api}, separators=(',', ':')).encode('utf-8'))
+ data=json.dumps({'api': api}, separators=(',', ':')).encode())
def _get_anvato_videos(self, access_key, video_id, token):
video_data = self._get_video_json(access_key, video_id, token)
@@ -358,7 +358,7 @@ class AnvatoIE(InfoExtractor):
for caption in video_data.get('captions', []):
a_caption = {
'url': caption['url'],
- 'ext': 'tt' if caption.get('format') == 'SMPTE-TT' else None
+ 'ext': 'tt' if caption.get('format') == 'SMPTE-TT' else None,
}
subtitles.setdefault(caption['language'], []).append(a_caption)
subtitles = self._merge_subtitles(subtitles, hls_subs, vtt_subs)
diff --git a/yt_dlp/extractor/aol.py b/yt_dlp/extractor/aol.py
index 455f66795..893dce7b0 100644
--- a/yt_dlp/extractor/aol.py
+++ b/yt_dlp/extractor/aol.py
@@ -30,7 +30,7 @@ class AolIE(YahooIE): # XXX: Do not subclass from concrete IE
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
# video with vidible ID
'url': 'https://www.aol.com/video/view/netflix-is-raising-rates/5707d6b8e4b090497b04f706/',
@@ -46,7 +46,7 @@ class AolIE(YahooIE): # XXX: Do not subclass from concrete IE
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.aol.com/video/view/park-bench-season-2-trailer/559a1b9be4b0c3bfad3357a7/',
'only_matching': True,
@@ -83,10 +83,10 @@ class AolIE(YahooIE): # XXX: Do not subclass from concrete IE
return self._extract_yahoo_video(video_id, 'us')
response = self._download_json(
- 'https://feedapi.b2c.on.aol.com/v1.0/app/videos/aolon/%s/details' % video_id,
+ f'https://feedapi.b2c.on.aol.com/v1.0/app/videos/aolon/{video_id}/details',
video_id)['response']
if response['statusText'] != 'Ok':
- raise ExtractorError('%s said: %s' % (self.IE_NAME, response['statusText']), expected=True)
+ raise ExtractorError('{} said: {}'.format(self.IE_NAME, response['statusText']), expected=True)
video_data = response['data']
formats = []
diff --git a/yt_dlp/extractor/apa.py b/yt_dlp/extractor/apa.py
index 1ea0b1de4..fed597042 100644
--- a/yt_dlp/extractor/apa.py
+++ b/yt_dlp/extractor/apa.py
@@ -34,7 +34,7 @@ class APAIE(InfoExtractor):
video_id, base_url = mobj.group('id', 'base_url')
webpage = self._download_webpage(
- '%s/player/%s' % (base_url, video_id), video_id)
+ f'{base_url}/player/{video_id}', video_id)
jwplatform_id = self._search_regex(
r'media[iI]d\s*:\s*["\'](?P<id>[a-zA-Z0-9]{8})', webpage,
@@ -47,7 +47,7 @@ class APAIE(InfoExtractor):
def extract(field, name=None):
return self._search_regex(
- r'\b%s["\']\s*:\s*(["\'])(?P<value>(?:(?!\1).)+)\1' % field,
+ rf'\b{field}["\']\s*:\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
webpage, name or field, default=None, group='value')
title = extract('title') or video_id
diff --git a/yt_dlp/extractor/applepodcasts.py b/yt_dlp/extractor/applepodcasts.py
index 49bbeab82..bd301e904 100644
--- a/yt_dlp/extractor/applepodcasts.py
+++ b/yt_dlp/extractor/applepodcasts.py
@@ -24,7 +24,7 @@ class ApplePodcastsIE(InfoExtractor):
'duration': 6454,
'series': 'The Tim Dillon Show',
'thumbnail': 're:.+[.](png|jpe?g|webp)',
- }
+ },
}, {
'url': 'https://podcasts.apple.com/podcast/207-whitney-webb-returns/id1135137367?i=1000482637777',
'only_matching': True,
diff --git a/yt_dlp/extractor/appletrailers.py b/yt_dlp/extractor/appletrailers.py
index 21103aee5..0a600f6df 100644
--- a/yt_dlp/extractor/appletrailers.py
+++ b/yt_dlp/extractor/appletrailers.py
@@ -1,8 +1,8 @@
import json
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
int_or_none,
parse_duration,
@@ -64,7 +64,7 @@ class AppleTrailersIE(InfoExtractor):
'uploader_id': 'wb',
},
},
- ]
+ ],
}, {
'url': 'http://trailers.apple.com/trailers/magnolia/blackthorn/',
'info_dict': {
@@ -99,7 +99,7 @@ class AppleTrailersIE(InfoExtractor):
webpage = self._download_webpage(url, movie)
film_id = self._search_regex(r"FilmId\s*=\s*'(\d+)'", webpage, 'film id')
film_data = self._download_json(
- 'http://trailers.apple.com/trailers/feeds/data/%s.json' % film_id,
+ f'http://trailers.apple.com/trailers/feeds/data/{film_id}.json',
film_id, fatal=False)
if film_data:
@@ -114,7 +114,7 @@ class AppleTrailersIE(InfoExtractor):
if not src:
continue
formats.append({
- 'format_id': '%s-%s' % (version, size),
+ 'format_id': f'{version}-{size}',
'url': re.sub(r'_(\d+p\.mov)', r'_h\1', src),
'width': int_or_none(size_data.get('width')),
'height': int_or_none(size_data.get('height')),
@@ -134,7 +134,7 @@ class AppleTrailersIE(InfoExtractor):
page_data = film_data.get('page', {})
return self.playlist_result(entries, film_id, page_data.get('movie_title'))
- playlist_url = compat_urlparse.urljoin(url, 'includes/playlists/itunes.inc')
+ playlist_url = urllib.parse.urljoin(url, 'includes/playlists/itunes.inc')
def fix_html(s):
s = re.sub(r'(?s)<script[^<]*?>.*?</script>', '', s)
@@ -143,10 +143,9 @@ class AppleTrailersIE(InfoExtractor):
# like: http://trailers.apple.com/trailers/wb/gravity/
def _clean_json(m):
- return 'iTunes.playURL(%s);' % m.group(1).replace('\'', '&#39;')
+ return 'iTunes.playURL({});'.format(m.group(1).replace('\'', '&#39;'))
s = re.sub(self._JSON_RE, _clean_json, s)
- s = '<html>%s</html>' % s
- return s
+ return f'<html>{s}</html>'
doc = self._download_xml(playlist_url, movie, transform_source=fix_html)
playlist = []
@@ -170,18 +169,18 @@ class AppleTrailersIE(InfoExtractor):
duration = 60 * int(m.group('minutes')) + int(m.group('seconds'))
trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower()
- settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id)
+ settings_json_url = urllib.parse.urljoin(url, f'includes/settings/{trailer_id}.json')
settings = self._download_json(settings_json_url, trailer_id, 'Downloading settings json')
formats = []
- for format in settings['metadata']['sizes']:
+ for fmt in settings['metadata']['sizes']:
# The src is a file pointing to the real video file
- format_url = re.sub(r'_(\d*p\.mov)', r'_h\1', format['src'])
+ format_url = re.sub(r'_(\d*p\.mov)', r'_h\1', fmt['src'])
formats.append({
'url': format_url,
- 'format': format['type'],
- 'width': int_or_none(format['width']),
- 'height': int_or_none(format['height']),
+ 'format': fmt['type'],
+ 'width': int_or_none(fmt['width']),
+ 'height': int_or_none(fmt['height']),
})
playlist.append({
@@ -229,7 +228,7 @@ class AppleTrailersSectionIE(InfoExtractor):
'title': 'Movie Studios',
},
}
- _VALID_URL = r'https?://(?:www\.)?trailers\.apple\.com/#section=(?P<id>%s)' % '|'.join(_SECTIONS)
+ _VALID_URL = r'https?://(?:www\.)?trailers\.apple\.com/#section=(?P<id>{})'.format('|'.join(_SECTIONS))
_TESTS = [{
'url': 'http://trailers.apple.com/#section=justadded',
'info_dict': {
@@ -270,7 +269,7 @@ class AppleTrailersSectionIE(InfoExtractor):
def _real_extract(self, url):
section = self._match_id(url)
section_data = self._download_json(
- 'http://trailers.apple.com/trailers/home/feeds/%s.json' % self._SECTIONS[section]['feed_path'],
+ 'http://trailers.apple.com/trailers/home/feeds/{}.json'.format(self._SECTIONS[section]['feed_path']),
section)
entries = [
self.url_result('http://trailers.apple.com' + e['location'])
diff --git a/yt_dlp/extractor/archiveorg.py b/yt_dlp/extractor/archiveorg.py
index 41f3a4ff2..f5a55efc4 100644
--- a/yt_dlp/extractor/archiveorg.py
+++ b/yt_dlp/extractor/archiveorg.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import json
import re
import urllib.parse
from .common import InfoExtractor
from .youtube import YoutubeBaseInfoExtractor, YoutubeIE
-from ..compat import compat_urllib_parse_unquote
from ..networking import HEADRequest
from ..networking.exceptions import HTTPError
from ..utils import (
@@ -145,7 +146,7 @@ class ArchiveOrgIE(InfoExtractor):
'title': 'Bells Of Rostov',
'ext': 'mp3',
},
- 'skip': 'restricted'
+ 'skip': 'restricted',
}, {
'url': 'https://archive.org/details/lp_the-music-of-russia_various-artists-a-askaryan-alexander-melik/disc1/02.02.+Song+And+Chorus+In+The+Polovetsian+Camp+From+%22Prince+Igor%22+(Act+2%2C+Scene+1).mp3',
'md5': '1d0aabe03edca83ca58d9ed3b493a3c3',
@@ -158,7 +159,7 @@ class ArchiveOrgIE(InfoExtractor):
'description': 'md5:012b2d668ae753be36896f343d12a236',
'upload_date': '20190928',
},
- 'skip': 'restricted'
+ 'skip': 'restricted',
}, {
# Original formats are private
'url': 'https://archive.org/details/irelandthemakingofarepublic',
@@ -202,8 +203,8 @@ class ArchiveOrgIE(InfoExtractor):
'thumbnail': 'https://archive.org/download/irelandthemakingofarepublic/irelandthemakingofarepublic.thumbs/irelandthemakingofarepublicreel2_001554.jpg',
'display_id': 'irelandthemakingofarepublicreel2.mov',
},
- }
- ]
+ },
+ ],
}]
@staticmethod
@@ -220,7 +221,7 @@ class ArchiveOrgIE(InfoExtractor):
def _real_extract(self, url):
video_id = urllib.parse.unquote_plus(self._match_id(url))
- identifier, entry_id = (video_id.split('/', 1) + [None])[:2]
+ identifier, _, entry_id = video_id.partition('/')
# Archive.org metadata API doesn't clearly demarcate playlist entries
# or subtitle tracks, so we get them from the embeddable player.
@@ -246,7 +247,7 @@ class ArchiveOrgIE(InfoExtractor):
if track['kind'] != 'subtitles':
continue
entries[p['orig']][track['label']] = {
- 'url': 'https://archive.org/' + track['file'].lstrip('/')
+ 'url': 'https://archive.org/' + track['file'].lstrip('/'),
}
metadata = self._download_json('http://archive.org/metadata/' + identifier, identifier)
@@ -293,7 +294,9 @@ class ArchiveOrgIE(InfoExtractor):
'height': int_or_none(f.get('width')),
'filesize': int_or_none(f.get('size'))})
- extension = (f['name'].rsplit('.', 1) + [None])[1]
+ _, has_ext, extension = f['name'].rpartition('.')
+ if not has_ext:
+ extension = None
# We don't want to skip private formats if the user has access to them,
# however without access to an account with such privileges we can't implement/test this.
@@ -308,7 +311,7 @@ class ArchiveOrgIE(InfoExtractor):
'filesize': int_or_none(f.get('size')),
'protocol': 'https',
'source_preference': 0 if f.get('source') == 'original' else -1,
- 'format_note': f.get('source')
+ 'format_note': f.get('source'),
})
for entry in entries.values():
@@ -371,7 +374,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader_url': 'https://www.youtube.com/user/Zeurel',
'thumbnail': r're:https?://.*\.(jpg|webp)',
'channel_url': 'https://www.youtube.com/channel/UCukCyHaD-bK3in_pKpfH9Eg',
- }
+ },
}, {
# Internal link
'url': 'https://web.archive.org/web/2oe/http://wayback-fakeurl.archive.org/yt/97t7Xj_iBv0',
@@ -388,7 +391,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader_url': 'https://www.youtube.com/user/1veritasium',
'thumbnail': r're:https?://.*\.(jpg|webp)',
'channel_url': 'https://www.youtube.com/channel/UCHnyfMqiRRG1u-2MsSQLbXA',
- }
+ },
}, {
# Video from 2012, webm format itag 45. Newest capture is deleted video, with an invalid description.
# Should use the date in the link. Title ends with '- Youtube'. Capture has description in eow-description
@@ -403,8 +406,8 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader_id': 'machinima',
'uploader_url': 'https://www.youtube.com/user/machinima',
'thumbnail': r're:https?://.*\.(jpg|webp)',
- 'uploader': 'machinima'
- }
+ 'uploader': 'machinima',
+ },
}, {
# FLV video. Video file URL does not provide itag information
'url': 'https://web.archive.org/web/20081211103536/http://www.youtube.com/watch?v=jNQXAC9IVRw',
@@ -421,7 +424,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A',
'thumbnail': r're:https?://.*\.(jpg|webp)',
'uploader': 'jawed',
- }
+ },
}, {
'url': 'https://web.archive.org/web/20110712231407/http://www.youtube.com/watch?v=lTx3G6h2xyA',
'info_dict': {
@@ -437,7 +440,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader_url': 'https://www.youtube.com/user/itsmadeon',
'channel_url': 'https://www.youtube.com/channel/UCqMDNf3Pn5L7pcNkuSEeO3w',
'thumbnail': r're:https?://.*\.(jpg|webp)',
- }
+ },
}, {
# First capture is of dead video, second is the oldest from CDX response.
'url': 'https://web.archive.org/https://www.youtube.com/watch?v=1JYutPM8O6E',
@@ -454,7 +457,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UCdIaNUarhzLSXGoItz7BHVA',
'thumbnail': r're:https?://.*\.(jpg|webp)',
'uploader': 'ETC News',
- }
+ },
}, {
# First capture of dead video, capture date in link links to dead capture.
'url': 'https://web.archive.org/web/20180803221945/https://www.youtube.com/watch?v=6FPhZJGvf4E',
@@ -473,15 +476,15 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader': 'ETC News',
},
'expected_warnings': [
- r'unable to download capture webpage \(it may not be archived\)'
- ]
+ r'unable to download capture webpage \(it may not be archived\)',
+ ],
}, { # Very old YouTube page, has - YouTube in title.
'url': 'http://web.archive.org/web/20070302011044/http://youtube.com/watch?v=-06-KB9XTzg',
'info_dict': {
'id': '-06-KB9XTzg',
'ext': 'flv',
- 'title': 'New Coin Hack!! 100% Safe!!'
- }
+ 'title': 'New Coin Hack!! 100% Safe!!',
+ },
}, {
'url': 'web.archive.org/https://www.youtube.com/watch?v=dWW7qP423y8',
'info_dict': {
@@ -495,7 +498,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'description': 'md5:7b567f898d8237b256f36c1a07d6d7bc',
'thumbnail': r're:https?://.*\.(jpg|webp)',
'uploader': 'DankPods',
- }
+ },
}, {
# player response contains '};' See: https://github.com/ytdl-org/youtube-dl/issues/27093
'url': 'https://web.archive.org/web/20200827003909if_/http://www.youtube.com/watch?v=6Dh-RL__uN4',
@@ -512,7 +515,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader_id': 'PewDiePie',
'uploader_url': 'https://www.youtube.com/user/PewDiePie',
'thumbnail': r're:https?://.*\.(jpg|webp)',
- }
+ },
}, {
# ~June 2010 Capture. swfconfig
'url': 'https://web.archive.org/web/0/https://www.youtube.com/watch?v=8XeW5ilk-9Y',
@@ -527,7 +530,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'thumbnail': r're:https?://.*\.(jpg|webp)',
'uploader_url': 'https://www.youtube.com/user/HowTheWorldWorks',
'upload_date': '20090520',
- }
+ },
}, {
# Jan 2011: watch-video-date/eow-date surrounded by whitespace
'url': 'https://web.archive.org/web/20110126141719/http://www.youtube.com/watch?v=Q_yjX80U7Yc',
@@ -542,7 +545,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'thumbnail': r're:https?://.*\.(jpg|webp)',
'duration': 132,
'uploader_url': 'https://www.youtube.com/user/claybutlermusic',
- }
+ },
}, {
# ~May 2009 swfArgs. ytcfg is spread out over various vars
'url': 'https://web.archive.org/web/0/https://www.youtube.com/watch?v=c5uJgG05xUY',
@@ -557,7 +560,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'description': 'md5:4ca77d79538064e41e4cc464e93f44f0',
'thumbnail': r're:https?://.*\.(jpg|webp)',
'duration': 754,
- }
+ },
}, {
# ~June 2012. Upload date is in another lang so cannot extract.
'url': 'https://web.archive.org/web/20120607174520/http://www.youtube.com/watch?v=xWTLLl-dQaA',
@@ -571,7 +574,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'uploader': 'BlackNerdComedy',
'duration': 182,
'thumbnail': r're:https?://.*\.(jpg|webp)',
- }
+ },
}, {
# ~July 2013
'url': 'https://web.archive.org/web/*/https://www.youtube.com/watch?v=9eO1aasHyTM',
@@ -587,7 +590,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UC62R2cBezNBOqxSerfb1nMQ',
'upload_date': '20060428',
'uploader': 'punkybird',
- }
+ },
}, {
# April 2020: Player response in player config
'url': 'https://web.archive.org/web/20200416034815/https://www.youtube.com/watch?v=Cf7vS8jc7dY&gl=US&hl=en',
@@ -604,7 +607,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'thumbnail': r're:https?://.*\.(jpg|webp)',
'description': 'md5:c625bb3c02c4f5fb4205971e468fa341',
'uploader_url': 'https://www.youtube.com/user/GameGrumps',
- }
+ },
}, {
# watch7-user-header with yt-user-info
'url': 'ytarchive:kbh4T_b4Ixw:20160307085057',
@@ -619,7 +622,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
'thumbnail': r're:https?://.*\.(jpg|webp)',
'upload_date': '20150503',
'channel_id': 'UCnTaGvsHmMy792DWeT6HbGA',
- }
+ },
}, {
# April 2012
'url': 'https://web.archive.org/web/0/https://www.youtube.com/watch?v=SOm7mPoPskU',
@@ -634,35 +637,35 @@ class YoutubeWebArchiveIE(InfoExtractor):
'duration': 200,
'upload_date': '20120407',
'uploader_id': 'thecomputernerd01',
- }
+ },
}, {
'url': 'https://web.archive.org/web/http://www.youtube.com/watch?v=kH-G_aIBlFw',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://web.archive.org/web/20050214000000_if/http://www.youtube.com/watch?v=0altSZ96U4M',
- 'only_matching': True
+ 'only_matching': True,
}, {
# Video not archived, only capture is unavailable video page
'url': 'https://web.archive.org/web/20210530071008/https://www.youtube.com/watch?v=lHJTf93HL1s&spfreload=10',
- 'only_matching': True
+ 'only_matching': True,
}, { # Encoded url
'url': 'https://web.archive.org/web/20120712231619/http%3A//www.youtube.com/watch%3Fgl%3DUS%26v%3DAkhihxRKcrs%26hl%3Den',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://web.archive.org/web/20120712231619/http%3A//www.youtube.com/watch%3Fv%3DAkhihxRKcrs%26gl%3DUS%26hl%3Den',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://web.archive.org/web/20060527081937/http://www.youtube.com:80/watch.php?v=ELTFsLT73fA&amp;search=soccer',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://web.archive.org/http://www.youtube.com:80/watch?v=-05VVye-ffg',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'ytarchive:BaW_jenozKc:20050214000000',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'ytarchive:BaW_jenozKc',
- 'only_matching': True
+ 'only_matching': True,
},
]
_YT_INITIAL_DATA_RE = YoutubeBaseInfoExtractor._YT_INITIAL_DATA_RE
@@ -673,13 +676,13 @@ class YoutubeWebArchiveIE(InfoExtractor):
_YT_DEFAULT_THUMB_SERVERS = ['i.ytimg.com'] # thumbnails most likely archived on these servers
_YT_ALL_THUMB_SERVERS = orderedSet(
- _YT_DEFAULT_THUMB_SERVERS + ['img.youtube.com', *[f'{c}{n or ""}.ytimg.com' for c in ('i', 's') for n in (*range(0, 5), 9)]])
+ [*_YT_DEFAULT_THUMB_SERVERS, 'img.youtube.com', *[f'{c}{n or ""}.ytimg.com' for c in ('i', 's') for n in (*range(5), 9)]])
_WAYBACK_BASE_URL = 'https://web.archive.org/web/%sif_/'
_OLDEST_CAPTURE_DATE = 20050214000000
_NEWEST_CAPTURE_DATE = 20500101000000
- def _call_cdx_api(self, item_id, url, filters: list = None, collapse: list = None, query: dict = None, note=None, fatal=False):
+ def _call_cdx_api(self, item_id, url, filters: list | None = None, collapse: list | None = None, query: dict | None = None, note=None, fatal=False):
# CDX docs: https://github.com/internetarchive/wayback/blob/master/wayback-cdx-server/README.md
query = {
'url': url,
@@ -688,14 +691,14 @@ class YoutubeWebArchiveIE(InfoExtractor):
'limit': 500,
'filter': ['statuscode:200'] + (filters or []),
'collapse': collapse or [],
- **(query or {})
+ **(query or {}),
}
res = self._download_json(
'https://web.archive.org/cdx/search/cdx', item_id,
note or 'Downloading CDX API JSON', query=query, fatal=fatal)
if isinstance(res, list) and len(res) >= 2:
# format response to make it easier to use
- return list(dict(zip(res[0], v)) for v in res[1:])
+ return [dict(zip(res[0], v)) for v in res[1:]]
elif not isinstance(res, list) or len(res) != 0:
self.report_warning('Error while parsing CDX API response' + bug_reports_message())
@@ -852,7 +855,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
{
'url': (self._WAYBACK_BASE_URL % (int_or_none(thumbnail_dict.get('timestamp')) or self._OLDEST_CAPTURE_DATE)) + thumbnail_dict.get('original'),
'filesize': int_or_none(thumbnail_dict.get('length')),
- 'preference': int_or_none(thumbnail_dict.get('length'))
+ 'preference': int_or_none(thumbnail_dict.get('length')),
} for thumbnail_dict in response)
if not try_all:
break
@@ -893,7 +896,7 @@ class YoutubeWebArchiveIE(InfoExtractor):
for retry in retry_manager:
try:
urlh = self._request_webpage(
- HEADRequest('https://web.archive.org/web/2oe_/http://wayback-fakeurl.archive.org/yt/%s' % video_id),
+ HEADRequest(f'https://web.archive.org/web/2oe_/http://wayback-fakeurl.archive.org/yt/{video_id}'),
video_id, note='Fetching archived video file url', expected_status=True)
except ExtractorError as e:
# HTTP Error 404 is expected if the video is not saved.
@@ -924,21 +927,21 @@ class YoutubeWebArchiveIE(InfoExtractor):
info['thumbnails'] = self._extract_thumbnails(video_id)
if urlh:
- url = compat_urllib_parse_unquote(urlh.url)
+ url = urllib.parse.unquote(urlh.url)
video_file_url_qs = parse_qs(url)
# Attempt to recover any ext & format info from playback url & response headers
- format = {'url': url, 'filesize': int_or_none(urlh.headers.get('x-archive-orig-content-length'))}
+ fmt = {'url': url, 'filesize': int_or_none(urlh.headers.get('x-archive-orig-content-length'))}
itag = try_get(video_file_url_qs, lambda x: x['itag'][0])
if itag and itag in YoutubeIE._formats:
- format.update(YoutubeIE._formats[itag])
- format.update({'format_id': itag})
+ fmt.update(YoutubeIE._formats[itag])
+ fmt.update({'format_id': itag})
else:
mime = try_get(video_file_url_qs, lambda x: x['mime'][0])
ext = (mimetype2ext(mime)
or urlhandle_detect_ext(urlh)
or mimetype2ext(urlh.headers.get('x-archive-guessed-content-type')))
- format.update({'ext': ext})
- info['formats'] = [format]
+ fmt.update({'ext': ext})
+ info['formats'] = [fmt]
if not info.get('duration'):
info['duration'] = str_to_int(try_get(video_file_url_qs, lambda x: x['dur'][0]))
diff --git a/yt_dlp/extractor/arcpublishing.py b/yt_dlp/extractor/arcpublishing.py
index febd3d28a..338bada7c 100644
--- a/yt_dlp/extractor/arcpublishing.py
+++ b/yt_dlp/extractor/arcpublishing.py
@@ -11,7 +11,7 @@ from ..utils import (
class ArcPublishingIE(InfoExtractor):
_UUID_REGEX = r'[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12}'
- _VALID_URL = r'arcpublishing:(?P<org>[a-z]+):(?P<id>%s)' % _UUID_REGEX
+ _VALID_URL = rf'arcpublishing:(?P<org>[a-z]+):(?P<id>{_UUID_REGEX})'
_TESTS = [{
# https://www.adn.com/politics/2020/11/02/video-senate-candidates-campaign-in-anchorage-on-eve-of-election-day/
'url': 'arcpublishing:adn:8c99cb6e-b29c-4bc9-9173-7bf9979225ab',
@@ -74,12 +74,12 @@ class ArcPublishingIE(InfoExtractor):
def _extract_embed_urls(cls, url, webpage):
entries = []
# https://arcpublishing.atlassian.net/wiki/spaces/POWA/overview
- for powa_el in re.findall(r'(<div[^>]+class="[^"]*\bpowa\b[^"]*"[^>]+data-uuid="%s"[^>]*>)' % ArcPublishingIE._UUID_REGEX, webpage):
+ for powa_el in re.findall(rf'(<div[^>]+class="[^"]*\bpowa\b[^"]*"[^>]+data-uuid="{ArcPublishingIE._UUID_REGEX}"[^>]*>)', webpage):
powa = extract_attributes(powa_el) or {}
org = powa.get('data-org')
uuid = powa.get('data-uuid')
if org and uuid:
- entries.append('arcpublishing:%s:%s' % (org, uuid))
+ entries.append(f'arcpublishing:{org}:{uuid}')
return entries
def _real_extract(self, url):
@@ -122,7 +122,7 @@ class ArcPublishingIE(InfoExtractor):
elif stream_type in ('ts', 'hls'):
m3u8_formats = self._extract_m3u8_formats(
s_url, uuid, 'mp4', live=is_live, m3u8_id='hls', fatal=False)
- if all([f.get('acodec') == 'none' for f in m3u8_formats]):
+ if all(f.get('acodec') == 'none' for f in m3u8_formats):
continue
for f in m3u8_formats:
height = f.get('height')
@@ -136,7 +136,7 @@ class ArcPublishingIE(InfoExtractor):
else:
vbr = int_or_none(s.get('bitrate'))
formats.append({
- 'format_id': '%s-%d' % (stream_type, vbr) if vbr else stream_type,
+ 'format_id': f'{stream_type}-{vbr}' if vbr else stream_type,
'vbr': vbr,
'width': int_or_none(s.get('width')),
'height': int_or_none(s.get('height')),
diff --git a/yt_dlp/extractor/ard.py b/yt_dlp/extractor/ard.py
index 3db59c5ca..6fd641347 100644
--- a/yt_dlp/extractor/ard.py
+++ b/yt_dlp/extractor/ard.py
@@ -85,7 +85,7 @@ class ARDMediathekBaseIE(InfoExtractor):
formats.extend(self._extract_f4m_formats(
update_url_query(stream_url, {
'hdcore': '3.1.1',
- 'plugin': 'aasp-3.1.1.69.124'
+ 'plugin': 'aasp-3.1.1.69.124',
}), video_id, f4m_id='hds', fatal=False))
elif ext == 'm3u8':
formats.extend(self._extract_m3u8_formats(
@@ -96,12 +96,12 @@ class ARDMediathekBaseIE(InfoExtractor):
f = {
'url': server,
'play_path': stream_url,
- 'format_id': 'a%s-rtmp-%s' % (num, quality),
+ 'format_id': f'a{num}-rtmp-{quality}',
}
else:
f = {
'url': stream_url,
- 'format_id': 'a%s-%s-%s' % (num, ext, quality)
+ 'format_id': f'a{num}-{ext}-{quality}',
}
m = re.search(
r'_(?P<width>\d+)x(?P<height>\d+)\.mp4$',
diff --git a/yt_dlp/extractor/arkena.py b/yt_dlp/extractor/arkena.py
index de36ec886..b0e853d57 100644
--- a/yt_dlp/extractor/arkena.py
+++ b/yt_dlp/extractor/arkena.py
@@ -64,7 +64,7 @@ class ArkenaIE(InfoExtractor):
raise ExtractorError('Invalid URL', expected=True)
media = self._download_json(
- 'https://video.qbrick.com/api/v1/public/accounts/%s/medias/%s' % (account_id, video_id),
+ f'https://video.qbrick.com/api/v1/public/accounts/{account_id}/medias/{video_id}',
video_id, query={
# https://video.qbrick.com/docs/api/examples/library-api.html
'fields': 'asset/resources/*/renditions/*(height,id,language,links/*(href,mimeType),type,size,videos/*(audios/*(codec,sampleRate),bitrate,codec,duration,height,width),width),created,metadata/*(title,description),tags',
diff --git a/yt_dlp/extractor/arnes.py b/yt_dlp/extractor/arnes.py
index 9a5524aab..f196f611a 100644
--- a/yt_dlp/extractor/arnes.py
+++ b/yt_dlp/extractor/arnes.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urllib_parse_urlparse,
-)
from ..utils import (
float_or_none,
format_field,
@@ -35,7 +33,7 @@ class ArnesIE(InfoExtractor):
'view_count': int,
'tags': ['linearna_algebra'],
'start_time': 10,
- }
+ },
}, {
'url': 'https://video.arnes.si/api/asset/s1YjnV7hadlC/play.mp4',
'only_matching': True,
@@ -93,6 +91,6 @@ class ArnesIE(InfoExtractor):
'duration': float_or_none(video.get('duration'), 1000),
'view_count': int_or_none(video.get('views')),
'tags': video.get('hashtags'),
- 'start_time': int_or_none(compat_parse_qs(
- compat_urllib_parse_urlparse(url).query).get('t', [None])[0]),
+ 'start_time': int_or_none(urllib.parse.parse_qs(
+ urllib.parse.urlparse(url).query).get('t', [None])[0]),
}
diff --git a/yt_dlp/extractor/art19.py b/yt_dlp/extractor/art19.py
index 271c505da..deec7ad01 100644
--- a/yt_dlp/extractor/art19.py
+++ b/yt_dlp/extractor/art19.py
@@ -153,7 +153,7 @@ class Art19IE(InfoExtractor):
'series_id': ('series_id', {str}),
'timestamp': ('created_at', {parse_iso8601}),
'release_timestamp': ('released_at', {parse_iso8601}),
- 'modified_timestamp': ('updated_at', {parse_iso8601})
+ 'modified_timestamp': ('updated_at', {parse_iso8601}),
})),
**traverse_obj(rss_metadata, ('content', {
'title': ('episode_title', {str}),
diff --git a/yt_dlp/extractor/arte.py b/yt_dlp/extractor/arte.py
index 46fe006cc..142d4b066 100644
--- a/yt_dlp/extractor/arte.py
+++ b/yt_dlp/extractor/arte.py
@@ -20,15 +20,15 @@ class ArteTVBaseIE(InfoExtractor):
class ArteTVIE(ArteTVBaseIE):
- _VALID_URL = r'''(?x)
+ _VALID_URL = rf'''(?x)
(?:https?://
(?:
- (?:www\.)?arte\.tv/(?P<lang>%(langs)s)/videos|
- api\.arte\.tv/api/player/v\d+/config/(?P<lang_2>%(langs)s)
+ (?:www\.)?arte\.tv/(?P<lang>{ArteTVBaseIE._ARTE_LANGUAGES})/videos|
+ api\.arte\.tv/api/player/v\d+/config/(?P<lang_2>{ArteTVBaseIE._ARTE_LANGUAGES})
)
|arte://program)
- /(?P<id>\d{6}-\d{3}-[AF]|LIVE)
- ''' % {'langs': ArteTVBaseIE._ARTE_LANGUAGES}
+ /(?P<id>\d{{6}}-\d{{3}}-[AF]|LIVE)
+ '''
_TESTS = [{
'url': 'https://www.arte.tv/en/videos/088501-000-A/mexico-stealing-petrol-to-survive/',
'only_matching': True,
@@ -145,7 +145,7 @@ class ArteTVIE(ArteTVBaseIE):
language_code = self._LANG_MAP.get(lang)
config = self._download_json(f'{self._API_BASE}/config/{lang}/{video_id}', video_id, headers={
- 'x-validated-age': '18'
+ 'x-validated-age': '18',
})
geoblocking = traverse_obj(config, ('data', 'attributes', 'restriction', 'geoblocking')) or {}
@@ -247,7 +247,7 @@ class ArteTVEmbedIE(InfoExtractor):
'description': 'md5:be40b667f45189632b78c1425c7c2ce1',
'upload_date': '20201116',
},
- 'skip': 'No video available'
+ 'skip': 'No video available',
}, {
'url': 'https://www.arte.tv/player/v3/index.php?json_url=https://api.arte.tv/api/player/v2/config/de/100605-013-A',
'only_matching': True,
@@ -262,7 +262,7 @@ class ArteTVEmbedIE(InfoExtractor):
class ArteTVPlaylistIE(ArteTVBaseIE):
- _VALID_URL = r'https?://(?:www\.)?arte\.tv/(?P<lang>%s)/videos/(?P<id>RC-\d{6})' % ArteTVBaseIE._ARTE_LANGUAGES
+ _VALID_URL = rf'https?://(?:www\.)?arte\.tv/(?P<lang>{ArteTVBaseIE._ARTE_LANGUAGES})/videos/(?P<id>RC-\d{{6}})'
_TESTS = [{
'url': 'https://www.arte.tv/en/videos/RC-016954/earn-a-living/',
'only_matching': True,
@@ -298,7 +298,7 @@ class ArteTVPlaylistIE(ArteTVBaseIE):
class ArteTVCategoryIE(ArteTVBaseIE):
- _VALID_URL = r'https?://(?:www\.)?arte\.tv/(?P<lang>%s)/videos/(?P<id>[\w-]+(?:/[\w-]+)*)/?\s*$' % ArteTVBaseIE._ARTE_LANGUAGES
+ _VALID_URL = rf'https?://(?:www\.)?arte\.tv/(?P<lang>{ArteTVBaseIE._ARTE_LANGUAGES})/videos/(?P<id>[\w-]+(?:/[\w-]+)*)/?\s*$'
_TESTS = [{
'url': 'https://www.arte.tv/en/videos/politics-and-society/',
'info_dict': {
@@ -312,7 +312,7 @@ class ArteTVCategoryIE(ArteTVBaseIE):
@classmethod
def suitable(cls, url):
return (
- not any(ie.suitable(url) for ie in (ArteTVIE, ArteTVPlaylistIE, ))
+ not any(ie.suitable(url) for ie in (ArteTVIE, ArteTVPlaylistIE))
and super().suitable(url))
def _real_extract(self, url):
@@ -321,12 +321,12 @@ class ArteTVCategoryIE(ArteTVBaseIE):
items = []
for video in re.finditer(
- r'<a\b[^>]*?href\s*=\s*(?P<q>"|\'|\b)(?P<url>https?://www\.arte\.tv/%s/videos/[\w/-]+)(?P=q)' % lang,
+ rf'<a\b[^>]*?href\s*=\s*(?P<q>"|\'|\b)(?P<url>https?://www\.arte\.tv/{lang}/videos/[\w/-]+)(?P=q)',
webpage):
video = video.group('url')
if video == url:
continue
- if any(ie.suitable(video) for ie in (ArteTVIE, ArteTVPlaylistIE, )):
+ if any(ie.suitable(video) for ie in (ArteTVIE, ArteTVPlaylistIE)):
items.append(video)
title = strip_or_none(self._generic_title('', webpage, default='').rsplit('|', 1)[0]) or None
diff --git a/yt_dlp/extractor/atresplayer.py b/yt_dlp/extractor/atresplayer.py
index 3a44e5265..7c8139714 100644
--- a/yt_dlp/extractor/atresplayer.py
+++ b/yt_dlp/extractor/atresplayer.py
@@ -20,7 +20,7 @@ class AtresPlayerIE(InfoExtractor):
'description': 'md5:7634cdcb4d50d5381bedf93efb537fbc',
'duration': 3413,
},
- 'skip': 'This video is only available for registered users'
+ 'skip': 'This video is only available for registered users',
},
{
'url': 'https://www.atresplayer.com/lasexta/programas/el-club-de-la-comedia/temporada-4/capitulo-10-especial-solidario-nochebuena_5ad08edf986b2855ed47adc4/',
@@ -49,7 +49,7 @@ class AtresPlayerIE(InfoExtractor):
target_url = self._download_json(
'https://account.atresmedia.com/api/login', None,
'Logging in', headers={
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
}, data=urlencode_postdata({
'username': username,
'password': password,
diff --git a/yt_dlp/extractor/atscaleconf.py b/yt_dlp/extractor/atscaleconf.py
index 3f7b1e9f8..b219eeec5 100644
--- a/yt_dlp/extractor/atscaleconf.py
+++ b/yt_dlp/extractor/atscaleconf.py
@@ -12,7 +12,7 @@ class AtScaleConfEventIE(InfoExtractor):
'info_dict': {
'id': 'data-scale-spring-2022',
'title': 'Data @Scale Spring 2022',
- 'description': 'md5:7d7ca1c42ac9c6d8a785092a1aea4b55'
+ 'description': 'md5:7d7ca1c42ac9c6d8a785092a1aea4b55',
},
}, {
'url': 'https://atscaleconference.com/events/video-scale-2021/',
@@ -20,15 +20,15 @@ class AtScaleConfEventIE(InfoExtractor):
'info_dict': {
'id': 'video-scale-2021',
'title': 'Video @Scale 2021',
- 'description': 'md5:7d7ca1c42ac9c6d8a785092a1aea4b55'
+ 'description': 'md5:7d7ca1c42ac9c6d8a785092a1aea4b55',
},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ playlist_id = self._match_id(url)
+ webpage = self._download_webpage(url, playlist_id)
return self.playlist_from_matches(
re.findall(r'data-url\s*=\s*"(https?://(?:www\.)?atscaleconference\.com/videos/[^"]+)"', webpage),
- ie='Generic', playlist_id=id,
+ ie='Generic', playlist_id=playlist_id,
title=self._og_search_title(webpage), description=self._og_search_description(webpage))
diff --git a/yt_dlp/extractor/atvat.py b/yt_dlp/extractor/atvat.py
index 20ee34cca..37bb61695 100644
--- a/yt_dlp/extractor/atvat.py
+++ b/yt_dlp/extractor/atvat.py
@@ -19,7 +19,7 @@ class ATVAtIE(InfoExtractor):
'id': 'v-ce9cgn1e70n5-1',
'ext': 'mp4',
'title': 'Bauer sucht Frau - Staffel 18 Folge 3 - Die Hofwochen',
- }
+ },
}, {
'url': 'https://www.atv.at/tv/bauer-sucht-frau/staffel-18/episode-01/bauer-sucht-frau-staffel-18-vorstellungsfolge-1',
'only_matching': True,
@@ -66,10 +66,10 @@ class ATVAtIE(InfoExtractor):
video_id=video_id)
video_title = json_data['views']['default']['page']['title']
- contentResource = json_data['views']['default']['page']['contentResource']
- content_id = contentResource[0]['id']
- content_ids = [{'id': id, 'subclip_start': content['start'], 'subclip_end': content['end']}
- for id, content in enumerate(contentResource)]
+ content_resource = json_data['views']['default']['page']['contentResource']
+ content_id = content_resource[0]['id']
+ content_ids = [{'id': id_, 'subclip_start': content['start'], 'subclip_end': content['end']}
+ for id_, content in enumerate(content_resource)]
time_of_request = dt.datetime.now()
not_before = time_of_request - dt.timedelta(minutes=5)
@@ -87,17 +87,17 @@ class ATVAtIE(InfoExtractor):
videos = self._download_json(
'https://vas-v4.p7s1video.net/4.0/getsources',
content_id, 'Downloading videos JSON', query={
- 'token': jwt_token.decode('utf-8')
+ 'token': jwt_token.decode('utf-8'),
})
- video_id, videos_data = list(videos['data'].items())[0]
+ video_id, videos_data = next(iter(videos['data'].items()))
error_msg = try_get(videos_data, lambda x: x['error']['title'])
if error_msg == 'Geo check failed':
self.raise_geo_restricted(error_msg)
elif error_msg:
raise ExtractorError(error_msg)
entries = [
- self._extract_video_info(url, contentResource[video['id']], video)
+ self._extract_video_info(url, content_resource[video['id']], video)
for video in videos_data]
return {
diff --git a/yt_dlp/extractor/audimedia.py b/yt_dlp/extractor/audimedia.py
index 35114e545..c5a9c7e29 100644
--- a/yt_dlp/extractor/audimedia.py
+++ b/yt_dlp/extractor/audimedia.py
@@ -19,7 +19,7 @@ class AudiMediaIE(InfoExtractor):
'timestamp': 1448354940,
'duration': 74022,
'view_count': int,
- }
+ },
}, {
'url': 'https://www.audi-mediacenter.com/en/audimediatv/video/60-seconds-of-audi-sport-104-2015-wec-bahrain-rookie-test-2991',
'only_matching': True,
@@ -73,7 +73,7 @@ class AudiMediaIE(InfoExtractor):
bitrate = self._search_regex(r'(\d+)k', video_version_url, 'bitrate', default=None)
if bitrate:
f.update({
- 'format_id': 'http-%s' % bitrate,
+ 'format_id': f'http-{bitrate}',
})
formats.append(f)
diff --git a/yt_dlp/extractor/audioboom.py b/yt_dlp/extractor/audioboom.py
index a23fcd299..751b74add 100644
--- a/yt_dlp/extractor/audioboom.py
+++ b/yt_dlp/extractor/audioboom.py
@@ -15,7 +15,7 @@ class AudioBoomIE(InfoExtractor):
'duration': 4000.99,
'uploader': 'Sue Perkins: An hour or so with...',
'uploader_url': r're:https?://(?:www\.)?audioboom\.com/channel/perkins',
- }
+ },
}, { # Direct mp3-file link
'url': 'https://audioboom.com/posts/8128496.mp3',
'md5': 'e329edf304d450def95c7f86a9165ee1',
@@ -27,7 +27,7 @@ class AudioBoomIE(InfoExtractor):
'duration': 1689.7,
'uploader': 'Lost Dot Podcast: The Trans Pyrenees and Transcontinental Race',
'uploader_url': r're:https?://(?:www\.)?audioboom\.com/channels/5003904',
- }
+ },
}, {
'url': 'https://audioboom.com/posts/4279833-3-09-2016-czaban-hour-3?t=0',
'only_matching': True,
diff --git a/yt_dlp/extractor/audiodraft.py b/yt_dlp/extractor/audiodraft.py
index 71e5afd8c..484ad4e1a 100644
--- a/yt_dlp/extractor/audiodraft.py
+++ b/yt_dlp/extractor/audiodraft.py
@@ -9,7 +9,7 @@ class AudiodraftBaseIE(InfoExtractor):
headers={
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
- }, data=f'id={player_entry_id}'.encode('utf-8'))
+ }, data=f'id={player_entry_id}'.encode())
return {
'id': str(data_json['entry_id']),
@@ -65,9 +65,10 @@ class AudiodraftCustomIE(AudiodraftBaseIE):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
- player_entry_id = self._search_regex(r'playAudio\(\'(player_entry_\d+)\'\);', webpage, id, 'play entry id')
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
+ player_entry_id = self._search_regex(
+ r'playAudio\(\'(player_entry_\d+)\'\);', webpage, video_id, 'play entry id')
return self._audiodraft_extract_from_id(player_entry_id)
@@ -89,5 +90,5 @@ class AudiodraftGenericIE(AudiodraftBaseIE):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- return self._audiodraft_extract_from_id(f'player_entry_{id}')
+ video_id = self._match_id(url)
+ return self._audiodraft_extract_from_id(f'player_entry_{video_id}')
diff --git a/yt_dlp/extractor/audiomack.py b/yt_dlp/extractor/audiomack.py
index 5c4160fe4..1d4460c9f 100644
--- a/yt_dlp/extractor/audiomack.py
+++ b/yt_dlp/extractor/audiomack.py
@@ -3,7 +3,6 @@ import time
from .common import InfoExtractor
from .soundcloud import SoundcloudIE
-from ..compat import compat_str
from ..utils import (
ExtractorError,
url_basename,
@@ -22,8 +21,8 @@ class AudiomackIE(InfoExtractor):
'id': '310086',
'ext': 'mp3',
'uploader': 'Roosh Williams',
- 'title': 'Extraordinary'
- }
+ 'title': 'Extraordinary',
+ },
},
# audiomack wrapper around soundcloud song
# Needs new test URL.
@@ -56,7 +55,7 @@ class AudiomackIE(InfoExtractor):
# API is inconsistent with errors
if 'url' not in api_response or not api_response['url'] or 'error' in api_response:
- raise ExtractorError('Invalid url %s' % url)
+ raise ExtractorError(f'Invalid url {url}')
# Audiomack wraps a lot of soundcloud tracks in their branded wrapper
# if so, pass the work off to the soundcloud extractor
@@ -64,7 +63,7 @@ class AudiomackIE(InfoExtractor):
return self.url_result(api_response['url'], SoundcloudIE.ie_key())
return {
- 'id': compat_str(api_response.get('id', album_url_tag)),
+ 'id': str(api_response.get('id', album_url_tag)),
'uploader': api_response.get('artist'),
'title': api_response.get('title'),
'url': api_response['url'],
@@ -82,8 +81,8 @@ class AudiomackAlbumIE(InfoExtractor):
'info_dict':
{
'id': '812251',
- 'title': 'Tha Tour: Part 2 (Official Mixtape)'
- }
+ 'title': 'Tha Tour: Part 2 (Official Mixtape)',
+ },
},
# Album playlist ripped from fakeshoredrive with no metadata
{
@@ -98,16 +97,16 @@ class AudiomackAlbumIE(InfoExtractor):
'id': '837576',
'ext': 'mp3',
'uploader': 'Lil Herb a.k.a. G Herbo',
- }
+ },
}, {
'info_dict': {
'title': 'PPP (Pistol P Project) - 10. 4 Minutes Of Hell Part 4 (prod by DY OF 808 MAFIA)',
'id': '837580',
'ext': 'mp3',
'uploader': 'Lil Herb a.k.a. G Herbo',
- }
+ },
}],
- }
+ },
]
def _real_extract(self, url):
@@ -123,12 +122,12 @@ class AudiomackAlbumIE(InfoExtractor):
api_response = self._download_json(
'http://www.audiomack.com/api/music/url/album/%s/%d?extended=1&_=%d'
% (album_url_tag, track_no, time.time()), album_url_tag,
- note='Querying song information (%d)' % (track_no + 1))
+ note=f'Querying song information ({track_no + 1})')
# Total failure, only occurs when url is totally wrong
# Won't happen in middle of valid playlist (next case)
if 'url' not in api_response or 'error' in api_response:
- raise ExtractorError('Invalid url for track %d of album url %s' % (track_no, url))
+ raise ExtractorError(f'Invalid url for track {track_no} of album url {url}')
# URL is good but song id doesn't exist - usually means end of playlist
elif not api_response['url']:
break
@@ -136,10 +135,10 @@ class AudiomackAlbumIE(InfoExtractor):
# Pull out the album metadata and add to result (if it exists)
for resultkey, apikey in [('id', 'album_id'), ('title', 'album_title')]:
if apikey in api_response and resultkey not in result:
- result[resultkey] = compat_str(api_response[apikey])
+ result[resultkey] = str(api_response[apikey])
song_id = url_basename(api_response['url']).rpartition('.')[0]
result['entries'].append({
- 'id': compat_str(api_response.get('id', song_id)),
+ 'id': str(api_response.get('id', song_id)),
'uploader': api_response.get('artist'),
'title': api_response.get('title', song_id),
'url': api_response['url'],
diff --git a/yt_dlp/extractor/audius.py b/yt_dlp/extractor/audius.py
index 6448b449b..c611c6e08 100644
--- a/yt_dlp/extractor/audius.py
+++ b/yt_dlp/extractor/audius.py
@@ -1,7 +1,7 @@
import random
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_str, compat_urllib_parse_unquote
from ..utils import ExtractorError, str_or_none, try_get
@@ -15,13 +15,13 @@ class AudiusBaseIE(InfoExtractor):
if response_data is not None:
return response_data
if len(response) == 1 and 'message' in response:
- raise ExtractorError('API error: %s' % response['message'],
+ raise ExtractorError('API error: {}'.format(response['message']),
expected=True)
raise ExtractorError('Unexpected API response')
def _select_api_base(self):
"""Selecting one of the currently available API hosts"""
- response = super(AudiusBaseIE, self)._download_json(
+ response = super()._download_json(
'https://api.audius.co/', None,
note='Requesting available API hosts',
errnote='Unable to request available API hosts')
@@ -41,8 +41,8 @@ class AudiusBaseIE(InfoExtractor):
anything from this link, since the Audius API won't be able to resolve
this url
"""
- url = compat_urllib_parse_unquote(url)
- title = compat_urllib_parse_unquote(title)
+ url = urllib.parse.unquote(url)
+ title = urllib.parse.unquote(title)
if '/' in title or '%2F' in title:
fixed_title = title.replace('/', '%5C').replace('%2F', '%5C')
return url.replace(title, fixed_title)
@@ -54,19 +54,19 @@ class AudiusBaseIE(InfoExtractor):
if self._API_BASE is None:
self._select_api_base()
try:
- response = super(AudiusBaseIE, self)._download_json(
- '%s%s%s' % (self._API_BASE, self._API_V, path), item_id, note=note,
+ response = super()._download_json(
+ f'{self._API_BASE}{self._API_V}{path}', item_id, note=note,
errnote=errnote, expected_status=expected_status)
except ExtractorError as exc:
# some of Audius API hosts may not work as expected and return HTML
- if 'Failed to parse JSON' in compat_str(exc):
+ if 'Failed to parse JSON' in str(exc):
raise ExtractorError('An error occurred while receiving data. Try again',
expected=True)
raise exc
return self._get_response_data(response)
def _resolve_url(self, url, item_id):
- return self._api_request('/resolve?url=%s' % url, item_id,
+ return self._api_request(f'/resolve?url={url}', item_id,
expected_status=404)
@@ -91,7 +91,7 @@ class AudiusIE(AudiusBaseIE):
'view_count': int,
'like_count': int,
'repost_count': int,
- }
+ },
},
{
# Regular track
@@ -109,14 +109,14 @@ class AudiusIE(AudiusBaseIE):
'view_count': int,
'like_count': int,
'repost_count': int,
- }
+ },
},
]
_ARTWORK_MAP = {
- "150x150": 150,
- "480x480": 480,
- "1000x1000": 1000
+ '150x150': 150,
+ '480x480': 480,
+ '1000x1000': 1000,
}
def _real_extract(self, url):
@@ -130,7 +130,7 @@ class AudiusIE(AudiusBaseIE):
else: # API link
title = None
# uploader = None
- track_data = self._api_request('/tracks/%s' % track_id, track_id)
+ track_data = self._api_request(f'/tracks/{track_id}', track_id)
if not isinstance(track_data, dict):
raise ExtractorError('Unexpected API response')
@@ -144,7 +144,7 @@ class AudiusIE(AudiusBaseIE):
if isinstance(artworks_data, dict):
for quality_key, thumbnail_url in artworks_data.items():
thumbnail = {
- "url": thumbnail_url
+ 'url': thumbnail_url,
}
quality_code = self._ARTWORK_MAP.get(quality_key)
if quality_code is not None:
@@ -154,12 +154,12 @@ class AudiusIE(AudiusBaseIE):
return {
'id': track_id,
'title': track_data.get('title', title),
- 'url': '%s/v1/tracks/%s/stream' % (self._API_BASE, track_id),
+ 'url': f'{self._API_BASE}/v1/tracks/{track_id}/stream',
'ext': 'mp3',
'description': track_data.get('description'),
'duration': track_data.get('duration'),
'track': track_data.get('title'),
- 'artist': try_get(track_data, lambda x: x['user']['name'], compat_str),
+ 'artist': try_get(track_data, lambda x: x['user']['name'], str),
'genre': track_data.get('genre'),
'thumbnails': thumbnails,
'view_count': track_data.get('play_count'),
@@ -175,11 +175,11 @@ class AudiusTrackIE(AudiusIE): # XXX: Do not subclass from concrete IE
_TESTS = [
{
'url': 'audius:9RWlo',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'audius:http://discoveryprovider.audius.prod-us-west-2.staked.cloud/v1/tracks/9RWlo',
- 'only_matching': True
+ 'only_matching': True,
},
]
@@ -207,7 +207,7 @@ class AudiusPlaylistIE(AudiusBaseIE):
if not track_id:
raise ExtractorError('Unable to get track ID from playlist')
entries.append(self.url_result(
- 'audius:%s' % track_id,
+ f'audius:{track_id}',
ie=AudiusTrackIE.ie_key(), video_id=track_id))
return entries
@@ -231,7 +231,7 @@ class AudiusPlaylistIE(AudiusBaseIE):
raise ExtractorError('Unable to get playlist ID')
playlist_tracks = self._api_request(
- '/playlists/%s/tracks' % playlist_id,
+ f'/playlists/{playlist_id}/tracks',
title, note='Downloading playlist tracks metadata',
errnote='Unable to download playlist tracks metadata')
if not isinstance(playlist_tracks, list):
@@ -267,5 +267,5 @@ class AudiusProfileIE(AudiusPlaylistIE): # XXX: Do not subclass from concrete I
profile_audius_id = _profile_data[0]['id']
profile_bio = _profile_data[0].get('bio')
- api_call = self._api_request('/full/users/handle/%s/tracks' % profile_id, profile_id)
+ api_call = self._api_request(f'/full/users/handle/{profile_id}/tracks', profile_id)
return self.playlist_result(self._build_playlist(api_call), profile_audius_id, profile_id, profile_bio)
diff --git a/yt_dlp/extractor/awaan.py b/yt_dlp/extractor/awaan.py
index a8dfb3efc..4066a5a83 100644
--- a/yt_dlp/extractor/awaan.py
+++ b/yt_dlp/extractor/awaan.py
@@ -1,10 +1,7 @@
import base64
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_urlencode,
-)
from ..utils import (
format_field,
int_or_none,
@@ -22,14 +19,14 @@ class AWAANIE(InfoExtractor):
show_id, video_id, season_id = self._match_valid_url(url).groups()
if video_id and int(video_id) > 0:
return self.url_result(
- 'http://awaan.ae/media/%s' % video_id, 'AWAANVideo')
+ f'http://awaan.ae/media/{video_id}', 'AWAANVideo')
elif season_id and int(season_id) > 0:
return self.url_result(smuggle_url(
- 'http://awaan.ae/program/season/%s' % season_id,
+ f'http://awaan.ae/program/season/{season_id}',
{'show_id': show_id}), 'AWAANSeason')
else:
return self.url_result(
- 'http://awaan.ae/program/%s' % show_id, 'AWAANSeason')
+ f'http://awaan.ae/program/{show_id}', 'AWAANSeason')
class AWAANBaseIE(InfoExtractor):
@@ -75,11 +72,11 @@ class AWAANVideoIE(AWAANBaseIE):
video_id = self._match_id(url)
video_data = self._download_json(
- 'http://admin.mangomolo.com/analytics/index.php/plus/video?id=%s' % video_id,
+ f'http://admin.mangomolo.com/analytics/index.php/plus/video?id={video_id}',
video_id, headers={'Origin': 'http://awaan.ae'})
info = self._parse_video_data(video_data, video_id, False)
- embed_url = 'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' + compat_urllib_parse_urlencode({
+ embed_url = 'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' + urllib.parse.urlencode({
'id': video_data['id'],
'user_id': video_data['user_id'],
'signature': video_data['signature'],
@@ -117,11 +114,11 @@ class AWAANLiveIE(AWAANBaseIE):
channel_id = self._match_id(url)
channel_data = self._download_json(
- 'http://admin.mangomolo.com/analytics/index.php/plus/getchanneldetails?channel_id=%s' % channel_id,
+ f'http://admin.mangomolo.com/analytics/index.php/plus/getchanneldetails?channel_id={channel_id}',
channel_id, headers={'Origin': 'http://awaan.ae'})
info = self._parse_video_data(channel_data, channel_id, True)
- embed_url = 'http://admin.mangomolo.com/analytics/index.php/customers/embed/index?' + compat_urllib_parse_urlencode({
+ embed_url = 'http://admin.mangomolo.com/analytics/index.php/customers/embed/index?' + urllib.parse.urlencode({
'id': base64.b64encode(channel_data['user_id'].encode()).decode(),
'channelid': base64.b64encode(channel_data['id'].encode()).decode(),
'signature': channel_data['signature'],
@@ -159,7 +156,7 @@ class AWAANSeasonIE(InfoExtractor):
show_id = smuggled_data.get('show_id')
if show_id is None:
season = self._download_json(
- 'http://admin.mangomolo.com/analytics/index.php/plus/season_info?id=%s' % season_id,
+ f'http://admin.mangomolo.com/analytics/index.php/plus/season_info?id={season_id}',
season_id, headers={'Origin': 'http://awaan.ae'})
show_id = season['id']
data['show_id'] = show_id
@@ -167,7 +164,7 @@ class AWAANSeasonIE(InfoExtractor):
'http://admin.mangomolo.com/analytics/index.php/plus/show',
show_id, data=urlencode_postdata(data), headers={
'Origin': 'http://awaan.ae',
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
})
if not season_id:
season_id = show['default_season']
@@ -177,8 +174,8 @@ class AWAANSeasonIE(InfoExtractor):
entries = []
for video in show['videos']:
- video_id = compat_str(video['id'])
+ video_id = str(video['id'])
entries.append(self.url_result(
- 'http://awaan.ae/media/%s' % video_id, 'AWAANVideo', video_id))
+ f'http://awaan.ae/media/{video_id}', 'AWAANVideo', video_id))
return self.playlist_result(entries, season_id, title)
diff --git a/yt_dlp/extractor/aws.py b/yt_dlp/extractor/aws.py
index 4ebef9295..177c41027 100644
--- a/yt_dlp/extractor/aws.py
+++ b/yt_dlp/extractor/aws.py
@@ -1,9 +1,9 @@
import datetime as dt
import hashlib
import hmac
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_urlencode
class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor
@@ -18,20 +18,20 @@ class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with
'Accept': 'application/json',
'Host': self._AWS_PROXY_HOST,
'X-Amz-Date': amz_date,
- 'X-Api-Key': self._AWS_API_KEY
+ 'X-Api-Key': self._AWS_API_KEY,
}
session_token = aws_dict.get('session_token')
if session_token:
headers['X-Amz-Security-Token'] = session_token
def aws_hash(s):
- return hashlib.sha256(s.encode('utf-8')).hexdigest()
+ return hashlib.sha256(s.encode()).hexdigest()
# Task 1: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
- canonical_querystring = compat_urllib_parse_urlencode(query)
+ canonical_querystring = urllib.parse.urlencode(query)
canonical_headers = ''
for header_name, header_value in sorted(headers.items()):
- canonical_headers += '%s:%s\n' % (header_name.lower(), header_value)
+ canonical_headers += f'{header_name.lower()}:{header_value}\n'
signed_headers = ';'.join([header.lower() for header in sorted(headers.keys())])
canonical_request = '\n'.join([
'GET',
@@ -39,7 +39,7 @@ class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with
canonical_querystring,
canonical_headers,
signed_headers,
- aws_hash('')
+ aws_hash(''),
])
# Task 2: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
@@ -49,7 +49,7 @@ class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with
# Task 3: http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
def aws_hmac(key, msg):
- return hmac.new(key, msg.encode('utf-8'), hashlib.sha256)
+ return hmac.new(key, msg.encode(), hashlib.sha256)
def aws_hmac_digest(key, msg):
return aws_hmac(key, msg).digest()
@@ -57,7 +57,7 @@ class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with
def aws_hmac_hexdigest(key, msg):
return aws_hmac(key, msg).hexdigest()
- k_signing = ('AWS4' + aws_dict['secret_key']).encode('utf-8')
+ k_signing = ('AWS4' + aws_dict['secret_key']).encode()
for value in credential_scope_list:
k_signing = aws_hmac_digest(k_signing, value)
@@ -65,11 +65,11 @@ class AWSIE(InfoExtractor): # XXX: Conventionally, base classes should end with
# Task 4: http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
headers['Authorization'] = ', '.join([
- '%s Credential=%s/%s' % (self._AWS_ALGORITHM, aws_dict['access_key'], credential_scope),
- 'SignedHeaders=%s' % signed_headers,
- 'Signature=%s' % signature,
+ '{} Credential={}/{}'.format(self._AWS_ALGORITHM, aws_dict['access_key'], credential_scope),
+ f'SignedHeaders={signed_headers}',
+ f'Signature={signature}',
])
return self._download_json(
- 'https://%s%s%s' % (self._AWS_PROXY_HOST, aws_dict['uri'], '?' + canonical_querystring if canonical_querystring else ''),
+ 'https://{}{}{}'.format(self._AWS_PROXY_HOST, aws_dict['uri'], '?' + canonical_querystring if canonical_querystring else ''),
video_id, headers=headers)
diff --git a/yt_dlp/extractor/azmedien.py b/yt_dlp/extractor/azmedien.py
index d1686eed6..0e3a03f03 100644
--- a/yt_dlp/extractor/azmedien.py
+++ b/yt_dlp/extractor/azmedien.py
@@ -38,14 +38,14 @@ class AZMedienIE(InfoExtractor):
'timestamp': 1538328802,
'view_count': int,
'thumbnail': 'http://cfvod.kaltura.com/p/1719221/sp/171922100/thumbnail/entry_id/1_anruz3wy/version/100031',
- 'duration': 1930
+ 'duration': 1930,
},
'params': {
'skip_download': True,
},
}, {
'url': 'https://www.telebaern.tv/telebaern-news/montag-1-oktober-2018-ganze-sendung-133531189#video=0_7xjo9lf1',
- 'only_matching': True
+ 'only_matching': True,
}]
_API_TEMPL = 'https://www.%s/api/pub/gql/%s/NewsArticleTeaser/a4016f65fe62b81dc6664dd9f4910e4ab40383be'
_PARTNER_ID = '1719221'
@@ -62,5 +62,5 @@ class AZMedienIE(InfoExtractor):
})['data']['context']['mainAsset']['video']['kaltura']['kalturaId']
return self.url_result(
- 'kaltura:%s:%s' % (self._PARTNER_ID, entry_id),
+ f'kaltura:{self._PARTNER_ID}:{entry_id}',
ie=KalturaIE.ie_key(), video_id=entry_id)
diff --git a/yt_dlp/extractor/baidu.py b/yt_dlp/extractor/baidu.py
index 8786d67e0..a1ad4240f 100644
--- a/yt_dlp/extractor/baidu.py
+++ b/yt_dlp/extractor/baidu.py
@@ -24,8 +24,9 @@ class BaiduVideoIE(InfoExtractor):
}]
def _call_api(self, path, category, playlist_id, note):
- return self._download_json('http://app.video.baidu.com/%s/?worktype=adnative%s&id=%s' % (
- path, category, playlist_id), playlist_id, note)
+ return self._download_json(
+ f'http://app.video.baidu.com/{path}/?worktype=adnative{category}&id={playlist_id}',
+ playlist_id, note)
def _real_extract(self, url):
category, playlist_id = self._match_valid_url(url).groups()
@@ -44,7 +45,7 @@ class BaiduVideoIE(InfoExtractor):
'xqsingle', category, playlist_id, 'Download episodes JSON metadata')
entries = [self.url_result(
- episode['url'], video_title=episode['title']
+ episode['url'], video_title=episode['title'],
) for episode in episodes_detail['videos']]
return self.playlist_result(
diff --git a/yt_dlp/extractor/banbye.py b/yt_dlp/extractor/banbye.py
index c4e07a79a..d10bdf8da 100644
--- a/yt_dlp/extractor/banbye.py
+++ b/yt_dlp/extractor/banbye.py
@@ -1,10 +1,7 @@
import math
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urllib_parse_urlparse,
-)
from ..utils import (
InAdvancePagedList,
format_field,
@@ -20,8 +17,8 @@ class BanByeBaseIE(InfoExtractor):
@staticmethod
def _extract_playlist_id(url, param='playlist'):
- return compat_parse_qs(
- compat_urllib_parse_urlparse(url).query).get(param, [None])[0]
+ return urllib.parse.parse_qs(
+ urllib.parse.urlparse(url).query).get(param, [None])[0]
def _extract_playlist(self, playlist_id):
data = self._download_json(f'{self._API_BASE}/playlists/{playlist_id}', playlist_id)
diff --git a/yt_dlp/extractor/bandcamp.py b/yt_dlp/extractor/bandcamp.py
index e89b3a69b..6128de791 100644
--- a/yt_dlp/extractor/bandcamp.py
+++ b/yt_dlp/extractor/bandcamp.py
@@ -3,7 +3,6 @@ import re
import time
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
KNOWN_EXTENSIONS,
ExtractorError,
@@ -42,7 +41,7 @@ class BandcampIE(InfoExtractor):
'uploader_id': 'youtube-dl',
'thumbnail': 'https://f4.bcbits.com/img/a3216802731_5.jpg',
},
- '_skip': 'There is a limit of 200 free downloads / month for the test song'
+ '_skip': 'There is a limit of 200 free downloads / month for the test song',
}, {
# free download
'url': 'http://benprunty.bandcamp.com/track/lanius-battle',
@@ -119,7 +118,7 @@ class BandcampIE(InfoExtractor):
def _extract_data_attr(self, webpage, video_id, attr='tralbum', fatal=True):
return self._parse_json(self._html_search_regex(
- r'data-%s=(["\'])({.+?})\1' % attr, webpage,
+ rf'data-{attr}=(["\'])({{.+?}})\1', webpage,
attr + ' data', group=2), video_id, fatal=fatal)
def _real_extract(self, url):
@@ -167,7 +166,7 @@ class BandcampIE(InfoExtractor):
download_link = tralbum.get('freeDownloadPage')
if download_link:
- track_id = compat_str(tralbum['id'])
+ track_id = str(tralbum['id'])
download_webpage = self._download_webpage(
download_link, track_id, 'Downloading free downloads page')
@@ -192,7 +191,7 @@ class BandcampIE(InfoExtractor):
if isinstance(download_formats_list, list):
for f in blob['download_formats']:
name, ext = f.get('name'), f.get('file_extension')
- if all(isinstance(x, compat_str) for x in (name, ext)):
+ if all(isinstance(x, str) for x in (name, ext)):
download_formats[name] = ext.strip('.')
for format_id, f in downloads.items():
@@ -207,7 +206,7 @@ class BandcampIE(InfoExtractor):
})
format_id = f.get('encoding_name') or format_id
stat = self._download_json(
- stat_url, track_id, 'Downloading %s JSON' % format_id,
+ stat_url, track_id, f'Downloading {format_id} JSON',
transform_source=lambda s: s[s.index('{'):s.rindex('}') + 1],
fatal=False)
if not stat:
@@ -225,7 +224,7 @@ class BandcampIE(InfoExtractor):
'acodec': format_id.split('-')[0],
})
- title = '%s - %s' % (artist, track) if artist else track
+ title = f'{artist} - {track}' if artist else track
if not duration:
duration = float_or_none(self._html_search_meta(
@@ -267,7 +266,7 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
'timestamp': 1311756226,
'upload_date': '20110727',
'uploader': 'Blazo',
- }
+ },
},
{
'md5': '1a2c32e2691474643e912cc6cd4bffaa',
@@ -278,7 +277,7 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
'timestamp': 1311757238,
'upload_date': '20110727',
'uploader': 'Blazo',
- }
+ },
},
],
'info_dict': {
@@ -287,9 +286,9 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
'uploader_id': 'blazo',
},
'params': {
- 'playlistend': 2
+ 'playlistend': 2,
},
- 'skip': 'Bandcamp imposes download limits.'
+ 'skip': 'Bandcamp imposes download limits.',
}, {
'url': 'http://nightbringer.bandcamp.com/album/hierophany-of-the-open-grave',
'info_dict': {
@@ -324,7 +323,7 @@ class BandcampAlbumIE(BandcampIE): # XXX: Do not subclass from concrete IE
def suitable(cls, url):
return (False
if BandcampWeeklyIE.suitable(url) or BandcampIE.suitable(url)
- else super(BandcampAlbumIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
uploader_id, album_id = self._match_valid_url(url).groups()
@@ -376,7 +375,7 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
},
}, {
'url': 'https://bandcamp.com/?blah/blah@&show=228',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -407,7 +406,7 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
title = show.get('audio_title') or 'Bandcamp Weekly'
subtitle = show.get('subtitle')
if subtitle:
- title += ' - %s' % subtitle
+ title += f' - {subtitle}'
return {
'id': show_id,
@@ -419,7 +418,7 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
'series': 'Bandcamp Weekly',
'episode': show.get('subtitle'),
'episode_id': show_id,
- 'formats': formats
+ 'formats': formats,
}
@@ -440,7 +439,7 @@ class BandcampUserIE(InfoExtractor):
'url': 'http://dotscale.bandcamp.com',
'info_dict': {
'id': 'dotscale',
- 'title': 'Discography of dotscale'
+ 'title': 'Discography of dotscale',
},
'playlist_count': 1,
}, {
diff --git a/yt_dlp/extractor/bannedvideo.py b/yt_dlp/extractor/bannedvideo.py
index 82dc9ab02..46f2978f7 100644
--- a/yt_dlp/extractor/bannedvideo.py
+++ b/yt_dlp/extractor/bannedvideo.py
@@ -23,7 +23,7 @@ class BannedVideoIE(InfoExtractor):
'description': 'md5:560d96f02abbebe6c6b78b47465f6b28',
'upload_date': '20200324',
'timestamp': 1585087895,
- }
+ },
}]
_GRAPHQL_GETMETADATA_QUERY = '''
@@ -84,15 +84,15 @@ query GetCommentReplies($id: String!) {
'GetCommentReplies': _GRAPHQL_GETCOMMENTSREPLIES_QUERY,
}
- def _call_api(self, video_id, id, operation, note):
+ def _call_api(self, video_id, id_var, operation, note):
return self._download_json(
'https://api.infowarsmedia.com/graphql', video_id, note=note,
headers={
- 'Content-Type': 'application/json; charset=utf-8'
+ 'Content-Type': 'application/json; charset=utf-8',
}, data=json.dumps({
- 'variables': {'id': id},
+ 'variables': {'id': id_var},
'operationName': operation,
- 'query': self._GRAPHQL_QUERIES[operation]
+ 'query': self._GRAPHQL_QUERIES[operation],
}).encode('utf8')).get('data')
def _get_comments(self, video_id, comments, comment_data):
@@ -151,5 +151,5 @@ query GetCommentReplies($id: String!) {
'tags': [tag.get('name') for tag in video_info.get('tags')],
'availability': self._availability(is_unlisted=video_info.get('unlisted')),
'comments': comments,
- '__post_extractor': self.extract_comments(video_id, comments, video_json.get('getVideoComments'))
+ '__post_extractor': self.extract_comments(video_id, comments, video_json.get('getVideoComments')),
}
diff --git a/yt_dlp/extractor/bbc.py b/yt_dlp/extractor/bbc.py
index f6b58b361..3af923f95 100644
--- a/yt_dlp/extractor/bbc.py
+++ b/yt_dlp/extractor/bbc.py
@@ -2,10 +2,10 @@ import functools
import itertools
import json
import re
+import urllib.parse
import xml.etree.ElementTree
from .common import InfoExtractor
-from ..compat import compat_str, compat_urlparse
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -35,7 +35,7 @@ class BBCCoUkIE(InfoExtractor):
IE_NAME = 'bbc.co.uk'
IE_DESC = 'BBC iPlayer'
_ID_REGEX = r'(?:[pbml][\da-z]{7}|w[\da-z]{7,14})'
- _VALID_URL = r'''(?x)
+ _VALID_URL = rf'''(?x)
https?://
(?:www\.)?bbc\.co\.uk/
(?:
@@ -45,8 +45,8 @@ class BBCCoUkIE(InfoExtractor):
radio/player/|
events/[^/]+/play/[^/]+/
)
- (?P<id>%s)(?!/(?:episodes|broadcasts|clips))
- ''' % _ID_REGEX
+ (?P<id>{_ID_REGEX})(?!/(?:episodes|broadcasts|clips))
+ '''
_EMBED_REGEX = [r'setPlaylist\("(?P<url>https?://www\.bbc\.co\.uk/iplayer/[^/]+/[\da-z]{8})"\)']
_LOGIN_URL = 'https://account.bbc.com/signin'
@@ -75,7 +75,7 @@ class BBCCoUkIE(InfoExtractor):
'params': {
# rtmp download
'skip_download': True,
- }
+ },
},
{
'url': 'http://www.bbc.co.uk/iplayer/episode/b00yng5w/The_Man_in_Black_Series_3_The_Printed_Name/',
@@ -148,7 +148,7 @@ class BBCCoUkIE(InfoExtractor):
'params': {
# rtmp download
'skip_download': True,
- }
+ },
}, {
'url': 'http://www.bbc.co.uk/music/clips/p025c0zz',
'note': 'Video',
@@ -162,7 +162,7 @@ class BBCCoUkIE(InfoExtractor):
'params': {
# rtmp download
'skip_download': True,
- }
+ },
}, {
'url': 'http://www.bbc.co.uk/iplayer/episode/b054fn09/ad/natural-world-20152016-2-super-powered-owls',
'info_dict': {
@@ -268,19 +268,19 @@ class BBCCoUkIE(InfoExtractor):
error = clean_html(get_element_by_class('form-message', response))
if error:
raise ExtractorError(
- 'Unable to login: %s' % error, expected=True)
+ f'Unable to login: {error}', expected=True)
raise ExtractorError('Unable to log in')
class MediaSelectionError(Exception):
- def __init__(self, id):
- self.id = id
+ def __init__(self, error_id):
+ self.id = error_id
def _extract_asx_playlist(self, connection, programme_id):
asx = self._download_xml(connection.get('href'), programme_id, 'Downloading ASX playlist')
return [ref.get('href') for ref in asx.findall('./Entry/ref')]
def _extract_items(self, playlist):
- return playlist.findall('./{%s}item' % self._EMP_PLAYLIST_NS)
+ return playlist.findall(f'./{{{self._EMP_PLAYLIST_NS}}}item')
def _extract_medias(self, media_selection):
error = media_selection.get('result')
@@ -312,7 +312,7 @@ class BBCCoUkIE(InfoExtractor):
def _raise_extractor_error(self, media_selection_error):
raise ExtractorError(
- '%s returned error: %s' % (self.IE_NAME, media_selection_error.id),
+ f'{self.IE_NAME} returned error: {media_selection_error.id}',
expected=True)
def _download_media_selector(self, programme_id):
@@ -372,7 +372,7 @@ class BBCCoUkIE(InfoExtractor):
for i, ref in enumerate(self._extract_asx_playlist(connection, programme_id)):
formats.append({
'url': ref,
- 'format_id': 'ref%s_%s' % (i, format_id),
+ 'format_id': f'ref{i}_{format_id}',
})
elif transfer_format == 'dash':
formats.extend(self._extract_mpd_formats(
@@ -394,7 +394,7 @@ class BBCCoUkIE(InfoExtractor):
href, programme_id, f4m_id=format_id, fatal=False))
else:
if not supplier and bitrate:
- format_id += '-%d' % bitrate
+ format_id += f'-{bitrate}'
fmt = {
'format_id': format_id,
'filesize': file_size,
@@ -423,9 +423,9 @@ class BBCCoUkIE(InfoExtractor):
identifier = connection.get('identifier')
server = connection.get('server')
fmt.update({
- 'url': '%s://%s/%s?%s' % (protocol, server, application, auth_string),
+ 'url': f'{protocol}://{server}/{application}?{auth_string}',
'play_path': identifier,
- 'app': '%s?%s' % (application, auth_string),
+ 'app': f'{application}?{auth_string}',
'page_url': 'http://www.bbc.co.uk',
'player_url': 'http://www.bbc.co.uk/emp/releases/iplayer/revisions/617463_618125_4/617463_618125_4_emp.swf',
'rtmp_live': False,
@@ -441,7 +441,7 @@ class BBCCoUkIE(InfoExtractor):
def _download_playlist(self, playlist_id):
try:
playlist = self._download_json(
- 'http://www.bbc.co.uk/programmes/%s/playlist.json' % playlist_id,
+ f'http://www.bbc.co.uk/programmes/{playlist_id}/playlist.json',
playlist_id, 'Downloading playlist JSON')
formats = []
subtitles = {}
@@ -480,32 +480,32 @@ class BBCCoUkIE(InfoExtractor):
def _process_legacy_playlist(self, playlist_id):
return self._process_legacy_playlist_url(
- 'http://www.bbc.co.uk/iplayer/playlist/%s' % playlist_id, playlist_id)
+ f'http://www.bbc.co.uk/iplayer/playlist/{playlist_id}', playlist_id)
def _download_legacy_playlist_url(self, url, playlist_id=None):
return self._download_xml(
url, playlist_id, 'Downloading legacy playlist XML')
def _extract_from_legacy_playlist(self, playlist, playlist_id):
- no_items = playlist.find('./{%s}noItems' % self._EMP_PLAYLIST_NS)
+ no_items = playlist.find(f'./{{{self._EMP_PLAYLIST_NS}}}noItems')
if no_items is not None:
reason = no_items.get('reason')
if reason == 'preAvailability':
- msg = 'Episode %s is not yet available' % playlist_id
+ msg = f'Episode {playlist_id} is not yet available'
elif reason == 'postAvailability':
- msg = 'Episode %s is no longer available' % playlist_id
+ msg = f'Episode {playlist_id} is no longer available'
elif reason == 'noMedia':
- msg = 'Episode %s is not currently available' % playlist_id
+ msg = f'Episode {playlist_id} is not currently available'
else:
- msg = 'Episode %s is not available: %s' % (playlist_id, reason)
+ msg = f'Episode {playlist_id} is not available: {reason}'
raise ExtractorError(msg, expected=True)
for item in self._extract_items(playlist):
kind = item.get('kind')
if kind not in ('programme', 'radioProgramme'):
continue
- title = playlist.find('./{%s}title' % self._EMP_PLAYLIST_NS).text
- description_el = playlist.find('./{%s}summary' % self._EMP_PLAYLIST_NS)
+ title = playlist.find(f'./{{{self._EMP_PLAYLIST_NS}}}title').text
+ description_el = playlist.find(f'./{{{self._EMP_PLAYLIST_NS}}}summary')
description = description_el.text if description_el is not None else None
def get_programme_id(item):
@@ -515,7 +515,7 @@ class BBCCoUkIE(InfoExtractor):
if value and re.match(r'^[pb][\da-z]{7}$', value):
return value
get_from_attributes(item)
- mediator = item.find('./{%s}mediator' % self._EMP_PLAYLIST_NS)
+ mediator = item.find(f'./{{{self._EMP_PLAYLIST_NS}}}mediator')
if mediator is not None:
return get_from_attributes(mediator)
@@ -555,7 +555,7 @@ class BBCCoUkIE(InfoExtractor):
if not programme_id:
programme_id = self._search_regex(
- r'"vpid"\s*:\s*"(%s)"' % self._ID_REGEX, webpage, 'vpid', fatal=False, default=None)
+ rf'"vpid"\s*:\s*"({self._ID_REGEX})"', webpage, 'vpid', fatal=False, default=None)
if programme_id:
formats, subtitles = self._download_media_selector(programme_id)
@@ -641,7 +641,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
},
'params': {
'skip_download': True,
- }
+ },
}, {
# article with single video embedded with data-playable containing XML playlist
# with direct video links as progressiveDownloadUrl (for now these are extracted)
@@ -884,7 +884,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
'uploader_id': 'bbc_world_service',
'series': 'CrowdScience',
'chapters': [],
- }
+ },
}, { # onion routes
'url': 'https://www.bbcnewsd73hkzno2ini43t4gblxvycyac5aw4gnv7t2rccijh7745uqd.onion/news/av/world-europe-63208576',
'only_matching': True,
@@ -897,7 +897,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
def suitable(cls, url):
EXCLUDE_IE = (BBCCoUkIE, BBCCoUkArticleIE, BBCCoUkIPlayerEpisodesIE, BBCCoUkIPlayerGroupIE, BBCCoUkPlaylistIE)
return (False if any(ie.suitable(url) for ie in EXCLUDE_IE)
- else super(BBCIE, cls).suitable(url))
+ else super().suitable(url))
def _extract_from_media_meta(self, media_meta, video_id):
# Direct links to media in media metadata (e.g.
@@ -1009,7 +1009,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
if playlist:
entry = None
for key in ('streaming', 'progressiveDownload'):
- playlist_url = playlist.get('%sUrl' % key)
+ playlist_url = playlist.get(f'{key}Url')
if not playlist_url:
continue
try:
@@ -1035,7 +1035,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
# http://www.bbc.co.uk/learningenglish/chinese/features/lingohack/ep-181227
group_id = self._search_regex(
- r'<div[^>]+\bclass=["\']video["\'][^>]+\bdata-pid=["\'](%s)' % self._ID_REGEX,
+ rf'<div[^>]+\bclass=["\']video["\'][^>]+\bdata-pid=["\']({self._ID_REGEX})',
webpage, 'group id', default=None)
if group_id:
return self.url_result(
@@ -1043,9 +1043,9 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
# single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
programme_id = self._search_regex(
- [r'data-(?:video-player|media)-vpid="(%s)"' % self._ID_REGEX,
- r'<param[^>]+name="externalIdentifier"[^>]+value="(%s)"' % self._ID_REGEX,
- r'videoId\s*:\s*["\'](%s)["\']' % self._ID_REGEX],
+ [rf'data-(?:video-player|media)-vpid="({self._ID_REGEX})"',
+ rf'<param[^>]+name="externalIdentifier"[^>]+value="({self._ID_REGEX})"',
+ rf'videoId\s*:\s*["\']({self._ID_REGEX})["\']'],
webpage, 'vpid', default=None)
if programme_id:
@@ -1142,7 +1142,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
video_id, url_transparent=True)
entry.update({
'timestamp': traverse_obj(morph_payload, (
- 'body', 'content', 'article', 'dateTimeInfo', 'dateTime', {parse_iso8601})
+ 'body', 'content', 'article', 'dateTimeInfo', 'dateTime', {parse_iso8601}),
),
**traverse_obj(video_data, {
'thumbnail': (('iChefImage', 'image'), {url_or_none}, any),
@@ -1189,7 +1189,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
'primary', 'secondary', 'tertiary', delim=' - ', from_dict=x)}),
'start_time': ('offset', 'start', {float_or_none}),
'end_time': ('offset', 'end', {float_or_none}),
- })
+ }),
),
}
@@ -1287,7 +1287,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
'description': ('synopses', ('long', 'medium', 'short'), {str}, {lambda x: x or None}, any),
'duration': ('versions', 0, 'duration', {int}),
'timestamp': ('versions', 0, 'availableFrom', {functools.partial(int_or_none, scale=1000)}),
- })
+ }),
}
def is_type(*types):
@@ -1331,7 +1331,7 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
if blocks:
summary = []
for block in blocks:
- text = try_get(block, lambda x: x['model']['text'], compat_str)
+ text = try_get(block, lambda x: x['model']['text'], str)
if text:
summary.append(text)
if summary:
@@ -1411,9 +1411,9 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
entries, playlist_id, playlist_title, playlist_description)
def extract_all(pattern):
- return list(filter(None, map(
- lambda s: self._parse_json(s, playlist_id, fatal=False),
- re.findall(pattern, webpage))))
+ return list(filter(None, (
+ self._parse_json(s, playlist_id, fatal=False)
+ for s in re.findall(pattern, webpage))))
# US accessed article with single embedded video (e.g.
# https://www.bbc.com/news/uk-68546268)
@@ -1435,14 +1435,14 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
# Multiple video article (e.g.
# http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
- EMBED_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+%s(?:\b[^"]+)?' % self._ID_REGEX
+ EMBED_URL = rf'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+{self._ID_REGEX}(?:\b[^"]+)?'
entries = []
for match in extract_all(r'new\s+SMP\(({.+?})\)'):
embed_url = match.get('playerSettings', {}).get('externalEmbedUrl')
if embed_url and re.match(EMBED_URL, embed_url):
entries.append(embed_url)
entries.extend(re.findall(
- r'setPlaylist\("(%s)"\)' % EMBED_URL, webpage))
+ rf'setPlaylist\("({EMBED_URL})"\)', webpage))
if entries:
return self.playlist_result(
[self.url_result(entry_, 'BBCCoUk') for entry_ in entries],
@@ -1492,11 +1492,11 @@ class BBCIE(BBCCoUkIE): # XXX: Do not subclass from concrete IE
video_id = media_meta.get('externalId')
if not video_id:
- video_id = playlist_id if len(medias) == 1 else '%s-%s' % (playlist_id, num)
+ video_id = playlist_id if len(medias) == 1 else f'{playlist_id}-{num}'
title = media_meta.get('caption')
if not title:
- title = playlist_title if len(medias) == 1 else '%s - Video %s' % (playlist_title, num)
+ title = playlist_title if len(medias) == 1 else f'{playlist_title} - Video {num}'
duration = int_or_none(media_meta.get('durationInSeconds')) or parse_duration(media_meta.get('duration'))
@@ -1557,8 +1557,8 @@ class BBCCoUkArticleIE(InfoExtractor):
class BBCCoUkPlaylistBaseIE(InfoExtractor):
def _entries(self, webpage, url, playlist_id):
- single_page = 'page' in compat_urlparse.parse_qs(
- compat_urlparse.urlparse(url).query)
+ single_page = 'page' in urllib.parse.parse_qs(
+ urllib.parse.urlparse(url).query)
for page_num in itertools.count(2):
for video_id in re.findall(
self._VIDEO_ID_TEMPLATE % BBCCoUkIE._ID_REGEX, webpage):
@@ -1572,8 +1572,8 @@ class BBCCoUkPlaylistBaseIE(InfoExtractor):
if not next_page:
break
webpage = self._download_webpage(
- compat_urlparse.urljoin(url, next_page), playlist_id,
- 'Downloading page %d' % page_num, page_num)
+ urllib.parse.urljoin(url, next_page), playlist_id,
+ f'Downloading page {page_num}', page_num)
def _real_extract(self, url):
playlist_id = self._match_id(url)
@@ -1588,7 +1588,7 @@ class BBCCoUkPlaylistBaseIE(InfoExtractor):
class BBCCoUkIPlayerPlaylistBaseIE(InfoExtractor):
- _VALID_URL_TMPL = r'https?://(?:www\.)?bbc\.co\.uk/iplayer/%%s/(?P<id>%s)' % BBCCoUkIE._ID_REGEX
+ _VALID_URL_TMPL = rf'https?://(?:www\.)?bbc\.co\.uk/iplayer/%s/(?P<id>{BBCCoUkIE._ID_REGEX})'
@staticmethod
def _get_default(episode, key, default_key='default'):
@@ -1712,11 +1712,11 @@ class BBCCoUkIPlayerEpisodesIE(BBCCoUkIPlayerPlaylistBaseIE):
variables['sliceId'] = series_id
return self._download_json(
'https://graph.ibl.api.bbc.co.uk/', pid, headers={
- 'Content-Type': 'application/json'
+ 'Content-Type': 'application/json',
}, data=json.dumps({
'id': '5692d93d5aac8d796a0305e895e61551',
'variables': variables,
- }).encode('utf-8'))['data']['programme']
+ }).encode())['data']['programme']
@staticmethod
def _get_playlist_data(data):
@@ -1776,7 +1776,7 @@ class BBCCoUkIPlayerGroupIE(BBCCoUkIPlayerPlaylistBaseIE):
def _call_api(self, pid, per_page, page=1, series_id=None):
return self._download_json(
- 'http://ibl.api.bbc.co.uk/ibl/v1/groups/%s/episodes' % pid,
+ f'http://ibl.api.bbc.co.uk/ibl/v1/groups/{pid}/episodes',
pid, query={
'page': page,
'per_page': per_page,
@@ -1792,7 +1792,7 @@ class BBCCoUkIPlayerGroupIE(BBCCoUkIPlayerPlaylistBaseIE):
class BBCCoUkPlaylistIE(BBCCoUkPlaylistBaseIE):
IE_NAME = 'bbc.co.uk:playlist'
- _VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/programmes/(?P<id>%s)/(?:episodes|broadcasts|clips)' % BBCCoUkIE._ID_REGEX
+ _VALID_URL = rf'https?://(?:www\.)?bbc\.co\.uk/programmes/(?P<id>{BBCCoUkIE._ID_REGEX})/(?:episodes|broadcasts|clips)'
_URL_TEMPLATE = 'http://www.bbc.co.uk/programmes/%s'
_VIDEO_ID_TEMPLATE = r'data-pid=["\'](%s)'
_TESTS = [{
diff --git a/yt_dlp/extractor/beatport.py b/yt_dlp/extractor/beatport.py
index 0aecbd089..acc8d1259 100644
--- a/yt_dlp/extractor/beatport.py
+++ b/yt_dlp/extractor/beatport.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import int_or_none
@@ -33,7 +32,7 @@ class BeatportIE(InfoExtractor):
'display_id': 'birds-original-mix',
'ext': 'mp4',
'title': "Tos, Middle Milk, Mumblin' Johnsson - Birds (Original Mix)",
- }
+ },
}]
def _real_extract(self, url):
@@ -51,7 +50,7 @@ class BeatportIE(InfoExtractor):
track = next(t for t in playables['tracks'] if t['id'] == int(track_id))
- title = ', '.join((a['name'] for a in track['artists'])) + ' - ' + track['name']
+ title = ', '.join(a['name'] for a in track['artists']) + ' - ' + track['name']
if track['mix']:
title += ' (' + track['mix'] + ')'
@@ -89,7 +88,7 @@ class BeatportIE(InfoExtractor):
images.append(image)
return {
- 'id': compat_str(track.get('id')) or track_id,
+ 'id': str(track.get('id')) or track_id,
'display_id': track.get('slug') or display_id,
'title': title,
'formats': formats,
diff --git a/yt_dlp/extractor/beeg.py b/yt_dlp/extractor/beeg.py
index da98ac314..960cdfabd 100644
--- a/yt_dlp/extractor/beeg.py
+++ b/yt_dlp/extractor/beeg.py
@@ -23,7 +23,7 @@ class BeegIE(InfoExtractor):
'upload_date': '20220131',
'timestamp': 1643656455,
'display_id': '2540839',
- }
+ },
}, {
'url': 'https://beeg.com/-0599050563103750?t=4-861',
'md5': 'bd8b5ea75134f7f07fad63008db2060e',
@@ -38,7 +38,7 @@ class BeegIE(InfoExtractor):
'timestamp': 1643623200,
'display_id': '2569965',
'upload_date': '20220131',
- }
+ },
}, {
# api/v6 v2
'url': 'https://beeg.com/1941093077?t=911-1391',
@@ -55,8 +55,8 @@ class BeegIE(InfoExtractor):
webpage = self._download_webpage(url, video_id)
video = self._download_json(
- 'https://store.externulls.com/facts/file/%s' % video_id,
- video_id, 'Downloading JSON for %s' % video_id)
+ f'https://store.externulls.com/facts/file/{video_id}',
+ video_id, f'Downloading JSON for {video_id}')
fc_facts = video.get('fc_facts')
first_fact = {}
diff --git a/yt_dlp/extractor/behindkink.py b/yt_dlp/extractor/behindkink.py
index 9d2324f4f..45f45d03b 100644
--- a/yt_dlp/extractor/behindkink.py
+++ b/yt_dlp/extractor/behindkink.py
@@ -16,7 +16,7 @@ class BehindKinkIE(InfoExtractor):
'upload_date': '20141205',
'thumbnail': 'http://www.behindkink.com/wp-content/uploads/2014/12/blaze-1.jpg',
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/bellmedia.py b/yt_dlp/extractor/bellmedia.py
index 677680b42..ac45dd477 100644
--- a/yt_dlp/extractor/bellmedia.py
+++ b/yt_dlp/extractor/bellmedia.py
@@ -86,6 +86,6 @@ class BellMediaIE(InfoExtractor):
return {
'_type': 'url_transparent',
'id': video_id,
- 'url': '9c9media:%s_web:%s' % (self._DOMAINS.get(domain, domain), video_id),
+ 'url': f'9c9media:{self._DOMAINS.get(domain, domain)}_web:{video_id}',
'ie_key': 'NineCNineMedia',
}
diff --git a/yt_dlp/extractor/berufetv.py b/yt_dlp/extractor/berufetv.py
index 8160cbd9a..5bba33a44 100644
--- a/yt_dlp/extractor/berufetv.py
+++ b/yt_dlp/extractor/berufetv.py
@@ -16,7 +16,7 @@ class BerufeTVIE(InfoExtractor):
'tags': ['Studienfilm'],
'duration': 602.440,
'thumbnail': r're:^https://asset-out-cdn\.video-cdn\.net/private/videos/DvKC3DUpMKvUZ_6fEnfg3u/thumbnails/793063\?quality=thumbnail&__token__=[^\s]+$',
- }
+ },
}]
def _real_extract(self, url):
@@ -54,7 +54,7 @@ class BerufeTVIE(InfoExtractor):
subtitles.setdefault(track['language'], []).append({
'url': track['source'],
'name': track.get('label'),
- 'ext': 'vtt'
+ 'ext': 'vtt',
})
return {
diff --git a/yt_dlp/extractor/bet.py b/yt_dlp/extractor/bet.py
index cbf3dd082..3a8e74309 100644
--- a/yt_dlp/extractor/bet.py
+++ b/yt_dlp/extractor/bet.py
@@ -19,7 +19,7 @@ class BetIE(MTVServicesInfoExtractor):
'thumbnail': r're:(?i)^https?://.*\.jpg$',
'subtitles': {
'en': 'mincount:2',
- }
+ },
},
'params': {
# rtmp download
@@ -39,16 +39,16 @@ class BetIE(MTVServicesInfoExtractor):
'thumbnail': r're:(?i)^https?://.*\.jpg$',
'subtitles': {
'en': 'mincount:2',
- }
+ },
},
'params': {
# rtmp download
'skip_download': True,
},
- }
+ },
]
- _FEED_URL = "http://feeds.mtvnservices.com/od/feed/bet-mrss-player"
+ _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/bet-mrss-player'
def _get_feed_query(self, uri):
return {
diff --git a/yt_dlp/extractor/bfmtv.py b/yt_dlp/extractor/bfmtv.py
index c4621ca82..87f011783 100644
--- a/yt_dlp/extractor/bfmtv.py
+++ b/yt_dlp/extractor/bfmtv.py
@@ -98,8 +98,8 @@ class BFMTVArticleIE(BFMTVBaseIE):
'timestamp': 1673341692,
'duration': 109.269,
'tags': ['rmc', 'show', 'apolline de malherbe', 'info', 'talk', 'matinale', 'radio'],
- 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/876630703001/5bef74b8-9d5e-4480-a21f-60c2e2480c46/96c88b74-f9db-45e1-8040-e199c5da216c/1920x1080/match/image.jpg'
- }
+ 'thumbnail': 'https://cf-images.eu-west-1.prod.boltdns.net/v1/static/876630703001/5bef74b8-9d5e-4480-a21f-60c2e2480c46/96c88b74-f9db-45e1-8040-e199c5da216c/1920x1080/match/image.jpg',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/bigflix.py b/yt_dlp/extractor/bigflix.py
index 02d1ba0e3..9c55bb968 100644
--- a/yt_dlp/extractor/bigflix.py
+++ b/yt_dlp/extractor/bigflix.py
@@ -1,10 +1,8 @@
+import base64
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_b64decode,
- compat_urllib_parse_unquote,
-)
class BigflixIE(InfoExtractor):
@@ -21,7 +19,7 @@ class BigflixIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
# multiple formats
'url': 'http://www.bigflix.com/Malayalam-movies/Drama-movies/Indian-Rupee/15967',
@@ -38,7 +36,7 @@ class BigflixIE(InfoExtractor):
webpage, 'title')
def decode_url(quoted_b64_url):
- return compat_b64decode(compat_urllib_parse_unquote(
+ return base64.b64decode(urllib.parse.unquote(
quoted_b64_url)).decode('utf-8')
formats = []
@@ -47,7 +45,7 @@ class BigflixIE(InfoExtractor):
video_url = decode_url(encoded_url)
f = {
'url': video_url,
- 'format_id': '%sp' % height,
+ 'format_id': f'{height}p',
'height': int(height),
}
if video_url.startswith('rtmp'):
@@ -69,5 +67,5 @@ class BigflixIE(InfoExtractor):
'id': video_id,
'title': title,
'description': description,
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/bigo.py b/yt_dlp/extractor/bigo.py
index acf78e49a..b1c230f35 100644
--- a/yt_dlp/extractor/bigo.py
+++ b/yt_dlp/extractor/bigo.py
@@ -36,7 +36,7 @@ class BigoIE(InfoExtractor):
raise ExtractorError('Received invalid JSON data')
if info_raw.get('code'):
raise ExtractorError(
- 'Bigo says: %s (code %s)' % (info_raw.get('msg'), info_raw.get('code')), expected=True)
+ 'Bigo says: {} (code {})'.format(info_raw.get('msg'), info_raw.get('code')), expected=True)
info = info_raw.get('data') or {}
if not info.get('alive'):
diff --git a/yt_dlp/extractor/bild.py b/yt_dlp/extractor/bild.py
index eb289329d..2ba63700c 100644
--- a/yt_dlp/extractor/bild.py
+++ b/yt_dlp/extractor/bild.py
@@ -20,7 +20,7 @@ class BildIE(InfoExtractor):
'description': 'md5:a4058c4fa2a804ab59c00d7244bbf62f',
'thumbnail': r're:^https?://.*\.jpg$',
'duration': 196,
- }
+ },
}, {
'note': 'static MP4 and HLS',
'url': 'https://www.bild.de/video/clip/news-ausland/deftiger-abgang-vom-10m-turm-bademeister-sorgt-fuer-skandal-85158620.bild.html',
@@ -32,7 +32,7 @@ class BildIE(InfoExtractor):
'description': 'md5:709b543c24dc31bbbffee73bccda34ad',
'thumbnail': r're:^https?://.*\.jpg$',
'duration': 69,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/bilibili.py b/yt_dlp/extractor/bilibili.py
index b38c90b1d..411b48c28 100644
--- a/yt_dlp/extractor/bilibili.py
+++ b/yt_dlp/extractor/bilibili.py
@@ -112,7 +112,7 @@ class BilibiliBaseIE(InfoExtractor):
'danmaku': [{
'ext': 'xml',
'url': f'https://comment.bilibili.com/{cid}.xml',
- }]
+ }],
}
subtitle_info = traverse_obj(self._download_json(
@@ -126,7 +126,7 @@ class BilibiliBaseIE(InfoExtractor):
for s in subs_list:
subtitles.setdefault(s['lan'], []).append({
'ext': 'srt',
- 'data': self.json2srt(self._download_json(s['subtitle_url'], video_id))
+ 'data': self.json2srt(self._download_json(s['subtitle_url'], video_id)),
})
return subtitles
@@ -215,7 +215,7 @@ class BilibiliBaseIE(InfoExtractor):
yield {
**metainfo,
'id': f'{video_id}_{cid}',
- 'title': f'{metainfo.get("title")} - {list(edges.values())[0].get("title")}',
+ 'title': f'{metainfo.get("title")} - {next(iter(edges.values())).get("title")}',
'formats': self.extract_formats(play_info),
'description': f'{json.dumps(edges, ensure_ascii=False)}\n{metainfo.get("description", "")}',
'duration': float_or_none(play_info.get('timelength'), scale=1000),
@@ -269,7 +269,7 @@ class BiliBiliIE(BilibiliBaseIE):
'url': 'https://www.bilibili.com/video/BV1bK411W797',
'info_dict': {
'id': 'BV1bK411W797',
- 'title': '物语中的人物是如何吐槽自己的OP的'
+ 'title': '物语中的人物是如何吐槽自己的OP的',
},
'playlist_count': 18,
'playlist': [{
@@ -288,8 +288,8 @@ class BiliBiliIE(BilibiliBaseIE):
'view_count': int,
'description': 'md5:e3c401cf7bc363118d1783dd74068a68',
'duration': 90.314,
- }
- }]
+ },
+ }],
}, {
'note': 'Specific page of Anthology',
'url': 'https://www.bilibili.com/video/BV1bK411W797?p=1',
@@ -308,7 +308,7 @@ class BiliBiliIE(BilibiliBaseIE):
'view_count': int,
'description': 'md5:e3c401cf7bc363118d1783dd74068a68',
'duration': 90.314,
- }
+ },
}, {
'note': 'video has subtitles',
'url': 'https://www.bilibili.com/video/BV12N4y1M7rh',
@@ -327,7 +327,7 @@ class BiliBiliIE(BilibiliBaseIE):
'view_count': int,
'like_count': int,
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
- 'subtitles': 'count:2'
+ 'subtitles': 'count:2',
},
'params': {'listsubtitles': True},
}, {
@@ -586,10 +586,9 @@ class BiliBiliIE(BilibiliBaseIE):
is_interactive = traverse_obj(video_data, ('rights', 'is_stein_gate'))
if is_interactive:
return self.playlist_result(
- self._get_interactive_entries(video_id, cid, metainfo), **metainfo, **{
- 'duration': traverse_obj(initial_state, ('videoData', 'duration', {int_or_none})),
- '__post_extractor': self.extract_comments(aid),
- })
+ self._get_interactive_entries(video_id, cid, metainfo), **metainfo,
+ duration=traverse_obj(initial_state, ('videoData', 'duration', {int_or_none})),
+ __post_extractor=self.extract_comments(aid))
else:
return {
**metainfo,
@@ -640,7 +639,7 @@ class BiliBiliBangumiIE(BilibiliBaseIE):
'duration': 1425.256,
'timestamp': 1554566400,
'upload_date': '20190406',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
'skip': 'Geo-restricted',
}, {
@@ -661,7 +660,7 @@ class BiliBiliBangumiIE(BilibiliBaseIE):
'duration': 1922.129,
'timestamp': 1602853860,
'upload_date': '20201016',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
}]
@@ -764,7 +763,7 @@ class BiliBiliBangumiMediaIE(BilibiliBaseIE):
'duration': 1525.777,
'timestamp': 1425074413,
'upload_date': '20150227',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
}],
}]
@@ -794,7 +793,7 @@ class BiliBiliBangumiSeasonIE(BilibiliBaseIE):
'title': '鬼灭之刃',
'description': 'md5:e2cc9848b6f69be6db79fc2a82d9661b',
},
- 'playlist_mincount': 26
+ 'playlist_mincount': 26,
}, {
'url': 'https://www.bilibili.com/bangumi/play/ss2251',
'info_dict': {
@@ -819,7 +818,7 @@ class BiliBiliBangumiSeasonIE(BilibiliBaseIE):
'duration': 1436.992,
'timestamp': 1343185080,
'upload_date': '20120725',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
}],
}]
@@ -906,7 +905,7 @@ class BilibiliCheeseIE(BilibiliCheeseBaseIE):
'upload_date': '20230924',
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
'view_count': int,
- }
+ },
}]
def _real_extract(self, url):
@@ -939,7 +938,7 @@ class BilibiliCheeseSeasonIE(BilibiliCheeseBaseIE):
'upload_date': '20230924',
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
'view_count': int,
- }
+ },
}],
'params': {'playlist_items': '1'},
}, {
@@ -1012,7 +1011,7 @@ class BilibiliSpaceVideoIE(BilibiliSpaceBaseIE):
for position in (
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39,
12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63,
- 57, 62, 11, 36, 20, 34, 44, 52
+ 57, 62, 11, 36, 20, 34, 44, 52,
):
char_at_position = try_call(lambda: session_key[position])
if char_at_position:
@@ -1163,7 +1162,7 @@ class BilibiliCollectionListIE(BilibiliSpaceListBaseIE):
'uploader_id': ('meta', 'mid', {str_or_none}),
'timestamp': ('meta', 'ptime', {int_or_none}),
'thumbnail': ('meta', 'cover', {url_or_none}),
- })
+ }),
}
def get_entries(page_data):
@@ -1195,7 +1194,7 @@ class BilibiliSeriesListIE(BilibiliSpaceListBaseIE):
mid, sid = self._match_valid_url(url).group('mid', 'sid')
playlist_id = f'{mid}_{sid}'
playlist_meta = traverse_obj(self._download_json(
- f'https://api.bilibili.com/x/series/series?series_id={sid}', playlist_id, fatal=False
+ f'https://api.bilibili.com/x/series/series?series_id={sid}', playlist_id, fatal=False,
), {
'title': ('data', 'meta', 'name', {str}),
'description': ('data', 'meta', 'description', {str}),
@@ -1217,7 +1216,7 @@ class BilibiliSeriesListIE(BilibiliSpaceListBaseIE):
'page_count': math.ceil(entry_count / page_size),
'page_size': page_size,
'uploader': self._get_uploader(mid, playlist_id),
- **playlist_meta
+ **playlist_meta,
}
def get_entries(page_data):
@@ -1241,7 +1240,7 @@ class BilibiliFavoritesListIE(BilibiliSpaceListBaseIE):
'upload_date': '20201109',
'modified_timestamp': int,
'modified_date': str,
- 'thumbnail': r"re:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg",
+ 'thumbnail': r're:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg',
'view_count': int,
'like_count': int,
},
@@ -1345,7 +1344,7 @@ class BilibiliPlaylistIE(BilibiliSpaceListBaseIE):
'uploader_id': '84912',
'timestamp': 1604905176,
'upload_date': '20201109',
- 'thumbnail': r"re:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg",
+ 'thumbnail': r're:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg',
},
'playlist_mincount': 22,
}, {
@@ -1371,7 +1370,7 @@ class BilibiliPlaylistIE(BilibiliSpaceListBaseIE):
for page_num in itertools.count(1):
page_data = self._download_json(
'https://api.bilibili.com/x/v2/medialist/resource/list',
- list_id, query=query, note=f'getting playlist {query["biz_id"]} page {page_num}'
+ list_id, query=query, note=f'getting playlist {query["biz_id"]} page {page_num}',
)['data']
yield from self._get_entries(page_data, 'media_list', ending_key='bv_id')
query['oid'] = traverse_obj(page_data, ('media_list', -1, 'id'))
@@ -1407,7 +1406,7 @@ class BilibiliPlaylistIE(BilibiliSpaceListBaseIE):
'tid': ('tid', {int_or_none}),
'sort_field': ('sortFiled', {int_or_none}),
'desc': ('desc', {bool_or_none}, {str_or_none}, {str.lower}),
- })
+ }),
}
metadata = {
'id': f'{query["type"]}_{query["biz_id"]}',
@@ -1430,26 +1429,26 @@ class BilibiliCategoryIE(InfoExtractor):
'url': 'https://www.bilibili.com/v/kichiku/mad',
'info_dict': {
'id': 'kichiku: mad',
- 'title': 'kichiku: mad'
+ 'title': 'kichiku: mad',
},
'playlist_mincount': 45,
'params': {
- 'playlistend': 45
- }
+ 'playlistend': 45,
+ },
}]
def _fetch_page(self, api_url, num_pages, query, page_num):
parsed_json = self._download_json(
api_url, query, query={'Search_key': query, 'pn': page_num},
- note='Extracting results from page %s of %s' % (page_num, num_pages))
+ note=f'Extracting results from page {page_num} of {num_pages}')
video_list = traverse_obj(parsed_json, ('data', 'archives'), expected_type=list)
if not video_list:
- raise ExtractorError('Failed to retrieve video list for page %d' % page_num)
+ raise ExtractorError(f'Failed to retrieve video list for page {page_num}')
for video in video_list:
yield self.url_result(
- 'https://www.bilibili.com/video/%s' % video['bvid'], 'BiliBili', video['bvid'])
+ 'https://www.bilibili.com/video/{}'.format(video['bvid']), 'BiliBili', video['bvid'])
def _entries(self, category, subcategory, query):
# map of categories : subcategories : RIDs
@@ -1459,7 +1458,7 @@ class BilibiliCategoryIE(InfoExtractor):
'manual_vocaloid': 126,
'guide': 22,
'theatre': 216,
- 'course': 127
+ 'course': 127,
},
}
@@ -1485,7 +1484,7 @@ class BilibiliCategoryIE(InfoExtractor):
def _real_extract(self, url):
category, subcategory = urllib.parse.urlparse(url).path.split('/')[2:4]
- query = '%s: %s' % (category, subcategory)
+ query = f'{category}: {subcategory}'
return self.playlist_result(self._entries(category, subcategory, query), query, query)
@@ -1588,7 +1587,7 @@ class BilibiliAudioIE(BilibiliAudioBaseIE):
formats = [{
'url': play_data['cdns'][0],
'filesize': int_or_none(play_data.get('size')),
- 'vcodec': 'none'
+ 'vcodec': 'none',
}]
for a_format in formats:
@@ -1606,7 +1605,7 @@ class BilibiliAudioIE(BilibiliAudioBaseIE):
subtitles = {
'origin': [{
'url': lyric,
- }]
+ }],
}
return {
@@ -1674,7 +1673,7 @@ class BiliBiliPlayerIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
return self.url_result(
- 'http://www.bilibili.tv/video/av%s/' % video_id,
+ f'http://www.bilibili.tv/video/av{video_id}/',
ie=BiliBiliIE.ie_key(), video_id=video_id)
@@ -1702,11 +1701,10 @@ class BiliIntlBaseIE(InfoExtractor):
return json.get('data')
def json2srt(self, json):
- data = '\n\n'.join(
+ return '\n\n'.join(
f'{i + 1}\n{srt_subtitles_timecode(line["from"])} --> {srt_subtitles_timecode(line["to"])}\n{line["content"]}'
for i, line in enumerate(traverse_obj(json, (
'body', lambda _, l: l['content'] and l['from'] and l['to']))))
- return data
def _get_subtitles(self, *, ep_id=None, aid=None):
sub_json = self._call_api(
@@ -1808,14 +1806,14 @@ class BiliIntlBaseIE(InfoExtractor):
note='Downloading login key', errnote='Unable to download login key')['data']
public_key = Cryptodome.RSA.importKey(key_data['key'])
- password_hash = Cryptodome.PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode('utf-8'))
+ password_hash = Cryptodome.PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode())
login_post = self._download_json(
'https://passport.bilibili.tv/x/intl/passport-login/web/login/password?lang=en-US', None, data=urlencode_postdata({
'username': username,
'password': base64.b64encode(password_hash).decode('ascii'),
'keep_me': 'true',
's_locale': 'en_US',
- 'isTrusted': 'true'
+ 'isTrusted': 'true',
}), note='Logging in', errnote='Unable to log in')
if login_post.get('code'):
if login_post.get('message'):
@@ -1842,17 +1840,17 @@ class BiliIntlIE(BiliIntlBaseIE):
'chapters': [{
'start_time': 0,
'end_time': 76.242,
- 'title': '<Untitled Chapter 1>'
+ 'title': '<Untitled Chapter 1>',
}, {
'start_time': 76.242,
'end_time': 161.161,
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': 1325.742,
'end_time': 1403.903,
- 'title': 'Outro'
+ 'title': 'Outro',
}],
- }
+ },
}, {
# Non-Bstation page
'url': 'https://www.bilibili.tv/en/play/1033760/11005006',
@@ -1869,17 +1867,17 @@ class BiliIntlIE(BiliIntlBaseIE):
'chapters': [{
'start_time': 0,
'end_time': 88.0,
- 'title': '<Untitled Chapter 1>'
+ 'title': '<Untitled Chapter 1>',
}, {
'start_time': 88.0,
'end_time': 156.0,
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': 1173.0,
'end_time': 1259.535,
- 'title': 'Outro'
+ 'title': 'Outro',
}],
- }
+ },
}, {
# Subtitle with empty content
'url': 'https://www.bilibili.tv/en/play/1005144/10131790',
@@ -1890,7 +1888,7 @@ class BiliIntlIE(BiliIntlBaseIE):
'thumbnail': r're:^https://pic\.bstarstatic\.com/ogv/.+\.png$',
'episode_number': 140,
},
- 'skip': 'According to the copyright owner\'s request, you may only watch the video after you log in.'
+ 'skip': 'According to the copyright owner\'s request, you may only watch the video after you log in.',
}, {
# episode comment extraction
'url': 'https://www.bilibili.tv/en/play/34580/340317',
@@ -1908,20 +1906,20 @@ class BiliIntlIE(BiliIntlBaseIE):
'chapters': [{
'start_time': 0,
'end_time': 61.0,
- 'title': '<Untitled Chapter 1>'
+ 'title': '<Untitled Chapter 1>',
}, {
'start_time': 61.0,
'end_time': 134.0,
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': 1290.0,
'end_time': 1379.0,
- 'title': 'Outro'
+ 'title': 'Outro',
}],
},
'params': {
- 'getcomments': True
- }
+ 'getcomments': True,
+ },
}, {
# user generated content comment extraction
'url': 'https://www.bilibili.tv/en/video/2045730385',
@@ -1936,8 +1934,8 @@ class BiliIntlIE(BiliIntlBaseIE):
'thumbnail': r're:https://pic\.bstarstatic\.(?:com|net)/ugc/f6c363659efd2eabe5683fbb906b1582\.jpg',
},
'params': {
- 'getcomments': True
- }
+ 'getcomments': True,
+ },
}, {
# episode id without intro and outro
'url': 'https://www.bilibili.tv/en/play/1048837/11246489',
@@ -1992,7 +1990,7 @@ class BiliIntlIE(BiliIntlBaseIE):
# Non-Bstation layout, read through episode list
season_json = self._call_api(f'/web/v2/ogv/play/episodes?season_id={season_id}&platform=web', video_id)
video_data = traverse_obj(season_json, (
- 'sections', ..., 'episodes', lambda _, v: str(v['episode_id']) == video_id
+ 'sections', ..., 'episodes', lambda _, v: str(v['episode_id']) == video_id,
), expected_type=dict, get_all=False)
# XXX: webpage metadata may not accurate, it just used to not crash when video_data not found
@@ -2024,7 +2022,7 @@ class BiliIntlIE(BiliIntlBaseIE):
'id': replies.get('rpid'),
'like_count': int_or_none(replies.get('like_count')),
'parent': replies.get('parent'),
- 'timestamp': unified_timestamp(replies.get('ctime_text'))
+ 'timestamp': unified_timestamp(replies.get('ctime_text')),
}
if not traverse_obj(comment_api_raw_data, ('data', 'cursor', 'is_end')):
@@ -2077,11 +2075,11 @@ class BiliIntlIE(BiliIntlBaseIE):
chapters = [{
'start_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'opening_start_time')), 1000),
'end_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'opening_end_time')), 1000),
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'ending_start_time')), 1000),
'end_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'ending_end_time')), 1000),
- 'title': 'Outro'
+ 'title': 'Outro',
}]
return {
@@ -2137,7 +2135,7 @@ class BiliIntlSeriesIE(BiliIntlBaseIE):
episode_id = str(episode['episode_id'])
yield self.url_result(smuggle_url(
BiliIntlIE._make_url(episode_id, series_id),
- self._parse_video_metadata(episode)
+ self._parse_video_metadata(episode),
), BiliIntlIE, episode_id)
def _real_extract(self, url):
@@ -2156,19 +2154,19 @@ class BiliLiveIE(InfoExtractor):
'url': 'https://live.bilibili.com/196',
'info_dict': {
'id': '33989',
- 'description': "周六杂谈回,其他时候随机游戏。 | \n录播:@下播型泛式录播组。 | \n直播通知群(全员禁言):666906670,902092584,59971⑧481 (功能一样,别多加)",
+ 'description': '周六杂谈回,其他时候随机游戏。 | \n录播:@下播型泛式录播组。 | \n直播通知群(全员禁言):666906670,902092584,59971⑧481 (功能一样,别多加)',
'ext': 'flv',
- 'title': "太空狼人杀联动,不被爆杀就算赢",
- 'thumbnail': "https://i0.hdslb.com/bfs/live/new_room_cover/e607bc1529057ef4b332e1026e62cf46984c314d.jpg",
+ 'title': '太空狼人杀联动,不被爆杀就算赢',
+ 'thumbnail': 'https://i0.hdslb.com/bfs/live/new_room_cover/e607bc1529057ef4b332e1026e62cf46984c314d.jpg',
'timestamp': 1650802769,
},
- 'skip': 'not live'
+ 'skip': 'not live',
}, {
'url': 'https://live.bilibili.com/196?broadcast_type=0&is_room_feed=1?spm_id_from=333.999.space_home.strengthen_live_card.click',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://live.bilibili.com/blanc/196',
- 'only_matching': True
+ 'only_matching': True,
}]
_FORMATS = {
@@ -2209,7 +2207,7 @@ class BiliLiveIE(InfoExtractor):
raise ExtractorError('Streamer is not live', expected=True)
formats = []
- for qn in self._FORMATS.keys():
+ for qn in self._FORMATS:
stream_data = self._call_api('xlive/web-room/v2/index/getRoomPlayInfo', room_id, {
'room_id': room_id,
'qn': qn,
diff --git a/yt_dlp/extractor/bitchute.py b/yt_dlp/extractor/bitchute.py
index 194bf1f46..c74f34c2a 100644
--- a/yt_dlp/extractor/bitchute.py
+++ b/yt_dlp/extractor/bitchute.py
@@ -39,7 +39,7 @@ class BitChuteIE(InfoExtractor):
'upload_date': '20170103',
'uploader_url': 'https://www.bitchute.com/profile/I5NgtHZn9vPj/',
'channel': 'BitChute',
- 'channel_url': 'https://www.bitchute.com/channel/bitchute/'
+ 'channel_url': 'https://www.bitchute.com/channel/bitchute/',
},
}, {
# test case: video with different channel and uploader
@@ -55,7 +55,7 @@ class BitChuteIE(InfoExtractor):
'upload_date': '20231106',
'uploader_url': 'https://www.bitchute.com/profile/9K0kUWA9zmd9/',
'channel': 'Full Measure with Sharyl Attkisson',
- 'channel_url': 'https://www.bitchute.com/channel/sharylattkisson/'
+ 'channel_url': 'https://www.bitchute.com/channel/sharylattkisson/',
},
}, {
# video not downloadable in browser, but we can recover it
@@ -72,7 +72,7 @@ class BitChuteIE(InfoExtractor):
'upload_date': '20181113',
'uploader_url': 'https://www.bitchute.com/profile/I5NgtHZn9vPj/',
'channel': 'BitChute',
- 'channel_url': 'https://www.bitchute.com/channel/bitchute/'
+ 'channel_url': 'https://www.bitchute.com/channel/bitchute/',
},
'params': {'check_formats': None},
}, {
@@ -115,7 +115,7 @@ class BitChuteIE(InfoExtractor):
continue
return {
'url': url,
- 'filesize': int_or_none(response.headers.get('Content-Length'))
+ 'filesize': int_or_none(response.headers.get('Content-Length')),
}
def _raise_if_restricted(self, webpage):
@@ -196,7 +196,7 @@ class BitChuteChannelIE(InfoExtractor):
'duration': 16,
'view_count': int,
},
- }
+ },
],
'params': {
'skip_download': True,
@@ -209,7 +209,7 @@ class BitChuteChannelIE(InfoExtractor):
'id': 'wV9Imujxasw9',
'title': 'Bruce MacDonald and "The Light of Darkness"',
'description': 'md5:747724ef404eebdfc04277714f81863e',
- }
+ },
}]
_TOKEN = 'zyG6tQcGPE5swyAEFLqKUwMuMMuF6IO2DZ6ZDQjGfsL0e4dcTLwqkTTul05Jdve7'
@@ -224,7 +224,7 @@ class BitChuteChannelIE(InfoExtractor):
'container': 'playlist-video',
'title': 'title',
'description': 'description',
- }
+ },
}
diff --git a/yt_dlp/extractor/blackboardcollaborate.py b/yt_dlp/extractor/blackboardcollaborate.py
index 8f41c897a..535890979 100644
--- a/yt_dlp/extractor/blackboardcollaborate.py
+++ b/yt_dlp/extractor/blackboardcollaborate.py
@@ -47,7 +47,7 @@ class BlackboardCollaborateIE(InfoExtractor):
region = mobj.group('region')
video_id = mobj.group('id')
info = self._download_json(
- 'https://{}.bbcollab.com/collab/api/csa/recordings/{}/data'.format(region, video_id), video_id)
+ f'https://{region}.bbcollab.com/collab/api/csa/recordings/{video_id}/data', video_id)
duration = info.get('duration')
title = info['name']
upload_date = info.get('created')
diff --git a/yt_dlp/extractor/bleacherreport.py b/yt_dlp/extractor/bleacherreport.py
index aa3d63ee7..71b237d4b 100644
--- a/yt_dlp/extractor/bleacherreport.py
+++ b/yt_dlp/extractor/bleacherreport.py
@@ -44,7 +44,7 @@ class BleacherReportIE(InfoExtractor):
def _real_extract(self, url):
article_id = self._match_id(url)
- article_data = self._download_json('http://api.bleacherreport.com/api/v1/articles/%s' % article_id, article_id)['article']
+ article_data = self._download_json(f'http://api.bleacherreport.com/api/v1/articles/{article_id}', article_id)['article']
thumbnails = []
primary_photo = article_data.get('primaryPhoto')
@@ -71,11 +71,11 @@ class BleacherReportIE(InfoExtractor):
if video:
video_type = video['type']
if video_type in ('cms.bleacherreport.com', 'vid.bleacherreport.com'):
- info['url'] = 'http://bleacherreport.com/video_embed?id=%s' % video['id']
+ info['url'] = 'http://bleacherreport.com/video_embed?id={}'.format(video['id'])
elif video_type == 'youtube.com':
info['url'] = video['id']
elif video_type == 'vine.co':
- info['url'] = 'https://vine.co/v/%s' % video['id']
+ info['url'] = 'https://vine.co/v/{}'.format(video['id'])
else:
info['url'] = video_type + video['id']
return info
@@ -99,12 +99,12 @@ class BleacherReportCMSIE(AMPIE):
},
'expected_warnings': [
- 'Unable to download f4m manifest'
- ]
+ 'Unable to download f4m manifest',
+ ],
}]
def _real_extract(self, url):
video_id = self._match_id(url)
- info = self._extract_feed_info('http://vid.bleacherreport.com/videos/%s.akamai' % video_id)
+ info = self._extract_feed_info(f'http://vid.bleacherreport.com/videos/{video_id}.akamai')
info['id'] = video_id
return info
diff --git a/yt_dlp/extractor/blerp.py b/yt_dlp/extractor/blerp.py
index 4631ad2e9..f4f22488e 100644
--- a/yt_dlp/extractor/blerp.py
+++ b/yt_dlp/extractor/blerp.py
@@ -16,7 +16,7 @@ class BlerpIE(InfoExtractor):
'uploader_id': '5fb81e51aa66ae000c395478',
'ext': 'mp3',
'tags': ['samsung', 'galaxy', 's8', 'over the horizon', '2016', 'ringtone'],
- }
+ },
}, {
'url': 'https://blerp.com/soundbites/5bc94ef4796001000498429f',
'info_dict': {
@@ -25,11 +25,11 @@ class BlerpIE(InfoExtractor):
'uploader': '179617322678353920',
'uploader_id': '5ba99cf71386730004552c42',
'ext': 'mp3',
- 'tags': ['YEE', 'YEET', 'wo ha haah catchy tune yee', 'yee']
- }
+ 'tags': ['YEE', 'YEET', 'wo ha haah catchy tune yee', 'yee'],
+ },
}]
- _GRAPHQL_OPERATIONNAME = "webBitePageGetBite"
+ _GRAPHQL_OPERATIONNAME = 'webBitePageGetBite'
_GRAPHQL_QUERY = (
'''query webBitePageGetBite($_id: MongoID!) {
web {
@@ -141,27 +141,26 @@ class BlerpIE(InfoExtractor):
'operationName': self._GRAPHQL_OPERATIONNAME,
'query': self._GRAPHQL_QUERY,
'variables': {
- '_id': audio_id
- }
+ '_id': audio_id,
+ },
}
headers = {
- 'Content-Type': 'application/json'
+ 'Content-Type': 'application/json',
}
- json_result = self._download_json('https://api.blerp.com/graphql',
- audio_id, data=json.dumps(data).encode('utf-8'), headers=headers)
+ json_result = self._download_json(
+ 'https://api.blerp.com/graphql', audio_id,
+ data=json.dumps(data).encode(), headers=headers)
bite_json = json_result['data']['web']['biteById']
- info_dict = {
+ return {
'id': bite_json['_id'],
'url': bite_json['audio']['mp3']['url'],
'title': bite_json['title'],
'uploader': traverse_obj(bite_json, ('ownerObject', 'username'), expected_type=strip_or_none),
'uploader_id': traverse_obj(bite_json, ('ownerObject', '_id'), expected_type=strip_or_none),
'ext': 'mp3',
- 'tags': list(filter(None, map(strip_or_none, (traverse_obj(bite_json, 'userKeywords', expected_type=list) or []))) or None)
+ 'tags': list(filter(None, map(strip_or_none, (traverse_obj(bite_json, 'userKeywords', expected_type=list) or []))) or None),
}
-
- return info_dict
diff --git a/yt_dlp/extractor/blogger.py b/yt_dlp/extractor/blogger.py
index ef0151de6..1614b6f94 100644
--- a/yt_dlp/extractor/blogger.py
+++ b/yt_dlp/extractor/blogger.py
@@ -21,14 +21,14 @@ class BloggerIE(InfoExtractor):
'ext': 'mp4',
'thumbnail': r're:^https?://.*',
'duration': 76.068,
- }
+ },
}]
def _real_extract(self, url):
token_id = self._match_id(url)
webpage = self._download_webpage(url, token_id)
data_json = self._search_regex(r'var\s+VIDEO_CONFIG\s*=\s*(\{.*)', webpage, 'JSON data')
- data = self._parse_json(data_json.encode('utf-8').decode('unicode_escape'), token_id)
+ data = self._parse_json(data_json.encode().decode('unicode_escape'), token_id)
streams = data['streams']
formats = [{
'ext': mimetype2ext(traverse_obj(parse_qs(stream['play_url']), ('mime', 0))),
diff --git a/yt_dlp/extractor/bloomberg.py b/yt_dlp/extractor/bloomberg.py
index 792155e51..ec6b7a86e 100644
--- a/yt_dlp/extractor/bloomberg.py
+++ b/yt_dlp/extractor/bloomberg.py
@@ -55,7 +55,7 @@ class BloombergIE(InfoExtractor):
title = re.sub(': Video$', '', self._og_search_title(webpage))
embed_info = self._download_json(
- 'http://www.bloomberg.com/multimedia/api/embed?id=%s' % video_id, video_id)
+ f'http://www.bloomberg.com/multimedia/api/embed?id={video_id}', video_id)
formats = []
for stream in embed_info['streams']:
stream_url = stream.get('url')
diff --git a/yt_dlp/extractor/bokecc.py b/yt_dlp/extractor/bokecc.py
index ca326f25f..5fe937a6a 100644
--- a/yt_dlp/extractor/bokecc.py
+++ b/yt_dlp/extractor/bokecc.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_parse_qs
from ..utils import ExtractorError
@@ -9,20 +10,18 @@ class BokeCCBaseIE(InfoExtractor):
r'<(?:script|embed)[^>]+src=(?P<q>["\'])(?:https?:)?//p\.bokecc\.com/(?:player|flash/player\.swf)\?(?P<query>.+?)(?P=q)',
webpage, 'player params', group='query')
- player_params = compat_parse_qs(player_params_str)
+ player_params = urllib.parse.parse_qs(player_params_str)
info_xml = self._download_xml(
- 'http://p.bokecc.com/servlet/playinfo?uid=%s&vid=%s&m=1' % (
+ 'http://p.bokecc.com/servlet/playinfo?uid={}&vid={}&m=1'.format(
player_params['siteid'][0], player_params['vid'][0]), video_id)
- formats = [{
+ return [{
'format_id': format_id,
'url': quality.find('./copy').attrib['playurl'],
'quality': int(quality.attrib['value']),
} for quality in info_xml.findall('./video/quality')]
- return formats
-
class BokeCCIE(BokeCCBaseIE):
_IE_DESC = 'CC视频'
@@ -38,11 +37,11 @@ class BokeCCIE(BokeCCBaseIE):
}]
def _real_extract(self, url):
- qs = compat_parse_qs(self._match_valid_url(url).group('query'))
+ qs = urllib.parse.parse_qs(self._match_valid_url(url).group('query'))
if not qs.get('vid') or not qs.get('uid'):
raise ExtractorError('Invalid URL', expected=True)
- video_id = '%s_%s' % (qs['uid'][0], qs['vid'][0])
+ video_id = '{}_{}'.format(qs['uid'][0], qs['vid'][0])
webpage = self._download_webpage(url, video_id)
diff --git a/yt_dlp/extractor/bongacams.py b/yt_dlp/extractor/bongacams.py
index bf955668d..ab85477de 100644
--- a/yt_dlp/extractor/bongacams.py
+++ b/yt_dlp/extractor/bongacams.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
try_get,
@@ -38,7 +37,7 @@ class BongaCamsIE(InfoExtractor):
channel_id = mobj.group('id')
amf = self._download_json(
- 'https://%s/tools/amf.php' % host, channel_id,
+ f'https://{host}/tools/amf.php', channel_id,
data=urlencode_postdata((
('method', 'getRoomData'),
('args[]', channel_id),
@@ -48,14 +47,14 @@ class BongaCamsIE(InfoExtractor):
server_url = amf['localData']['videoServerUrl']
uploader_id = try_get(
- amf, lambda x: x['performerData']['username'], compat_str) or channel_id
+ amf, lambda x: x['performerData']['username'], str) or channel_id
uploader = try_get(
- amf, lambda x: x['performerData']['displayName'], compat_str)
+ amf, lambda x: x['performerData']['displayName'], str)
like_count = int_or_none(try_get(
amf, lambda x: x['performerData']['loversCount']))
formats = self._extract_m3u8_formats(
- '%s/hls/stream_%s/playlist.m3u8' % (server_url, uploader_id),
+ f'{server_url}/hls/stream_{uploader_id}/playlist.m3u8',
channel_id, 'mp4', m3u8_id='hls', live=True)
return {
diff --git a/yt_dlp/extractor/bostonglobe.py b/yt_dlp/extractor/bostonglobe.py
index 267586687..f5b819678 100644
--- a/yt_dlp/extractor/bostonglobe.py
+++ b/yt_dlp/extractor/bostonglobe.py
@@ -57,8 +57,7 @@ class BostonGlobeIE(InfoExtractor):
if video_id and account_id and player_id and embed:
entries.append(
- 'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
- % (account_id, player_id, embed, video_id))
+ f'http://players.brightcove.net/{account_id}/{player_id}_{embed}/index.html?videoId={video_id}')
if len(entries) == 0:
return self.url_result(url, 'Generic')
diff --git a/yt_dlp/extractor/box.py b/yt_dlp/extractor/box.py
index 008c011cc..3547ad997 100644
--- a/yt_dlp/extractor/box.py
+++ b/yt_dlp/extractor/box.py
@@ -72,20 +72,20 @@ class BoxIE(InfoExtractor):
'BoxApi': 'shared_link=' + shared_link,
'X-Rep-Hints': '[dash]', # TODO: extract `hls` formats
}, query={
- 'fields': 'authenticated_download_url,created_at,created_by,description,extension,is_download_available,name,representations,size'
+ 'fields': 'authenticated_download_url,created_at,created_by,description,extension,is_download_available,name,representations,size',
})
title = f['name']
query = {
'access_token': access_token,
- 'shared_link': shared_link
+ 'shared_link': shared_link,
}
formats = []
for url_tmpl in traverse_obj(f, (
'representations', 'entries', lambda _, v: v['representation'] == 'dash',
- 'content', 'url_template', {url_or_none}
+ 'content', 'url_template', {url_or_none},
)):
manifest_url = update_url_query(url_tmpl.replace('{+asset_path}', 'manifest.mpd'), query)
fmts = self._extract_mpd_formats(manifest_url, file_id)
diff --git a/yt_dlp/extractor/boxcast.py b/yt_dlp/extractor/boxcast.py
index da06cc3f8..efa66994a 100644
--- a/yt_dlp/extractor/boxcast.py
+++ b/yt_dlp/extractor/boxcast.py
@@ -21,7 +21,7 @@ class BoxCastVideoIE(InfoExtractor):
'release_date': '20221210',
'uploader_id': 're8w0v8hohhvpqtbskpe',
'uploader': 'Children\'s Health Defense',
- }
+ },
}, {
'url': 'https://boxcast.tv/video-portal/vctwevwntun3o0ikq7af/rvyblnn0fxbfjx5nwxhl/otbpltj2kzkveo2qz3ad',
'info_dict': {
@@ -30,8 +30,8 @@ class BoxCastVideoIE(InfoExtractor):
'uploader_id': 'vctwevwntun3o0ikq7af',
'uploader': 'Legacy Christian Church',
'title': 'The Quest | 1: Beginner\'s Bay | Jamie Schools',
- 'thumbnail': r're:https?://uploads.boxcast.com/(?:[\w-]+/){3}.+\.jpg'
- }
+ 'thumbnail': r're:https?://uploads.boxcast.com/(?:[\w-]+/){3}.+\.jpg',
+ },
}, {
'url': 'https://boxcast.tv/channel/z03fqwaeaby5lnaawox2?b=ssihlw5gvfij2by8tkev',
'info_dict': {
@@ -44,7 +44,7 @@ class BoxCastVideoIE(InfoExtractor):
'uploader': 'Lighthouse Ministries International - Beltsville, Maryland',
'description': 'md5:ac23e3d01b0b0be592e8f7fe0ec3a340',
'title': 'New Year\'s Eve CROSSOVER Service at LHMI | December 31, 2022',
- }
+ },
}]
_WEBPAGE_TESTS = [{
'url': 'https://childrenshealthdefense.eu/live-stream/',
@@ -57,7 +57,7 @@ class BoxCastVideoIE(InfoExtractor):
'release_date': '20221210',
'uploader_id': 're8w0v8hohhvpqtbskpe',
'uploader': 'Children\'s Health Defense',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/br.py b/yt_dlp/extractor/br.py
index 6e1c63e2b..0568e06f6 100644
--- a/yt_dlp/extractor/br.py
+++ b/yt_dlp/extractor/br.py
@@ -61,7 +61,7 @@ class BRIE(InfoExtractor):
'title': 'Umweltbewusster Häuslebauer',
'description': 'md5:d52dae9792d00226348c1dbb13c9bae2',
'duration': 116,
- }
+ },
},
{
'url': 'http://www.br.de/fernsehen/br-alpha/sendungen/kant-fuer-anfaenger/kritik-der-reinen-vernunft/kant-kritik-01-metaphysik100.html',
@@ -74,7 +74,7 @@ class BRIE(InfoExtractor):
'duration': 893,
'uploader': 'Eva Maria Steimle',
'upload_date': '20170208',
- }
+ },
},
]
@@ -142,7 +142,7 @@ class BRIE(InfoExtractor):
http_format_info = format_info.copy()
http_format_info.update({
'url': format_url,
- 'format_id': 'http-%s' % asset_type,
+ 'format_id': f'http-{asset_type}',
})
formats.append(http_format_info)
server_prefix = xpath_text(asset, 'serverPrefix')
@@ -151,7 +151,7 @@ class BRIE(InfoExtractor):
rtmp_format_info.update({
'url': server_prefix,
'play_path': xpath_text(asset, 'fileName'),
- 'format_id': 'rtmp-%s' % asset_type,
+ 'format_id': f'rtmp-{asset_type}',
})
formats.append(rtmp_format_info)
return formats
diff --git a/yt_dlp/extractor/brainpop.py b/yt_dlp/extractor/brainpop.py
index 04b1dd80c..df10299a0 100644
--- a/yt_dlp/extractor/brainpop.py
+++ b/yt_dlp/extractor/brainpop.py
@@ -52,8 +52,8 @@ class BrainPOPBaseIE(InfoExtractor):
'%s': {},
'ad_%s': {
'format_note': 'Audio description',
- 'source_preference': -2
- }
+ 'source_preference': -2,
+ },
}
for additional_key_format, additional_key_fields in additional_key_formats.items():
for key_quality, key_index in enumerate(('high', 'low')):
@@ -62,7 +62,7 @@ class BrainPOPBaseIE(InfoExtractor):
formats.extend(self._assemble_formats(data[full_key_index], full_key_index, display_id, token, {
'quality': -1 - key_quality,
**additional_key_fields,
- **extra_fields
+ **extra_fields,
}))
return formats
@@ -72,7 +72,7 @@ class BrainPOPBaseIE(InfoExtractor):
data=json.dumps({'username': username, 'password': password}).encode(),
headers={
'Content-Type': 'application/json',
- 'Referer': self._ORIGIN
+ 'Referer': self._ORIGIN,
}, note='Logging in', errnote='Unable to log in', expected_status=400)
status_code = int_or_none(login_res['status_code'])
if status_code != 1505:
@@ -131,12 +131,12 @@ class BrainPOPIE(BrainPOPBaseIE):
formats, subtitles = [], {}
formats.extend(self._extract_adaptive_formats(movie_feature_data, movie_feature_data.get('token', ''), display_id, '%s_v2', {
'language': movie_feature.get('language') or 'en',
- 'language_preference': 10
+ 'language_preference': 10,
}))
for lang, localized_feature in traverse_obj(movie_feature, 'localization', default={}, expected_type=dict).items():
formats.extend(self._extract_adaptive_formats(localized_feature, localized_feature.get('token', ''), display_id, '%s_v2', {
'language': lang,
- 'language_preference': -10
+ 'language_preference': -10,
}))
# TODO: Do localization fields also have subtitles?
@@ -145,7 +145,7 @@ class BrainPOPIE(BrainPOPBaseIE):
r'^subtitles_(?P<lang>\w+)$', name, 'subtitle metadata', default=None)
if lang and url:
subtitles.setdefault(lang, []).append({
- 'url': urljoin(self._CDN_URL, url)
+ 'url': urljoin(self._CDN_URL, url),
})
return {
diff --git a/yt_dlp/extractor/bravotv.py b/yt_dlp/extractor/bravotv.py
index 419fe8c9c..ec72f0d88 100644
--- a/yt_dlp/extractor/bravotv.py
+++ b/yt_dlp/extractor/bravotv.py
@@ -185,5 +185,5 @@ class BravoTVIE(AdobePassIE):
'episode_number': ('episodeNumber', {int_or_none}),
'episode': 'episodeTitle',
'series': 'show',
- }))
+ })),
}
diff --git a/yt_dlp/extractor/breitbart.py b/yt_dlp/extractor/breitbart.py
index b5abb7f19..fedf4772a 100644
--- a/yt_dlp/extractor/breitbart.py
+++ b/yt_dlp/extractor/breitbart.py
@@ -13,7 +13,7 @@ class BreitBartIE(InfoExtractor):
'description': 'md5:bac35eb0256d1cb17f517f54c79404d5',
'thumbnail': 'https://cdn.jwplayer.com/thumbs/5cOz1yup-1920.jpg',
'age_limit': 0,
- }
+ },
}, {
'url': 'https://www.breitbart.com/videos/v/eaiZjVOn/',
'only_matching': True,
@@ -30,5 +30,5 @@ class BreitBartIE(InfoExtractor):
'description': self._og_search_description(webpage),
'thumbnail': self._og_search_thumbnail(webpage),
'age_limit': self._rta_search(webpage),
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/brightcove.py b/yt_dlp/extractor/brightcove.py
index 4190e1a09..dc0c83572 100644
--- a/yt_dlp/extractor/brightcove.py
+++ b/yt_dlp/extractor/brightcove.py
@@ -1,15 +1,12 @@
import base64
import re
import struct
+import urllib.parse
import xml.etree.ElementTree
from .adobepass import AdobePassIE
from .common import InfoExtractor
-from ..compat import (
- compat_etree_fromstring,
- compat_parse_qs,
- compat_urlparse,
-)
+from ..compat import compat_etree_fromstring
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -142,7 +139,7 @@ class BrightcoveLegacyIE(InfoExtractor):
# from http://www.un.org/chinese/News/story.asp?NewsID=27724
'url': 'https://link.brightcove.com/services/player/bcpid1722935254001/?bctid=5360463607001&autoStart=false&secureConnections=true&width=650&height=350',
'only_matching': True, # Tested in GenericIE
- }
+ },
]
_WEBPAGE_TESTS = [{
@@ -315,7 +312,7 @@ class BrightcoveLegacyIE(InfoExtractor):
object_str = fix_xml_ampersands(object_str)
try:
- object_doc = compat_etree_fromstring(object_str.encode('utf-8'))
+ object_doc = compat_etree_fromstring(object_str.encode())
except xml.etree.ElementTree.ParseError:
return
@@ -323,7 +320,7 @@ class BrightcoveLegacyIE(InfoExtractor):
if fv_el is not None:
flashvars = dict(
(k, v[0])
- for k, v in compat_parse_qs(fv_el.attrib['value']).items())
+ for k, v in urllib.parse.parse_qs(fv_el.attrib['value']).items())
else:
flashvars = {}
@@ -340,32 +337,32 @@ class BrightcoveLegacyIE(InfoExtractor):
params = {}
- playerID = find_param('playerID') or find_param('playerId')
- if playerID is None:
+ player_id = find_param('playerID') or find_param('playerId')
+ if player_id is None:
raise ExtractorError('Cannot find player ID')
- params['playerID'] = playerID
+ params['playerID'] = player_id
- playerKey = find_param('playerKey')
+ player_key = find_param('playerKey')
# Not all pages define this value
- if playerKey is not None:
- params['playerKey'] = playerKey
+ if player_key is not None:
+ params['playerKey'] = player_key
# These fields hold the id of the video
- videoPlayer = find_param('@videoPlayer') or find_param('videoId') or find_param('videoID') or find_param('@videoList')
- if videoPlayer is not None:
- if isinstance(videoPlayer, list):
- videoPlayer = videoPlayer[0]
- videoPlayer = videoPlayer.strip()
+ video_player = find_param('@videoPlayer') or find_param('videoId') or find_param('videoID') or find_param('@videoList')
+ if video_player is not None:
+ if isinstance(video_player, list):
+ video_player = video_player[0]
+ video_player = video_player.strip()
# UUID is also possible for videoPlayer (e.g.
# http://www.popcornflix.com/hoodies-vs-hooligans/7f2d2b87-bbf2-4623-acfb-ea942b4f01dd
# or http://www8.hp.com/cn/zh/home.html)
if not (re.match(
r'^(?:\d+|[\da-fA-F]{8}-?[\da-fA-F]{4}-?[\da-fA-F]{4}-?[\da-fA-F]{4}-?[\da-fA-F]{12})$',
- videoPlayer) or videoPlayer.startswith('ref:')):
+ video_player) or video_player.startswith('ref:')):
return None
- params['@videoPlayer'] = videoPlayer
- linkBase = find_param('linkBaseURL')
- if linkBase is not None:
- params['linkBaseURL'] = linkBase
+ params['@videoPlayer'] = video_player
+ link_base = find_param('linkBaseURL')
+ if link_base is not None:
+ params['linkBaseURL'] = link_base
return cls._make_brightcove_url(params)
@classmethod
@@ -448,13 +445,13 @@ class BrightcoveLegacyIE(InfoExtractor):
url = re.sub(r'(?<=[?&])bckey', 'playerKey', url)
mobj = self._match_valid_url(url)
query_str = mobj.group('query')
- query = compat_urlparse.parse_qs(query_str)
+ query = urllib.parse.parse_qs(query_str)
- videoPlayer = query.get('@videoPlayer')
- if videoPlayer:
+ video_player = query.get('@videoPlayer')
+ if video_player:
# We set the original url as the default 'Referer' header
referer = query.get('linkBaseURL', [None])[0] or smuggled_data.get('Referer', url)
- video_id = videoPlayer[0]
+ video_id = video_player[0]
if 'playerID' not in query:
mobj = re.search(r'/bcpid(\d+)', url)
if mobj is not None:
@@ -483,7 +480,7 @@ class BrightcoveLegacyIE(InfoExtractor):
enc_pub_id = player_key.split(',')[1].replace('~', '=')
publisher_id = struct.unpack('>Q', base64.urlsafe_b64decode(enc_pub_id))[0]
if publisher_id:
- brightcove_new_url = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s' % (publisher_id, video_id)
+ brightcove_new_url = f'http://players.brightcove.net/{publisher_id}/default_default/index.html?videoId={video_id}'
if referer:
brightcove_new_url = smuggle_url(brightcove_new_url, {'referrer': referer})
return self.url_result(brightcove_new_url, BrightcoveNewIE.ie_key(), video_id)
@@ -543,9 +540,9 @@ class BrightcoveNewBaseIE(AdobePassIE):
def build_format_id(kind):
format_id = kind
if tbr:
- format_id += '-%dk' % int(tbr)
+ format_id += f'-{int(tbr)}k'
if height:
- format_id += '-%dp' % height
+ format_id += f'-{height}p'
return format_id
if src or streaming_src:
@@ -654,7 +651,7 @@ class BrightcoveNewIE(BrightcoveNewBaseIE):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
# playlist stream
'url': 'https://players.brightcove.net/1752604059001/S13cJdUBz_default/index.html?playlistId=5718313430001',
@@ -666,7 +663,7 @@ class BrightcoveNewIE(BrightcoveNewBaseIE):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
'url': 'http://players.brightcove.net/5690807595001/HyZNerRl7_default/index.html?playlistId=5743160747001',
'only_matching': True,
@@ -833,8 +830,7 @@ class BrightcoveNewIE(BrightcoveNewBaseIE):
player_id = player_id or attrs.get('data-player') or 'default'
embed = embed or attrs.get('data-embed') or 'default'
- bc_url = 'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s' % (
- account_id, player_id, embed, video_id)
+ bc_url = f'http://players.brightcove.net/{account_id}/{player_id}_{embed}/index.html?videoId={video_id}'
# Some brightcove videos may be embedded with video tag only and
# without script tag or any mentioning of brightcove at all. Such
@@ -865,13 +861,13 @@ class BrightcoveNewIE(BrightcoveNewBaseIE):
account_id, player_id, embed, content_type, video_id = self._match_valid_url(url).groups()
- policy_key_id = '%s_%s' % (account_id, player_id)
+ policy_key_id = f'{account_id}_{player_id}'
policy_key = self.cache.load('brightcove', policy_key_id)
policy_key_extracted = False
store_pk = lambda x: self.cache.store('brightcove', policy_key_id, x)
def extract_policy_key():
- base_url = 'http://players.brightcove.net/%s/%s_%s/' % (account_id, player_id, embed)
+ base_url = f'http://players.brightcove.net/{account_id}/{player_id}_{embed}/'
config = self._download_json(
base_url + 'config.json', video_id, fatal=False) or {}
policy_key = try_get(
@@ -910,7 +906,7 @@ class BrightcoveNewIE(BrightcoveNewBaseIE):
if not policy_key:
policy_key = extract_policy_key()
policy_key_extracted = True
- headers['Accept'] = 'application/json;pk=%s' % policy_key
+ headers['Accept'] = f'application/json;pk={policy_key}'
try:
json_data = self._download_json(api_url, video_id, headers=headers)
break
@@ -936,7 +932,7 @@ class BrightcoveNewIE(BrightcoveNewBaseIE):
custom_fields['bcadobepassresourceid'])
json_data = self._download_json(
api_url, video_id, headers={
- 'Accept': 'application/json;pk=%s' % policy_key
+ 'Accept': f'application/json;pk={policy_key}',
}, query={
'tveToken': tve_token,
})
diff --git a/yt_dlp/extractor/bundesliga.py b/yt_dlp/extractor/bundesliga.py
index e76dd58dd..29f8f9415 100644
--- a/yt_dlp/extractor/bundesliga.py
+++ b/yt_dlp/extractor/bundesliga.py
@@ -16,17 +16,17 @@ class BundesligaIE(InfoExtractor):
'upload_date': '20220928',
'duration': 146,
'timestamp': 1664366511,
- 'description': 'md5:803d4411bd134140c774021dd4b7598b'
- }
+ 'description': 'md5:803d4411bd134140c774021dd4b7598b',
+ },
},
{
'url': 'https://www.bundesliga.com/en/bundesliga/videos/latest-features/T8IKc8TX?vid=ROHjs06G',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.bundesliga.com/en/bundesliga/videos/goals?vid=mOG56vWA',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/businessinsider.py b/yt_dlp/extractor/businessinsider.py
index 4b3f5e68b..7cb9af692 100644
--- a/yt_dlp/extractor/businessinsider.py
+++ b/yt_dlp/extractor/businessinsider.py
@@ -10,7 +10,7 @@ class BusinessInsiderIE(InfoExtractor):
'info_dict': {
'id': 'cjGDb0X9',
'ext': 'mp4',
- 'title': "Bananas give you more radiation exposure than living next to a nuclear power plant",
+ 'title': 'Bananas give you more radiation exposure than living next to a nuclear power plant',
'description': 'md5:0175a3baf200dd8fa658f94cade841b3',
'upload_date': '20160611',
'timestamp': 1465675620,
@@ -41,5 +41,5 @@ class BusinessInsiderIE(InfoExtractor):
r'(?:jwplatform\.com/players/|jwplayer_)([a-zA-Z0-9]{8})'),
webpage, 'jwplatform id')
return self.url_result(
- 'jwplatform:%s' % jwplatform_id, ie=JWPlatformIE.ie_key(),
+ f'jwplatform:{jwplatform_id}', ie=JWPlatformIE.ie_key(),
video_id=video_id)
diff --git a/yt_dlp/extractor/buzzfeed.py b/yt_dlp/extractor/buzzfeed.py
index b30a3b7ae..9847095bc 100644
--- a/yt_dlp/extractor/buzzfeed.py
+++ b/yt_dlp/extractor/buzzfeed.py
@@ -23,8 +23,8 @@ class BuzzFeedIE(InfoExtractor):
'upload_date': '20141024',
'uploader_id': 'Buddhanz1',
'uploader': 'Angry Ram',
- }
- }]
+ },
+ }],
}, {
'url': 'http://www.buzzfeed.com/sheridanwatson/look-at-this-cute-dog-omg?utm_term=4ldqpia',
'params': {
@@ -45,7 +45,7 @@ class BuzzFeedIE(InfoExtractor):
'uploader_id': 'CindysMunchkin',
'uploader': 're:^Munchkin the',
},
- }]
+ }],
}, {
'url': 'http://www.buzzfeed.com/craigsilverman/the-most-adorable-crash-landing-ever#.eq7pX0BAmK',
'info_dict': {
diff --git a/yt_dlp/extractor/byutv.py b/yt_dlp/extractor/byutv.py
index ad35427ed..e9796f7da 100644
--- a/yt_dlp/extractor/byutv.py
+++ b/yt_dlp/extractor/byutv.py
@@ -36,7 +36,7 @@ class BYUtvIE(InfoExtractor):
'duration': 11645,
},
'params': {
- 'skip_download': True
+ 'skip_download': True,
},
}, {
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d',
diff --git a/yt_dlp/extractor/c56.py b/yt_dlp/extractor/c56.py
index e4b1c9a84..6264803dd 100644
--- a/yt_dlp/extractor/c56.py
+++ b/yt_dlp/extractor/c56.py
@@ -38,7 +38,7 @@ class C56IE(InfoExtractor):
return self.url_result(sohu_video_info['url'], 'Sohu')
page = self._download_json(
- 'http://vxml.56.com/json/%s/' % text_id, text_id, 'Downloading video info')
+ f'http://vxml.56.com/json/{text_id}/', text_id, 'Downloading video info')
info = page['info']
@@ -46,7 +46,7 @@ class C56IE(InfoExtractor):
{
'format_id': f['type'],
'filesize': int(f['filesize']),
- 'url': f['url']
+ 'url': f['url'],
} for f in info['rfiles']
]
diff --git a/yt_dlp/extractor/callin.py b/yt_dlp/extractor/callin.py
index c77179c7b..b7061a7d1 100644
--- a/yt_dlp/extractor/callin.py
+++ b/yt_dlp/extractor/callin.py
@@ -29,8 +29,8 @@ class CallinIE(InfoExtractor):
'series_id': '436d1f82ddeb30cd2306ea9156044d8d2cfdc3f1f1552d245117a42173e78553',
'episode': 'The Title IX Regime and the Long March Through and Beyond the Institutions',
'episode_number': 1,
- 'episode_id': '218b979630a35ead12c6fd096f2996c56c37e4d0dc1f6dc0feada32dcf7b31cd'
- }
+ 'episode_id': '218b979630a35ead12c6fd096f2996c56c37e4d0dc1f6dc0feada32dcf7b31cd',
+ },
}, {
'url': 'https://www.callin.com/episode/fcc-commissioner-brendan-carr-on-elons-PrumRdSQJW',
'md5': '14ede27ee2c957b7e4db93140fc0745c',
@@ -54,7 +54,7 @@ class CallinIE(InfoExtractor):
'thumbnail': 'https://d1z76fhpoqkd01.cloudfront.net/shows/legacy/1ade9142625344045dc17cf523469ced1d93610762f4c886d06aa190a2f979e8.png',
'episode_id': 'c3dab47f237bf953d180d3f243477a84302798be0e0b29bc9ade6d60a69f04f5',
'timestamp': 1662100688.005,
- }
+ },
}, {
'url': 'https://www.callin.com/episode/episode-81-elites-melt-down-over-student-debt-lzxMidUnjA',
'md5': '16f704ddbf82a27e3930533b12062f07',
@@ -78,7 +78,7 @@ class CallinIE(InfoExtractor):
'thumbnail': 'https://d1z76fhpoqkd01.cloudfront.net/shows/legacy/461ea0d86172cb6aff7d6c80fd49259cf5e64bdf737a4650f8bc24cf392ca218.png',
'episode_id': '8d06f869798f93a7814e380bceabea72d501417e620180416ff6bd510596e83c',
'timestamp': 1661476708.282,
- }
+ },
}]
def try_get_user_name(self, d):
@@ -94,7 +94,7 @@ class CallinIE(InfoExtractor):
next_data = self._search_nextjs_data(webpage, display_id)
episode = next_data['props']['pageProps']['episode']
- id = episode['id']
+ video_id = episode['id']
title = episode.get('title') or self._generic_title('', webpage)
url = episode['m3u8']
formats = self._extract_m3u8_formats(url, display_id, ext='ts')
@@ -125,11 +125,11 @@ class CallinIE(InfoExtractor):
episode_list = traverse_obj(show_json, ('pageProps', 'show', 'episodes')) or []
episode_number = next(
- (len(episode_list) - i for (i, e) in enumerate(episode_list) if e.get('id') == id),
+ (len(episode_list) - i for i, e in enumerate(episode_list) if e.get('id') == video_id),
None)
return {
- 'id': id,
+ 'id': video_id,
'_old_archive_ids': [make_archive_id(self, display_id.rsplit('-', 1)[-1])],
'display_id': display_id,
'title': title,
@@ -151,5 +151,5 @@ class CallinIE(InfoExtractor):
'series_id': show_id,
'episode': title,
'episode_number': episode_number,
- 'episode_id': id
+ 'episode_id': video_id,
}
diff --git a/yt_dlp/extractor/caltrans.py b/yt_dlp/extractor/caltrans.py
index f4a4a834b..5513bb2df 100644
--- a/yt_dlp/extractor/caltrans.py
+++ b/yt_dlp/extractor/caltrans.py
@@ -11,7 +11,7 @@ class CaltransIE(InfoExtractor):
'title': 'US-50 : Sacramento : Hwy 50 at 24th',
'live_status': 'is_live',
'thumbnail': 'https://cwwp2.dot.ca.gov/data/d3/cctv/image/hwy50at24th/hwy50at24th.jpg',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/cam4.py b/yt_dlp/extractor/cam4.py
index 2650cc1ef..0d0dccb79 100644
--- a/yt_dlp/extractor/cam4.py
+++ b/yt_dlp/extractor/cam4.py
@@ -12,12 +12,12 @@ class CAM4IE(InfoExtractor):
'age_limit': 18,
'live_status': 'is_live',
'thumbnail': 'https://snapshots.xcdnpro.com/thumbnails/foxynesss',
- }
+ },
}
def _real_extract(self, url):
channel_id = self._match_id(url)
- m3u8_playlist = self._download_json('https://www.cam4.com/rest/v1.0/profile/{}/streamInfo'.format(channel_id), channel_id).get('cdnURL')
+ m3u8_playlist = self._download_json(f'https://www.cam4.com/rest/v1.0/profile/{channel_id}/streamInfo', channel_id).get('cdnURL')
formats = self._extract_m3u8_formats(m3u8_playlist, channel_id, 'mp4', m3u8_id='hls', live=True)
diff --git a/yt_dlp/extractor/camdemy.py b/yt_dlp/extractor/camdemy.py
index c7079e422..34dc095af 100644
--- a/yt_dlp/extractor/camdemy.py
+++ b/yt_dlp/extractor/camdemy.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_urllib_parse_urlencode,
- compat_urlparse,
-)
from ..utils import (
clean_html,
parse_duration,
@@ -28,7 +25,7 @@ class CamdemyIE(InfoExtractor):
'duration': 1591,
'upload_date': '20130114',
'view_count': int,
- }
+ },
}, {
# With non-empty description
# webpage returns "No permission or not login"
@@ -42,7 +39,7 @@ class CamdemyIE(InfoExtractor):
'description': 'md5:2a9f989c2b153a2342acee579c6e7db6',
'creator': 'evercam',
'duration': 318,
- }
+ },
}, {
# External source (YouTube)
'url': 'http://www.camdemy.com/media/14842',
@@ -76,12 +73,12 @@ class CamdemyIE(InfoExtractor):
title = oembed_obj['title']
thumb_url = oembed_obj['thumbnail_url']
- video_folder = compat_urlparse.urljoin(thumb_url, 'video/')
+ video_folder = urllib.parse.urljoin(thumb_url, 'video/')
file_list_doc = self._download_xml(
- compat_urlparse.urljoin(video_folder, 'fileList.xml'),
+ urllib.parse.urljoin(video_folder, 'fileList.xml'),
video_id, 'Downloading filelist XML')
file_name = file_list_doc.find('./video/item/fileName').text
- video_url = compat_urlparse.urljoin(video_folder, file_name)
+ video_url = urllib.parse.urljoin(video_folder, file_name)
# Some URLs return "No permission or not login" in a webpage despite being
# freely available via oembed JSON URL (e.g. http://www.camdemy.com/media/13885)
@@ -117,35 +114,35 @@ class CamdemyFolderIE(InfoExtractor):
'id': '450',
'title': '信號與系統 2012 & 2011 (Signals and Systems)',
},
- 'playlist_mincount': 145
+ 'playlist_mincount': 145,
}, {
# links without trailing slash
# and multi-page
'url': 'http://www.camdemy.com/folder/853',
'info_dict': {
'id': '853',
- 'title': '科學計算 - 使用 Matlab'
+ 'title': '科學計算 - 使用 Matlab',
},
- 'playlist_mincount': 20
+ 'playlist_mincount': 20,
}, {
# with displayMode parameter. For testing the codes to add parameters
'url': 'http://www.camdemy.com/folder/853/?displayMode=defaultOrderByOrg',
'info_dict': {
'id': '853',
- 'title': '科學計算 - 使用 Matlab'
+ 'title': '科學計算 - 使用 Matlab',
},
- 'playlist_mincount': 20
+ 'playlist_mincount': 20,
}]
def _real_extract(self, url):
folder_id = self._match_id(url)
# Add displayMode=list so that all links are displayed in a single page
- parsed_url = list(compat_urlparse.urlparse(url))
- query = dict(compat_urlparse.parse_qsl(parsed_url[4]))
+ parsed_url = list(urllib.parse.urlparse(url))
+ query = dict(urllib.parse.parse_qsl(parsed_url[4]))
query.update({'displayMode': 'list'})
- parsed_url[4] = compat_urllib_parse_urlencode(query)
- final_url = compat_urlparse.urlunparse(parsed_url)
+ parsed_url[4] = urllib.parse.urlencode(query)
+ final_url = urllib.parse.urlunparse(parsed_url)
page = self._download_webpage(final_url, folder_id)
matches = re.findall(r"href='(/media/\d+/?)'", page)
diff --git a/yt_dlp/extractor/camfm.py b/yt_dlp/extractor/camfm.py
index 11dafa4a2..6036f136f 100644
--- a/yt_dlp/extractor/camfm.py
+++ b/yt_dlp/extractor/camfm.py
@@ -37,7 +37,7 @@ class CamFMShowIE(InfoExtractor):
'thumbnail': urljoin('https://camfm.co.uk', self._search_regex(
r'<img[^>]+class="thumb-expand"[^>]+src="([^"]+)"', page, 'thumbnail', fatal=False)),
'title': self._html_search_regex('<h1>([^<]+)</h1>', page, 'title', fatal=False),
- 'description': clean_html(get_element_by_class('small-12 medium-8 cell', page))
+ 'description': clean_html(get_element_by_class('small-12 medium-8 cell', page)),
}
@@ -56,7 +56,7 @@ class CamFMEpisodeIE(InfoExtractor):
'series': 'AITAA: Am I the Agony Aunt?',
'thumbnail': 'md5:5980a831360d0744c3764551be3d09c1',
'categories': ['Entertainment'],
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/cammodels.py b/yt_dlp/extractor/cammodels.py
index 135b31529..7388cfb6c 100644
--- a/yt_dlp/extractor/cammodels.py
+++ b/yt_dlp/extractor/cammodels.py
@@ -7,14 +7,14 @@ class CamModelsIE(InfoExtractor):
_TESTS = [{
'url': 'https://www.cammodels.com/cam/AutumnKnight/',
'only_matching': True,
- 'age_limit': 18
+ 'age_limit': 18,
}]
def _real_extract(self, url):
user_id = self._match_id(url)
manifest = self._download_json(
- 'https://manifest-server.naiadsystems.com/live/s:%s.json' % user_id, user_id)
+ f'https://manifest-server.naiadsystems.com/live/s:{user_id}.json', user_id)
formats = []
thumbnails = []
@@ -36,7 +36,7 @@ class CamModelsIE(InfoExtractor):
format_id_list = [format_id]
height = int_or_none(media.get('videoHeight'))
if height is not None:
- format_id_list.append('%dp' % height)
+ format_id_list.append(f'{height}p')
f = {
'url': media_url,
'format_id': '-'.join(format_id_list),
@@ -73,5 +73,5 @@ class CamModelsIE(InfoExtractor):
'thumbnails': thumbnails,
'is_live': True,
'formats': formats,
- 'age_limit': 18
+ 'age_limit': 18,
}
diff --git a/yt_dlp/extractor/camtasia.py b/yt_dlp/extractor/camtasia.py
index 70ab6c62a..326643175 100644
--- a/yt_dlp/extractor/camtasia.py
+++ b/yt_dlp/extractor/camtasia.py
@@ -17,7 +17,7 @@ class CamtasiaEmbedIE(InfoExtractor):
'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final - video1',
'ext': 'flv',
'duration': 2235.90,
- }
+ },
}, {
'md5': '10e4bb3aaca9fd630e273ff92d9f3c63',
'info_dict': {
@@ -25,12 +25,12 @@ class CamtasiaEmbedIE(InfoExtractor):
'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final - pip',
'ext': 'flv',
'duration': 2235.93,
- }
+ },
}],
'info_dict': {
'title': 'Fenn-AA_PA_Radar_Course_Lecture_1c_Final',
},
- 'skip': 'webpage dead'
+ 'skip': 'webpage dead',
},
]
diff --git a/yt_dlp/extractor/canalalpha.py b/yt_dlp/extractor/canalalpha.py
index 745e6954c..3a0df9545 100644
--- a/yt_dlp/extractor/canalalpha.py
+++ b/yt_dlp/extractor/canalalpha.py
@@ -21,7 +21,7 @@ class CanalAlphaIE(InfoExtractor):
'upload_date': '20211028',
'duration': 1125,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.canalalpha.ch/play/le-journal/topic/24512/la-poste-fait-de-neuchatel-un-pole-cryptographique',
'info_dict': {
@@ -33,7 +33,7 @@ class CanalAlphaIE(InfoExtractor):
'upload_date': '20211028',
'duration': 138,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.canalalpha.ch/play/eureka/episode/24484/ces-innovations-qui-veulent-rendre-lagriculture-plus-durable',
'info_dict': {
@@ -45,7 +45,7 @@ class CanalAlphaIE(InfoExtractor):
'upload_date': '20211026',
'duration': 360,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.canalalpha.ch/play/avec-le-temps/episode/23516/redonner-de-leclat-grace-au-polissage',
'info_dict': {
@@ -57,7 +57,7 @@ class CanalAlphaIE(InfoExtractor):
'upload_date': '20210726',
'duration': 360,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.canalalpha.ch/play/le-journal/topic/33500/encore-des-mesures-deconomie-dans-le-jura',
'info_dict': {
diff --git a/yt_dlp/extractor/canalc2.py b/yt_dlp/extractor/canalc2.py
index 597cb2a6b..c725545fa 100644
--- a/yt_dlp/extractor/canalc2.py
+++ b/yt_dlp/extractor/canalc2.py
@@ -26,7 +26,7 @@ class Canalc2IE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://www.canalc2.tv/video/%s' % video_id, video_id)
+ f'http://www.canalc2.tv/video/{video_id}', video_id)
title = self._html_search_regex(
r'(?s)class="[^"]*col_description[^"]*">.*?<h3>(.+?)</h3>',
diff --git a/yt_dlp/extractor/canalplus.py b/yt_dlp/extractor/canalplus.py
index 3ff5c3fbf..728b7a047 100644
--- a/yt_dlp/extractor/canalplus.py
+++ b/yt_dlp/extractor/canalplus.py
@@ -53,7 +53,7 @@ class CanalplusIE(InfoExtractor):
video_data = self._download_json(info_url, video_id, 'Downloading video JSON')
if isinstance(video_data, list):
- video_data = [video for video in video_data if video.get('ID') == video_id][0]
+ video_data = next(video for video in video_data if video.get('ID') == video_id)
media = video_data['MEDIA']
infos = video_data['INFOS']
@@ -97,8 +97,7 @@ class CanalplusIE(InfoExtractor):
return {
'id': video_id,
'display_id': display_id,
- 'title': '%s - %s' % (titrage['TITRE'],
- titrage['SOUS_TITRE']),
+ 'title': '{} - {}'.format(titrage['TITRE'], titrage['SOUS_TITRE']),
'upload_date': unified_strdate(infos.get('PUBLICATION', {}).get('DATE')),
'thumbnails': thumbnails,
'description': infos.get('DESCRIPTION'),
diff --git a/yt_dlp/extractor/caracoltv.py b/yt_dlp/extractor/caracoltv.py
index 79f7752fe..493ffdae5 100644
--- a/yt_dlp/extractor/caracoltv.py
+++ b/yt_dlp/extractor/caracoltv.py
@@ -78,13 +78,13 @@ class CaracolTvPlayIE(InfoExtractor):
'device_data': {
'device_id': str(uuid.uuid4()),
'device_token': '',
- 'device_type': 'web'
+ 'device_type': 'web',
},
'login_data': {
'enabled': True,
'email': email,
'password': password,
- }
+ },
}).encode())['user_token']
def _extract_video(self, video_data, series_id=None, season_id=None, season_number=None):
diff --git a/yt_dlp/extractor/cartoonnetwork.py b/yt_dlp/extractor/cartoonnetwork.py
index 4dd7ac46d..1749a008a 100644
--- a/yt_dlp/extractor/cartoonnetwork.py
+++ b/yt_dlp/extractor/cartoonnetwork.py
@@ -27,7 +27,7 @@ class CartoonNetworkIE(TurnerBaseIE):
if content_re:
metadata_re = r'|video_metadata\.content_' + content_re
return self._search_regex(
- r'(?:_cnglobal\.currentVideo\.%s%s)\s*=\s*"(%s)";' % (global_re, metadata_re, value_re),
+ rf'(?:_cnglobal\.currentVideo\.{global_re}{metadata_re})\s*=\s*"({value_re})";',
webpage, name, fatal=fatal)
media_id = find_field('mediaId', 'media id', 'id', '[0-9a-f]{40}', True)
diff --git a/yt_dlp/extractor/cbc.py b/yt_dlp/extractor/cbc.py
index a4180262b..740e12926 100644
--- a/yt_dlp/extractor/cbc.py
+++ b/yt_dlp/extractor/cbc.py
@@ -6,9 +6,6 @@ import urllib.parse
import xml.etree.ElementTree
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..utils import (
ExtractorError,
int_or_none,
@@ -99,7 +96,7 @@ class CBCIE(InfoExtractor):
# multiple CBC.APP.Caffeine.initInstance(...)
'url': 'http://www.cbc.ca/news/canada/calgary/dog-indoor-exercise-winter-1.3928238',
'info_dict': {
- 'title': 'Keep Rover active during the deep freeze with doggie pushups and other fun indoor tasks', # FIXME
+ 'title': 'Keep Rover active during the deep freeze with doggie pushups and other fun indoor tasks', # FIXME: actual title includes " | CBC News"
'id': 'dog-indoor-exercise-winter-1.3928238',
'description': 'md5:c18552e41726ee95bd75210d1ca9194c',
},
@@ -108,7 +105,7 @@ class CBCIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if CBCPlayerIE.suitable(url) else super(CBCIE, cls).suitable(url)
+ return False if CBCPlayerIE.suitable(url) else super().suitable(url)
def _extract_player_init(self, player_init, display_id):
player_info = self._parse_json(player_init, display_id, js_to_json)
@@ -116,15 +113,15 @@ class CBCIE(InfoExtractor):
if not media_id:
clip_id = player_info['clipId']
feed = self._download_json(
- 'http://tpfeed.cbc.ca/f/ExhSPC/vms_5akSXx4Ng_Zn?byCustomValue={:mpsReleases}{%s}' % clip_id,
+ f'http://tpfeed.cbc.ca/f/ExhSPC/vms_5akSXx4Ng_Zn?byCustomValue={{:mpsReleases}}{{{clip_id}}}',
clip_id, fatal=False)
if feed:
- media_id = try_get(feed, lambda x: x['entries'][0]['guid'], compat_str)
+ media_id = try_get(feed, lambda x: x['entries'][0]['guid'], str)
if not media_id:
media_id = self._download_json(
'http://feed.theplatform.com/f/h9dtGB/punlNGjMlc1F?fields=id&byContent=byReleases%3DbyId%253D' + clip_id,
clip_id)['entries'][0]['id'].split('/')[-1]
- return self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id)
+ return self.url_result(f'cbcplayer:{media_id}', 'CBCPlayer', media_id)
def _real_extract(self, url):
display_id = self._match_id(url)
@@ -142,7 +139,7 @@ class CBCIE(InfoExtractor):
r'guid["\']\s*:\s*["\'](\d+)'):
media_ids.extend(re.findall(media_id_re, webpage))
entries.extend([
- self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id)
+ self.url_result(f'cbcplayer:{media_id}', 'CBCPlayer', media_id)
for media_id in orderedSet(media_ids)])
return self.playlist_result(
entries, display_id, strip_or_none(title),
@@ -322,11 +319,11 @@ class CBCPlayerIE(InfoExtractor):
'_type': 'url_transparent',
'ie_key': 'ThePlatform',
'url': smuggle_url(
- 'http://link.theplatform.com/s/ExhSPC/media/guid/2655402169/%s?mbr=true&formats=MPEG4,FLV,MP3' % video_id, {
- 'force_smil_url': True
+ f'http://link.theplatform.com/s/ExhSPC/media/guid/2655402169/{video_id}?mbr=true&formats=MPEG4,FLV,MP3', {
+ 'force_smil_url': True,
}),
'id': video_id,
- '_format_sort_fields': ('res', 'proto') # Prioritize direct http formats over HLS
+ '_format_sort_fields': ('res', 'proto'), # Prioritize direct http formats over HLS
}
@@ -338,13 +335,13 @@ class CBCPlayerPlaylistIE(InfoExtractor):
'playlist_mincount': 25,
'info_dict': {
'id': 'news/tv shows/the national/latest broadcast',
- }
+ },
}, {
'url': 'https://www.cbc.ca/player/news/Canada/North',
'playlist_mincount': 25,
'info_dict': {
'id': 'news/canada/north',
- }
+ },
}]
def _real_extract(self, url):
@@ -355,7 +352,7 @@ class CBCPlayerPlaylistIE(InfoExtractor):
def entries():
for video_id in traverse_obj(json_content, (
- 'video', 'clipsByCategory', lambda k, _: k.lower() == playlist_id, 'items', ..., 'id'
+ 'video', 'clipsByCategory', lambda k, _: k.lower() == playlist_id, 'items', ..., 'id',
)):
yield self.url_result(f'https://www.cbc.ca/player/play/{video_id}', CBCPlayerIE)
@@ -453,7 +450,7 @@ class CBCGemIE(InfoExtractor):
# JWT is decoded here and 'exp' field is extracted
# It is a Unix timestamp for when the token expires
b64_data = self._claims_token.split('.')[1]
- data = base64.urlsafe_b64decode(b64_data + "==")
+ data = base64.urlsafe_b64decode(b64_data + '==')
return json.loads(data)['exp']
def claims_token_expired(self):
@@ -535,17 +532,17 @@ class CBCGemIE(InfoExtractor):
self._remove_duplicate_formats(formats)
formats.extend(self._find_secret_formats(formats, video_id))
- for format in formats:
- if format.get('vcodec') == 'none':
- if format.get('ext') is None:
- format['ext'] = 'm4a'
- if format.get('acodec') is None:
- format['acodec'] = 'mp4a.40.2'
+ for fmt in formats:
+ if fmt.get('vcodec') == 'none':
+ if fmt.get('ext') is None:
+ fmt['ext'] = 'm4a'
+ if fmt.get('acodec') is None:
+ fmt['acodec'] = 'mp4a.40.2'
# Put described audio at the beginning of the list, so that it
# isn't chosen by default, as most people won't want it.
- if 'descriptive' in format['format_id'].lower():
- format['preference'] = -2
+ if 'descriptive' in fmt['format_id'].lower():
+ fmt['preference'] = -2
return {
'id': video_id,
@@ -670,7 +667,7 @@ class CBCGemLiveIE(InfoExtractor):
'title': r're:^Ottawa [0-9\-: ]+',
'description': 'The live TV channel and local programming from Ottawa',
'live_status': 'is_live',
- 'thumbnail': r're:https://images.gem.cbc.ca/v1/cbc-gem/live/.*'
+ 'thumbnail': r're:https://images.gem.cbc.ca/v1/cbc-gem/live/.*',
},
'params': {'skip_download': True},
'skip': 'Live might have ended',
@@ -690,7 +687,7 @@ class CBCGemLiveIE(InfoExtractor):
},
'params': {'skip_download': True},
'skip': 'Live might have ended',
- }
+ },
]
def _real_extract(self, url):
@@ -729,5 +726,5 @@ class CBCGemLiveIE(InfoExtractor):
'description': 'description',
'thumbnail': ('images', 'card', 'url'),
'timestamp': ('airDate', {parse_iso8601}),
- })
+ }),
}
diff --git a/yt_dlp/extractor/cbs.py b/yt_dlp/extractor/cbs.py
index aca9782c7..e82558897 100644
--- a/yt_dlp/extractor/cbs.py
+++ b/yt_dlp/extractor/cbs.py
@@ -31,7 +31,7 @@ class CBSBaseIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE
return subtitles
def _extract_common_video_info(self, content_id, asset_types, mpx_acc, extra_info):
- tp_path = 'dJ5BDC/media/guid/%d/%s' % (mpx_acc, content_id)
+ tp_path = f'dJ5BDC/media/guid/{mpx_acc}/{content_id}'
tp_release_url = f'https://link.theplatform.com/s/{tp_path}'
info = self._extract_theplatform_metadata(tp_path, content_id)
@@ -41,7 +41,7 @@ class CBSBaseIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE
try:
tp_formats, tp_subtitles = self._extract_theplatform_smil(
update_url_query(tp_release_url, query), content_id,
- 'Downloading %s SMIL data' % asset_type)
+ f'Downloading {asset_type} SMIL data')
except ExtractorError as e:
last_e = e
if asset_type != 'fallback':
@@ -50,7 +50,7 @@ class CBSBaseIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE
try:
tp_formats, tp_subtitles = self._extract_theplatform_smil(
update_url_query(tp_release_url, query), content_id,
- 'Downloading %s SMIL data, trying again with another format' % asset_type)
+ f'Downloading {asset_type} SMIL data, trying again with another format')
except ExtractorError as e:
last_e = e
continue
diff --git a/yt_dlp/extractor/ccc.py b/yt_dlp/extractor/ccc.py
index ca6b82c98..1d781cc47 100644
--- a/yt_dlp/extractor/ccc.py
+++ b/yt_dlp/extractor/ccc.py
@@ -25,7 +25,7 @@ class CCCIE(InfoExtractor):
'timestamp': 1388188800,
'duration': 3710,
'tags': list,
- }
+ },
}, {
'url': 'https://media.ccc.de/v/32c3-7368-shopshifting#download',
'only_matching': True,
@@ -35,7 +35,7 @@ class CCCIE(InfoExtractor):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id)
event_id = self._search_regex(r"data-id='(\d+)'", webpage, 'event id')
- event_data = self._download_json('https://media.ccc.de/public/events/%s' % event_id, event_id)
+ event_data = self._download_json(f'https://media.ccc.de/public/events/{event_id}', event_id)
formats = []
for recording in event_data.get('recordings', []):
@@ -96,7 +96,7 @@ class CCCPlaylistIE(InfoExtractor):
'title': 'Datenspuren 2023',
'id': 'DS2023',
},
- 'playlist_count': 37
+ 'playlist_count': 37,
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/ccma.py b/yt_dlp/extractor/ccma.py
index ab840f301..ffe4b49c1 100644
--- a/yt_dlp/extractor/ccma.py
+++ b/yt_dlp/extractor/ccma.py
@@ -24,7 +24,7 @@ class CCMAIE(InfoExtractor):
'timestamp': 1478608140,
'upload_date': '20161108',
'age_limit': 0,
- }
+ },
}, {
'url': 'http://www.ccma.cat/catradio/alacarta/programa/el-consell-de-savis-analitza-el-derbi/audio/943685/',
'md5': 'fa3e38f269329a278271276330261425',
@@ -37,7 +37,7 @@ class CCMAIE(InfoExtractor):
'timestamp': 1494622500,
'vcodec': 'none',
'categories': ['Esports'],
- }
+ },
}, {
'url': 'http://www.ccma.cat/tv3/alacarta/crims/crims-josep-tallada-lespereu-me-capitol-1/video/6031387/',
'md5': 'b43c3d3486f430f3032b5b160d80cbc3',
@@ -51,7 +51,7 @@ class CCMAIE(InfoExtractor):
'subtitles': 'mincount:4',
'age_limit': 16,
'series': 'Crims',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/cctv.py b/yt_dlp/extractor/cctv.py
index 8552ee511..18c080df1 100644
--- a/yt_dlp/extractor/cctv.py
+++ b/yt_dlp/extractor/cctv.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
float_or_none,
try_get,
@@ -167,17 +166,17 @@ class CCTVIE(InfoExtractor):
if isinstance(video, dict):
for quality, chapters_key in enumerate(('lowChapters', 'chapters')):
video_url = try_get(
- video, lambda x: x[chapters_key][0]['url'], compat_str)
+ video, lambda x: x[chapters_key][0]['url'], str)
if video_url:
formats.append({
'url': video_url,
'format_id': 'http',
'quality': quality,
# Sample clip
- 'preference': -10
+ 'preference': -10,
})
- hls_url = try_get(data, lambda x: x['hls_url'], compat_str)
+ hls_url = try_get(data, lambda x: x['hls_url'], str)
if hls_url:
hls_url = re.sub(r'maxbr=\d+&?', '', hls_url)
formats.extend(self._extract_m3u8_formats(
diff --git a/yt_dlp/extractor/cda.py b/yt_dlp/extractor/cda.py
index 0a5a524c1..62ee8b17f 100644
--- a/yt_dlp/extractor/cda.py
+++ b/yt_dlp/extractor/cda.py
@@ -6,9 +6,10 @@ import hmac
import json
import random
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_ord, compat_urllib_parse_unquote
+from ..compat import compat_ord
from ..utils import (
ExtractorError,
float_or_none,
@@ -51,7 +52,7 @@ class CDAIE(InfoExtractor):
'age_limit': 0,
'upload_date': '20160221',
'timestamp': 1456078244,
- }
+ },
}, {
'url': 'http://www.cda.pl/video/57413289',
'md5': 'a88828770a8310fc00be6c95faf7f4d5',
@@ -67,7 +68,7 @@ class CDAIE(InfoExtractor):
'age_limit': 0,
'upload_date': '20160220',
'timestamp': 1455968218,
- }
+ },
}, {
# Age-restricted with vfilm redirection
'url': 'https://www.cda.pl/video/8753244c4',
@@ -85,7 +86,7 @@ class CDAIE(InfoExtractor):
'average_rating': float,
'timestamp': 1633888264,
'upload_date': '20211010',
- }
+ },
}, {
# Age-restricted without vfilm redirection
'url': 'https://www.cda.pl/video/17028157b8',
@@ -103,7 +104,7 @@ class CDAIE(InfoExtractor):
'average_rating': float,
'timestamp': 1699705901,
'upload_date': '20231111',
- }
+ },
}, {
'url': 'http://ebd.cda.pl/0x0/5749950c',
'only_matching': True,
@@ -263,7 +264,7 @@ class CDAIE(InfoExtractor):
def decrypt_file(a):
for p in ('_XDDD', '_CDA', '_ADC', '_CXD', '_QWE', '_Q5', '_IKSDE'):
a = a.replace(p, '')
- a = compat_urllib_parse_unquote(a)
+ a = urllib.parse.unquote(a)
b = []
for c in a:
f = compat_ord(c)
@@ -280,16 +281,16 @@ class CDAIE(InfoExtractor):
def extract_format(page, version):
json_str = self._html_search_regex(
r'player_data=(\\?["\'])(?P<player_data>.+?)\1', page,
- '%s player_json' % version, fatal=False, group='player_data')
+ f'{version} player_json', fatal=False, group='player_data')
if not json_str:
return
player_data = self._parse_json(
- json_str, '%s player_data' % version, fatal=False)
+ json_str, f'{version} player_data', fatal=False)
if not player_data:
return
video = player_data.get('video')
if not video or 'file' not in video:
- self.report_warning('Unable to extract %s version information' % version)
+ self.report_warning(f'Unable to extract {version} version information')
return
if video['file'].startswith('uggc'):
video['file'] = codecs.decode(video['file'], 'rot_13')
@@ -310,11 +311,11 @@ class CDAIE(InfoExtractor):
continue
data = {'jsonrpc': '2.0', 'method': 'videoGetLink', 'id': 2,
'params': [video_id, cda_quality, video.get('ts'), video.get('hash2'), {}]}
- data = json.dumps(data).encode('utf-8')
+ data = json.dumps(data).encode()
video_url = self._download_json(
f'https://www.cda.pl/video/{video_id}', video_id, headers={
'Content-Type': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest'
+ 'X-Requested-With': 'XMLHttpRequest',
}, data=data, note=f'Fetching {quality} url',
errnote=f'Failed to fetch {quality} url', fatal=False)
if try_get(video_url, lambda x: x['result']['status']) == 'ok':
@@ -322,7 +323,7 @@ class CDAIE(InfoExtractor):
info_dict['formats'].append({
'url': video_url,
'format_id': quality,
- 'height': int_or_none(quality[:-1])
+ 'height': int_or_none(quality[:-1]),
})
if not info_dict['duration']:
@@ -340,11 +341,11 @@ class CDAIE(InfoExtractor):
webpage = handler(
urljoin(self._BASE_URL, href), video_id,
- 'Downloading %s version information' % resolution, fatal=False)
+ f'Downloading {resolution} version information', fatal=False)
if not webpage:
# Manually report warning because empty page is returned when
# invalid version is requested.
- self.report_warning('Unable to download %s version information' % resolution)
+ self.report_warning(f'Unable to download {resolution} version information')
continue
extract_format(webpage, resolution)
diff --git a/yt_dlp/extractor/cellebrite.py b/yt_dlp/extractor/cellebrite.py
index 9896a31af..e90365a8b 100644
--- a/yt_dlp/extractor/cellebrite.py
+++ b/yt_dlp/extractor/cellebrite.py
@@ -14,7 +14,7 @@ class CellebriteIE(InfoExtractor):
'title': 'Ask the Expert: Chat Capture - Collect Data from Android Devices in Cellebrite UFED',
'duration': 455,
'tags': [],
- }
+ },
}, {
'url': 'https://cellebrite.com/en/how-to-lawfully-collect-the-maximum-amount-of-data-from-android-devices/',
'info_dict': {
@@ -25,7 +25,7 @@ class CellebriteIE(InfoExtractor):
'description': 'md5:e9a3d124c7287b0b07bad2547061cacf',
'thumbnail': 'https://cellebrite.com/wp-content/uploads/2022/07/How-to-Lawfully-Collect-the-Maximum-Amount-of-Data-From-Android-Devices.png',
'title': 'Android Extractions Explained',
- }
+ },
}]
def _get_formats_and_subtitles(self, json_data, display_id):
diff --git a/yt_dlp/extractor/ceskatelevize.py b/yt_dlp/extractor/ceskatelevize.py
index 5d6335729..c323985ca 100644
--- a/yt_dlp/extractor/ceskatelevize.py
+++ b/yt_dlp/extractor/ceskatelevize.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote, compat_urllib_parse_urlparse
from ..networking import Request
from ..utils import (
ExtractorError,
@@ -97,11 +97,11 @@ class CeskaTelevizeIE(InfoExtractor):
def _real_extract(self, url):
playlist_id = self._match_id(url)
webpage, urlh = self._download_webpage_handle(url, playlist_id)
- parsed_url = compat_urllib_parse_urlparse(urlh.url)
+ parsed_url = urllib.parse.urlparse(urlh.url)
site_name = self._og_search_property('site_name', webpage, fatal=False, default='Česká televize')
playlist_title = self._og_search_title(webpage, default=None)
if site_name and playlist_title:
- playlist_title = re.split(r'\s*[—|]\s*%s' % (site_name, ), playlist_title, maxsplit=1)[0]
+ playlist_title = re.split(rf'\s*[—|]\s*{site_name}', playlist_title, maxsplit=1)[0]
playlist_description = self._og_search_description(webpage, default=None)
if playlist_description:
playlist_description = playlist_description.replace('\xa0', ' ')
@@ -122,15 +122,15 @@ class CeskaTelevizeIE(InfoExtractor):
iframe_hash = self._download_webpage(
'https://www.ceskatelevize.cz/v-api/iframe-hash/',
playlist_id, note='Getting IFRAME hash')
- query = {'hash': iframe_hash, 'origin': 'iVysilani', 'autoStart': 'true', type_: idec, }
+ query = {'hash': iframe_hash, 'origin': 'iVysilani', 'autoStart': 'true', type_: idec}
webpage = self._download_webpage(
'https://www.ceskatelevize.cz/ivysilani/embed/iFramePlayer.php',
playlist_id, note='Downloading player', query=query)
NOT_AVAILABLE_STRING = 'This content is not available at your territory due to limited copyright.'
- if '%s</p>' % NOT_AVAILABLE_STRING in webpage:
+ if f'{NOT_AVAILABLE_STRING}</p>' in webpage:
self.raise_geo_restricted(NOT_AVAILABLE_STRING)
- if any(not_found in webpage for not_found in ('Neplatný parametr pro videopřehrávač', 'IDEC nebyl nalezen', )):
+ if any(not_found in webpage for not_found in ('Neplatný parametr pro videopřehrávač', 'IDEC nebyl nalezen')):
raise ExtractorError('no video with IDEC available', video_id=idec, expected=True)
type_ = None
@@ -183,7 +183,7 @@ class CeskaTelevizeIE(InfoExtractor):
if playlist_url == 'error_region':
raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
- req = Request(compat_urllib_parse_unquote(playlist_url))
+ req = Request(urllib.parse.unquote(playlist_url))
req.headers['Referer'] = url
playlist = self._download_json(req, playlist_id, fatal=False)
@@ -203,11 +203,11 @@ class CeskaTelevizeIE(InfoExtractor):
if 'playerType=flash' in stream_url:
stream_formats = self._extract_m3u8_formats(
stream_url, playlist_id, 'mp4', 'm3u8_native',
- m3u8_id='hls-%s' % format_id, fatal=False)
+ m3u8_id=f'hls-{format_id}', fatal=False)
else:
stream_formats = self._extract_mpd_formats(
stream_url, playlist_id,
- mpd_id='dash-%s' % format_id, fatal=False)
+ mpd_id=f'dash-{format_id}', fatal=False)
if 'drmOnly=true' in stream_url:
for f in stream_formats:
f['has_drm'] = True
@@ -236,7 +236,7 @@ class CeskaTelevizeIE(InfoExtractor):
if playlist_len == 1:
final_title = playlist_title or title
else:
- final_title = '%s (%s)' % (playlist_title, title)
+ final_title = f'{playlist_title} ({title})'
entries.append({
'id': item_id,
@@ -261,7 +261,7 @@ class CeskaTelevizeIE(InfoExtractor):
'cs': [{
'ext': 'srt',
'data': srt_subs,
- }]
+ }],
}
@staticmethod
@@ -282,7 +282,7 @@ class CeskaTelevizeIE(InfoExtractor):
if m:
yield m.group(1)
start, stop = (_msectotimecode(int(t)) for t in m.groups()[1:])
- yield '{0} --> {1}'.format(start, stop)
+ yield f'{start} --> {stop}'
else:
yield line
diff --git a/yt_dlp/extractor/cgtn.py b/yt_dlp/extractor/cgtn.py
index 5d9d9bcde..b9757e063 100644
--- a/yt_dlp/extractor/cgtn.py
+++ b/yt_dlp/extractor/cgtn.py
@@ -20,8 +20,8 @@ class CGTNIE(InfoExtractor):
'categories': ['Video'],
},
'params': {
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}, {
'url': 'https://news.cgtn.com/news/2021-06-06/China-Indonesia-vow-to-further-deepen-maritime-cooperation-10REvJCewCY/index.html',
'info_dict': {
@@ -36,9 +36,9 @@ class CGTNIE(InfoExtractor):
'upload_date': '20210606',
},
'params': {
- 'skip_download': False
- }
- }
+ 'skip_download': False,
+ },
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/chaturbate.py b/yt_dlp/extractor/chaturbate.py
index 99dfcfdeb..b49f741ef 100644
--- a/yt_dlp/extractor/chaturbate.py
+++ b/yt_dlp/extractor/chaturbate.py
@@ -37,7 +37,7 @@ class ChaturbateIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'https://chaturbate.com/%s/' % video_id, video_id,
+ f'https://chaturbate.com/{video_id}/', video_id,
headers=self.geo_verification_headers())
found_m3u8_urls = []
@@ -85,7 +85,7 @@ class ChaturbateIE(InfoExtractor):
formats = []
for m3u8_url in m3u8_urls:
for known_id in ('fast', 'slow'):
- if '_%s' % known_id in m3u8_url:
+ if f'_{known_id}' in m3u8_url:
m3u8_id = known_id
break
else:
@@ -99,7 +99,7 @@ class ChaturbateIE(InfoExtractor):
return {
'id': video_id,
'title': video_id,
- 'thumbnail': 'https://roomimg.stream.highwebmedia.com/ri/%s.jpg' % video_id,
+ 'thumbnail': f'https://roomimg.stream.highwebmedia.com/ri/{video_id}.jpg',
'age_limit': self._rta_search(webpage),
'is_live': True,
'formats': formats,
diff --git a/yt_dlp/extractor/cinemax.py b/yt_dlp/extractor/cinemax.py
index 706ec8553..66831ef62 100644
--- a/yt_dlp/extractor/cinemax.py
+++ b/yt_dlp/extractor/cinemax.py
@@ -20,6 +20,6 @@ class CinemaxIE(HBOBaseIE):
def _real_extract(self, url):
path, video_id = self._match_valid_url(url).groups()
- info = self._extract_info('https://www.cinemax.com/%s.xml' % path, video_id)
+ info = self._extract_info(f'https://www.cinemax.com/{path}.xml', video_id)
info['id'] = video_id
return info
diff --git a/yt_dlp/extractor/cinetecamilano.py b/yt_dlp/extractor/cinetecamilano.py
index 745b71f24..834890d56 100644
--- a/yt_dlp/extractor/cinetecamilano.py
+++ b/yt_dlp/extractor/cinetecamilano.py
@@ -27,8 +27,8 @@ class CinetecaMilanoIE(InfoExtractor):
'modified_date': '20200520',
'duration': 3139,
'release_timestamp': 1643446208,
- 'modified_timestamp': int
- }
+ 'modified_timestamp': int,
+ },
}]
def _real_extract(self, url):
@@ -38,7 +38,7 @@ class CinetecaMilanoIE(InfoExtractor):
f'https://www.cinetecamilano.it/api/catalogo/{video_id}/?',
video_id, headers={
'Referer': url,
- 'Authorization': try_get(self._get_cookies('https://www.cinetecamilano.it'), lambda x: f'Bearer {x["cnt-token"].value}') or ''
+ 'Authorization': try_get(self._get_cookies('https://www.cinetecamilano.it'), lambda x: f'Bearer {x["cnt-token"].value}') or '',
})
except ExtractorError as e:
if ((isinstance(e.cause, HTTPError) and e.cause.status == 500)
@@ -58,5 +58,5 @@ class CinetecaMilanoIE(InfoExtractor):
'modified_timestamp': parse_iso8601(archive.get('created_at'), delimiter=' '),
'thumbnail': urljoin(url, try_get(archive, lambda x: x['thumb']['src'].replace('/public/', '/storage/'))),
'formats': self._extract_m3u8_formats(
- urljoin(url, traverse_obj(archive, ('drm', 'hls'))), video_id, 'mp4')
+ urljoin(url, traverse_obj(archive, ('drm', 'hls'))), video_id, 'mp4'),
}
diff --git a/yt_dlp/extractor/cineverse.py b/yt_dlp/extractor/cineverse.py
index 4405297c6..c8c6c48c2 100644
--- a/yt_dlp/extractor/cineverse.py
+++ b/yt_dlp/extractor/cineverse.py
@@ -13,7 +13,7 @@ from ..utils import (
class CineverseBaseIE(InfoExtractor):
- _VALID_URL_BASE = r'https?://www\.(?P<host>%s)' % '|'.join(map(re.escape, (
+ _VALID_URL_BASE = r'https?://www\.(?P<host>{})'.format('|'.join(map(re.escape, (
'cineverse.com',
'asiancrush.com',
'dovechannel.com',
@@ -21,7 +21,7 @@ class CineverseBaseIE(InfoExtractor):
'midnightpulp.com',
'fandor.com',
'retrocrush.tv',
- )))
+ ))))
class CineverseIE(CineverseBaseIE):
@@ -38,7 +38,7 @@ class CineverseIE(CineverseBaseIE):
'duration': 5811.597,
'description': 'md5:892fd62a05611d394141e8394ace0bc6',
'age_limit': 13,
- }
+ },
}, {
'url': 'https://www.retrocrush.tv/watch/1000000023016/Archenemy! Crystal Bowie',
'skip': 'geo-blocked',
@@ -55,7 +55,7 @@ class CineverseIE(CineverseBaseIE):
'duration': 1485.067,
'description': 'Cobra meets a beautiful bounty hunter by the name of Jane Royal.',
'series': 'Space Adventure COBRA (Original Japanese)',
- }
+ },
}]
def _real_extract(self, url):
@@ -104,7 +104,7 @@ class CineverseDetailsIE(CineverseBaseIE):
'info_dict': {
'title': 'Space Adventure COBRA (Original Japanese)',
'id': '1000000023012',
- }
+ },
}, {
'url': 'https://www.asiancrush.com/details/NNVG4938/Hansel-and-Gretel',
'info_dict': {
diff --git a/yt_dlp/extractor/ciscolive.py b/yt_dlp/extractor/ciscolive.py
index 066857817..1584ca665 100644
--- a/yt_dlp/extractor/ciscolive.py
+++ b/yt_dlp/extractor/ciscolive.py
@@ -105,7 +105,7 @@ class CiscoLiveSearchIE(CiscoLiveBaseIE):
@classmethod
def suitable(cls, url):
- return False if CiscoLiveSessionIE.suitable(url) else super(CiscoLiveSearchIE, cls).suitable(url)
+ return False if CiscoLiveSessionIE.suitable(url) else super().suitable(url)
@staticmethod
def _check_bc_id_exists(rf_item):
@@ -117,7 +117,7 @@ class CiscoLiveSearchIE(CiscoLiveBaseIE):
for page_num in itertools.count(1):
results = self._call_api(
'search', None, query, url,
- 'Downloading search JSON page %d' % page_num)
+ f'Downloading search JSON page {page_num}')
sl = try_get(results, lambda x: x['sectionList'][0], dict)
if sl:
results = sl
diff --git a/yt_dlp/extractor/ciscowebex.py b/yt_dlp/extractor/ciscowebex.py
index 85585dffb..d39347c82 100644
--- a/yt_dlp/extractor/ciscowebex.py
+++ b/yt_dlp/extractor/ciscowebex.py
@@ -46,7 +46,7 @@ class CiscoWebexIE(InfoExtractor):
headers['accessPwd'] = password
stream, urlh = self._download_json_handle(
- 'https://%s.webex.com/webappng/api/v1/recordings/%s/stream' % (subdomain, video_id),
+ f'https://{subdomain}.webex.com/webappng/api/v1/recordings/{video_id}/stream',
video_id, headers=headers, query={'siteurl': siteurl}, expected_status=(403, 429))
if urlh.status == 403:
@@ -101,6 +101,6 @@ class CiscoWebexIE(InfoExtractor):
'uploader_id': stream.get('ownerUserName') or stream.get('ownerId'),
'timestamp': unified_timestamp(stream.get('createTime')),
'duration': int_or_none(stream.get('duration'), 1000),
- 'webpage_url': 'https://%s.webex.com/recordingservice/sites/%s/recording/playback/%s' % (subdomain, siteurl, video_id),
+ 'webpage_url': f'https://{subdomain}.webex.com/recordingservice/sites/{siteurl}/recording/playback/{video_id}',
'formats': formats,
}
diff --git a/yt_dlp/extractor/cjsw.py b/yt_dlp/extractor/cjsw.py
index c37a3b848..b80236a7e 100644
--- a/yt_dlp/extractor/cjsw.py
+++ b/yt_dlp/extractor/cjsw.py
@@ -27,7 +27,7 @@ class CJSWIE(InfoExtractor):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
program, episode_id = mobj.group('program', 'id')
- audio_id = '%s/%s' % (program, episode_id)
+ audio_id = f'{program}/{episode_id}'
webpage = self._download_webpage(url, episode_id)
diff --git a/yt_dlp/extractor/clippit.py b/yt_dlp/extractor/clippit.py
index 67b56e00d..393f21730 100644
--- a/yt_dlp/extractor/clippit.py
+++ b/yt_dlp/extractor/clippit.py
@@ -23,7 +23,7 @@ class ClippitIE(InfoExtractor):
'upload_date': '20160826',
'description': 'BattleBots | ABC',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}
def _real_extract(self, url):
@@ -36,7 +36,7 @@ class ClippitIE(InfoExtractor):
quality = qualities(FORMATS)
formats = []
for format_id in FORMATS:
- url = self._html_search_regex(r'data-%s-file="(.+?)"' % format_id,
+ url = self._html_search_regex(rf'data-{format_id}-file="(.+?)"',
webpage, 'url', fatal=False)
if not url:
continue
diff --git a/yt_dlp/extractor/cliprs.py b/yt_dlp/extractor/cliprs.py
index c2add02da..42f78cac6 100644
--- a/yt_dlp/extractor/cliprs.py
+++ b/yt_dlp/extractor/cliprs.py
@@ -15,7 +15,7 @@ class ClipRsIE(OnetBaseIE):
'duration': 229,
'timestamp': 1459850243,
'upload_date': '20160405',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/closertotruth.py b/yt_dlp/extractor/closertotruth.py
index 1f9a5f611..77469eda9 100644
--- a/yt_dlp/extractor/closertotruth.py
+++ b/yt_dlp/extractor/closertotruth.py
@@ -15,7 +15,7 @@ class CloserToTruthIE(InfoExtractor):
'title': 'Solutions to the Mind-Body Problem?',
'upload_date': '20140221',
'timestamp': 1392956007,
- 'uploader_id': 'CTTXML'
+ 'uploader_id': 'CTTXML',
},
'params': {
'skip_download': True,
@@ -29,7 +29,7 @@ class CloserToTruthIE(InfoExtractor):
'title': 'How do Brains Work?',
'upload_date': '20140221',
'timestamp': 1392956024,
- 'uploader_id': 'CTTXML'
+ 'uploader_id': 'CTTXML',
},
'params': {
'skip_download': True,
@@ -69,7 +69,7 @@ class CloserToTruthIE(InfoExtractor):
entry_ids.add(entry_id)
entries.append({
'_type': 'url_transparent',
- 'url': 'kaltura:%s:%s' % (partner_id, entry_id),
+ 'url': f'kaltura:{partner_id}:{entry_id}',
'ie_key': 'Kaltura',
'title': mobj.group('title'),
})
@@ -83,7 +83,7 @@ class CloserToTruthIE(InfoExtractor):
return {
'_type': 'url_transparent',
'display_id': display_id,
- 'url': 'kaltura:%s:%s' % (partner_id, entry_id),
+ 'url': f'kaltura:{partner_id}:{entry_id}',
'ie_key': 'Kaltura',
- 'title': title
+ 'title': title,
}
diff --git a/yt_dlp/extractor/cloudflarestream.py b/yt_dlp/extractor/cloudflarestream.py
index a812c24af..f902daacf 100644
--- a/yt_dlp/extractor/cloudflarestream.py
+++ b/yt_dlp/extractor/cloudflarestream.py
@@ -53,7 +53,7 @@ class CloudflareStreamIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
domain = 'bytehighway.net' if 'bytehighway.net/' in url else 'videodelivery.net'
- base_url = 'https://%s/%s/' % (domain, video_id)
+ base_url = f'https://{domain}/{video_id}/'
if '.' in video_id:
video_id = self._parse_json(base64.urlsafe_b64decode(
video_id.split('.')[1] + '==='), video_id)['sub']
diff --git a/yt_dlp/extractor/cloudycdn.py b/yt_dlp/extractor/cloudycdn.py
index e6e470e07..58bde4666 100644
--- a/yt_dlp/extractor/cloudycdn.py
+++ b/yt_dlp/extractor/cloudycdn.py
@@ -22,7 +22,7 @@ class CloudyCDNIE(InfoExtractor):
'upload_date': '20231121',
'title': 'D23-6000-105_cetstud',
'thumbnail': 'https://store.cloudycdn.services/tmsp00060/assets/media/660858/placeholder1700589200.jpg',
- }
+ },
}, {
'url': 'https://embed.cloudycdn.services/izm/media/26e_lv-8-5-1',
'md5': '798828a479151e2444d8dcfbec76e482',
@@ -34,7 +34,7 @@ class CloudyCDNIE(InfoExtractor):
'thumbnail': 'https://store.cloudycdn.services/tmsp00120/assets/media/488306/placeholder1679423604.jpg',
'duration': 1205,
'upload_date': '20221130',
- }
+ },
}]
_WEBPAGE_TESTS = [{
'url': 'https://www.tavaklase.lv/video/es-esmu-mina-um-2/',
@@ -47,7 +47,7 @@ class CloudyCDNIE(InfoExtractor):
'thumbnail': 'https://store.cloudycdn.services/tmsp00120/assets/media/518407/placeholder1678748124.jpg',
'timestamp': 1677181513,
'title': 'LIB-2',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/clubic.py b/yt_dlp/extractor/clubic.py
index 716f25969..c908e61a1 100644
--- a/yt_dlp/extractor/clubic.py
+++ b/yt_dlp/extractor/clubic.py
@@ -18,7 +18,7 @@ class ClubicIE(InfoExtractor):
'title': 'Clubic Week 2.0 : le FBI se lance dans la photo d\u0092identité',
'description': 're:Gueule de bois chez Nokia. Le constructeur a indiqué cette.*',
'thumbnail': r're:^http://img\.clubic\.com/.*\.jpg$',
- }
+ },
}, {
'url': 'http://www.clubic.com/video/video-clubic-week-2-0-apple-iphone-6s-et-plus-mais-surtout-le-pencil-469792.html',
'only_matching': True,
@@ -27,7 +27,7 @@ class ClubicIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- player_url = 'http://player.m6web.fr/v1/player/clubic/%s.html' % video_id
+ player_url = f'http://player.m6web.fr/v1/player/clubic/{video_id}.html'
player_page = self._download_webpage(player_url, video_id)
config = self._parse_json(self._search_regex(
diff --git a/yt_dlp/extractor/clyp.py b/yt_dlp/extractor/clyp.py
index 273d0025f..2702427c8 100644
--- a/yt_dlp/extractor/clyp.py
+++ b/yt_dlp/extractor/clyp.py
@@ -58,13 +58,13 @@ class ClypIE(InfoExtractor):
query['token'] = token
metadata = self._download_json(
- 'https://api.clyp.it/%s' % audio_id, audio_id, query=query)
+ f'https://api.clyp.it/{audio_id}', audio_id, query=query)
formats = []
for secure in ('', 'Secure'):
for ext in ('Ogg', 'Mp3'):
- format_id = '%s%s' % (secure, ext)
- format_url = metadata.get('%sUrl' % format_id)
+ format_id = f'{secure}{ext}'
+ format_url = metadata.get(f'{format_id}Url')
if format_url:
formats.append({
'url': format_url,
diff --git a/yt_dlp/extractor/cmt.py b/yt_dlp/extractor/cmt.py
index 6359102aa..8e53b7fbf 100644
--- a/yt_dlp/extractor/cmt.py
+++ b/yt_dlp/extractor/cmt.py
@@ -1,6 +1,6 @@
from .mtv import MTVIE
-# TODO Remove - Reason: Outdated Site
+# TODO: Remove - Reason: Outdated Site
class CMTIE(MTVIE): # XXX: Do not subclass from concrete IE
@@ -52,4 +52,4 @@ class CMTIE(MTVIE): # XXX: Do not subclass from concrete IE
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
mgid = self._extract_mgid(webpage, url)
- return self.url_result('http://media.mtvnservices.com/embed/%s' % mgid)
+ return self.url_result(f'http://media.mtvnservices.com/embed/{mgid}')
diff --git a/yt_dlp/extractor/cnn.py b/yt_dlp/extractor/cnn.py
index 61b62fae9..fe7615a89 100644
--- a/yt_dlp/extractor/cnn.py
+++ b/yt_dlp/extractor/cnn.py
@@ -26,7 +26,7 @@ class CNNIE(TurnerBaseIE):
'id': 'us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology',
'ext': 'mp4',
'title': "Student's epic speech stuns new freshmen",
- 'description': "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
+ 'description': 'A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from "2001: A Space Odyssey."',
'upload_date': '20130821',
},
'expected_warnings': ['Failed to download m3u8 information'],
@@ -161,7 +161,7 @@ class CNNIndonesiaIE(InfoExtractor):
'release_timestamp': 1662859088,
'release_date': '20220911',
'uploader': 'Asfahan Yahsyi',
- }
+ },
}, {
'url': 'https://www.cnnindonesia.com/internasional/20220911104341-139-846189/video-momen-charles-disambut-meriah-usai-dilantik-jadi-raja-inggris',
'info_dict': {
@@ -178,7 +178,7 @@ class CNNIndonesiaIE(InfoExtractor):
'release_date': '20220911',
'uploader': 'REUTERS',
'release_timestamp': 1662869995,
- }
+ },
}]
def _real_extract(self, url):
@@ -194,5 +194,5 @@ class CNNIndonesiaIE(InfoExtractor):
'_type': 'url_transparent',
'url': embed_url,
'upload_date': upload_date,
- 'tags': try_call(lambda: self._html_search_meta('keywords', webpage).split(', '))
+ 'tags': try_call(lambda: self._html_search_meta('keywords', webpage).split(', ')),
})
diff --git a/yt_dlp/extractor/common.py b/yt_dlp/extractor/common.py
index 1d2c443c0..2799747ec 100644
--- a/yt_dlp/extractor/common.py
+++ b/yt_dlp/extractor/common.py
@@ -60,7 +60,6 @@ from ..utils import (
determine_ext,
dict_get,
encode_data_uri,
- error_to_compat_str,
extract_attributes,
filter_dict,
fix_xml_ampersands,
@@ -767,8 +766,8 @@ class InfoExtractor:
self._x_forwarded_for_ip = GeoUtils.random_ipv4(country_code)
if self._x_forwarded_for_ip:
self.report_warning(
- 'Video is geo restricted. Retrying extraction with fake IP %s (%s) as X-Forwarded-For.'
- % (self._x_forwarded_for_ip, country_code.upper()))
+ 'Video is geo restricted. Retrying extraction with fake IP '
+ f'{self._x_forwarded_for_ip} ({country_code.upper()}) as X-Forwarded-For.')
return True
return False
@@ -841,7 +840,7 @@ class InfoExtractor:
if not self._downloader._first_webpage_request:
sleep_interval = self.get_param('sleep_interval_requests') or 0
if sleep_interval > 0:
- self.to_screen('Sleeping %s seconds ...' % sleep_interval)
+ self.to_screen(f'Sleeping {sleep_interval} seconds ...')
time.sleep(sleep_interval)
else:
self._downloader._first_webpage_request = False
@@ -898,7 +897,7 @@ class InfoExtractor:
if errnote is None:
errnote = 'Unable to download webpage'
- errmsg = f'{errnote}: {error_to_compat_str(err)}'
+ errmsg = f'{errnote}: {err}'
if fatal:
raise ExtractorError(errmsg, cause=err)
else:
@@ -987,7 +986,7 @@ class InfoExtractor:
r'<iframe src="([^"]+)"', content,
'Websense information URL', default=None)
if blocked_iframe:
- msg += ' Visit %s for more details' % blocked_iframe
+ msg += f' Visit {blocked_iframe} for more details'
raise ExtractorError(msg, expected=True)
if '<title>The URL you requested has been blocked</title>' in first_block:
msg = (
@@ -997,7 +996,7 @@ class InfoExtractor:
r'</h1><p>(.*?)</p>',
content, 'block message', default=None)
if block_msg:
- msg += ' (Message: "%s")' % block_msg.replace('\n', ' ')
+ msg += ' (Message: "{}")'.format(block_msg.replace('\n', ' '))
raise ExtractorError(msg, expected=True)
if ('<title>TTK :: Доступ к ресурсу ограничен</title>' in content
and 'blocklist.rkn.gov.ru' in content):
@@ -1012,7 +1011,7 @@ class InfoExtractor:
basen = join_nonempty(video_id, data, url, delim='_')
trim_length = self.get_param('trim_file_name') or 240
if len(basen) > trim_length:
- h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
+ h = '___' + hashlib.md5(basen.encode()).hexdigest()
basen = basen[:trim_length - len(h)] + h
filename = sanitize_filename(f'{basen}.dump', restricted=True)
# Working around MAX_PATH limitation on Windows (see
@@ -1063,7 +1062,7 @@ class InfoExtractor:
if transform_source:
xml_string = transform_source(xml_string)
try:
- return compat_etree_fromstring(xml_string.encode('utf-8'))
+ return compat_etree_fromstring(xml_string.encode())
except xml.etree.ElementTree.ParseError as ve:
self.__print_error('Failed to parse XML' if errnote is None else errnote, fatal, video_id, ve)
@@ -1214,11 +1213,11 @@ class InfoExtractor:
def report_extraction(self, id_or_name):
"""Report information extraction."""
- self.to_screen('%s: Extracting information' % id_or_name)
+ self.to_screen(f'{id_or_name}: Extracting information')
def report_download_webpage(self, video_id):
"""Report webpage download."""
- self.to_screen('%s: Downloading webpage' % video_id)
+ self.to_screen(f'{video_id}: Downloading webpage')
def report_age_confirmation(self):
"""Report attempt to confirm age."""
@@ -1324,9 +1323,9 @@ class InfoExtractor:
elif default is not NO_DEFAULT:
return default
elif fatal:
- raise RegexNotFoundError('Unable to extract %s' % _name)
+ raise RegexNotFoundError(f'Unable to extract {_name}')
else:
- self.report_warning('unable to extract %s' % _name + bug_reports_message())
+ self.report_warning(f'unable to extract {_name}' + bug_reports_message())
return None
def _search_json(self, start_pattern, string, name, video_id, *, end_pattern='',
@@ -1425,14 +1424,14 @@ class InfoExtractor:
if tfa is not None:
return tfa
- return getpass.getpass('Type %s and press [Return]: ' % note)
+ return getpass.getpass(f'Type {note} and press [Return]: ')
# Helper functions for extracting OpenGraph info
@staticmethod
def _og_regexes(prop):
content_re = r'content=(?:"([^"]+?)"|\'([^\']+?)\'|\s*([^\s"\'=<>`]+?)(?=\s|/?>))'
- property_re = (r'(?:name|property)=(?:\'og%(sep)s%(prop)s\'|"og%(sep)s%(prop)s"|\s*og%(sep)s%(prop)s\b)'
- % {'prop': re.escape(prop), 'sep': '(?:&#x3A;|[:-])'})
+ property_re = r'(?:name|property)=(?:\'og{sep}{prop}\'|"og{sep}{prop}"|\s*og{sep}{prop}\b)'.format(
+ prop=re.escape(prop), sep='(?:&#x3A;|[:-])')
template = r'<meta[^>]+?%s[^>]+?%s'
return [
template % (property_re, content_re),
@@ -1441,14 +1440,14 @@ class InfoExtractor:
@staticmethod
def _meta_regex(prop):
- return r'''(?isx)<meta
- (?=[^>]+(?:itemprop|name|property|id|http-equiv)=(["\']?)%s\1)
- [^>]+?content=(["\'])(?P<content>.*?)\2''' % re.escape(prop)
+ return rf'''(?isx)<meta
+ (?=[^>]+(?:itemprop|name|property|id|http-equiv)=(["\']?){re.escape(prop)}\1)
+ [^>]+?content=(["\'])(?P<content>.*?)\2'''
def _og_search_property(self, prop, html, name=None, **kargs):
prop = variadic(prop)
if name is None:
- name = 'OpenGraph %s' % prop[0]
+ name = f'OpenGraph {prop[0]}'
og_regexes = []
for p in prop:
og_regexes.extend(self._og_regexes(p))
@@ -1571,7 +1570,7 @@ class InfoExtractor:
elif fatal:
raise RegexNotFoundError('Unable to extract JSON-LD')
else:
- self.report_warning('unable to extract JSON-LD %s' % bug_reports_message())
+ self.report_warning(f'unable to extract JSON-LD {bug_reports_message()}')
return {}
def _json_ld(self, json_ld, video_id, fatal=True, expected_type=None):
@@ -1593,8 +1592,8 @@ class InfoExtractor:
}
def is_type(e, *expected_types):
- type = variadic(traverse_obj(e, '@type'))
- return any(x in type for x in expected_types)
+ type_ = variadic(traverse_obj(e, '@type'))
+ return any(x in type_ for x in expected_types)
def extract_interaction_type(e):
interaction_type = e.get('interactionType')
@@ -1623,7 +1622,7 @@ class InfoExtractor:
count_kind = INTERACTION_TYPE_MAP.get(interaction_type.split('/')[-1])
if not count_kind:
continue
- count_key = '%s_count' % count_kind
+ count_key = f'{count_kind}_count'
if info.get(count_key) is not None:
continue
info[count_key] = interaction_count
@@ -1635,7 +1634,7 @@ class InfoExtractor:
'end_time': part.get('endOffset'),
} for part in variadic(e.get('hasPart') or []) if part.get('@type') == 'Clip']
for idx, (last_c, current_c, next_c) in enumerate(zip(
- [{'end_time': 0}] + chapters, chapters, chapters[1:])):
+ [{'end_time': 0}, *chapters], chapters, chapters[1:])):
current_c['end_time'] = current_c['end_time'] or next_c['start_time']
current_c['start_time'] = current_c['start_time'] or last_c['end_time']
if None in current_c.values():
@@ -1776,9 +1775,9 @@ class InfoExtractor:
def _hidden_inputs(html):
html = re.sub(r'<!--(?:(?!<!--).)*-->', '', html)
hidden_inputs = {}
- for input in re.findall(r'(?i)(<input[^>]+>)', html):
- attrs = extract_attributes(input)
- if not input:
+ for input_el in re.findall(r'(?i)(<input[^>]+>)', html):
+ attrs = extract_attributes(input_el)
+ if not input_el:
continue
if attrs.get('type') not in ('hidden', 'submit'):
continue
@@ -1790,8 +1789,8 @@ class InfoExtractor:
def _form_hidden_inputs(self, form_id, html):
form = self._search_regex(
- r'(?is)<form[^>]+?id=(["\'])%s\1[^>]*>(?P<form>.+?)</form>' % form_id,
- html, '%s form' % form_id, group='form')
+ rf'(?is)<form[^>]+?id=(["\']){form_id}\1[^>]*>(?P<form>.+?)</form>',
+ html, f'{form_id} form', group='form')
return self._hidden_inputs(form)
@classproperty(cache=True)
@@ -1821,7 +1820,7 @@ class InfoExtractor:
formats[:] = filter(
lambda f: self._is_valid_url(
f['url'], video_id,
- item='%s video format' % f.get('format_id') if f.get('format_id') else 'video'),
+ item='{} video format'.format(f.get('format_id')) if f.get('format_id') else 'video'),
formats)
@staticmethod
@@ -1837,15 +1836,14 @@ class InfoExtractor:
def _is_valid_url(self, url, video_id, item='video', headers={}):
url = self._proto_relative_url(url, scheme='http:')
# For now assume non HTTP(S) URLs always valid
- if not (url.startswith('http://') or url.startswith('https://')):
+ if not url.startswith(('http://', 'https://')):
return True
try:
- self._request_webpage(url, video_id, 'Checking %s URL' % item, headers=headers)
+ self._request_webpage(url, video_id, f'Checking {item} URL', headers=headers)
return True
except ExtractorError as e:
self.to_screen(
- '%s: %s URL is invalid, skipping: %s'
- % (video_id, item, error_to_compat_str(e.cause)))
+ f'{video_id}: {item} URL is invalid, skipping: {e.cause!s}')
return False
def http_scheme(self):
@@ -1899,8 +1897,8 @@ class InfoExtractor:
# currently yt-dlp cannot decode the playerVerificationChallenge as Akamai uses Adobe Alchemy
akamai_pv = manifest.find('{http://ns.adobe.com/f4m/1.0}pv-2.0')
if akamai_pv is not None and ';' in akamai_pv.text:
- playerVerificationChallenge = akamai_pv.text.split(';')[0]
- if playerVerificationChallenge.strip() != '':
+ player_verification_challenge = akamai_pv.text.split(';')[0]
+ if player_verification_challenge.strip() != '':
return []
formats = []
@@ -1946,7 +1944,7 @@ class InfoExtractor:
if not media_url:
continue
manifest_url = (
- media_url if media_url.startswith('http://') or media_url.startswith('https://')
+ media_url if media_url.startswith(('http://', 'https://'))
else ((manifest_base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
# If media_url is itself a f4m manifest do the recursive extraction
# since bitrates in parent manifest (this one) and media_url manifest
@@ -2007,7 +2005,7 @@ class InfoExtractor:
def _report_ignoring_subs(self, name):
self.report_warning(bug_reports_message(
f'Ignoring subtitle tracks found in the {name} manifest; '
- 'if any subtitle tracks are missing,'
+ 'if any subtitle tracks are missing,',
), only_once=True)
def _extract_m3u8_formats(self, *args, **kwargs):
@@ -2098,7 +2096,7 @@ class InfoExtractor:
formats = [{
'format_id': join_nonempty(m3u8_id, idx),
'format_index': idx,
- 'url': m3u8_url or encode_data_uri(m3u8_doc.encode('utf-8'), 'application/x-mpegurl'),
+ 'url': m3u8_url or encode_data_uri(m3u8_doc.encode(), 'application/x-mpegurl'),
'ext': ext,
'protocol': entry_protocol,
'preference': preference,
@@ -2310,7 +2308,7 @@ class InfoExtractor:
if not c or c == '.':
out.append(c)
else:
- out.append('{%s}%s' % (namespace, c))
+ out.append(f'{{{namespace}}}{c}')
return '/'.join(out)
def _extract_smil_formats_and_subtitles(self, smil_url, video_id, fatal=True, f4m_params=None, transform_source=None):
@@ -2507,7 +2505,7 @@ class InfoExtractor:
imgs_count += 1
formats.append({
- 'format_id': 'imagestream-%d' % (imgs_count),
+ 'format_id': f'imagestream-{imgs_count}',
'url': src,
'ext': mimetype2ext(medium.get('type')),
'acodec': 'none',
@@ -2525,7 +2523,7 @@ class InfoExtractor:
def _parse_smil_subtitles(self, smil, namespace=None, subtitles_lang='en'):
urls = []
subtitles = {}
- for num, textstream in enumerate(smil.findall(self._xpath_ns('.//textstream', namespace))):
+ for textstream in smil.findall(self._xpath_ns('.//textstream', namespace)):
src = textstream.get('src')
if not src or src in urls:
continue
@@ -2656,7 +2654,7 @@ class InfoExtractor:
if subtitles and period['subtitles']:
self.report_warning(bug_reports_message(
'Found subtitles in multiple periods in the DASH manifest; '
- 'if part of the subtitles are missing,'
+ 'if part of the subtitles are missing,',
), only_once=True)
for sub_lang, sub_info in period['subtitles'].items():
@@ -2782,7 +2780,7 @@ class InfoExtractor:
elif mimetype2ext(mime_type) in ('tt', 'dfxp', 'ttml', 'xml', 'json'):
content_type = 'text'
else:
- self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
+ self.report_warning(f'Unknown MIME type {mime_type} in DASH manifest')
continue
base_url = ''
@@ -2820,10 +2818,10 @@ class InfoExtractor:
'asr': int_or_none(representation_attrib.get('audioSamplingRate')),
'fps': int_or_none(representation_attrib.get('frameRate')),
'language': lang if lang not in ('mul', 'und', 'zxx', 'mis') else None,
- 'format_note': 'DASH %s' % content_type,
+ 'format_note': f'DASH {content_type}',
'filesize': filesize,
'container': mimetype2ext(mime_type) + '_dash',
- **codecs
+ **codecs,
}
elif content_type == 'text':
f = {
@@ -2864,8 +2862,8 @@ class InfoExtractor:
t += c
# Next, $...$ templates are translated to their
# %(...) counterparts to be used with % operator
- t = re.sub(r'\$(%s)\$' % '|'.join(identifiers), r'%(\1)d', t)
- t = re.sub(r'\$(%s)%%([^$]+)\$' % '|'.join(identifiers), r'%(\1)\2', t)
+ t = re.sub(r'\$({})\$'.format('|'.join(identifiers)), r'%(\1)d', t)
+ t = re.sub(r'\$({})%([^$]+)\$'.format('|'.join(identifiers)), r'%(\1)\2', t)
t.replace('$$', '$')
return t
@@ -2928,12 +2926,12 @@ class InfoExtractor:
'duration': float_or_none(segment_d, representation_ms_info['timescale']),
})
- for num, s in enumerate(representation_ms_info['s']):
+ for s in representation_ms_info['s']:
segment_time = s.get('t') or segment_time
segment_d = s['d']
add_segment_url()
segment_number += 1
- for r in range(s.get('r', 0)):
+ for _ in range(s.get('r', 0)):
segment_time += segment_d
add_segment_url()
segment_number += 1
@@ -2947,7 +2945,7 @@ class InfoExtractor:
timescale = representation_ms_info['timescale']
for s in representation_ms_info['s']:
duration = float_or_none(s['d'], timescale)
- for r in range(s.get('r', 0) + 1):
+ for _ in range(s.get('r', 0) + 1):
segment_uri = representation_ms_info['segment_urls'][segment_index]
fragments.append({
location_key(segment_uri): segment_uri,
@@ -3055,7 +3053,7 @@ class InfoExtractor:
fourcc = track.get('FourCC') or KNOWN_TAGS.get(track.get('AudioTag'))
# TODO: add support for WVC1 and WMAP
if fourcc not in ('H264', 'AVC1', 'AACL', 'TTML', 'EC-3'):
- self.report_warning('%s is not a supported codec' % fourcc)
+ self.report_warning(f'{fourcc} is not a supported codec')
continue
tbr = int(track.attrib['Bitrate']) // 1000
# [1] does not mention Width and Height attributes. However,
@@ -3104,7 +3102,7 @@ class InfoExtractor:
'fourcc': fourcc,
'language': stream_language,
'codec_private_data': track.get('CodecPrivateData'),
- }
+ },
})
elif stream_type in ('video', 'audio'):
formats.append({
@@ -3186,13 +3184,13 @@ class InfoExtractor:
_MEDIA_TAG_NAME_RE = r'(?:(?:amp|dl8(?:-live)?)-)?(video|audio)'
media_tags = [(media_tag, media_tag_name, media_type, '')
for media_tag, media_tag_name, media_type
- in re.findall(r'(?s)(<(%s)[^>]*/>)' % _MEDIA_TAG_NAME_RE, webpage)]
+ in re.findall(rf'(?s)(<({_MEDIA_TAG_NAME_RE})[^>]*/>)', webpage)]
media_tags.extend(re.findall(
# We only allow video|audio followed by a whitespace or '>'.
# Allowing more characters may end up in significant slow down (see
# https://github.com/ytdl-org/youtube-dl/issues/11979,
# e.g. http://www.porntrex.com/maps/videositemap.xml).
- r'(?s)(<(?P<tag>%s)(?:\s+[^>]*)?>)(.*?)</(?P=tag)>' % _MEDIA_TAG_NAME_RE, webpage))
+ rf'(?s)(<(?P<tag>{_MEDIA_TAG_NAME_RE})(?:\s+[^>]*)?>)(.*?)</(?P=tag)>', webpage))
for media_tag, _, media_type, media_content in media_tags:
media_info = {
'formats': [],
@@ -3336,13 +3334,13 @@ class InfoExtractor:
mobj = re.search(
r'(?:(?:http|rtmp|rtsp)(?P<s>s)?:)?(?P<url>//[^?]+)', url)
url_base = mobj.group('url')
- http_base_url = '%s%s:%s' % ('http', mobj.group('s') or '', url_base)
+ http_base_url = '{}{}:{}'.format('http', mobj.group('s') or '', url_base)
formats = []
def manifest_url(manifest):
m_url = f'{http_base_url}/{manifest}'
if query:
- m_url += '?%s' % query
+ m_url += f'?{query}'
return m_url
if 'm3u8' not in skip_protocols:
@@ -3364,7 +3362,7 @@ class InfoExtractor:
video_id, fatal=False)
for rtmp_format in rtmp_formats:
rtsp_format = rtmp_format.copy()
- rtsp_format['url'] = '%s/%s' % (rtmp_format['url'], rtmp_format['play_path'])
+ rtsp_format['url'] = '{}/{}'.format(rtmp_format['url'], rtmp_format['play_path'])
del rtsp_format['play_path']
del rtsp_format['ext']
rtsp_format.update({
@@ -3431,7 +3429,7 @@ class InfoExtractor:
if not track_url:
continue
subtitles.setdefault(track.get('label') or 'en', []).append({
- 'url': self._proto_relative_url(track_url)
+ 'url': self._proto_relative_url(track_url),
})
entry = {
@@ -3510,7 +3508,7 @@ class InfoExtractor:
'tbr': int_or_none(source.get('bitrate'), scale=1000),
'filesize': int_or_none(source.get('filesize')),
'ext': ext,
- 'format_id': format_id
+ 'format_id': format_id,
}
if source_url.startswith('rtmp'):
a_format['ext'] = 'flv'
@@ -3584,7 +3582,7 @@ class InfoExtractor:
continue
cookies = cookies.encode('iso-8859-1').decode('utf-8')
cookie_value = re.search(
- r'%s=(.+?);.*?\b[Dd]omain=(.+?)(?:[,;]|$)' % cookie, cookies)
+ rf'{cookie}=(.+?);.*?\b[Dd]omain=(.+?)(?:[,;]|$)', cookies)
if cookie_value:
value, domain = cookie_value.groups()
self._set_cookie(domain, cookie, value)
@@ -3668,7 +3666,7 @@ class InfoExtractor:
desc += ' (**Currently broken**)' if markdown else ' (Currently broken)'
# Escape emojis. Ref: https://github.com/github/markup/issues/1153
- name = (' - **%s**' % re.sub(r':(\w+:)', ':\u200B\\g<1>', cls.IE_NAME)) if markdown else cls.IE_NAME
+ name = (' - **{}**'.format(re.sub(r':(\w+:)', ':\u200B\\g<1>', cls.IE_NAME))) if markdown else cls.IE_NAME
return f'{name}:{desc}' if desc else name
def extract_subtitles(self, *args, **kwargs):
@@ -3708,7 +3706,7 @@ class InfoExtractor:
self.to_screen(f'Extracted {comment_count} comments')
return {
'comments': comments,
- 'comment_count': None if interrupted else comment_count
+ 'comment_count': None if interrupted else comment_count,
}
return extractor
@@ -3812,9 +3810,9 @@ class InfoExtractor:
@staticmethod
def _availability(is_private=None, needs_premium=None, needs_subscription=None, needs_auth=None, is_unlisted=None):
- all_known = all(map(
- lambda x: x is not None,
- (is_private, needs_premium, needs_subscription, needs_auth, is_unlisted)))
+ all_known = all(
+ x is not None for x in
+ (is_private, needs_premium, needs_subscription, needs_auth, is_unlisted))
return (
'private' if is_private
else 'premium_only' if needs_premium
@@ -3934,7 +3932,7 @@ class SearchInfoExtractor(InfoExtractor):
@classproperty
def _VALID_URL(cls):
- return r'%s(?P<prefix>|[1-9][0-9]*|all):(?P<query>[\s\S]+)' % cls._SEARCH_KEY
+ return rf'{cls._SEARCH_KEY}(?P<prefix>|[1-9][0-9]*|all):(?P<query>[\s\S]+)'
def _real_extract(self, query):
prefix, query = self._match_valid_url(query).group('prefix', 'query')
diff --git a/yt_dlp/extractor/commonmistakes.py b/yt_dlp/extractor/commonmistakes.py
index 4514424e8..8ddb164b9 100644
--- a/yt_dlp/extractor/commonmistakes.py
+++ b/yt_dlp/extractor/commonmistakes.py
@@ -16,10 +16,10 @@ class CommonMistakesIE(InfoExtractor):
def _real_extract(self, url):
msg = (
- 'You\'ve asked yt-dlp to download the URL "%s". '
+ f'You\'ve asked yt-dlp to download the URL "{url}". '
'That doesn\'t make any sense. '
'Simply remove the parameter in your command or configuration.'
- ) % url
+ )
if not self.get_param('verbose'):
msg += ' Add -v to the command line to see what arguments and configuration yt-dlp has'
raise ExtractorError(msg, expected=True)
@@ -38,7 +38,7 @@ class UnicodeBOMIE(InfoExtractor):
real_url = self._match_id(url)
self.report_warning(
'Your URL starts with a Byte Order Mark (BOM). '
- 'Removing the BOM and looking for "%s" ...' % real_url)
+ f'Removing the BOM and looking for "{real_url}" ...')
return self.url_result(real_url)
diff --git a/yt_dlp/extractor/commonprotocols.py b/yt_dlp/extractor/commonprotocols.py
index 2f93e8ea5..7b3a5b654 100644
--- a/yt_dlp/extractor/commonprotocols.py
+++ b/yt_dlp/extractor/commonprotocols.py
@@ -63,7 +63,7 @@ class ViewSourceIE(InfoExtractor):
_TEST = {
'url': 'view-source:https://www.youtube.com/watch?v=BaW_jenozKc',
- 'only_matching': True
+ 'only_matching': True,
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/condenast.py b/yt_dlp/extractor/condenast.py
index 3170c2990..9c02cd342 100644
--- a/yt_dlp/extractor/condenast.py
+++ b/yt_dlp/extractor/condenast.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_urllib_parse_urlparse,
- compat_urlparse,
-)
from ..utils import (
determine_ext,
extract_attributes,
@@ -48,20 +45,20 @@ class CondeNastIE(InfoExtractor):
'wmagazine': 'W Magazine',
}
- _VALID_URL = r'''(?x)https?://(?:video|www|player(?:-backend)?)\.(?:%s)\.com/
+ _VALID_URL = r'''(?x)https?://(?:video|www|player(?:-backend)?)\.(?:{})\.com/
(?:
(?:
embed(?:js)?|
(?:script|inline)/video
- )/(?P<id>[0-9a-f]{24})(?:/(?P<player_id>[0-9a-f]{24}))?(?:.+?\btarget=(?P<target>[^&]+))?|
+ )/(?P<id>[0-9a-f]{{24}})(?:/(?P<player_id>[0-9a-f]{{24}}))?(?:.+?\btarget=(?P<target>[^&]+))?|
(?P<type>watch|series|video)/(?P<display_id>[^/?#]+)
- )''' % '|'.join(_SITES.keys())
- IE_DESC = 'Condé Nast media group: %s' % ', '.join(sorted(_SITES.values()))
+ )'''.format('|'.join(_SITES.keys()))
+ IE_DESC = 'Condé Nast media group: {}'.format(', '.join(sorted(_SITES.values())))
_EMBED_REGEX = [r'''(?x)
<(?:iframe|script)[^>]+?src=(["\'])(?P<url>
- (?:https?:)?//player(?:-backend)?\.(?:%s)\.com/(?:embed(?:js)?|(?:script|inline)/video)/.+?
- )\1''' % '|'.join(_SITES.keys())]
+ (?:https?:)?//player(?:-backend)?\.(?:{})\.com/(?:embed(?:js)?|(?:script|inline)/video)/.+?
+ )\1'''.format('|'.join(_SITES.keys()))]
_TESTS = [{
'url': 'http://video.wired.com/watch/3d-printed-speakers-lit-with-led',
@@ -74,7 +71,7 @@ class CondeNastIE(InfoExtractor):
'uploader': 'wired',
'upload_date': '20130314',
'timestamp': 1363219200,
- }
+ },
}, {
'url': 'http://video.gq.com/watch/the-closer-with-keith-olbermann-the-only-true-surprise-trump-s-an-idiot?c=series',
'info_dict': {
@@ -97,7 +94,7 @@ class CondeNastIE(InfoExtractor):
'uploader': 'arstechnica',
'upload_date': '20150916',
'timestamp': 1442434920,
- }
+ },
}, {
'url': 'https://player.cnevids.com/inline/video/59138decb57ac36b83000005.js?target=js-cne-player',
'only_matching': True,
@@ -110,12 +107,12 @@ class CondeNastIE(InfoExtractor):
title = self._html_search_regex(
r'(?s)<div class="cne-series-info">.*?<h1>(.+?)</h1>',
webpage, 'series title')
- url_object = compat_urllib_parse_urlparse(url)
- base_url = '%s://%s' % (url_object.scheme, url_object.netloc)
+ url_object = urllib.parse.urlparse(url)
+ base_url = f'{url_object.scheme}://{url_object.netloc}'
m_paths = re.finditer(
r'(?s)<p class="cne-thumb-title">.*?<a href="(/watch/.+?)["\?]', webpage)
paths = orderedSet(m.group(1) for m in m_paths)
- build_url = lambda path: compat_urlparse.urljoin(base_url, path)
+ build_url = lambda path: urllib.parse.urljoin(base_url, path)
entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
return self.playlist_result(entries, playlist_title=title)
@@ -166,9 +163,9 @@ class CondeNastIE(InfoExtractor):
video_id, 'Downloading loader info', query=params)
if not video_info:
info_page = self._download_webpage(
- 'https://player.cnevids.com/inline/video/%s.js' % video_id,
+ f'https://player.cnevids.com/inline/video/{video_id}.js',
video_id, 'Downloading inline info', query={
- 'target': params.get('target', 'embedplayer')
+ 'target': params.get('target', 'embedplayer'),
})
if not video_info:
@@ -192,7 +189,7 @@ class CondeNastIE(InfoExtractor):
continue
quality = fdata.get('quality')
formats.append({
- 'format_id': ext + ('-%s' % quality if quality else ''),
+ 'format_id': ext + (f'-{quality}' if quality else ''),
'url': src,
'ext': ext,
'quality': 1 if quality == 'high' else 0,
diff --git a/yt_dlp/extractor/contv.py b/yt_dlp/extractor/contv.py
index d69e81610..63d760a39 100644
--- a/yt_dlp/extractor/contv.py
+++ b/yt_dlp/extractor/contv.py
@@ -73,7 +73,7 @@ class CONtvIE(InfoExtractor):
captions = m_details.get('captions') or {}
for caption_url in captions.values():
subtitles.setdefault('en', []).append({
- 'url': caption_url
+ 'url': caption_url,
})
thumbnails = []
diff --git a/yt_dlp/extractor/corus.py b/yt_dlp/extractor/corus.py
index 0a98c980f..4af2d186d 100644
--- a/yt_dlp/extractor/corus.py
+++ b/yt_dlp/extractor/corus.py
@@ -69,16 +69,16 @@ class CorusIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE
'only_matching': True,
}, {
'url': 'http://www.bigbrothercanada.ca/video/1457812035894/',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.bigbrothercanada.ca/video/big-brother-canada-704/1457812035894/',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.seriesplus.com/emissions/dre-mary-mort-sur-ordonnance/videos/deux-coeurs-battant/SERP0055626330000200/',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.disneychannel.ca/shows/gabby-duran-the-unsittables/video/crybaby-duran-clip/2f557eec-0588-11ea-ae2b-e2c6776b770e/',
- 'only_matching': True
+ 'only_matching': True,
}]
_GEO_BYPASS = False
_SITE_MAP = {
@@ -97,7 +97,7 @@ class CorusIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE
if path != 'series':
path = 'migration/' + path
video = self._download_json(
- 'https://globalcontent.corusappservices.com/templates/%s/playlist/' % path,
+ f'https://globalcontent.corusappservices.com/templates/{path}/playlist/',
video_id, query={'byId': video_id},
headers={'Accept': 'application/json'})[0]
title = video['title']
@@ -108,7 +108,7 @@ class CorusIE(ThePlatformFeedIE): # XXX: Do not subclass from concrete IE
if not smil_url:
continue
source_type = source.get('type')
- note = 'Downloading%s smil file' % (' ' + source_type if source_type else '')
+ note = 'Downloading{} smil file'.format(' ' + source_type if source_type else '')
resp = self._download_webpage(
smil_url, video_id, note, fatal=False,
headers=self.geo_verification_headers())
diff --git a/yt_dlp/extractor/coub.py b/yt_dlp/extractor/coub.py
index 9bab698a3..95ad66518 100644
--- a/yt_dlp/extractor/coub.py
+++ b/yt_dlp/extractor/coub.py
@@ -44,11 +44,11 @@ class CoubIE(InfoExtractor):
video_id = self._match_id(url)
coub = self._download_json(
- 'http://coub.com/api/v2/coubs/%s.json' % video_id, video_id)
+ f'http://coub.com/api/v2/coubs/{video_id}.json', video_id)
if coub.get('error'):
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, coub['error']), expected=True)
+ '{} said: {}'.format(self.IE_NAME, coub['error']), expected=True)
title = coub['title']
@@ -80,7 +80,7 @@ class CoubIE(InfoExtractor):
continue
formats.append({
'url': item_url,
- 'format_id': '%s-%s-%s' % (HTML5, kind, quality),
+ 'format_id': f'{HTML5}-{kind}-{quality}',
'filesize': int_or_none(item.get('size')),
'vcodec': 'none' if kind == 'audio' else None,
'acodec': 'none' if kind == 'video' else None,
@@ -100,7 +100,7 @@ class CoubIE(InfoExtractor):
if mobile_url:
formats.append({
'url': mobile_url,
- 'format_id': '%s-audio' % MOBILE,
+ 'format_id': f'{MOBILE}-audio',
'source_preference': preference_key(MOBILE),
})
diff --git a/yt_dlp/extractor/cozytv.py b/yt_dlp/extractor/cozytv.py
index 5ef5afcc2..b84dd2ad5 100644
--- a/yt_dlp/extractor/cozytv.py
+++ b/yt_dlp/extractor/cozytv.py
@@ -16,17 +16,17 @@ class CozyTVIE(InfoExtractor):
'was_live': True,
'duration': 7981,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
uploader, date = self._match_valid_url(url).groups()
- id = f'{uploader}-{date}'
- data_json = self._download_json(f'https://api.cozy.tv/cache/{uploader}/replay/{date}', id)
+ video_id = f'{uploader}-{date}'
+ data_json = self._download_json(f'https://api.cozy.tv/cache/{uploader}/replay/{date}', video_id)
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
- f'https://cozycdn.foxtrotstream.xyz/replays/{uploader}/{date}/index.m3u8', id, ext='mp4')
+ f'https://cozycdn.foxtrotstream.xyz/replays/{uploader}/{date}/index.m3u8', video_id, ext='mp4')
return {
- 'id': id,
+ 'id': video_id,
'title': data_json.get('title'),
'uploader': data_json.get('user') or uploader,
'upload_date': unified_strdate(data_json.get('date')),
diff --git a/yt_dlp/extractor/cpac.py b/yt_dlp/extractor/cpac.py
index 32bba1e5a..08d79a22f 100644
--- a/yt_dlp/extractor/cpac.py
+++ b/yt_dlp/extractor/cpac.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
str_or_none,
@@ -38,11 +37,11 @@ class CPACIE(InfoExtractor):
content = self._download_json(
'https://www.cpac.ca/api/1/services/contentModel.json?url=/site/website/episode/index.xml&crafterSite=cpacca&id=' + video_id,
video_id)
- video_url = try_get(content, lambda x: x['page']['details']['videoUrl'], compat_str)
+ video_url = try_get(content, lambda x: x['page']['details']['videoUrl'], str)
formats = []
if video_url:
content = content['page']
- title = str_or_none(content['details']['title_%s_t' % (url_lang, )])
+ title = str_or_none(content['details'][f'title_{url_lang}_t'])
formats = self._extract_m3u8_formats(video_url, video_id, m3u8_id='hls', ext='mp4')
for fmt in formats:
# prefer language to match URL
@@ -54,7 +53,7 @@ class CPACIE(InfoExtractor):
else:
fmt['language_preference'] = -10
- category = str_or_none(content['details']['category_%s_t' % (url_lang, )])
+ category = str_or_none(content['details'][f'category_{url_lang}_t'])
def is_live(v_type):
return (v_type == 'live') if v_type is not None else None
@@ -63,10 +62,10 @@ class CPACIE(InfoExtractor):
'id': video_id,
'formats': formats,
'title': title,
- 'description': str_or_none(content['details'].get('description_%s_t' % (url_lang, ))),
+ 'description': str_or_none(content['details'].get(f'description_{url_lang}_t')),
'timestamp': unified_timestamp(content['details'].get('liveDateTime')),
'categories': [category] if category else None,
- 'thumbnail': urljoin(url, str_or_none(content['details'].get('image_%s_s' % (url_lang, )))),
+ 'thumbnail': urljoin(url, str_or_none(content['details'].get(f'image_{url_lang}_s'))),
'is_live': is_live(content['details'].get('type')),
}
@@ -110,27 +109,26 @@ class CPACPlaylistIE(InfoExtractor):
url_lang = 'fr' if any(x in url for x in ('/emission?', '/rechercher?')) else 'en'
pl_type, list_type = ('program', 'itemList') if any(x in url for x in ('/program?', '/emission?')) else ('search', 'searchResult')
api_url = (
- 'https://www.cpac.ca/api/1/services/contentModel.json?url=/site/website/%s/index.xml&crafterSite=cpacca&%s'
- % (pl_type, video_id, ))
+ f'https://www.cpac.ca/api/1/services/contentModel.json?url=/site/website/{pl_type}/index.xml&crafterSite=cpacca&{video_id}')
content = self._download_json(api_url, video_id)
entries = []
total_pages = int_or_none(try_get(content, lambda x: x['page'][list_type]['totalPages']), default=1)
for page in range(1, total_pages + 1):
if page > 1:
- api_url = update_url_query(api_url, {'page': '%d' % (page, ), })
+ api_url = update_url_query(api_url, {'page': page})
content = self._download_json(
api_url, video_id,
- note='Downloading continuation - %d' % (page, ),
+ note=f'Downloading continuation - {page}',
fatal=False)
for item in try_get(content, lambda x: x['page'][list_type]['item'], list) or []:
- episode_url = urljoin(url, try_get(item, lambda x: x['url_%s_s' % (url_lang, )]))
+ episode_url = urljoin(url, try_get(item, lambda x: x[f'url_{url_lang}_s']))
if episode_url:
entries.append(episode_url)
return self.playlist_result(
(self.url_result(entry) for entry in entries),
playlist_id=video_id,
- playlist_title=try_get(content, lambda x: x['page']['program']['title_%s_t' % (url_lang, )]) or video_id.split('=')[-1],
- playlist_description=try_get(content, lambda x: x['page']['program']['description_%s_t' % (url_lang, )]),
+ playlist_title=try_get(content, lambda x: x['page']['program'][f'title_{url_lang}_t']) or video_id.split('=')[-1],
+ playlist_description=try_get(content, lambda x: x['page']['program'][f'description_{url_lang}_t']),
)
diff --git a/yt_dlp/extractor/cracked.py b/yt_dlp/extractor/cracked.py
index c6aabccc6..f2276547e 100644
--- a/yt_dlp/extractor/cracked.py
+++ b/yt_dlp/extractor/cracked.py
@@ -19,7 +19,7 @@ class CrackedIE(InfoExtractor):
'title': 'If Animal Actors Got E! True Hollywood Stories',
'timestamp': 1404954000,
'upload_date': '20140710',
- }
+ },
}, {
# youtube embed
'url': 'http://www.cracked.com/video_19006_4-plot-holes-you-didnt-notice-in-your-favorite-movies.html',
@@ -32,7 +32,7 @@ class CrackedIE(InfoExtractor):
'upload_date': '20140725',
'uploader_id': 'Cracked',
'uploader': 'Cracked',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/crackle.py b/yt_dlp/extractor/crackle.py
index 0cb7d940c..c4ceba940 100644
--- a/yt_dlp/extractor/crackle.py
+++ b/yt_dlp/extractor/crackle.py
@@ -45,7 +45,7 @@ class CrackleIE(InfoExtractor):
'skip_download': True,
},
'expected_warnings': [
- 'Trying with a list of known countries'
+ 'Trying with a list of known countries',
],
}, {
'url': 'https://www.sonycrackle.com/thanksgiving/2510064',
@@ -89,7 +89,7 @@ class CrackleIE(InfoExtractor):
for num, country in enumerate(countries):
if num == 1: # start hard-coded list
self.report_warning('%s. Trying with a list of known countries' % (
- 'Unable to obtain video formats from %s API' % geo_bypass_country if geo_bypass_country
+ f'Unable to obtain video formats from {geo_bypass_country} API' if geo_bypass_country
else 'No country code was given using --geo-bypass-country'))
elif num == num_countries: # end of list
geo_info = self._download_json(
@@ -99,17 +99,17 @@ class CrackleIE(InfoExtractor):
country = geo_info.get('CountryCode')
if country is None:
continue
- self.to_screen('%s identified country as %s' % (self.IE_NAME, country))
+ self.to_screen(f'{self.IE_NAME} identified country as {country}')
if country in countries:
- self.to_screen('Downloading from %s API was already attempted. Skipping...' % country)
+ self.to_screen(f'Downloading from {country} API was already attempted. Skipping...')
continue
if country is None:
continue
try:
media = self._download_json(
- 'https://web-api-us.crackle.com/Service.svc/details/media/%s/%s?disableProtocols=true' % (video_id, country),
- video_id, note='Downloading media JSON from %s API' % country,
+ f'https://web-api-us.crackle.com/Service.svc/details/media/{video_id}/{country}?disableProtocols=true',
+ video_id, note=f'Downloading media JSON from {country} API',
errnote='Unable to download media JSON')
except ExtractorError as e:
# 401 means geo restriction, trying next country
@@ -120,7 +120,7 @@ class CrackleIE(InfoExtractor):
status = media.get('status')
if status.get('messageCode') != '0':
raise ExtractorError(
- '%s said: %s %s - %s' % (
+ '{} said: {} {} - {}'.format(
self.IE_NAME, status.get('messageCodeDescription'), status.get('messageCode'), status.get('message')),
expected=True)
diff --git a/yt_dlp/extractor/craftsy.py b/yt_dlp/extractor/craftsy.py
index 3a05ed48a..0d7d759ab 100644
--- a/yt_dlp/extractor/craftsy.py
+++ b/yt_dlp/extractor/craftsy.py
@@ -56,7 +56,7 @@ class CraftsyIE(InfoExtractor):
if not lessons and not has_access:
self.report_warning(
'Only extracting preview. For the full class, pass cookies '
- + f'from an account that has access. {self._login_hint()}')
+ f'from an account that has access. {self._login_hint()}')
lessons.append({'video_id': preview_id})
if not lessons and not has_access:
diff --git a/yt_dlp/extractor/crooksandliars.py b/yt_dlp/extractor/crooksandliars.py
index 2ee0730c9..abd3322a9 100644
--- a/yt_dlp/extractor/crooksandliars.py
+++ b/yt_dlp/extractor/crooksandliars.py
@@ -21,7 +21,7 @@ class CrooksAndLiarsIE(InfoExtractor):
'upload_date': '20150405',
'uploader': 'Heather',
'duration': 236,
- }
+ },
}, {
'url': 'http://embed.crooksandliars.com/v/MTE3MjUtMzQ2MzA',
'only_matching': True,
@@ -31,7 +31,7 @@ class CrooksAndLiarsIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://embed.crooksandliars.com/embed/%s' % video_id, video_id)
+ f'http://embed.crooksandliars.com/embed/{video_id}', video_id)
manifest = self._search_json(r'var\s+manifest\s*=', webpage, 'manifest JSON', video_id)
diff --git a/yt_dlp/extractor/crowdbunker.py b/yt_dlp/extractor/crowdbunker.py
index d83c01560..bf814570f 100644
--- a/yt_dlp/extractor/crowdbunker.py
+++ b/yt_dlp/extractor/crowdbunker.py
@@ -24,15 +24,16 @@ class CrowdBunkerIE(InfoExtractor):
'uploader_id': 'UCeN_qQV829NYf0pvPJhW5dQ',
'like_count': int,
'upload_date': '20211218',
- 'thumbnail': 'https://scw.divulg.org/cb-medias4/images/0z4Kms8pi8I/maxres.jpg'
+ 'thumbnail': 'https://scw.divulg.org/cb-medias4/images/0z4Kms8pi8I/maxres.jpg',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://api.divulg.org/post/{id}/details',
- id, headers={'accept': 'application/json, text/plain, */*'})
+ video_id = self._match_id(url)
+ data_json = self._download_json(
+ f'https://api.divulg.org/post/{video_id}/details', video_id,
+ headers={'accept': 'application/json, text/plain, */*'})
video_json = data_json['video']
formats, subtitles = [], {}
for sub in video_json.get('captions') or []:
@@ -45,12 +46,12 @@ class CrowdBunkerIE(InfoExtractor):
mpd_url = try_get(video_json, lambda x: x['dashManifest']['url'])
if mpd_url:
- fmts, subs = self._extract_mpd_formats_and_subtitles(mpd_url, id)
+ fmts, subs = self._extract_mpd_formats_and_subtitles(mpd_url, video_id)
formats.extend(fmts)
subtitles = self._merge_subtitles(subtitles, subs)
m3u8_url = try_get(video_json, lambda x: x['hlsManifest']['url'])
if m3u8_url:
- fmts, subs = self._extract_m3u8_formats_and_subtitles(mpd_url, id)
+ fmts, subs = self._extract_m3u8_formats_and_subtitles(mpd_url, video_id)
formats.extend(fmts)
subtitles = self._merge_subtitles(subtitles, subs)
@@ -61,7 +62,7 @@ class CrowdBunkerIE(InfoExtractor):
} for image in video_json.get('thumbnails') or [] if image.get('url')]
return {
- 'id': id,
+ 'id': video_id,
'title': video_json.get('title'),
'description': video_json.get('description'),
'view_count': video_json.get('viewCount'),
@@ -87,23 +88,24 @@ class CrowdBunkerChannelIE(InfoExtractor):
},
}]
- def _entries(self, id):
+ def _entries(self, playlist_id):
last = None
for page in itertools.count():
channel_json = self._download_json(
- f'https://api.divulg.org/organization/{id}/posts', id, headers={'accept': 'application/json, text/plain, */*'},
+ f'https://api.divulg.org/organization/{playlist_id}/posts', playlist_id,
+ headers={'accept': 'application/json, text/plain, */*'},
query={'after': last} if last else {}, note=f'Downloading Page {page}')
for item in channel_json.get('items') or []:
v_id = item.get('uid')
if not v_id:
continue
yield self.url_result(
- 'https://crowdbunker.com/v/%s' % v_id, ie=CrowdBunkerIE.ie_key(), video_id=v_id)
+ f'https://crowdbunker.com/v/{v_id}', ie=CrowdBunkerIE.ie_key(), video_id=v_id)
last = channel_json.get('last')
if not last:
break
def _real_extract(self, url):
- id = self._match_id(url)
- return self.playlist_result(self._entries(id), playlist_id=id)
+ playlist_id = self._match_id(url)
+ return self.playlist_result(self._entries(playlist_id), playlist_id=playlist_id)
diff --git a/yt_dlp/extractor/crtvg.py b/yt_dlp/extractor/crtvg.py
index 21325e331..6d9a77824 100644
--- a/yt_dlp/extractor/crtvg.py
+++ b/yt_dlp/extractor/crtvg.py
@@ -17,7 +17,7 @@ class CrtvgIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.(?:jpg|png)',
'_old_archive_ids': ['crtvg 5839623'],
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://www.crtvg.es/tvg/a-carta/a-parabolica-love-story',
'md5': '9a47b95a1749db7b7eb3214904624584',
@@ -28,7 +28,7 @@ class CrtvgIE(InfoExtractor):
'description': 'md5:f71cfba21ae564f0a6f415b31de1f842',
'thumbnail': r're:^https?://.*\.(?:jpg|png)',
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/crunchyroll.py b/yt_dlp/extractor/crunchyroll.py
index ea54f0195..1b124c655 100644
--- a/yt_dlp/extractor/crunchyroll.py
+++ b/yt_dlp/extractor/crunchyroll.py
@@ -442,7 +442,7 @@ class CrunchyrollBetaIE(CrunchyrollCmsBaseIE):
return {
'id': data['id'],
'title': ' \u2013 '.join((
- ('%s%s' % (
+ ('{}{}'.format(
format_field(metadata, 'season_title'),
format_field(metadata, 'episode', ' Episode %s'))),
format_field(data, 'title'))),
@@ -519,7 +519,7 @@ class CrunchyrollBetaShowIE(CrunchyrollCmsBaseIE):
seasons_response = self._call_cms_api_signed(f'seasons?series_id={internal_id}', internal_id, lang, 'seasons')
for season in traverse_obj(seasons_response, ('items', ..., {dict})):
episodes_response = self._call_cms_api_signed(
- f'episodes?season_id={season["id"]}', season["id"], lang, 'episode list')
+ f'episodes?season_id={season["id"]}', season['id'], lang, 'episode list')
for episode_response in traverse_obj(episodes_response, ('items', ..., {dict})):
yield self.url_result(
f'{self._BASE_URL}/{lang}watch/{episode_response["id"]}',
@@ -535,7 +535,7 @@ class CrunchyrollBetaShowIE(CrunchyrollCmsBaseIE):
'url': ('source', {url_or_none}),
'width': ('width', {int_or_none}),
'height': ('height', {int_or_none}),
- })
+ }),
})))
diff --git a/yt_dlp/extractor/cspan.py b/yt_dlp/extractor/cspan.py
index e56584e4e..e940c2dea 100644
--- a/yt_dlp/extractor/cspan.py
+++ b/yt_dlp/extractor/cspan.py
@@ -43,7 +43,7 @@ class CSpanIE(InfoExtractor):
'ext': 'mp4',
'title': 'CSPAN - International Health Care Models',
'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
- }
+ },
}, {
'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
'info_dict': {
@@ -61,7 +61,7 @@ class CSpanIE(InfoExtractor):
},
'params': {
'skip_download': True, # m3u8 downloads
- }
+ },
}, {
# Ustream embedded video
'url': 'https://www.c-span.org/video/?114917-1/armed-services',
@@ -151,7 +151,7 @@ class CSpanIE(InfoExtractor):
# Obsolete
# We first look for clipid, because clipprog always appears before
- patterns = [r'id=\'clip(%s)\'\s*value=\'([0-9]+)\'' % t for t in ('id', 'prog')]
+ patterns = [rf'id=\'clip({t})\'\s*value=\'([0-9]+)\'' for t in ('id', 'prog')]
results = list(filter(None, (re.search(p, webpage) for p in patterns)))
if results:
matches = results[0]
@@ -183,13 +183,13 @@ class CSpanIE(InfoExtractor):
return d.get(attr, {}).get('#text')
data = self._download_json(
- 'http://www.c-span.org/assets/player/ajax-player.php?os=android&html5=%s&id=%s' % (video_type, video_id),
+ f'http://www.c-span.org/assets/player/ajax-player.php?os=android&html5={video_type}&id={video_id}',
video_id)['video']
if data['@status'] != 'Success':
- raise ExtractorError('%s said: %s' % (self.IE_NAME, get_text_attr(data, 'error')), expected=True)
+ raise ExtractorError('{} said: {}'.format(self.IE_NAME, get_text_attr(data, 'error')), expected=True)
doc = self._download_xml(
- 'http://www.c-span.org/common/services/flashXml.php?%sid=%s' % (video_type, video_id),
+ f'http://www.c-span.org/common/services/flashXml.php?{video_type}id={video_id}',
video_id)
description = self._html_search_meta('description', webpage)
@@ -205,7 +205,7 @@ class CSpanIE(InfoExtractor):
formats = []
for quality in f.get('qualities', []):
formats.append({
- 'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
+ 'format_id': '{}-{}p'.format(get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
'url': unescapeHTML(get_text_attr(quality, 'file')),
'height': int_or_none(get_text_attr(quality, 'height')),
'tbr': int_or_none(get_text_attr(quality, 'bitrate')),
@@ -216,13 +216,13 @@ class CSpanIE(InfoExtractor):
continue
formats = self._extract_m3u8_formats(
path, video_id, 'mp4', entry_protocol='m3u8_native',
- m3u8_id='hls') if determine_ext(path) == 'm3u8' else [{'url': path, }]
+ m3u8_id='hls') if determine_ext(path) == 'm3u8' else [{'url': path}]
add_referer(formats)
entries.append({
- 'id': '%s_%d' % (video_id, partnum + 1),
+ 'id': f'{video_id}_{partnum + 1}',
'title': (
title if len(files) == 1 else
- '%s part %d' % (title, partnum + 1)),
+ f'{title} part {partnum + 1}'),
'formats': formats,
'description': description,
'thumbnail': thumbnail,
@@ -230,7 +230,7 @@ class CSpanIE(InfoExtractor):
'subtitles': {
'en': [{
'url': capfile,
- 'ext': determine_ext(capfile, 'dfxp')
+ 'ext': determine_ext(capfile, 'dfxp'),
}],
} if capfile else None,
})
@@ -257,8 +257,8 @@ class CSpanCongressIE(InfoExtractor):
'title': 'Congressional Chronicle - Members of Congress, Hearings and More',
'description': 'md5:54c264b7a8f219937987610243305a84',
'thumbnail': r're:https://ximage.c-spanvideo.org/.+',
- 'ext': 'mp4'
- }
+ 'ext': 'mp4',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/ctsnews.py b/yt_dlp/extractor/ctsnews.py
index 1817bd2ff..b249c7b55 100644
--- a/yt_dlp/extractor/ctsnews.py
+++ b/yt_dlp/extractor/ctsnews.py
@@ -16,7 +16,7 @@ class CtsNewsIE(InfoExtractor):
'description': '以色列和黎巴嫩真主黨,爆發五年最嚴重衝突,雙方砲轟交火,兩名以軍死亡,還有一名西班牙籍的聯合國維和人員也不幸罹難。大陸陝西、河南、安徽、江蘇和湖北五個省份出現大暴雪,嚴重影響陸空交通,不過九華山卻出現...',
'timestamp': 1422528540,
'upload_date': '20150129',
- }
+ },
}, {
# News count not appear on page but still available in database
'url': 'http://news.cts.com.tw/cts/international/201309/201309031304098.html',
@@ -29,7 +29,7 @@ class CtsNewsIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'timestamp': 1378205880,
'upload_date': '20130903',
- }
+ },
}, {
# With Youtube embedded video
'url': 'http://news.cts.com.tw/cts/money/201501/201501291578003.html',
diff --git a/yt_dlp/extractor/ctv.py b/yt_dlp/extractor/ctv.py
index f125c1ce9..a41dab11b 100644
--- a/yt_dlp/extractor/ctv.py
+++ b/yt_dlp/extractor/ctv.py
@@ -41,9 +41,9 @@ class CTVIE(InfoExtractor):
}
}
}
-}''' % display_id,
+}''' % display_id, # noqa: UP031
})['data']['resolvedPath']['lastSegment']['content']
video_id = content['axisId']
return self.url_result(
- '9c9media:%s:%s' % (content['videoPlayerDestCode'], video_id),
+ '9c9media:{}:{}'.format(content['videoPlayerDestCode'], video_id),
'NineCNineMedia', video_id)
diff --git a/yt_dlp/extractor/ctvnews.py b/yt_dlp/extractor/ctvnews.py
index ad3f0d8e4..ebed9eb2d 100644
--- a/yt_dlp/extractor/ctvnews.py
+++ b/yt_dlp/extractor/ctvnews.py
@@ -16,7 +16,7 @@ class CTVNewsIE(InfoExtractor):
'description': 'md5:958dd3b4f5bbbf0ed4d045c790d89285',
'timestamp': 1467286284,
'upload_date': '20160630',
- }
+ },
}, {
'url': 'http://www.ctvnews.ca/video?playlistId=1.2966224',
'info_dict':
@@ -49,14 +49,14 @@ class CTVNewsIE(InfoExtractor):
return {
'_type': 'url_transparent',
'id': clip_id,
- 'url': '9c9media:ctvnews_web:%s' % clip_id,
+ 'url': f'9c9media:ctvnews_web:{clip_id}',
'ie_key': 'NineCNineMedia',
}
if page_id.isdigit():
return ninecninemedia_url_result(page_id)
else:
- webpage = self._download_webpage('http://www.ctvnews.ca/%s' % page_id, page_id, query={
+ webpage = self._download_webpage(f'http://www.ctvnews.ca/{page_id}', page_id, query={
'ot': 'example.AjaxPageLayout.ot',
'maxItemsPerPage': 1000000,
})
diff --git a/yt_dlp/extractor/cultureunplugged.py b/yt_dlp/extractor/cultureunplugged.py
index 9c8509f1f..8e6579c35 100644
--- a/yt_dlp/extractor/cultureunplugged.py
+++ b/yt_dlp/extractor/cultureunplugged.py
@@ -20,7 +20,7 @@ class CultureUnpluggedIE(InfoExtractor):
'creator': 'Coldstream Creative',
'duration': 2203,
'view_count': int,
- }
+ },
}, {
'url': 'http://www.cultureunplugged.com/documentary/watch-online/play/53662',
'only_matching': True,
@@ -35,7 +35,7 @@ class CultureUnpluggedIE(InfoExtractor):
self._request_webpage(HEADRequest(
'http://www.cultureunplugged.com/setClientTimezone.php?timeOffset=%d' % -(time.timezone / 3600)), display_id)
movie_data = self._download_json(
- 'http://www.cultureunplugged.com/movie-data/cu-%s.json' % video_id, display_id)
+ f'http://www.cultureunplugged.com/movie-data/cu-{video_id}.json', display_id)
video_url = movie_data['url']
title = movie_data['title']
@@ -46,11 +46,11 @@ class CultureUnpluggedIE(InfoExtractor):
view_count = int_or_none(movie_data.get('views'))
thumbnails = [{
- 'url': movie_data['%s_thumb' % size],
+ 'url': movie_data[f'{size}_thumb'],
'id': size,
'preference': preference,
} for preference, size in enumerate((
- 'small', 'large')) if movie_data.get('%s_thumb' % size)]
+ 'small', 'large')) if movie_data.get(f'{size}_thumb')]
return {
'id': video_id,
diff --git a/yt_dlp/extractor/curiositystream.py b/yt_dlp/extractor/curiositystream.py
index 941cf4e79..f5a2c3c31 100644
--- a/yt_dlp/extractor/curiositystream.py
+++ b/yt_dlp/extractor/curiositystream.py
@@ -2,7 +2,6 @@ import re
import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import ExtractorError, int_or_none, urlencode_postdata
@@ -16,7 +15,7 @@ class CuriosityStreamBaseIE(InfoExtractor):
if isinstance(error, dict):
error = ', '.join(error.values())
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, error), expected=True)
+ f'{self.IE_NAME} said: {error}', expected=True)
def _call_api(self, path, video_id, query=None):
headers = {}
@@ -59,7 +58,7 @@ class CuriosityStreamIE(CuriosityStreamBaseIE):
'series_id': '2',
'thumbnail': r're:https://img.curiositystream.com/.+\.jpg',
'tags': [],
- 'duration': 158
+ 'duration': 158,
},
'params': {
# m3u8 download
@@ -157,10 +156,10 @@ class CuriosityStreamCollectionBaseIE(CuriosityStreamBaseIE):
collection = self._call_api(collection_id, collection_id)
entries = []
for media in collection.get('media', []):
- media_id = compat_str(media.get('id'))
+ media_id = str(media.get('id'))
media_type, ie = ('series', CuriosityStreamSeriesIE) if media.get('is_collection') else ('video', CuriosityStreamIE)
entries.append(self.url_result(
- 'https://curiositystream.com/%s/%s' % (media_type, media_id),
+ f'https://curiositystream.com/{media_type}/{media_id}',
ie=ie.ie_key(), video_id=media_id))
return self.playlist_result(
entries, collection_id,
diff --git a/yt_dlp/extractor/cwtv.py b/yt_dlp/extractor/cwtv.py
index 69d50daf6..870d4f39e 100644
--- a/yt_dlp/extractor/cwtv.py
+++ b/yt_dlp/extractor/cwtv.py
@@ -75,7 +75,7 @@ class CWTVIE(InfoExtractor):
raise ExtractorError(data['msg'], expected=True)
video_data = data['video']
title = video_data['title']
- mpx_url = video_data.get('mpx_url') or 'http://link.theplatform.com/s/cwtv/media/guid/2703454149/%s?formats=M3U' % video_id
+ mpx_url = video_data.get('mpx_url') or f'http://link.theplatform.com/s/cwtv/media/guid/2703454149/{video_id}?formats=M3U'
season = str_or_none(video_data.get('season'))
episode = str_or_none(video_data.get('episode'))
@@ -95,5 +95,5 @@ class CWTVIE(InfoExtractor):
'timestamp': parse_iso8601(video_data.get('start_time')),
'age_limit': parse_age_limit(video_data.get('rating')),
'ie_key': 'ThePlatform',
- 'thumbnail': video_data.get('large_thumbnail')
+ 'thumbnail': video_data.get('large_thumbnail'),
}
diff --git a/yt_dlp/extractor/cybrary.py b/yt_dlp/extractor/cybrary.py
index c6995b25b..59c8ab473 100644
--- a/yt_dlp/extractor/cybrary.py
+++ b/yt_dlp/extractor/cybrary.py
@@ -61,9 +61,9 @@ class CybraryIE(CybraryBaseIE):
'series': 'Cybrary Orientation',
'uploader': 'Cybrary',
'chapter': 'Cybrary Orientation Series',
- 'chapter_id': '63110'
+ 'chapter_id': '63110',
},
- 'expected_warnings': ['No authenticators for vimeo']
+ 'expected_warnings': ['No authenticators for vimeo'],
}, {
'url': 'https://app.cybrary.it/immersive/12747143/activity/52686',
'md5': '62f26547dccc59c44363e2a13d4ad08d',
@@ -79,9 +79,9 @@ class CybraryIE(CybraryBaseIE):
'series': 'AZ-500: Microsoft Azure Security Technologies',
'uploader': 'Cybrary',
'chapter': 'Implement Network Security',
- 'chapter_id': '52693'
+ 'chapter_id': '52693',
},
- 'expected_warnings': ['No authenticators for vimeo']
+ 'expected_warnings': ['No authenticators for vimeo'],
}]
def _real_extract(self, url):
@@ -93,7 +93,7 @@ class CybraryIE(CybraryBaseIE):
raise ExtractorError('The activity is not a video', expected=True)
module = next((m for m in course.get('learning_modules') or []
- if int(activity_id) in traverse_obj(m, ('activities', ..., 'id') or [])), None)
+ if int(activity_id) in traverse_obj(m, ('activities', ..., 'id'))), None)
vimeo_id = self._get_vimeo_id(activity_id)
@@ -105,7 +105,7 @@ class CybraryIE(CybraryBaseIE):
'chapter': module.get('title'),
'chapter_id': str_or_none(module.get('id')),
'title': activity.get('title'),
- 'url': smuggle_url(f'https://player.vimeo.com/video/{vimeo_id}', {'referer': 'https://api.cybrary.it'})
+ 'url': smuggle_url(f'https://player.vimeo.com/video/{vimeo_id}', {'referer': 'https://api.cybrary.it'}),
}
@@ -116,17 +116,17 @@ class CybraryCourseIE(CybraryBaseIE):
'info_dict': {
'id': '898',
'title': 'AZ-500: Microsoft Azure Security Technologies',
- 'description': 'md5:69549d379c0fc1dec92926d4e8b6fbd4'
+ 'description': 'md5:69549d379c0fc1dec92926d4e8b6fbd4',
},
- 'playlist_count': 59
+ 'playlist_count': 59,
}, {
'url': 'https://app.cybrary.it/browse/course/cybrary-orientation',
'info_dict': {
'id': '1245',
'title': 'Cybrary Orientation',
- 'description': 'md5:9e69ff66b32fe78744e0ad4babe2e88e'
+ 'description': 'md5:9e69ff66b32fe78744e0ad4babe2e88e',
},
- 'playlist_count': 4
+ 'playlist_count': 4,
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/dailymail.py b/yt_dlp/extractor/dailymail.py
index 4c25bea11..516df1b5c 100644
--- a/yt_dlp/extractor/dailymail.py
+++ b/yt_dlp/extractor/dailymail.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
determine_protocol,
int_or_none,
@@ -19,7 +18,7 @@ class DailyMailIE(InfoExtractor):
'ext': 'mp4',
'title': 'The Mountain appears in sparkling water ad for \'Heavy Bubbles\'',
'description': 'md5:a93d74b6da172dd5dc4d973e0b766a84',
- }
+ },
}, {
'url': 'http://www.dailymail.co.uk/embed/video/1295863.html',
'only_matching': True,
@@ -35,8 +34,8 @@ class DailyMailIE(InfoExtractor):
sources_url = (try_get(
video_data,
(lambda x: x['plugins']['sources']['url'],
- lambda x: x['sources']['url']), compat_str)
- or 'http://www.dailymail.co.uk/api/player/%s/video-sources.json' % video_id)
+ lambda x: x['sources']['url']), str)
+ or f'http://www.dailymail.co.uk/api/player/{video_id}/video-sources.json')
video_sources = self._download_json(sources_url, video_id)
body = video_sources.get('body')
@@ -53,7 +52,7 @@ class DailyMailIE(InfoExtractor):
is_hls = container == 'M2TS'
protocol = 'm3u8_native' if is_hls else determine_protocol({'url': rendition_url})
formats.append({
- 'format_id': ('hls' if is_hls else protocol) + ('-%d' % tbr if tbr else ''),
+ 'format_id': ('hls' if is_hls else protocol) + (f'-{tbr}' if tbr else ''),
'url': rendition_url,
'width': int_or_none(rendition.get('frameWidth')),
'height': int_or_none(rendition.get('frameHeight')),
diff --git a/yt_dlp/extractor/dailymotion.py b/yt_dlp/extractor/dailymotion.py
index c570a4f52..632335e5b 100644
--- a/yt_dlp/extractor/dailymotion.py
+++ b/yt_dlp/extractor/dailymotion.py
@@ -87,7 +87,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
%s(xid: "%s"%s) {
%s
}
-}''' % (object_type, xid, ', ' + filter_extra if filter_extra else '', object_fields),
+}''' % (object_type, xid, ', ' + filter_extra if filter_extra else '', object_fields), # noqa: UP031
}).encode(), headers=self._HEADERS)
obj = resp['data'][object_type]
if not obj:
@@ -143,7 +143,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
'like_count': int,
'tags': ['en_quete_d_esprit'],
'thumbnail': r're:https://(?:s[12]\.)dmcdn\.net/v/Tncwi1YNg_RUl7ueu/x1080',
- }
+ },
}, {
'url': 'https://www.dailymotion.com/video/x2iuewm_steam-machine-models-pricing-listed-on-steam-store-ign-news_videogames',
'md5': '2137c41a8e78554bb09225b8eb322406',
@@ -260,8 +260,8 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
%s
audienceCount
isOnAir
- }''' % (self._COMMON_MEDIA_FIELDS, self._COMMON_MEDIA_FIELDS), 'Downloading media JSON metadata',
- 'password: "%s"' % self.get_param('videopassword') if password else None)
+ }''' % (self._COMMON_MEDIA_FIELDS, self._COMMON_MEDIA_FIELDS), 'Downloading media JSON metadata', # noqa: UP031
+ 'password: "{}"'.format(self.get_param('videopassword')) if password else None)
xid = media['xid']
metadata = self._download_json(
@@ -277,7 +277,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
allowed_countries = try_get(media, lambda x: x['geoblockedCountries']['allowed'], list)
self.raise_geo_restricted(msg=title, countries=allowed_countries)
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, title), expected=True)
+ f'{self.IE_NAME} said: {title}', expected=True)
title = metadata['title']
is_live = media.get('isOnAir')
@@ -363,7 +363,7 @@ class DailymotionPlaylistBaseIE(DailymotionBaseInfoExtractor):
}
}
}''' % ('false' if self._FAMILY_FILTER else 'true', self._PAGE_SIZE, page),
- 'Downloading page %d' % page)['videos']
+ f'Downloading page {page}')['videos']
for edge in videos['edges']:
node = edge['node']
yield self.url_result(
@@ -396,7 +396,7 @@ class DailymotionPlaylistIE(DailymotionPlaylistBaseIE):
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.[a-z]{2,3}/widget/jukebox\?.+?)\1',
webpage):
for p in re.findall(r'list\[\]=/playlist/([^/]+)/', unescapeHTML(mobj.group('url'))):
- yield '//dailymotion.com/playlist/%s' % p
+ yield f'//dailymotion.com/playlist/{p}'
class DailymotionSearchIE(DailymotionPlaylistBaseIE):
@@ -424,7 +424,7 @@ class DailymotionSearchIE(DailymotionPlaylistBaseIE):
'limit': 20,
'page': page,
'query': term,
- }
+ },
}).encode(), headers=self._HEADERS)
obj = traverse_obj(resp, ('data', 'search', {dict}))
if not obj:
diff --git a/yt_dlp/extractor/dailywire.py b/yt_dlp/extractor/dailywire.py
index f177c9d9c..3d5bb257c 100644
--- a/yt_dlp/extractor/dailywire.py
+++ b/yt_dlp/extractor/dailywire.py
@@ -35,7 +35,7 @@ class DailyWireIE(DailyWireBaseIE):
'creator': 'Caroline Roberts',
'series_id': 'ckzplm0a097fn0826r2vc3j7h',
'series': 'China: The Enemy Within',
- }
+ },
}, {
'url': 'https://www.dailywire.com/episode/ep-124-bill-maher',
'info_dict': {
@@ -48,7 +48,7 @@ class DailyWireIE(DailyWireBaseIE):
'description': 'md5:adb0de584bcfa9c41374999d9e324e98',
'series_id': 'cjzvep7270hp00786l9hwccob',
'series': 'The Sunday Special',
- }
+ },
}, {
'url': 'https://www.dailywire.com/videos/the-hyperions',
'only_matching': True,
@@ -95,7 +95,7 @@ class DailyWirePodcastIE(DailyWireBaseIE):
'description': 'md5:c4afbadda4e1c38a4496f6d62be55634',
'thumbnail': 'https://daily-wire-production.imgix.net/podcasts/ckx4otgd71jm508699tzb6hf4-1639506575562.jpg',
'duration': 900.117667,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/damtomo.py b/yt_dlp/extractor/damtomo.py
index 2e0f6f0d3..9ac0b6f2f 100644
--- a/yt_dlp/extractor/damtomo.py
+++ b/yt_dlp/extractor/damtomo.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import ExtractorError, clean_html, int_or_none, try_get, unified_strdate
@@ -32,7 +31,7 @@ class DamtomoBaseIE(InfoExtractor):
# and never likely to happen in the future
transform_source=lambda x: re.sub(r'\s*encoding="[^"]+?"', '', x))
m3u8_url = try_get(stream_tree, lambda x: x.find(
- './/d:streamingUrl', {'d': self._DKML_XML_NS}).text.strip(), compat_str)
+ './/d:streamingUrl', {'d': self._DKML_XML_NS}).text.strip(), str)
if not m3u8_url:
raise ExtractorError('Failed to obtain m3u8 URL')
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4')
@@ -68,7 +67,7 @@ class DamtomoVideoIE(DamtomoBaseIE):
'track': 'Get Wild',
'artist': 'TM NETWORK(TMN)',
'upload_date': '20201226',
- }
+ },
}]
@@ -90,7 +89,7 @@ class DamtomoRecordIE(DamtomoBaseIE):
'like_count': 1,
'track': 'イカSUMMER [良音]',
'artist': 'ORANGE RANGE',
- }
+ },
}, {
'url': 'https://www.clubdam.com/app/damtomo/karaokePost/StreamingKrk.do?karaokeContributeId=27489418',
'info_dict': {
@@ -104,5 +103,5 @@ class DamtomoRecordIE(DamtomoBaseIE):
'like_count': 3,
'track': '心みだれて〜say it with flowers〜(生音)',
'artist': '小林明子',
- }
+ },
}]
diff --git a/yt_dlp/extractor/daum.py b/yt_dlp/extractor/daum.py
index 24c520855..ee8444914 100644
--- a/yt_dlp/extractor/daum.py
+++ b/yt_dlp/extractor/daum.py
@@ -1,9 +1,7 @@
import itertools
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_urllib_parse_unquote,
-)
from ..utils import parse_qs
@@ -83,7 +81,7 @@ class DaumIE(DaumBaseIE):
}]
def _real_extract(self, url):
- video_id = compat_urllib_parse_unquote(self._match_id(url))
+ video_id = urllib.parse.unquote(self._match_id(url))
if not video_id.isdigit():
video_id += '@my'
return self.url_result(
@@ -117,7 +115,7 @@ class DaumClipIE(DaumBaseIE):
@classmethod
def suitable(cls, url):
- return False if DaumPlaylistIE.suitable(url) or DaumUserIE.suitable(url) else super(DaumClipIE, cls).suitable(url)
+ return False if DaumPlaylistIE.suitable(url) or DaumUserIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -131,12 +129,12 @@ class DaumListIE(InfoExtractor): # XXX: Conventionally, base classes should end
entries = []
for pagenum in itertools.count(1):
list_info = self._download_json(
- 'http://tvpot.daum.net/mypot/json/GetClipInfo.do?size=48&init=true&order=date&page=%d&%s=%s' % (
- pagenum, list_id_type, list_id), list_id, 'Downloading list info - %s' % pagenum)
+ f'http://tvpot.daum.net/mypot/json/GetClipInfo.do?size=48&init=true&order=date&page={pagenum}&{list_id_type}={list_id}',
+ list_id, f'Downloading list info - {pagenum}')
entries.extend([
self.url_result(
- 'http://tvpot.daum.net/v/%s' % clip['vid'])
+ 'http://tvpot.daum.net/v/{}'.format(clip['vid']))
for clip in list_info['clip_list']
])
@@ -169,7 +167,7 @@ class DaumPlaylistIE(DaumListIE):
'id': '6213966',
'title': 'Woorissica Official',
},
- 'playlist_mincount': 181
+ 'playlist_mincount': 181,
}, {
'note': 'Playlist url with clipid - noplaylist',
'url': 'http://tvpot.daum.net/mypot/View.do?playlistid=6213966&clipid=73806844',
@@ -182,12 +180,12 @@ class DaumPlaylistIE(DaumListIE):
'params': {
'noplaylist': True,
'skip_download': True,
- }
+ },
}]
@classmethod
def suitable(cls, url):
- return False if DaumUserIE.suitable(url) else super(DaumPlaylistIE, cls).suitable(url)
+ return False if DaumUserIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
list_id = self._match_id(url)
@@ -211,7 +209,7 @@ class DaumUserIE(DaumListIE):
'id': 'o2scDLIVbHc0',
'title': '마이 리틀 텔레비전',
},
- 'playlist_mincount': 213
+ 'playlist_mincount': 213,
}, {
'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0&clipid=73801156',
'info_dict': {
@@ -219,12 +217,12 @@ class DaumUserIE(DaumListIE):
'ext': 'mp4',
'title': '[미공개] 김구라, 오만석이 부릅니다 \'오케피\' - 마이 리틀 텔레비전 20160116',
'upload_date': '20160117',
- 'description': 'md5:5e91d2d6747f53575badd24bd62b9f36'
+ 'description': 'md5:5e91d2d6747f53575badd24bd62b9f36',
},
'params': {
'noplaylist': True,
'skip_download': True,
- }
+ },
}, {
'note': 'Playlist url has ownerid and playlistid, playlistid takes precedence',
'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0&playlistid=6196631',
@@ -232,7 +230,7 @@ class DaumUserIE(DaumListIE):
'id': '6196631',
'title': '마이 리틀 텔레비전 - 20160109',
},
- 'playlist_count': 11
+ 'playlist_count': 11,
}, {
'url': 'http://tvpot.daum.net/mypot/Top.do?ownerid=o2scDLIVbHc0',
'only_matching': True,
diff --git a/yt_dlp/extractor/dbtv.py b/yt_dlp/extractor/dbtv.py
index 18be46f7e..795fbacc4 100644
--- a/yt_dlp/extractor/dbtv.py
+++ b/yt_dlp/extractor/dbtv.py
@@ -18,7 +18,7 @@ class DBTVIE(InfoExtractor):
'uploader_id': 'UCk5pvsyZJoYJBd7_oFPTlRQ',
'uploader': 'Dagbladet',
},
- 'add_ie': ['Youtube']
+ 'add_ie': ['Youtube'],
}, {
'url': 'https://www.dagbladet.no/video/embed/xlGmyIeN9Jo/?autoplay=false',
'only_matching': True,
diff --git a/yt_dlp/extractor/dctp.py b/yt_dlp/extractor/dctp.py
index 24bb6aca2..09bdbf243 100644
--- a/yt_dlp/extractor/dctp.py
+++ b/yt_dlp/extractor/dctp.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
float_or_none,
int_or_none,
@@ -37,18 +36,18 @@ class DctpTvIE(InfoExtractor):
display_id = self._match_id(url)
version = self._download_json(
- '%s/version.json' % self._BASE_URL, display_id,
+ f'{self._BASE_URL}/version.json', display_id,
'Downloading version JSON')
- restapi_base = '%s/%s/restapi' % (
+ restapi_base = '{}/{}/restapi'.format(
self._BASE_URL, version['version_name'])
info = self._download_json(
- '%s/slugs/%s.json' % (restapi_base, display_id), display_id,
+ f'{restapi_base}/slugs/{display_id}.json', display_id,
'Downloading video info JSON')
media = self._download_json(
- '%s/media/%s.json' % (restapi_base, compat_str(info['object_id'])),
+ '{}/media/{}.json'.format(restapi_base, str(info['object_id'])),
display_id, 'Downloading media JSON')
uuid = media['uuid']
@@ -57,7 +56,7 @@ class DctpTvIE(InfoExtractor):
formats = []
def add_formats(suffix):
- templ = 'https://%%s/%s_dctp_%s.m4v' % (uuid, suffix)
+ templ = f'https://%s/{uuid}_dctp_{suffix}.m4v'
formats.extend([{
'format_id': 'hls-' + suffix,
'url': templ % 'cdn-segments.dctp.tv' + '/playlist.m3u8',
diff --git a/yt_dlp/extractor/deezer.py b/yt_dlp/extractor/deezer.py
index f61f12af0..2ca8be5ca 100644
--- a/yt_dlp/extractor/deezer.py
+++ b/yt_dlp/extractor/deezer.py
@@ -22,7 +22,7 @@ class DeezerBaseInfoExtractor(InfoExtractor):
default=None)
if geoblocking_msg is not None:
raise ExtractorError(
- 'Deezer said: %s' % geoblocking_msg, expected=True)
+ f'Deezer said: {geoblocking_msg}', expected=True)
data_json = self._search_regex(
(r'__DZR_APP_STATE__\s*=\s*({.+?})\s*</script>',
@@ -67,7 +67,7 @@ class DeezerPlaylistIE(DeezerBaseInfoExtractor):
entries.append({
'id': s.get('SNG_ID'),
'duration': int_or_none(s.get('DURATION')),
- 'title': '%s - %s' % (artists, s.get('SNG_TITLE')),
+ 'title': '{} - {}'.format(artists, s.get('SNG_TITLE')),
'uploader': s.get('ART_NAME'),
'uploader_id': s.get('ART_ID'),
'age_limit': 16 if s.get('EXPLICIT_LYRICS') == '1' else 0,
@@ -119,7 +119,7 @@ class DeezerAlbumIE(DeezerBaseInfoExtractor):
entries.append({
'id': s.get('SNG_ID'),
'duration': int_or_none(s.get('DURATION')),
- 'title': '%s - %s' % (artists, s.get('SNG_TITLE')),
+ 'title': '{} - {}'.format(artists, s.get('SNG_TITLE')),
'uploader': s.get('ART_NAME'),
'uploader_id': s.get('ART_ID'),
'age_limit': 16 if s.get('EXPLICIT_LYRICS') == '1' else 0,
diff --git a/yt_dlp/extractor/democracynow.py b/yt_dlp/extractor/democracynow.py
index 177424937..80c56b4d4 100644
--- a/yt_dlp/extractor/democracynow.py
+++ b/yt_dlp/extractor/democracynow.py
@@ -1,8 +1,8 @@
import os.path
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
remove_start,
url_basename,
@@ -52,7 +52,7 @@ class DemocracynowIE(InfoExtractor):
media_url = json_data.get(key, '')
if not media_url:
continue
- media_url = re.sub(r'\?.*', '', compat_urlparse.urljoin(url, media_url))
+ media_url = re.sub(r'\?.*', '', urllib.parse.urljoin(url, media_url))
video_id = video_id or remove_start(os.path.splitext(url_basename(media_url))[0], 'dn')
formats.append({
'url': media_url,
@@ -70,13 +70,13 @@ class DemocracynowIE(InfoExtractor):
# chapter_file are not subtitles
if 'caption_file' in json_data:
add_subtitle_item(default_lang, {
- 'url': compat_urlparse.urljoin(url, json_data['caption_file']),
+ 'url': urllib.parse.urljoin(url, json_data['caption_file']),
})
for subtitle_item in json_data.get('captions', []):
lang = subtitle_item.get('language', '').lower() or default_lang
add_subtitle_item(lang, {
- 'url': compat_urlparse.urljoin(url, subtitle_item['url']),
+ 'url': urllib.parse.urljoin(url, subtitle_item['url']),
})
description = self._og_search_description(webpage, default=None)
diff --git a/yt_dlp/extractor/detik.py b/yt_dlp/extractor/detik.py
index f14805424..509775919 100644
--- a/yt_dlp/extractor/detik.py
+++ b/yt_dlp/extractor/detik.py
@@ -17,8 +17,8 @@ class DetikEmbedIE(InfoExtractor):
'tags': ['raja charles', ' raja charles iii', ' ratu elizabeth', ' ratu elizabeth meninggal dunia', ' raja inggris', ' inggris'],
'release_timestamp': 1662869995,
'release_date': '20220911',
- 'uploader': 'REUTERS'
- }
+ 'uploader': 'REUTERS',
+ },
}, {
# 20.detik
'url': 'https://20.detik.com/otobuzz/20220704-220704093/mulai-rp-10-jutaan-ini-skema-kredit-mitsubishi-pajero-sport',
@@ -36,8 +36,8 @@ class DetikEmbedIE(InfoExtractor):
'release_timestamp': 1656926321,
'release_date': '20220704',
'age_limit': 0,
- 'uploader': 'Ridwan Arifin ' # TODO: strip trailling whitespace at uploader
- }
+ 'uploader': 'Ridwan Arifin ', # TODO: strip trailling whitespace at uploader
+ },
}, {
# pasangmata.detik
'url': 'https://pasangmata.detik.com/contribution/366649',
@@ -49,7 +49,7 @@ class DetikEmbedIE(InfoExtractor):
'age_limit': 0,
'tags': 'count:17',
'thumbnail': 'https://akcdn.detik.net.id/community/data/media/thumbs-pasangmata/2022/09/08/366649-16626229351533009620.mp4-03.jpg',
- }
+ },
}, {
# insertlive embed
'url': 'https://www.insertlive.com/embed/video/290482',
@@ -64,7 +64,7 @@ class DetikEmbedIE(InfoExtractor):
'title': 'Diincar Leonardo DiCaprio, Gigi Hadid Ngaku Tertarik Tapi Belum Cinta',
'tags': ['leonardo dicaprio', ' gigi hadid', ' hollywood'],
'uploader': '!nsertlive',
- }
+ },
}, {
# beautynesia embed
'url': 'https://www.beautynesia.id/embed/video/261636',
@@ -79,7 +79,7 @@ class DetikEmbedIE(InfoExtractor):
'tags': ['zodiac update', ' zodiak', ' ramalan bintang', ' zodiak beruntung 2022', ' zodiak hoki september 2022', ' zodiak beruntung september 2022'],
'thumbnail': 'https://akcdn.detik.net.id/visual/2022/09/05/3-zodiak-paling-beruntung-selama-september-2022_169.jpeg?w=600&q=90',
'uploader': 'amh',
- }
+ },
}, {
# cnbcindonesia embed
'url': 'https://www.cnbcindonesia.com/embed/video/371839',
@@ -91,7 +91,7 @@ class DetikEmbedIE(InfoExtractor):
'age_limit': 0,
'thumbnail': 'https://awsimages.detik.net.id/visual/2022/09/13/cnbc-indonesia-tv-3_169.png?w=600&q=80',
'description': 'md5:8b9111e37555fcd95fe549a9b4ae6fdc',
- }
+ },
}, {
# detik shortlink (we can get it from https://dtk.id/?<url>)
'url': 'https://dtk.id/NkISKr',
@@ -110,7 +110,7 @@ class DetikEmbedIE(InfoExtractor):
'timestamp': 1663139688,
'duration': 213.0,
'tags': ['hacker bjorka', 'bjorka', 'hacker bjorka bocorkan data rahasia presiden jokowi', 'jokowi'],
- }
+ },
}]
def _extract_from_webpage(self, url, webpage):
@@ -142,7 +142,7 @@ class DetikEmbedIE(InfoExtractor):
'timestamp': int_or_none(self._html_search_meta('dtk:createdateunix', webpage, fatal=False, default=None), 1000),
'uploader': self._search_regex(
r'([^-]+)', self._html_search_meta('dtk:author', webpage, default='').strip(), 'uploader',
- default=None)
+ default=None),
}
formats, subtitles = self._extract_m3u8_formats_and_subtitles(video_url, display_id)
diff --git a/yt_dlp/extractor/deuxm.py b/yt_dlp/extractor/deuxm.py
index 74a6da6c6..c8ce32ca9 100644
--- a/yt_dlp/extractor/deuxm.py
+++ b/yt_dlp/extractor/deuxm.py
@@ -12,8 +12,8 @@ class DeuxMIE(InfoExtractor):
'id': '6351d439b15e1a613b3debe8',
'ext': 'mp4',
'title': 'Grand Angle : Jeudi 20 Octobre 2022',
- 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$'
- }
+ 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$',
+ },
}, {
'url': 'https://2m.ma/fr/replay/single/635c0aeab4eec832622356da',
'md5': 'ad6af2f5e4d5b2ad2194a84b6e890b4c',
@@ -21,8 +21,8 @@ class DeuxMIE(InfoExtractor):
'id': '635c0aeab4eec832622356da',
'ext': 'mp4',
'title': 'Journal Amazigh : Vendredi 28 Octobre 2022',
- 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$'
- }
+ 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$',
+ },
}]
def _real_extract(self, url):
@@ -49,8 +49,8 @@ class DeuxMNewsIE(InfoExtractor):
'ext': 'mp4',
'title': 'Kan Ya Mkan d\u00e9poussi\u00e8re l\u2019histoire du phare du Cap Beddouza',
'description': 'md5:99dcf29b82f1d7f2a4acafed1d487527',
- 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$'
- }
+ 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$',
+ },
}, {
'url': 'https://2m.ma/fr/news/Interview-Casablanca-hors-des-sentiers-battus-avec-Abderrahim-KASSOU-Replay--20221017',
'md5': '7aca29f02230945ef635eb8290283c0c',
@@ -59,8 +59,8 @@ class DeuxMNewsIE(InfoExtractor):
'ext': 'mp4',
'title': 'Interview: Casablanca hors des sentiers battus avec Abderrahim KASSOU (Replay) ',
'description': 'md5:3b8e78111de9fcc6ef7f7dd6cff2430c',
- 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$'
- }
+ 'thumbnail': r're:^https?://2msoread-ww.amagi.tv/mediasfiles/videos/images/.*\.png$',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/dfb.py b/yt_dlp/extractor/dfb.py
index c4fb5c2a4..b397ed909 100644
--- a/yt_dlp/extractor/dfb.py
+++ b/yt_dlp/extractor/dfb.py
@@ -22,7 +22,7 @@ class DFBIE(InfoExtractor):
display_id, video_id = self._match_valid_url(url).groups()
player_info = self._download_xml(
- 'http://tv.dfb.de/server/hd_video.php?play=%s' % video_id,
+ f'http://tv.dfb.de/server/hd_video.php?play={video_id}',
display_id)
video_info = player_info.find('video')
stream_access_url = self._proto_relative_url(video_info.find('url').text.strip())
@@ -46,7 +46,7 @@ class DFBIE(InfoExtractor):
'id': video_id,
'display_id': display_id,
'title': video_info.find('title').text,
- 'thumbnail': 'http://tv.dfb.de/images/%s_640x360.jpg' % video_id,
+ 'thumbnail': f'http://tv.dfb.de/images/{video_id}_640x360.jpg',
'upload_date': unified_strdate(video_info.find('time_date').text),
'formats': formats,
}
diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py
index 4380c414e..594ce2d0b 100644
--- a/yt_dlp/extractor/digitalconcerthall.py
+++ b/yt_dlp/extractor/digitalconcerthall.py
@@ -78,7 +78,7 @@ class DigitalConcertHallIE(InfoExtractor):
}), headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': 'https://www.digitalconcerthall.com',
- 'Authorization': f'Bearer {self._ACCESS_TOKEN}'
+ 'Authorization': f'Bearer {self._ACCESS_TOKEN}',
})
except ExtractorError:
self.raise_login_required(msg='Login info incorrect')
@@ -94,7 +94,7 @@ class DigitalConcertHallIE(InfoExtractor):
self._proto_relative_url(item['_links']['streams']['href']), video_id, headers={
'Accept': 'application/json',
'Authorization': f'Bearer {self._ACCESS_TOKEN}',
- 'Accept-Language': language
+ 'Accept-Language': language,
})
m3u8_url = traverse_obj(
@@ -128,13 +128,13 @@ class DigitalConcertHallIE(InfoExtractor):
self._download_webpage(url, video_id), 'thumbnail')
thumbnails = [{
'url': thumbnail_url,
- **parse_resolution(thumbnail_url)
+ **parse_resolution(thumbnail_url),
}]
vid_info = self._download_json(
f'https://api.digitalconcerthall.com/v2/{type_}/{video_id}', video_id, headers={
'Accept': 'application/json',
- 'Accept-Language': language
+ 'Accept-Language': language,
})
album_artist = ' / '.join(traverse_obj(vid_info, ('_links', 'artist', ..., 'name')) or '')
videos = [vid_info] if type_ == 'film' else traverse_obj(vid_info, ('_embedded', ..., ...))
diff --git a/yt_dlp/extractor/digiteka.py b/yt_dlp/extractor/digiteka.py
index 912e33ba7..e56ec63e8 100644
--- a/yt_dlp/extractor/digiteka.py
+++ b/yt_dlp/extractor/digiteka.py
@@ -65,7 +65,7 @@ class DigitekaIE(InfoExtractor):
video_type = 'musique'
deliver_info = self._download_json(
- 'http://www.ultimedia.com/deliver/video?video=%s&topic=%s' % (video_id, video_type),
+ f'http://www.ultimedia.com/deliver/video?video={video_id}&topic={video_type}',
video_id)
yt_id = deliver_info.get('yt_id')
diff --git a/yt_dlp/extractor/discovery.py b/yt_dlp/extractor/discovery.py
index 75b464353..b98279d67 100644
--- a/yt_dlp/extractor/discovery.py
+++ b/yt_dlp/extractor/discovery.py
@@ -1,8 +1,8 @@
import random
import string
+import urllib.parse
from .discoverygo import DiscoveryGoBaseIE
-from ..compat import compat_urllib_parse_unquote
from ..networking.exceptions import HTTPError
from ..utils import ExtractorError
@@ -42,7 +42,7 @@ class DiscoveryIE(DiscoveryGoBaseIE):
},
'params': {
'skip_download': True, # requires ffmpeg
- }
+ },
}, {
'url': 'https://www.investigationdiscovery.com/tv-shows/final-vision/full-episodes/final-vision',
'only_matching': True,
@@ -67,14 +67,14 @@ class DiscoveryIE(DiscoveryGoBaseIE):
# prefer Affiliate Auth Token over Anonymous Auth Token
auth_storage_cookie = cookies.get('eosAf') or cookies.get('eosAn')
if auth_storage_cookie and auth_storage_cookie.value:
- auth_storage = self._parse_json(compat_urllib_parse_unquote(
- compat_urllib_parse_unquote(auth_storage_cookie.value)),
+ auth_storage = self._parse_json(urllib.parse.unquote(
+ urllib.parse.unquote(auth_storage_cookie.value)),
display_id, fatal=False) or {}
access_token = auth_storage.get('a') or auth_storage.get('access_token')
if not access_token:
access_token = self._download_json(
- 'https://%s.com/anonymous' % site, display_id,
+ f'https://{site}.com/anonymous', display_id,
'Downloading token JSON metadata', query={
'authRel': 'authorization',
'client_id': '3020a40c2356a645b4b4',
diff --git a/yt_dlp/extractor/discoverygo.py b/yt_dlp/extractor/discoverygo.py
index b2663a63d..964948548 100644
--- a/yt_dlp/extractor/discoverygo.py
+++ b/yt_dlp/extractor/discoverygo.py
@@ -40,7 +40,7 @@ class DiscoveryGoBaseIE(InfoExtractor):
formats = []
for stream_kind in ('', 'hds'):
suffix = STREAM_URL_SUFFIX.capitalize() if stream_kind else STREAM_URL_SUFFIX
- stream_url = stream.get('%s%s' % (stream_kind, suffix))
+ stream_url = stream.get(f'{stream_kind}{suffix}')
if not stream_url:
continue
if stream_kind == '':
@@ -143,8 +143,7 @@ class DiscoveryGoPlaylistIE(DiscoveryGoBaseIE):
@classmethod
def suitable(cls, url):
- return False if DiscoveryGoIE.suitable(url) else super(
- DiscoveryGoPlaylistIE, cls).suitable(url)
+ return False if DiscoveryGoIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
diff --git a/yt_dlp/extractor/disney.py b/yt_dlp/extractor/disney.py
index d8dde0ca7..a90f12389 100644
--- a/yt_dlp/extractor/disney.py
+++ b/yt_dlp/extractor/disney.py
@@ -26,7 +26,7 @@ class DisneyIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
# Grill.burger
'url': 'http://www.starwars.com/video/rogue-one-a-star-wars-story-intro-featurette',
@@ -40,7 +40,7 @@ class DisneyIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
'url': 'http://videos.disneylatino.com/ver/spider-man-de-regreso-a-casa-primer-adelanto-543a33a1850bdcfcca13bae2',
'only_matching': True,
@@ -84,7 +84,7 @@ class DisneyIE(InfoExtractor):
video_data = page_data['data'][0]
else:
webpage = self._download_webpage(
- 'http://%s/embed/%s' % (domain, video_id), video_id)
+ f'http://{domain}/embed/{video_id}', video_id)
page_data = self._parse_json(self._search_regex(
r'Disney\.EmbedVideo\s*=\s*({.+});',
webpage, 'embed data'), video_id)
@@ -132,7 +132,7 @@ class DisneyIE(InfoExtractor):
})
if not formats and video_data.get('expired'):
self.raise_no_formats(
- '%s said: %s' % (self.IE_NAME, page_data['translations']['video_expired']),
+ '{} said: {}'.format(self.IE_NAME, page_data['translations']['video_expired']),
expected=True)
subtitles = {}
diff --git a/yt_dlp/extractor/dispeak.py b/yt_dlp/extractor/dispeak.py
index 37f89b9bc..89c27e0b5 100644
--- a/yt_dlp/extractor/dispeak.py
+++ b/yt_dlp/extractor/dispeak.py
@@ -55,7 +55,7 @@ class DigitallySpeakingIE(InfoExtractor):
if video_root is None:
http_host = xpath_text(metadata, 'httpHost', default=None)
if http_host:
- video_root = 'http://%s/' % http_host
+ video_root = f'http://{http_host}/'
if video_root is None:
# Hard-coded in http://evt.dispeak.com/ubm/gdc/sf16/custom/player2.js
# Works for GPUTechConf, too
@@ -86,7 +86,7 @@ class DigitallySpeakingIE(InfoExtractor):
audios = metadata.findall('./audios/audio')
for audio in audios:
formats.append({
- 'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
+ 'url': f'rtmp://{akamai_url}/ondemand?ovpfv=1.1',
'play_path': remove_end(audio.get('url'), '.flv'),
'ext': 'flv',
'vcodec': 'none',
@@ -95,14 +95,14 @@ class DigitallySpeakingIE(InfoExtractor):
})
for video_key, format_id, preference in (
('slide', 'slides', -2), ('speaker', 'speaker', -1)):
- video_path = xpath_text(metadata, './%sVideo' % video_key)
+ video_path = xpath_text(metadata, f'./{video_key}Video')
if not video_path:
continue
formats.append({
- 'url': 'rtmp://%s/ondemand?ovpfv=1.1' % akamai_url,
+ 'url': f'rtmp://{akamai_url}/ondemand?ovpfv=1.1',
'play_path': remove_end(video_path, '.flv'),
'ext': 'flv',
- 'format_note': '%s video' % video_key,
+ 'format_note': f'{video_key} video',
'quality': preference,
'format_id': format_id,
})
diff --git a/yt_dlp/extractor/dlf.py b/yt_dlp/extractor/dlf.py
index 88a4149b5..eac219013 100644
--- a/yt_dlp/extractor/dlf.py
+++ b/yt_dlp/extractor/dlf.py
@@ -37,7 +37,7 @@ class DLFBaseIE(InfoExtractor):
'webpage_url': ('data-audio-download-tracking-path', {url_or_none}),
}, get_all=False),
'formats': (self._extract_m3u8_formats(url, audio_id, fatal=False)
- if ext == 'm3u8' else [{'url': url, 'ext': ext, 'vcodec': 'none'}])
+ if ext == 'm3u8' else [{'url': url, 'ext': ext, 'vcodec': 'none'}]),
}
@@ -56,12 +56,12 @@ class DLFIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/FALLBACK-IMAGE-AUDIO/512x512.png?t=1603714364673',
'uploader': 'Deutschlandfunk',
'series': 'On Stage',
- 'channel': 'deutschlandfunk'
+ 'channel': 'deutschlandfunk',
},
'params': {
- 'skip_download': 'm3u8'
+ 'skip_download': 'm3u8',
},
- 'skip': 'This webpage no longer exists'
+ 'skip': 'This webpage no longer exists',
}, {
'url': 'https://www.deutschlandfunk.de/russische-athleten-kehren-zurueck-auf-die-sportbuehne-ein-gefaehrlicher-tueroeffner-dlf-d9cc1856-100.html',
'info_dict': {
@@ -72,8 +72,8 @@ class DLFIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/FALLBACK-IMAGE-AUDIO/512x512.png?t=1603714364673',
'uploader': 'Deutschlandfunk',
'series': 'Kommentare und Themen der Woche',
- 'channel': 'deutschlandfunk'
- }
+ 'channel': 'deutschlandfunk',
+ },
},
]
@@ -96,7 +96,7 @@ class DLFCorpusIE(DLFBaseIE):
'info_dict': {
'id': 'fechten-russland-belarus-ukraine-protest-100',
'title': r're:Wiederzulassung als neutrale Athleten [-/] Was die Rückkehr russischer und belarussischer Sportler beim Fechten bedeutet',
- 'description': 'md5:91340aab29c71aa7518ad5be13d1e8ad'
+ 'description': 'md5:91340aab29c71aa7518ad5be13d1e8ad',
},
'playlist_mincount': 5,
'playlist': [{
@@ -108,8 +108,8 @@ class DLFCorpusIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/aad16241-6b76-4a09-958b-96d0ee1d6f57/512x512.jpg?t=1679480020313',
'uploader': 'Deutschlandfunk',
'series': 'Sport',
- 'channel': 'deutschlandfunk'
- }
+ 'channel': 'deutschlandfunk',
+ },
}, {
'info_dict': {
'id': '2ada145f',
@@ -119,8 +119,8 @@ class DLFCorpusIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/FILE_93982766f7317df30409b8a184ac044a/512x512.jpg?t=1678547581005',
'uploader': 'Deutschlandfunk',
'series': 'Deutschlandfunk Nova',
- 'channel': 'deutschlandfunk-nova'
- }
+ 'channel': 'deutschlandfunk-nova',
+ },
}, {
'info_dict': {
'id': '5e55e8c9',
@@ -130,8 +130,8 @@ class DLFCorpusIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/a595989d-1ed1-4a2e-8370-b64d7f11d757/512x512.jpg?t=1679173825412',
'uploader': 'Deutschlandfunk',
'series': 'Sport am Samstag',
- 'channel': 'deutschlandfunk'
- }
+ 'channel': 'deutschlandfunk',
+ },
}, {
'info_dict': {
'id': '47e1a096',
@@ -141,8 +141,8 @@ class DLFCorpusIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/da4c494a-21cc-48b4-9cc7-40e09fd442c2/512x512.jpg?t=1678562155770',
'uploader': 'Deutschlandfunk',
'series': 'Sport am Samstag',
- 'channel': 'deutschlandfunk'
- }
+ 'channel': 'deutschlandfunk',
+ },
}, {
'info_dict': {
'id': '5e55e8c9',
@@ -152,9 +152,9 @@ class DLFCorpusIE(DLFBaseIE):
'thumbnail': 'https://assets.deutschlandfunk.de/a595989d-1ed1-4a2e-8370-b64d7f11d757/512x512.jpg?t=1679173825412',
'uploader': 'Deutschlandfunk',
'series': 'Sport am Samstag',
- 'channel': 'deutschlandfunk'
- }
- }]
+ 'channel': 'deutschlandfunk',
+ },
+ }],
},
# Podcast feed with tag buttons, playlist count fluctuates
{
diff --git a/yt_dlp/extractor/dlive.py b/yt_dlp/extractor/dlive.py
index 30fcf9fce..157d06c40 100644
--- a/yt_dlp/extractor/dlive.py
+++ b/yt_dlp/extractor/dlive.py
@@ -16,7 +16,7 @@ class DLiveVODIE(InfoExtractor):
'upload_date': '20190701',
'timestamp': 1562011015,
'uploader_id': 'pdp',
- }
+ },
}, {
'url': 'https://dlive.tv/p/pdpreplay+D-RD-xSZg',
'only_matching': True,
@@ -36,7 +36,7 @@ class DLiveVODIE(InfoExtractor):
thumbnailUrl
viewCount
}
-}''' % (uploader_id, vod_id)}).encode())['data']['pastBroadcast']
+}''' % (uploader_id, vod_id)}).encode())['data']['pastBroadcast'] # noqa: UP031
title = broadcast['title']
formats = self._extract_m3u8_formats(
broadcast['playbackUrl'], vod_id, 'mp4', 'm3u8_native')
@@ -71,12 +71,12 @@ class DLiveStreamIE(InfoExtractor):
}
username
}
-}''' % display_name}).encode())['data']['userByDisplayName']
+}''' % display_name}).encode())['data']['userByDisplayName'] # noqa: UP031
livestream = user['livestream']
title = livestream['title']
username = user['username']
formats = self._extract_m3u8_formats(
- 'https://live.prd.dlive.tv/hls/live/%s.m3u8' % username,
+ f'https://live.prd.dlive.tv/hls/live/{username}.m3u8',
display_name, 'mp4')
return {
'id': display_name,
diff --git a/yt_dlp/extractor/douyutv.py b/yt_dlp/extractor/douyutv.py
index 244ffdf1c..fdf19c252 100644
--- a/yt_dlp/extractor/douyutv.py
+++ b/yt_dlp/extractor/douyutv.py
@@ -187,7 +187,7 @@ class DouyuTVIE(DouyuBaseIE):
}
stream_formats = [self._download_json(
f'https://www.douyu.com/lapi/live/getH5Play/{room_id}',
- video_id, note="Downloading livestream format",
+ video_id, note='Downloading livestream format',
data=urlencode_postdata(form_data))]
for rate_id in traverse_obj(stream_formats[0], ('data', 'multirates', ..., 'rate')):
@@ -208,7 +208,7 @@ class DouyuTVIE(DouyuBaseIE):
'description': ('show_details', {str}),
'uploader': ('nickname', {str}),
'thumbnail': ('room_src', {url_or_none}),
- })
+ }),
}
@@ -270,7 +270,7 @@ class DouyuShowIE(DouyuBaseIE):
}
url_info = self._download_json(
'https://v.douyu.com/api/stream/getStreamUrl', video_id,
- data=urlencode_postdata(form_data), note="Downloading video formats")
+ data=urlencode_postdata(form_data), note='Downloading video formats')
formats = []
for name, url in traverse_obj(url_info, ('data', 'thumb_video', {dict.items}, ...)):
@@ -284,7 +284,7 @@ class DouyuShowIE(DouyuBaseIE):
'quality': self._QUALITIES.get(name),
'ext': 'mp4' if ext == 'm3u8' else ext,
'protocol': 'm3u8_native' if ext == 'm3u8' else 'https',
- **parse_resolution(self._RESOLUTIONS.get(name))
+ **parse_resolution(self._RESOLUTIONS.get(name)),
})
else:
self.to_screen(
@@ -302,5 +302,5 @@ class DouyuShowIE(DouyuBaseIE):
'timestamp': ('content', 'create_time', {int_or_none}),
'view_count': ('content', 'view_num', {int_or_none}),
'tags': ('videoTag', ..., 'tagName', {str}),
- }))
+ })),
}
diff --git a/yt_dlp/extractor/dplay.py b/yt_dlp/extractor/dplay.py
index ddf2128b0..48eae1088 100644
--- a/yt_dlp/extractor/dplay.py
+++ b/yt_dlp/extractor/dplay.py
@@ -70,7 +70,7 @@ class DPlayBaseIE(InfoExtractor):
self._initialize_geo_bypass({
'countries': geo_countries,
})
- disco_base = 'https://%s/' % disco_host
+ disco_base = f'https://{disco_host}/'
headers = {
'Referer': url,
}
@@ -84,7 +84,7 @@ class DPlayBaseIE(InfoExtractor):
'fields[show]': 'name',
'fields[tag]': 'name',
'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration',
- 'include': 'images,primaryChannel,show,tags'
+ 'include': 'images,primaryChannel,show,tags',
})
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 400:
@@ -359,7 +359,7 @@ class DiscoveryPlusBaseIE(DPlayBaseIE):
},
'videoId': video_id,
'wisteriaProperties': {},
- }).encode('utf-8'))['data']['attributes']['streaming']
+ }).encode())['data']['attributes']['streaming']
def _real_extract(self, url):
return self._get_disco_api_info(url, self._match_id(url), **self._DISCO_API_PARAMS)
@@ -857,7 +857,7 @@ class DiscoveryPlusIndiaIE(DiscoveryPlusBaseIE):
},
'params': {
'skip_download': True,
- }
+ },
}]
_PRODUCT = 'dplus-india'
@@ -870,7 +870,7 @@ class DiscoveryPlusIndiaIE(DiscoveryPlusBaseIE):
def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
headers.update({
- 'x-disco-params': 'realm=%s' % realm,
+ 'x-disco-params': f'realm={realm}',
'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:17.0.0',
'Authorization': self._get_auth(disco_base, display_id, realm),
})
@@ -967,14 +967,14 @@ class DiscoveryPlusShowBaseIE(DPlayBaseIE):
while page_num < total_pages:
season_json = self._download_json(
season_url.format(season_id, show_id, str(page_num + 1)), show_name, headers=headers,
- note='Downloading season %s JSON metadata%s' % (season_id, ' page %d' % page_num if page_num else ''))
+ note='Downloading season {} JSON metadata{}'.format(season_id, f' page {page_num}' if page_num else ''))
if page_num == 0:
total_pages = try_get(season_json, lambda x: x['meta']['totalPages'], int) or 1
episodes_json = season_json['data']
for episode in episodes_json:
video_path = episode['attributes']['path']
yield self.url_result(
- '%svideos/%s' % (self._DOMAIN, video_path),
+ f'{self._DOMAIN}videos/{video_path}',
ie=self._VIDEO_IE.ie_key(), video_id=episode.get('id') or video_path)
page_num += 1
@@ -1002,7 +1002,7 @@ class DiscoveryPlusItalyIE(DiscoveryPlusBaseIE):
def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
headers.update({
- 'x-disco-params': 'realm=%s' % realm,
+ 'x-disco-params': f'realm={realm}',
'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:25.2.6',
'Authorization': self._get_auth(disco_base, display_id, realm),
})
diff --git a/yt_dlp/extractor/drbonanza.py b/yt_dlp/extractor/drbonanza.py
index 824d70def..40dc14168 100644
--- a/yt_dlp/extractor/drbonanza.py
+++ b/yt_dlp/extractor/drbonanza.py
@@ -40,7 +40,7 @@ class DRBonanzaIE(InfoExtractor):
def extract(field):
return self._search_regex(
- r'<div[^>]+>\s*<p>%s:<p>\s*</div>\s*<div[^>]+>\s*<p>([^<]+)</p>' % field,
+ rf'<div[^>]+>\s*<p>{field}:<p>\s*</div>\s*<div[^>]+>\s*<p>([^<]+)</p>',
webpage, field, default=None)
info.update({
diff --git a/yt_dlp/extractor/dreisat.py b/yt_dlp/extractor/dreisat.py
index 8a59c23ab..4b0a269b9 100644
--- a/yt_dlp/extractor/dreisat.py
+++ b/yt_dlp/extractor/dreisat.py
@@ -25,11 +25,11 @@ class DreiSatIE(ZDFIE): # XXX: Do not subclass from concrete IE
'title': 'Waidmannsheil',
'description': 'md5:cce00ca1d70e21425e72c86a98a56817',
'timestamp': 1410623100,
- 'upload_date': '20140913'
+ 'upload_date': '20140913',
},
'params': {
'skip_download': True,
- }
+ },
}, {
# Same as https://www.zdf.de/filme/filme-sonstige/der-hauptmann-112.html
'url': 'https://www.3sat.de/film/spielfilm/der-hauptmann-100.html',
diff --git a/yt_dlp/extractor/drooble.py b/yt_dlp/extractor/drooble.py
index 106e5c457..16e9a6194 100644
--- a/yt_dlp/extractor/drooble.py
+++ b/yt_dlp/extractor/drooble.py
@@ -24,7 +24,7 @@ class DroobleIE(InfoExtractor):
'timestamp': 1596241390,
'uploader_id': '95894',
'uploader': 'Bluebeat Shelter',
- }
+ },
}, {
'url': 'https://drooble.com/karl340758/videos/2859183',
'info_dict': {
@@ -35,7 +35,7 @@ class DroobleIE(InfoExtractor):
'description': 'md5:ffc0bd8ba383db5341a86a6cd7d9bcca',
'upload_date': '20200731',
'uploader': 'Bluebeat Shelter',
- }
+ },
}, {
'url': 'https://drooble.com/karl340758/music/albums/2858031',
'info_dict': {
@@ -88,7 +88,7 @@ class DroobleIE(InfoExtractor):
entites.append({
'_type': 'url',
'url': url,
- 'ie_key': 'Youtube'
+ 'ie_key': 'Youtube',
})
continue
is_audio = (media.get('type') or '').lower() == 'audio'
diff --git a/yt_dlp/extractor/dropbox.py b/yt_dlp/extractor/dropbox.py
index 0246975c1..51b40df42 100644
--- a/yt_dlp/extractor/dropbox.py
+++ b/yt_dlp/extractor/dropbox.py
@@ -1,9 +1,9 @@
import base64
import os.path
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
ExtractorError,
update_url_query,
@@ -19,8 +19,8 @@ class DropboxIE(InfoExtractor):
'info_dict': {
'id': 'nelirfsxnmcfbfh',
'ext': 'mp4',
- 'title': 'youtube-dl test video \'ä"BaW_jenozKc'
- }
+ 'title': 'youtube-dl test video \'ä"BaW_jenozKc',
+ },
}, {
'url': 'https://www.dropbox.com/s/nelirfsxnmcfbfh',
'only_matching': True,
@@ -40,7 +40,7 @@ class DropboxIE(InfoExtractor):
mobj = self._match_valid_url(url)
video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id)
- fn = compat_urllib_parse_unquote(url_basename(url))
+ fn = urllib.parse.unquote(url_basename(url))
title = os.path.splitext(fn)[0]
password = self.get_param('videopassword')
@@ -51,7 +51,7 @@ class DropboxIE(InfoExtractor):
content_id = self._search_regex(r'content_id=(.*?)["\']', webpage, 'content_id')
payload = f'is_xhr=true&t={self._get_cookies("https://www.dropbox.com").get("t").value}&content_id={content_id}&password={password}&url={url}'
response = self._download_json(
- 'https://www.dropbox.com/sm/auth', video_id, 'POSTing video password', data=payload.encode('UTF-8'),
+ 'https://www.dropbox.com/sm/auth', video_id, 'POSTing video password', data=payload.encode(),
headers={'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'})
if response.get('status') != 'authed':
@@ -81,12 +81,12 @@ class DropboxIE(InfoExtractor):
'url': update_url_query(url, {'dl': '1'}),
'format_id': 'original',
'format_note': 'Original',
- 'quality': 1
+ 'quality': 1,
})
return {
'id': video_id,
'title': title,
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
diff --git a/yt_dlp/extractor/dropout.py b/yt_dlp/extractor/dropout.py
index 80ae6c126..7e97c4d40 100644
--- a/yt_dlp/extractor/dropout.py
+++ b/yt_dlp/extractor/dropout.py
@@ -43,9 +43,9 @@ class DropoutIE(InfoExtractor):
'duration': 1180,
'uploader_id': 'user80538407',
'uploader_url': 'https://vimeo.com/user80538407',
- 'uploader': 'OTT Videos'
+ 'uploader': 'OTT Videos',
},
- 'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest']
+ 'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest'],
},
{
'url': 'https://www.dropout.tv/dimension-20-fantasy-high/season:1/videos/episode-1',
@@ -66,9 +66,9 @@ class DropoutIE(InfoExtractor):
'duration': 6838,
'uploader_id': 'user80538407',
'uploader_url': 'https://vimeo.com/user80538407',
- 'uploader': 'OTT Videos'
+ 'uploader': 'OTT Videos',
},
- 'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest']
+ 'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest'],
},
{
'url': 'https://www.dropout.tv/videos/misfits-magic-holiday-special',
@@ -85,10 +85,10 @@ class DropoutIE(InfoExtractor):
'duration': 11698,
'uploader_id': 'user80538407',
'uploader_url': 'https://vimeo.com/user80538407',
- 'uploader': 'OTT Videos'
+ 'uploader': 'OTT Videos',
},
- 'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest']
- }
+ 'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest'],
+ },
]
def _get_authenticity_token(self, display_id):
@@ -109,7 +109,7 @@ class DropoutIE(InfoExtractor):
'email': username,
'password': password,
'authenticity_token': self._get_authenticity_token(display_id),
- 'utf8': True
+ 'utf8': True,
}))
user_has_subscription = self._search_regex(
@@ -175,8 +175,8 @@ class DropoutSeasonIE(InfoExtractor):
'playlist_count': 24,
'info_dict': {
'id': 'dimension-20-fantasy-high-season-1',
- 'title': 'Dimension 20 Fantasy High - Season 1'
- }
+ 'title': 'Dimension 20 Fantasy High - Season 1',
+ },
},
{
'url': 'https://www.dropout.tv/dimension-20-fantasy-high',
@@ -184,8 +184,8 @@ class DropoutSeasonIE(InfoExtractor):
'playlist_count': 24,
'info_dict': {
'id': 'dimension-20-fantasy-high-season-1',
- 'title': 'Dimension 20 Fantasy High - Season 1'
- }
+ 'title': 'Dimension 20 Fantasy High - Season 1',
+ },
},
{
'url': 'https://www.dropout.tv/dimension-20-shriek-week',
@@ -193,8 +193,8 @@ class DropoutSeasonIE(InfoExtractor):
'playlist_count': 4,
'info_dict': {
'id': 'dimension-20-shriek-week-season-1',
- 'title': 'Dimension 20 Shriek Week - Season 1'
- }
+ 'title': 'Dimension 20 Shriek Week - Season 1',
+ },
},
{
'url': 'https://www.dropout.tv/breaking-news-no-laugh-newsroom/season:3',
@@ -202,9 +202,9 @@ class DropoutSeasonIE(InfoExtractor):
'playlist_count': 25,
'info_dict': {
'id': 'breaking-news-no-laugh-newsroom-season-3',
- 'title': 'Breaking News No Laugh Newsroom - Season 3'
- }
- }
+ 'title': 'Breaking News No Laugh Newsroom - Season 3',
+ },
+ },
]
def _fetch_page(self, url, season_id, page):
diff --git a/yt_dlp/extractor/drtuber.py b/yt_dlp/extractor/drtuber.py
index a9247edc0..6a1fe9010 100644
--- a/yt_dlp/extractor/drtuber.py
+++ b/yt_dlp/extractor/drtuber.py
@@ -25,7 +25,7 @@ class DrTuberIE(InfoExtractor):
'categories': ['Babe', 'Blonde', 'Erotic', 'Outdoor', 'Softcore', 'Solo'],
'thumbnail': r're:https?://.*\.jpg$',
'age_limit': 18,
- }
+ },
}, {
'url': 'http://www.drtuber.com/embed/489939',
'only_matching': True,
@@ -40,7 +40,7 @@ class DrTuberIE(InfoExtractor):
display_id = mobj.group('display_id') or video_id
webpage = self._download_webpage(
- 'http://www.drtuber.com/video/%s' % video_id, display_id)
+ f'http://www.drtuber.com/video/{video_id}', display_id)
video_data = self._download_json(
'http://www.drtuber.com/player_config_json/', video_id, query={
@@ -56,7 +56,7 @@ class DrTuberIE(InfoExtractor):
formats.append({
'format_id': format_id,
'quality': 2 if format_id == 'hq' else 1,
- 'url': video_url
+ 'url': video_url,
})
duration = int_or_none(video_data.get('duration')) or parse_duration(
@@ -76,8 +76,8 @@ class DrTuberIE(InfoExtractor):
def extract_count(id_, name, default=NO_DEFAULT):
return str_to_int(self._html_search_regex(
- r'<span[^>]+(?:class|id)="%s"[^>]*>([\d,\.]+)</span>' % id_,
- webpage, '%s count' % name, default=default, fatal=False))
+ rf'<span[^>]+(?:class|id)="{id_}"[^>]*>([\d,\.]+)</span>',
+ webpage, f'{name} count', default=default, fatal=False))
like_count = extract_count('rate_likes', 'like')
dislike_count = extract_count('rate_dislikes', 'dislike', default=None)
diff --git a/yt_dlp/extractor/drtv.py b/yt_dlp/extractor/drtv.py
index 2a6e337bf..32b684552 100644
--- a/yt_dlp/extractor/drtv.py
+++ b/yt_dlp/extractor/drtv.py
@@ -220,7 +220,7 @@ class DRTVIE(InfoExtractor):
lang = sub_track.get('language') or 'da'
subtitles.setdefault(self.SUBTITLE_LANGS.get(lang, lang) + subtitle_suffix, []).append({
'url': sub_track['link'],
- 'ext': mimetype2ext(sub_track.get('format')) or 'vtt'
+ 'ext': mimetype2ext(sub_track.get('format')) or 'vtt',
})
if not formats and traverse_obj(item, ('season', 'customFields', 'IsGeoRestricted')):
@@ -284,14 +284,14 @@ class DRTVLiveIE(InfoExtractor):
if not stream_path:
continue
stream_url = update_url_query(
- '%s/%s' % (server, stream_path), {'b': ''})
+ f'{server}/{stream_path}', {'b': ''})
if link_type == 'HLS':
formats.extend(self._extract_m3u8_formats(
stream_url, channel_id, 'mp4',
m3u8_id=link_type, fatal=False, live=True))
elif link_type == 'HDS':
formats.extend(self._extract_f4m_formats(update_url_query(
- '%s/%s' % (server, stream_path), {'hdcore': '3.7.0'}),
+ f'{server}/{stream_path}', {'hdcore': '3.7.0'}),
channel_id, f4m_id=link_type, fatal=False))
return {
@@ -317,7 +317,7 @@ class DRTVSeasonIE(InfoExtractor):
'season_number': 2008,
'alt_title': 'Season 2008',
},
- 'playlist_mincount': 8
+ 'playlist_mincount': 8,
}, {
'url': 'https://www.dr.dk/drtv/saeson/frank-and-kastaniegaarden_8761',
'info_dict': {
@@ -328,7 +328,7 @@ class DRTVSeasonIE(InfoExtractor):
'season_number': 2009,
'alt_title': 'Season 2009',
},
- 'playlist_mincount': 19
+ 'playlist_mincount': 19,
}]
def _real_extract(self, url):
@@ -356,7 +356,7 @@ class DRTVSeasonIE(InfoExtractor):
'alt_title': traverse_obj(data, ('entries', 0, 'item', 'contextualTitle')),
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
'entries': entries,
- 'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber'))
+ 'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber')),
}
@@ -373,7 +373,7 @@ class DRTVSeriesIE(InfoExtractor):
'series': 'Frank & Kastaniegaarden',
'alt_title': '',
},
- 'playlist_mincount': 15
+ 'playlist_mincount': 15,
}]
def _real_extract(self, url):
@@ -387,7 +387,7 @@ class DRTVSeriesIE(InfoExtractor):
'title': season.get('title'),
'alt_title': season.get('contextualTitle'),
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
- 'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber'))
+ 'season_number': traverse_obj(data, ('entries', 0, 'item', 'seasonNumber')),
} for season in traverse_obj(data, ('entries', 0, 'item', 'show', 'seasons', 'items'))]
return {
@@ -397,5 +397,5 @@ class DRTVSeriesIE(InfoExtractor):
'title': traverse_obj(data, ('entries', 0, 'item', 'title')),
'alt_title': traverse_obj(data, ('entries', 0, 'item', 'contextualTitle')),
'series': traverse_obj(data, ('entries', 0, 'item', 'title')),
- 'entries': entries
+ 'entries': entries,
}
diff --git a/yt_dlp/extractor/dtube.py b/yt_dlp/extractor/dtube.py
index 5ea014cf0..0d87820c4 100644
--- a/yt_dlp/extractor/dtube.py
+++ b/yt_dlp/extractor/dtube.py
@@ -49,16 +49,16 @@ class DTubeIE(InfoExtractor):
formats = []
for q in ('240', '480', '720', '1080', ''):
- video_url = canonical_url(content.get('video%shash' % q))
+ video_url = canonical_url(content.get(f'video{q}hash'))
if not video_url:
continue
format_id = (q + 'p') if q else 'Source'
try:
- self.to_screen('%s: Checking %s video format URL' % (video_id, format_id))
+ self.to_screen(f'{video_id}: Checking {format_id} video format URL')
self._downloader._opener.open(video_url, timeout=5).close()
except socket.timeout:
self.to_screen(
- '%s: %s URL is invalid, skipping' % (video_id, format_id))
+ f'{video_id}: {format_id} URL is invalid, skipping')
continue
formats.append({
'format_id': format_id,
diff --git a/yt_dlp/extractor/duboku.py b/yt_dlp/extractor/duboku.py
index adc7705bc..68c3f0573 100644
--- a/yt_dlp/extractor/duboku.py
+++ b/yt_dlp/extractor/duboku.py
@@ -3,7 +3,6 @@ import re
import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
ExtractorError,
clean_html,
@@ -24,23 +23,23 @@ def _get_elements_by_tag_and_attrib(html, tag=None, attribute=None, value=None,
if attribute is None:
attribute = ''
else:
- attribute = r'\s+(?P<attribute>%s)' % re.escape(attribute)
+ attribute = rf'\s+(?P<attribute>{re.escape(attribute)})'
if value is None:
value = ''
else:
value = re.escape(value) if escape_value else value
- value = '=[\'"]?(?P<value>%s)[\'"]?' % value
+ value = f'=[\'"]?(?P<value>{value})[\'"]?'
retlist = []
- for m in re.finditer(r'''(?xs)
- <(?P<tag>%s)
+ for m in re.finditer(rf'''(?xs)
+ <(?P<tag>{tag})
(?:\s+[a-zA-Z0-9:._-]+(?:=[a-zA-Z0-9:._-]*|="[^"]*"|='[^']*'|))*?
- %s%s
+ {attribute}{value}
(?:\s+[a-zA-Z0-9:._-]+(?:=[a-zA-Z0-9:._-]*|="[^"]*"|='[^']*'|))*?
\s*>
(?P<content>.*?)
</\1>
- ''' % (tag, attribute, value), html):
+ ''', html):
retlist.append(m)
return retlist
@@ -101,7 +100,7 @@ class DubokuIE(InfoExtractor):
season_id = temp[1]
episode_id = temp[2]
- webpage_url = 'https://w.duboku.io/vodplay/%s.html' % video_id
+ webpage_url = f'https://w.duboku.io/vodplay/{video_id}.html'
webpage_html = self._download_webpage(webpage_url, video_id)
# extract video url
@@ -165,7 +164,7 @@ class DubokuIE(InfoExtractor):
'episode_number': int_or_none(episode_id),
'episode_id': episode_id,
'formats': formats,
- 'http_headers': headers
+ 'http_headers': headers,
}
@@ -193,11 +192,11 @@ class DubokuPlaylistIE(InfoExtractor):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
if mobj is None:
- raise ExtractorError('Invalid URL: %s' % url)
+ raise ExtractorError(f'Invalid URL: {url}')
series_id = mobj.group('id')
- fragment = compat_urlparse.urlparse(url).fragment
+ fragment = urllib.parse.urlparse(url).fragment
- webpage_url = 'https://w.duboku.io/voddetail/%s.html' % series_id
+ webpage_url = f'https://w.duboku.io/voddetail/{series_id}.html'
webpage_html = self._download_webpage(webpage_url, series_id)
# extract title
@@ -221,7 +220,7 @@ class DubokuPlaylistIE(InfoExtractor):
div.group('content'), 'a', 'href', value='[^\'"]+?', escape_value=False):
playlist.append({
'href': unescapeHTML(a.group('value')),
- 'title': unescapeHTML(a.group('content'))
+ 'title': unescapeHTML(a.group('content')),
})
playlists[playlist_id] = playlist
@@ -237,11 +236,11 @@ class DubokuPlaylistIE(InfoExtractor):
(playlist_id, playlist) = first
if not playlist:
raise ExtractorError(
- 'Cannot find %s' % fragment if fragment else 'Cannot extract playlist')
+ f'Cannot find {fragment}' if fragment else 'Cannot extract playlist')
# return url results
return self.playlist_result([
self.url_result(
- compat_urlparse.urljoin('https://w.duboku.io', x['href']),
+ urllib.parse.urljoin('https://w.duboku.io', x['href']),
ie=DubokuIE.ie_key(), video_title=x.get('title'))
for x in playlist], series_id + '#' + playlist_id, title)
diff --git a/yt_dlp/extractor/dumpert.py b/yt_dlp/extractor/dumpert.py
index 5e7aef0c5..1c2050cc1 100644
--- a/yt_dlp/extractor/dumpert.py
+++ b/yt_dlp/extractor/dumpert.py
@@ -24,7 +24,7 @@ class DumpertIE(InfoExtractor):
'duration': 9,
'view_count': int,
'like_count': int,
- }
+ },
}, {
'url': 'https://www.dumpert.nl/embed/6675421_dc440fe7',
'only_matching': True,
@@ -46,7 +46,7 @@ class DumpertIE(InfoExtractor):
'view_count': int,
'like_count': int,
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://www.dumpert.nl/toppers?selectedId=100031688_b317a185',
'only_matching': True,
diff --git a/yt_dlp/extractor/dvtv.py b/yt_dlp/extractor/dvtv.py
index e6660dcd9..3e442b339 100644
--- a/yt_dlp/extractor/dvtv.py
+++ b/yt_dlp/extractor/dvtv.py
@@ -28,7 +28,7 @@ class DVTVIE(InfoExtractor):
'duration': 1484,
'upload_date': '20141217',
'timestamp': 1418792400,
- }
+ },
}, {
'url': 'http://video.aktualne.cz/dvtv/dvtv-16-12-2014-utok-talibanu-boj-o-kliniku-uprchlici/r~973eb3bc854e11e498be002590604f2e/',
'info_dict': {
@@ -44,7 +44,7 @@ class DVTVIE(InfoExtractor):
'description': 'md5:0916925dea8e30fe84222582280b47a0',
'timestamp': 1418760010,
'upload_date': '20141216',
- }
+ },
}, {
'md5': '5f7652a08b05009c1292317b449ffea2',
'info_dict': {
@@ -54,7 +54,7 @@ class DVTVIE(InfoExtractor):
'description': 'md5:ff2f9f6de73c73d7cef4f756c1c1af42',
'timestamp': 1418760010,
'upload_date': '20141216',
- }
+ },
}, {
'md5': '498eb9dfa97169f409126c617e2a3d64',
'info_dict': {
@@ -64,7 +64,7 @@ class DVTVIE(InfoExtractor):
'description': 'md5:889fe610a70fee5511dc3326a089188e',
'timestamp': 1418760010,
'upload_date': '20141216',
- }
+ },
}, {
'md5': 'b8dc6b744844032dab6ba3781a7274b9',
'info_dict': {
@@ -74,7 +74,7 @@ class DVTVIE(InfoExtractor):
'description': 'md5:544f86de6d20c4815bea11bf2ac3004f',
'timestamp': 1418760010,
'upload_date': '20141216',
- }
+ },
}],
}, {
'url': 'https://video.aktualne.cz/dvtv/zeman-si-jen-leci-mindraky-sobotku-nenavidi-a-babis-se-mu-te/r~960cdb3a365a11e7a83b0025900fea04/',
@@ -150,7 +150,7 @@ class DVTVIE(InfoExtractor):
'thumbnail': data.get('image'),
'duration': int_or_none(data.get('duration')),
'timestamp': int_or_none(timestamp),
- 'formats': formats
+ 'formats': formats,
}
def _real_extract(self, url):
@@ -170,7 +170,7 @@ class DVTVIE(InfoExtractor):
webpage, 'video', default=None)
if item:
# remove function calls (ex. htmldeentitize)
- # TODO this should be fixed in a general way in the js_to_json
+ # TODO: this should be fixed in a general way in the js_to_json
item = re.sub(r'\w+?\((.+)\)', r'\1', item)
return self._parse_video_metadata(item, video_id, timestamp)
diff --git a/yt_dlp/extractor/dw.py b/yt_dlp/extractor/dw.py
index feab804af..320e29bfd 100644
--- a/yt_dlp/extractor/dw.py
+++ b/yt_dlp/extractor/dw.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
int_or_none,
unified_strdate,
@@ -22,7 +23,7 @@ class DWIE(InfoExtractor):
'title': 'Intelligent light',
'description': 'md5:90e00d5881719f2a6a5827cb74985af1',
'upload_date': '20160605',
- }
+ },
}, {
# audio
'url': 'http://www.dw.com/en/worldlink-my-business/av-19111941',
@@ -33,7 +34,7 @@ class DWIE(InfoExtractor):
'title': 'WorldLink: My business',
'description': 'md5:bc9ca6e4e063361e21c920c53af12405',
'upload_date': '20160311',
- }
+ },
}, {
# DW documentaries, only last for one or two weeks
'url': 'http://www.dw.com/en/documentaries-welcome-to-the-90s-2016-05-21/e-19220158-9798',
@@ -60,7 +61,7 @@ class DWIE(InfoExtractor):
formats = [{'url': hidden_inputs['file_name']}]
else:
formats = self._extract_smil_formats(
- 'http://www.dw.com/smil/v-%s' % media_id, media_id,
+ f'http://www.dw.com/smil/v-{media_id}', media_id,
transform_source=lambda s: s.replace(
'rtmp://tv-od.dw.de/flash/',
'http://tv-download.dw.de/dwtv_video/flv/'))
@@ -97,7 +98,7 @@ class DWArticleIE(InfoExtractor):
'title': 'The harsh life of refugees in Idomeni',
'description': 'md5:196015cc7e48ebf474db9399420043c7',
'upload_date': '20160310',
- }
+ },
}
def _real_extract(self, url):
@@ -105,6 +106,6 @@ class DWArticleIE(InfoExtractor):
webpage = self._download_webpage(url, article_id)
hidden_inputs = self._hidden_inputs(webpage)
media_id = hidden_inputs['media_id']
- media_path = self._search_regex(r'href="([^"]+av-%s)"\s+class="overlayLink"' % media_id, webpage, 'media url')
- media_url = compat_urlparse.urljoin(url, media_path)
+ media_path = self._search_regex(rf'href="([^"]+av-{media_id})"\s+class="overlayLink"', webpage, 'media url')
+ media_url = urllib.parse.urljoin(url, media_path)
return self.url_result(media_url, 'DW', media_id)
diff --git a/yt_dlp/extractor/eagleplatform.py b/yt_dlp/extractor/eagleplatform.py
index 739d17912..685f8c059 100644
--- a/yt_dlp/extractor/eagleplatform.py
+++ b/yt_dlp/extractor/eagleplatform.py
@@ -70,14 +70,14 @@ class EaglePlatformIE(InfoExtractor):
'''
# "Basic usage" embedding (see http://dultonmedia.github.io/eplayer/)
mobj = re.search(
- r'''(?xs)
- %s
+ rf'''(?xs)
+ {PLAYER_JS_RE}
<div[^>]+
class=(?P<qclass>["\'])eagleplayer(?P=qclass)[^>]+
data-id=["\'](?P<id>\d+)
- ''' % PLAYER_JS_RE, webpage)
+ ''', webpage)
if mobj is not None:
- return [add_referer('eagleplatform:%(host)s:%(id)s' % mobj.groupdict())]
+ return [add_referer('eagleplatform:{host}:{id}'.format(**mobj.groupdict()))]
# Generalization of "Javascript code usage", "Combined usage" and
# "Usage without attaching to DOM" embeddings (see
# http://dultonmedia.github.io/eplayer/)
@@ -96,9 +96,9 @@ class EaglePlatformIE(InfoExtractor):
\s*\)
.+?
</script>
- ''' % PLAYER_JS_RE, webpage)
+ ''' % PLAYER_JS_RE, webpage) # noqa: UP031
if mobj is not None:
- return [add_referer('eagleplatform:%(host)s:%(id)s' % mobj.groupdict())]
+ return [add_referer('eagleplatform:{host}:{id}'.format(**mobj.groupdict()))]
@staticmethod
def _handle_error(response):
@@ -108,7 +108,7 @@ class EaglePlatformIE(InfoExtractor):
def _download_json(self, url_or_request, video_id, *args, **kwargs):
try:
- response = super(EaglePlatformIE, self)._download_json(
+ response = super()._download_json(
url_or_request, video_id, *args, **kwargs)
except ExtractorError as ee:
if isinstance(ee.cause, HTTPError):
@@ -137,7 +137,7 @@ class EaglePlatformIE(InfoExtractor):
query['referrer'] = referrer
player_data = self._download_json(
- 'http://%s/api/player_data' % host, video_id,
+ f'http://{host}/api/player_data', video_id,
headers=headers, query=query)
media = player_data['data']['playlist']['viewports'][0]['medialist'][0]
@@ -186,7 +186,7 @@ class EaglePlatformIE(InfoExtractor):
})
else:
f = {
- 'format_id': 'http-%s' % format_id,
+ 'format_id': f'http-{format_id}',
'height': int_or_none(format_id),
}
f['url'] = format_url
@@ -212,4 +212,4 @@ class ClipYouEmbedIE(InfoExtractor):
mobj = re.search(
r'<iframe[^>]+src="https?://(?P<host>media\.clipyou\.ru)/index/player\?.*\brecord_id=(?P<id>\d+).*"', webpage)
if mobj is not None:
- yield smuggle_url('eagleplatform:%(host)s:%(id)s' % mobj.groupdict(), {'referrer': url})
+ yield smuggle_url('eagleplatform:{host}:{id}'.format(**mobj.groupdict()), {'referrer': url})
diff --git a/yt_dlp/extractor/ebaumsworld.py b/yt_dlp/extractor/ebaumsworld.py
index 0854d0344..ac766b380 100644
--- a/yt_dlp/extractor/ebaumsworld.py
+++ b/yt_dlp/extractor/ebaumsworld.py
@@ -18,7 +18,7 @@ class EbaumsWorldIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
config = self._download_xml(
- 'http://www.ebaumsworld.com/video/player/%s' % video_id, video_id)
+ f'http://www.ebaumsworld.com/video/player/{video_id}', video_id)
video_url = config.find('file').text
return {
diff --git a/yt_dlp/extractor/ebay.py b/yt_dlp/extractor/ebay.py
index d0eb9fc51..f1d122f88 100644
--- a/yt_dlp/extractor/ebay.py
+++ b/yt_dlp/extractor/ebay.py
@@ -11,7 +11,7 @@ class EbayIE(InfoExtractor):
'ext': 'mp4',
'title': 'WiFi internal antenna adhesive for wifi 2.4GHz wifi 5 wifi 6 wifi 6E full bands',
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}]
def _real_extract(self, url):
@@ -32,5 +32,5 @@ class EbayIE(InfoExtractor):
return {
'id': video_id,
'title': remove_end(self._html_extract_title(webpage), ' | eBay'),
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/egghead.py b/yt_dlp/extractor/egghead.py
index c94f3f81f..62d2e544c 100644
--- a/yt_dlp/extractor/egghead.py
+++ b/yt_dlp/extractor/egghead.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
determine_ext,
int_or_none,
@@ -13,7 +12,7 @@ class EggheadBaseIE(InfoExtractor):
def _call_api(self, path, video_id, resource, fatal=True):
return self._download_json(
'https://app.egghead.io/api/v1/' + path,
- video_id, 'Downloading %s JSON' % resource, fatal=fatal)
+ video_id, f'Downloading {resource} JSON', fatal=fatal)
class EggheadCourseIE(EggheadBaseIE):
@@ -46,7 +45,7 @@ class EggheadCourseIE(EggheadBaseIE):
continue
lesson_id = lesson.get('id')
if lesson_id:
- lesson_id = compat_str(lesson_id)
+ lesson_id = str(lesson_id)
entries.append(self.url_result(
lesson_url, ie=EggheadLessonIE.ie_key(), video_id=lesson_id))
@@ -55,7 +54,7 @@ class EggheadCourseIE(EggheadBaseIE):
playlist_id = course.get('id')
if playlist_id:
- playlist_id = compat_str(playlist_id)
+ playlist_id = str(playlist_id)
return self.playlist_result(
entries, playlist_id, course.get('title'),
@@ -98,7 +97,7 @@ class EggheadLessonIE(EggheadBaseIE):
lesson = self._call_api(
'lessons/' + display_id, display_id, 'lesson')
- lesson_id = compat_str(lesson['id'])
+ lesson_id = str(lesson['id'])
title = lesson['title']
formats = []
@@ -129,6 +128,6 @@ class EggheadLessonIE(EggheadBaseIE):
'view_count': int_or_none(lesson.get('plays_count')),
'tags': try_get(lesson, lambda x: x['tag_list'], list),
'series': try_get(
- lesson, lambda x: x['series']['title'], compat_str),
+ lesson, lambda x: x['series']['title'], str),
'formats': formats,
}
diff --git a/yt_dlp/extractor/eighttracks.py b/yt_dlp/extractor/eighttracks.py
index 3dd9ab1b3..3ac4c56ae 100644
--- a/yt_dlp/extractor/eighttracks.py
+++ b/yt_dlp/extractor/eighttracks.py
@@ -2,9 +2,6 @@ import json
import random
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..utils import (
ExtractorError,
)
@@ -29,8 +26,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885610',
'ext': 'm4a',
'title': "youtue-dl project<>\"' - youtube-dl test track 1 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': '4ab26f05c1f7291ea460a3920be8021f',
@@ -38,8 +35,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885608',
'ext': 'm4a',
'title': "youtube-dl project - youtube-dl test track 2 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': 'd30b5b5f74217410f4689605c35d1fd7',
@@ -47,8 +44,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885679',
'ext': 'm4a',
'title': "youtube-dl project as well - youtube-dl test track 3 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': '4eb0a669317cd725f6bbd336a29f923a',
@@ -56,8 +53,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885680',
'ext': 'm4a',
'title': "youtube-dl project as well - youtube-dl test track 4 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': '1893e872e263a2705558d1d319ad19e8',
@@ -65,8 +62,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885682',
'ext': 'm4a',
'title': "PH - youtube-dl test track 5 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': 'b673c46f47a216ab1741ae8836af5899',
@@ -74,8 +71,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885683',
'ext': 'm4a',
'title': "PH - youtube-dl test track 6 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': '1d74534e95df54986da7f5abf7d842b7',
@@ -83,8 +80,8 @@ class EightTracksIE(InfoExtractor):
'id': '11885684',
'ext': 'm4a',
'title': "phihag - youtube-dl test track 7 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
+ 'uploader_id': 'ytdl',
+ },
},
{
'md5': 'f081f47af8f6ae782ed131d38b9cd1c0',
@@ -92,10 +89,10 @@ class EightTracksIE(InfoExtractor):
'id': '11885685',
'ext': 'm4a',
'title': "phihag - youtube-dl test track 8 \"'/\\\u00e4\u21ad",
- 'uploader_id': 'ytdl'
- }
- }
- ]
+ 'uploader_id': 'ytdl',
+ },
+ },
+ ],
}
def _real_extract(self, url):
@@ -105,7 +102,7 @@ class EightTracksIE(InfoExtractor):
data = self._parse_json(
self._search_regex(
- r"(?s)PAGE\.mix\s*=\s*({.+?});\n", webpage, 'trax information'),
+ r'(?s)PAGE\.mix\s*=\s*({.+?});\n', webpage, 'trax information'),
playlist_id)
session = str(random.randint(0, 1000000000))
@@ -116,7 +113,7 @@ class EightTracksIE(InfoExtractor):
# duration is sometimes negative, use predefined avg duration
if avg_song_duration <= 0:
avg_song_duration = 300
- first_url = 'http://8tracks.com/sets/%s/play?player=sm&mix_id=%s&format=jsonh' % (session, mix_id)
+ first_url = f'http://8tracks.com/sets/{session}/play?player=sm&mix_id={mix_id}&format=jsonh'
next_url = first_url
entries = []
@@ -140,7 +137,7 @@ class EightTracksIE(InfoExtractor):
api_data = json.loads(api_json)
track_data = api_data['set']['track']
info = {
- 'id': compat_str(track_data['id']),
+ 'id': str(track_data['id']),
'url': track_data['track_file_stream_url'],
'title': track_data['performer'] + ' - ' + track_data['name'],
'raw_title': track_data['name'],
@@ -149,12 +146,12 @@ class EightTracksIE(InfoExtractor):
}
entries.append(info)
- next_url = 'http://8tracks.com/sets/%s/next?player=sm&mix_id=%s&format=jsonh&track_id=%s' % (
+ next_url = 'http://8tracks.com/sets/{}/next?player=sm&mix_id={}&format=jsonh&track_id={}'.format(
session, mix_id, track_data['id'])
return {
'_type': 'playlist',
'entries': entries,
- 'id': compat_str(mix_id),
+ 'id': str(mix_id),
'display_id': playlist_id,
'title': data.get('name'),
'description': data.get('description'),
diff --git a/yt_dlp/extractor/eitb.py b/yt_dlp/extractor/eitb.py
index 66afbb6bb..3e2750796 100644
--- a/yt_dlp/extractor/eitb.py
+++ b/yt_dlp/extractor/eitb.py
@@ -26,7 +26,7 @@ class EitbIE(InfoExtractor):
video_id = self._match_id(url)
video = self._download_json(
- 'http://mam.eitb.eus/mam/REST/ServiceMultiweb/Video/MULTIWEBTV/%s/' % video_id,
+ f'http://mam.eitb.eus/mam/REST/ServiceMultiweb/Video/MULTIWEBTV/{video_id}/',
video_id, 'Downloading video JSON')
media = video['web_media'][0]
@@ -39,7 +39,7 @@ class EitbIE(InfoExtractor):
tbr = float_or_none(rendition.get('ENCODING_RATE'), 1000)
format_id = 'http'
if tbr:
- format_id += '-%d' % int(tbr)
+ format_id += f'-{int(tbr)}'
formats.append({
'url': rendition['PMD_URL'],
'format_id': format_id,
@@ -59,12 +59,12 @@ class EitbIE(InfoExtractor):
token = token_data.get('token')
if token:
formats.extend(self._extract_m3u8_formats(
- '%s?hdnts=%s' % (hls_url, token), video_id, m3u8_id='hls', fatal=False))
+ f'{hls_url}?hdnts={token}', video_id, m3u8_id='hls', fatal=False))
hds_url = media.get('HDS_SURL')
if hds_url:
formats.extend(self._extract_f4m_formats(
- '%s?hdcore=3.7.0' % hds_url.replace('euskalsvod', 'euskalvod'),
+ '{}?hdcore=3.7.0'.format(hds_url.replace('euskalsvod', 'euskalvod')),
video_id, f4m_id='hds', fatal=False))
return {
diff --git a/yt_dlp/extractor/elpais.py b/yt_dlp/extractor/elpais.py
index 7c6c88075..46fa29f8a 100644
--- a/yt_dlp/extractor/elpais.py
+++ b/yt_dlp/extractor/elpais.py
@@ -15,7 +15,7 @@ class ElPaisIE(InfoExtractor):
'title': 'Tiempo nuevo, recetas viejas',
'description': 'De lunes a viernes, a partir de las ocho de la mañana, Iñaki Gabilondo nos cuenta su visión de la actualidad nacional e internacional.',
'upload_date': '20140206',
- }
+ },
}, {
'url': 'http://elcomidista.elpais.com/elcomidista/2016/02/24/articulo/1456340311_668921.html#?id_externo_nwl=newsletter_diaria20160303t',
'md5': '3bd5b09509f3519d7d9e763179b013de',
@@ -25,7 +25,7 @@ class ElPaisIE(InfoExtractor):
'title': 'Cómo hacer el mejor café con cafetera italiana',
'description': 'Que sí, que las cápsulas son cómodas. Pero si le pides algo más a la vida, quizá deberías aprender a usar bien la cafetera italiana. No tienes más que ver este vídeo y seguir sus siete normas básicas.',
'upload_date': '20160303',
- }
+ },
}, {
'url': 'http://elpais.com/elpais/2017/01/26/ciencia/1485456786_417876.html',
'md5': '9c79923a118a067e1a45789e1e0b0f9c',
diff --git a/yt_dlp/extractor/eltrecetv.py b/yt_dlp/extractor/eltrecetv.py
index f64023af7..71cf92181 100644
--- a/yt_dlp/extractor/eltrecetv.py
+++ b/yt_dlp/extractor/eltrecetv.py
@@ -13,7 +13,7 @@ class ElTreceTVIE(InfoExtractor):
'ext': 'mp4',
'title': 'AHORA CAIGO - Programa 06/10/23',
'thumbnail': 'https://thumbs.vodgc.net/AHCA05102023145553329621094.JPG?649339',
- }
+ },
},
{
'url': 'https://www.eltrecetv.com.ar/poco-correctos/capitulos/temporada-2023/programa-del-250923-invitada-dalia-gutmann/',
@@ -34,7 +34,7 @@ class ElTreceTVIE(InfoExtractor):
{
'url': 'https://www.eltrecetv.com.ar/el-galpon/capitulos/temporada-2023/programa-del-160923-invitado-raul-lavie/',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/epicon.py b/yt_dlp/extractor/epicon.py
index 3bfcc5470..696f3e11f 100644
--- a/yt_dlp/extractor/epicon.py
+++ b/yt_dlp/extractor/epicon.py
@@ -14,7 +14,7 @@ class EpiconIE(InfoExtractor):
'title': 'Air Battle of Srinagar',
'description': 'md5:c4de2013af9bc05ae4392e4115d518d7',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'url': 'https://www.epicon.in/movies/krit',
'info_dict': {
@@ -23,7 +23,7 @@ class EpiconIE(InfoExtractor):
'title': 'Krit',
'description': 'md5:c12b35dad915d48ccff7f013c79bab4a',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'url': 'https://www.epicon.in/tv-shows/paapnaashini-ganga/season-1/vardaan',
'info_dict': {
@@ -32,7 +32,7 @@ class EpiconIE(InfoExtractor):
'title': 'Paapnaashini Ganga - Season 1 - Ep 1 - VARDAAN',
'description': 'md5:f517058c3d0402398eefa6242f4dd6ae',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'url': 'https://www.epicon.in/movies/jayadev',
'info_dict': {
@@ -41,16 +41,17 @@ class EpiconIE(InfoExtractor):
'title': 'Jayadev',
'description': 'md5:09e349eecd8e585a3b6466904f19df6c',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
cid = self._search_regex(r'class=\"mylist-icon\ iconclick\"\ id=\"(\d+)', webpage, 'cid')
headers = {'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'}
data = f'cid={cid}&action=st&type=video'.encode()
- data_json = self._parse_json(self._download_json('https://www.epicon.in/ajaxplayer/', id, headers=headers, data=data), id)
+ data_json = self._parse_json(
+ self._download_json('https://www.epicon.in/ajaxplayer/', video_id, headers=headers, data=data), video_id)
if not data_json['success']:
raise ExtractorError(data_json['message'], expected=True)
@@ -58,7 +59,7 @@ class EpiconIE(InfoExtractor):
title = self._search_regex(r'setplaytitle=\"([^\"]+)', webpage, 'title')
description = self._og_search_description(webpage) or None
thumbnail = self._og_search_thumbnail(webpage) or None
- formats = self._extract_m3u8_formats(data_json['url']['video_url'], id)
+ formats = self._extract_m3u8_formats(data_json['url']['video_url'], video_id)
subtitles = {}
for subtitle in data_json.get('subtitles', []):
@@ -70,7 +71,7 @@ class EpiconIE(InfoExtractor):
})
return {
- 'id': id,
+ 'id': video_id,
'formats': formats,
'title': title,
'description': description,
@@ -108,8 +109,8 @@ class EpiconSeriesIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
- episodes = re.findall(r'ct-tray-url=\"(tv-shows/%s/[^\"]+)' % id, webpage)
- entries = [self.url_result('https://www.epicon.in/%s' % episode, ie=EpiconIE.ie_key()) for episode in episodes]
- return self.playlist_result(entries, playlist_id=id)
+ playlist_id = self._match_id(url)
+ webpage = self._download_webpage(url, playlist_id)
+ episodes = re.findall(rf'ct-tray-url=\"(tv-shows/{playlist_id}/[^\"]+)', webpage)
+ entries = [self.url_result(f'https://www.epicon.in/{episode}', EpiconIE) for episode in episodes]
+ return self.playlist_result(entries, playlist_id=playlist_id)
diff --git a/yt_dlp/extractor/epoch.py b/yt_dlp/extractor/epoch.py
index 110e78c5b..9742f7691 100644
--- a/yt_dlp/extractor/epoch.py
+++ b/yt_dlp/extractor/epoch.py
@@ -11,7 +11,7 @@ class EpochIE(InfoExtractor):
'id': 'a3dd732c-4750-4bc8-8156-69180668bda1',
'ext': 'mp4',
'title': '‘They Can Do Audio, Video, Physical Surveillance on You 24H/365D a Year’: Rex Lee on Intrusive Apps',
- }
+ },
},
{
'url': 'https://www.theepochtimes.com/the-communist-partys-cyberattacks-on-america-explained-rex-lee-talks-tech-hybrid-warfare_4342413.html',
@@ -19,7 +19,7 @@ class EpochIE(InfoExtractor):
'id': '276c7f46-3bbf-475d-9934-b9bbe827cf0a',
'ext': 'mp4',
'title': 'The Communist Party’s Cyberattacks on America Explained; Rex Lee Talks Tech Hybrid Warfare',
- }
+ },
},
{
'url': 'https://www.theepochtimes.com/kash-patel-a-6-year-saga-of-government-corruption-from-russiagate-to-mar-a-lago_4690250.html',
@@ -27,7 +27,7 @@ class EpochIE(InfoExtractor):
'id': 'aa9ceecd-a127-453d-a2de-7153d6fd69b6',
'ext': 'mp4',
'title': 'Kash Patel: A ‘6-Year-Saga’ of Government Corruption, From Russiagate to Mar-a-Lago',
- }
+ },
},
{
'url': 'https://www.theepochtimes.com/dick-morris-discusses-his-book-the-return-trumps-big-2024-comeback_4819205.html',
@@ -35,7 +35,7 @@ class EpochIE(InfoExtractor):
'id': '9489f994-2a20-4812-b233-ac0e5c345632',
'ext': 'mp4',
'title': 'Dick Morris Discusses His Book ‘The Return: Trump’s Big 2024 Comeback’',
- }
+ },
},
]
@@ -51,5 +51,5 @@ class EpochIE(InfoExtractor):
'id': youmaker_video_id,
'formats': formats,
'subtitles': subtitles,
- 'title': self._html_extract_title(webpage)
+ 'title': self._html_extract_title(webpage),
}
diff --git a/yt_dlp/extractor/eporner.py b/yt_dlp/extractor/eporner.py
index b18a76c7c..e0b6ca96f 100644
--- a/yt_dlp/extractor/eporner.py
+++ b/yt_dlp/extractor/eporner.py
@@ -30,8 +30,8 @@ class EpornerIE(InfoExtractor):
'age_limit': 18,
},
'params': {
- 'proxy': '127.0.0.1:8118'
- }
+ 'proxy': '127.0.0.1:8118',
+ },
}, {
# New (May 2016) URL layout
'url': 'http://www.eporner.com/hd-porn/3YRUtzMcWn0/Star-Wars-XXX-Parody/',
@@ -56,7 +56,7 @@ class EpornerIE(InfoExtractor):
video_id = self._match_id(urlh.url)
- hash = self._search_regex(
+ vid_hash = self._search_regex(
r'hash\s*[:=]\s*["\']([\da-f]{32})', webpage, 'hash')
title = self._og_search_title(webpage, default=None) or self._html_search_regex(
@@ -64,13 +64,13 @@ class EpornerIE(InfoExtractor):
# Reverse engineered from vjs.js
def calc_hash(s):
- return ''.join((encode_base_n(int(s[lb:lb + 8], 16), 36) for lb in range(0, 32, 8)))
+ return ''.join(encode_base_n(int(s[lb:lb + 8], 16), 36) for lb in range(0, 32, 8))
video = self._download_json(
- 'http://www.eporner.com/xhr/video/%s' % video_id,
+ f'http://www.eporner.com/xhr/video/{video_id}',
display_id, note='Downloading video JSON',
query={
- 'hash': calc_hash(hash),
+ 'hash': calc_hash(vid_hash),
'device': 'generic',
'domain': 'www.eporner.com',
'fallback': 'false',
@@ -78,7 +78,7 @@ class EpornerIE(InfoExtractor):
if video.get('available') is False:
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, video['message']), expected=True)
+ '{} said: {}'.format(self.IE_NAME, video['message']), expected=True)
sources = video['sources']
diff --git a/yt_dlp/extractor/erocast.py b/yt_dlp/extractor/erocast.py
index 92a57536c..bd22137be 100644
--- a/yt_dlp/extractor/erocast.py
+++ b/yt_dlp/extractor/erocast.py
@@ -32,7 +32,7 @@ class ErocastIE(InfoExtractor):
'view_count': int,
'comment_count': int,
'webpage_url': 'https://erocast.me/track/9787/f4m-your-roommate-who-is-definitely-not-possessed-by-an-alien-suddenly-wants-to-fuck-you',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/eroprofile.py b/yt_dlp/extractor/eroprofile.py
index 2b61f3be7..2067217e7 100644
--- a/yt_dlp/extractor/eroprofile.py
+++ b/yt_dlp/extractor/eroprofile.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_urlencode
from ..utils import (
ExtractorError,
merge_dicts,
@@ -38,7 +38,7 @@ class EroProfileIE(InfoExtractor):
}]
def _perform_login(self, username, password):
- query = compat_urllib_parse_urlencode({
+ query = urllib.parse.urlencode({
'username': username,
'password': password,
'url': 'http://www.eroprofile.com/',
@@ -91,7 +91,7 @@ class EroProfileAlbumIE(InfoExtractor):
'url': 'https://www.eroprofile.com/m/videos/album/BBW-2-893',
'info_dict': {
'id': 'BBW-2-893',
- 'title': 'BBW 2'
+ 'title': 'BBW 2',
},
'playlist_mincount': 486,
},
diff --git a/yt_dlp/extractor/err.py b/yt_dlp/extractor/err.py
index abd00f2d5..7896cdbdc 100644
--- a/yt_dlp/extractor/err.py
+++ b/yt_dlp/extractor/err.py
@@ -145,7 +145,7 @@ class ERRJupiterIE(InfoExtractor):
'season_number': 0,
'series': 'Лесные истории | Аисты',
'series_id': '1037497',
- }
+ },
}, {
'note': 'Lasteekraan: Pätu',
'url': 'https://lasteekraan.err.ee/1092243/patu',
diff --git a/yt_dlp/extractor/ertgr.py b/yt_dlp/extractor/ertgr.py
index 19c6933e7..864aa6dc5 100644
--- a/yt_dlp/extractor/ertgr.py
+++ b/yt_dlp/extractor/ertgr.py
@@ -2,7 +2,6 @@ import json
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
clean_html,
@@ -30,19 +29,19 @@ class ERTFlixBaseIE(InfoExtractor):
headers = headers or {}
if data:
headers['Content-Type'] = headers_as_param['Content-Type'] = 'application/json;charset=utf-8'
- data = json.dumps(merge_dicts(platform_codename, data)).encode('utf-8')
+ data = json.dumps(merge_dicts(platform_codename, data)).encode()
query = merge_dicts(
{} if data else platform_codename,
{'$headers': json.dumps(headers_as_param)},
params)
response = self._download_json(
- 'https://api.app.ertflix.gr/v%s/%s' % (str(api_version), method),
+ f'https://api.app.ertflix.gr/v{api_version!s}/{method}',
video_id, fatal=False, query=query, data=data, headers=headers)
if try_get(response, lambda x: x['Result']['Success']) is True:
return response
def _call_api_get_tiles(self, video_id, *tile_ids):
- requested_tile_ids = [video_id] + list(tile_ids)
+ requested_tile_ids = [video_id, *tile_ids]
requested_tiles = [{'Id': tile_id} for tile_id in requested_tile_ids]
tiles_response = self._call_api(
video_id, method='Tile/GetTiles', api_version=2,
@@ -174,9 +173,9 @@ class ERTFlixIE(ERTFlixBaseIE):
}]
def _extract_episode(self, episode):
- codename = try_get(episode, lambda x: x['Codename'], compat_str)
+ codename = try_get(episode, lambda x: x['Codename'], str)
title = episode.get('Title')
- description = clean_html(dict_get(episode, ('ShortDescription', 'TinyDescription', )))
+ description = clean_html(dict_get(episode, ('ShortDescription', 'TinyDescription')))
if not codename or not title or not episode.get('HasPlayableStream', True):
return
thumbnail = next((
@@ -195,7 +194,7 @@ class ERTFlixIE(ERTFlixBaseIE):
'timestamp': parse_iso8601(episode.get('PublishDate')),
'duration': episode.get('DurationSeconds'),
'age_limit': self._parse_age_rating(episode),
- 'url': 'ertflix:%s' % (codename, ),
+ 'url': f'ertflix:{codename}',
}
@staticmethod
@@ -212,7 +211,7 @@ class ERTFlixIE(ERTFlixBaseIE):
series_info = {
'age_limit': self._parse_age_rating(series),
'title': series.get('Title'),
- 'description': dict_get(series, ('ShortDescription', 'TinyDescription', )),
+ 'description': dict_get(series, ('ShortDescription', 'TinyDescription')),
}
if season_numbers:
season_titles = season_titles or []
@@ -281,7 +280,7 @@ class ERTWebtvEmbedIE(InfoExtractor):
'id': 'trailers/E2251_TO_DIKTYO_E09_16-01_1900.mp4',
'title': 'md5:914f06a73cd8b62fbcd6fb90c636e497',
'ext': 'mp4',
- 'thumbnail': 'https://program.ert.gr/photos/2022/1/to_diktio_ep09_i_istoria_tou_diadiktiou_stin_Ellada_1021x576.jpg'
+ 'thumbnail': 'https://program.ert.gr/photos/2022/1/to_diktio_ep09_i_istoria_tou_diadiktiou_stin_Ellada_1021x576.jpg',
},
}]
diff --git a/yt_dlp/extractor/espn.py b/yt_dlp/extractor/espn.py
index 7ed824ca8..4e9b63524 100644
--- a/yt_dlp/extractor/espn.py
+++ b/yt_dlp/extractor/espn.py
@@ -100,13 +100,13 @@ class ESPNIE(OnceIE):
}, {
'url': 'http://www.espn.com/watch/player?bucketId=257&id=19505875',
'only_matching': True,
- }, ]
+ }]
def _real_extract(self, url):
video_id = self._match_id(url)
clip = self._download_json(
- 'http://api-app.espn.com/v1/video/clips/%s' % video_id,
+ f'http://api-app.espn.com/v1/video/clips/{video_id}',
video_id)['videos'][0]
title = clip['headline']
@@ -115,16 +115,16 @@ class ESPNIE(OnceIE):
formats = []
def traverse_source(source, base_source_id=None):
- for source_id, source in source.items():
- if source_id == 'alert':
+ for src_id, src_item in source.items():
+ if src_id == 'alert':
continue
- elif isinstance(source, str):
- extract_source(source, base_source_id)
- elif isinstance(source, dict):
+ elif isinstance(src_item, str):
+ extract_source(src_item, base_source_id)
+ elif isinstance(src_item, dict):
traverse_source(
- source,
- '%s-%s' % (base_source_id, source_id)
- if base_source_id else source_id)
+ src_item,
+ f'{base_source_id}-{src_id}'
+ if base_source_id else src_id)
def extract_source(source_url, source_id=None):
if source_url in format_urls:
@@ -209,7 +209,7 @@ class ESPNArticleIE(InfoExtractor):
webpage, 'video id', group='id')
return self.url_result(
- 'http://espn.go.com/video/clip?id=%s' % video_id, ESPNIE.ie_key())
+ f'http://espn.go.com/video/clip?id={video_id}', ESPNIE.ie_key())
class FiveThirtyEightIE(InfoExtractor):
@@ -251,7 +251,7 @@ class ESPNCricInfoIE(InfoExtractor):
'upload_date': '20211113',
'duration': 96,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.espncricinfo.com/cricket-videos/daryl-mitchell-mitchell-santner-is-one-of-the-best-white-ball-spinners-india-vs-new-zealand-1356225',
'info_dict': {
@@ -266,12 +266,13 @@ class ESPNCricInfoIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://hs-consumer-api.espncricinfo.com/v1/pages/video/video-details?videoId={id}', id)['video']
+ video_id = self._match_id(url)
+ data_json = self._download_json(
+ f'https://hs-consumer-api.espncricinfo.com/v1/pages/video/video-details?videoId={video_id}', video_id)['video']
formats, subtitles = [], {}
for item in data_json.get('playbacks') or []:
if item.get('type') == 'HLS' and item.get('url'):
- m3u8_frmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(item['url'], id)
+ m3u8_frmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(item['url'], video_id)
formats.extend(m3u8_frmts)
subtitles = self._merge_subtitles(subtitles, m3u8_subs)
elif item.get('type') == 'AUDIO' and item.get('url'):
@@ -280,7 +281,7 @@ class ESPNCricInfoIE(InfoExtractor):
'vcodec': 'none',
})
return {
- 'id': id,
+ 'id': video_id,
'title': data_json.get('title'),
'description': data_json.get('summary'),
'upload_date': unified_strdate(dict_get(data_json, ('publishedAt', 'recordedAt'))),
@@ -366,28 +367,28 @@ class WatchESPNIE(AdobePassIE):
'subject_token': assertion,
'subject_token_type': 'urn:bamtech:params:oauth:token-type:device',
'platform': 'android',
- 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange'
+ 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
})['access_token']
assertion = self._call_bamgrid_api(
'accounts/grant', video_id, payload={'id_token': cookie.value.split('|')[1]},
headers={
'Authorization': token,
- 'Content-Type': 'application/json; charset=UTF-8'
+ 'Content-Type': 'application/json; charset=UTF-8',
})['assertion']
token = self._call_bamgrid_api(
'token', video_id, payload={
'subject_token': assertion,
'subject_token_type': 'urn:bamtech:params:oauth:token-type:account',
'platform': 'android',
- 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange'
+ 'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
})['access_token']
playback = self._download_json(
video_data['videoHref'].format(scenario='browser~ssai'), video_id,
headers={
'Accept': 'application/vnd.media-service+json; version=5',
- 'Authorization': token
+ 'Authorization': token,
})
m3u8_url, headers = playback['stream']['complete'][0]['url'], {'authorization': token}
diff --git a/yt_dlp/extractor/ettutv.py b/yt_dlp/extractor/ettutv.py
index 133b52555..73671776f 100644
--- a/yt_dlp/extractor/ettutv.py
+++ b/yt_dlp/extractor/ettutv.py
@@ -56,5 +56,5 @@ class EttuTvIE(InfoExtractor):
'thumbnail': ('image', {url_or_none}),
'timestamp': ('date', {unified_timestamp}),
'is_live': ('isLivestream', {bool_or_none}),
- })
+ }),
}
diff --git a/yt_dlp/extractor/europa.py b/yt_dlp/extractor/europa.py
index 0cf889a1e..aa8baf2f7 100644
--- a/yt_dlp/extractor/europa.py
+++ b/yt_dlp/extractor/europa.py
@@ -28,7 +28,7 @@ class EuropaIE(InfoExtractor):
'duration': 34,
'view_count': int,
'formats': 'mincount:3',
- }
+ },
}, {
'url': 'http://ec.europa.eu/avservices/video/player.cfm?sitelang=en&ref=I107786',
'only_matching': True,
@@ -41,11 +41,11 @@ class EuropaIE(InfoExtractor):
video_id = self._match_id(url)
playlist = self._download_xml(
- 'http://ec.europa.eu/avservices/video/player/playlist.cfm?ID=%s' % video_id, video_id)
+ f'http://ec.europa.eu/avservices/video/player/playlist.cfm?ID={video_id}', video_id)
def get_item(type_, preference):
items = {}
- for item in playlist.findall('./info/%s/item' % type_):
+ for item in playlist.findall(f'./info/{type_}/item'):
lang, label = xpath_text(item, 'lg', default=None), xpath_text(item, 'label', default=None)
if lang and label:
items[lang] = label.strip()
@@ -77,7 +77,7 @@ class EuropaIE(InfoExtractor):
'url': video_url,
'format_id': lang,
'format_note': xpath_text(file_, './lglabel'),
- 'language_preference': language_preference(lang)
+ 'language_preference': language_preference(lang),
})
return {
@@ -88,7 +88,7 @@ class EuropaIE(InfoExtractor):
'upload_date': upload_date,
'duration': duration,
'view_count': view_count,
- 'formats': formats
+ 'formats': formats,
}
@@ -109,7 +109,7 @@ class EuroParlWebstreamIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
# live webstream
'url': 'https://multimedia.europarl.europa.eu/en/webstreaming/euroscola_20221115-1000-SPECIAL-EUROSCOLA',
@@ -121,7 +121,7 @@ class EuroParlWebstreamIE(InfoExtractor):
'release_date': '20221115',
'live_status': 'is_live',
},
- 'skip': 'not live anymore'
+ 'skip': 'not live anymore',
}, {
'url': 'https://multimedia.europarl.europa.eu/en/webstreaming/committee-on-culture-and-education_20230301-1130-COMMITTEE-CULT',
'info_dict': {
@@ -131,7 +131,7 @@ class EuroParlWebstreamIE(InfoExtractor):
'release_date': '20230301',
'title': 'Committee on Culture and Education',
'release_timestamp': 1677666641,
- }
+ },
}, {
# live stream
'url': 'https://multimedia.europarl.europa.eu/en/webstreaming/committee-on-environment-public-health-and-food-safety_20230524-0900-COMMITTEE-ENVI',
@@ -143,7 +143,7 @@ class EuroParlWebstreamIE(InfoExtractor):
'release_timestamp': 1684911541,
'live_status': 'is_live',
},
- 'skip': 'Not live anymore'
+ 'skip': 'Not live anymore',
}, {
'url': 'https://multimedia.europarl.europa.eu/en/webstreaming/20240320-1345-SPECIAL-PRESSER',
'info_dict': {
@@ -153,7 +153,7 @@ class EuroParlWebstreamIE(InfoExtractor):
'release_date': '20240320',
'title': 'md5:7c6c814cac55dea5e2d87bf8d3db2234',
'release_timestamp': 1710939767,
- }
+ },
}, {
'url': 'https://multimedia.europarl.europa.eu/webstreaming/briefing-for-media-on-2024-european-elections_20240429-1000-SPECIAL-OTHER',
'only_matching': True,
@@ -170,7 +170,7 @@ class EuroParlWebstreamIE(InfoExtractor):
query={
'api-version': 1.0,
'tenantId': 'bae646ca-1fc8-4363-80ba-2c04f06b4968',
- 'externalReference': display_id
+ 'externalReference': display_id,
})
formats, subtitles = [], {}
@@ -186,5 +186,5 @@ class EuroParlWebstreamIE(InfoExtractor):
'formats': formats,
'subtitles': subtitles,
'release_timestamp': parse_iso8601(json_info.get('startDateTime')),
- 'is_live': traverse_obj(webpage_nextjs, ('mediaItem', 'mediaSubType')) == 'Live'
+ 'is_live': traverse_obj(webpage_nextjs, ('mediaItem', 'mediaSubType')) == 'Live',
}
diff --git a/yt_dlp/extractor/europeantour.py b/yt_dlp/extractor/europeantour.py
index 1995a745d..a5503dbc5 100644
--- a/yt_dlp/extractor/europeantour.py
+++ b/yt_dlp/extractor/europeantour.py
@@ -17,16 +17,16 @@ class EuropeanTourIE(InfoExtractor):
'uploader_id': '5136026580001',
'tags': ['prod-imported'],
'thumbnail': 'md5:fdac52bc826548860edf8145ee74e71a',
- 'upload_date': '20211220'
+ 'upload_date': '20211220',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s'
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
vid, aid = re.search(r'(?s)brightcove-player\s?video-id="([^"]+)".*"ACCOUNT_ID":"([^"]+)"', webpage).groups()
if not aid:
aid = '5136026580001'
diff --git a/yt_dlp/extractor/eurosport.py b/yt_dlp/extractor/eurosport.py
index 6c426bb89..0c5e1238d 100644
--- a/yt_dlp/extractor/eurosport.py
+++ b/yt_dlp/extractor/eurosport.py
@@ -16,7 +16,7 @@ class EurosportIE(InfoExtractor):
'display_id': 'vid1694147',
'timestamp': 1654446698,
'upload_date': '20220605',
- }
+ },
}, {
'url': 'https://www.eurosport.com/tennis/roland-garros/2022/watch-the-top-five-shots-from-men-s-final-as-rafael-nadal-beats-casper-ruud-to-seal-14th-french-open_vid1694283/video.shtml',
'info_dict': {
@@ -29,7 +29,7 @@ class EurosportIE(InfoExtractor):
'display_id': 'vid1694283',
'timestamp': 1654456090,
'upload_date': '20220605',
- }
+ },
}, {
# geo-fence but can bypassed by xff
'url': 'https://www.eurosport.com/cycling/tour-de-france-femmes/2022/incredible-ride-marlen-reusser-storms-to-stage-4-win-at-tour-de-france-femmes_vid1722221/video.shtml',
@@ -43,7 +43,7 @@ class EurosportIE(InfoExtractor):
'thumbnail': 'https://imgresizer.eurosport.com/unsafe/1280x960/smart/filters:format(jpeg)/origin-imgresizer.eurosport.com/2022/07/27/3423347-69852108-2560-1440.jpg',
'description': 'md5:32bbe3a773ac132c57fb1e8cca4b7c71',
'upload_date': '20220727',
- }
+ },
}, {
'url': 'https://www.eurosport.com/football/champions-league/2022-2023/pep-guardiola-emotionally-destroyed-after-manchester-city-win-over-bayern-munich-in-champions-league_vid1896254/video.shtml',
'info_dict': {
@@ -56,7 +56,7 @@ class EurosportIE(InfoExtractor):
'timestamp': 1681292028,
'upload_date': '20230412',
'display_id': 'vid1896254',
- }
+ },
}, {
'url': 'https://www.eurosport.com/football/last-year-s-semi-final-pain-was-still-there-pep-guardiola-after-man-city-reach-cl-final_vid1914115/video.shtml',
'info_dict': {
@@ -69,7 +69,7 @@ class EurosportIE(InfoExtractor):
'thumbnail': 'https://imgresizer.eurosport.com/unsafe/1280x960/smart/filters:format(jpeg)/origin-imgresizer.eurosport.com/2023/05/18/3707254-75435008-2560-1440.jpg',
'duration': 105.0,
'upload_date': '20230518',
- }
+ },
}]
_TOKEN = None
diff --git a/yt_dlp/extractor/euscreen.py b/yt_dlp/extractor/euscreen.py
index 66fa42fa1..f08938fc9 100644
--- a/yt_dlp/extractor/euscreen.py
+++ b/yt_dlp/extractor/euscreen.py
@@ -20,37 +20,37 @@ class EUScreenIE(InfoExtractor):
'series': 'JA2 DERNIERE',
'episode': '-',
'uploader': 'INA / France',
- 'thumbnail': 'http://images3.noterik.com/domain/euscreenxl/user/eu_ina/video/EUS_0EBCBF356BFC4E12A014023BA41BD98C/image.jpg'
+ 'thumbnail': 'http://images3.noterik.com/domain/euscreenxl/user/eu_ina/video/EUS_0EBCBF356BFC4E12A014023BA41BD98C/image.jpg',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
_payload = b'<fsxml><screen><properties><screenId>-1</screenId></properties><capabilities id="1"><properties><platform>Win32</platform><appcodename>Mozilla</appcodename><appname>Netscape</appname><appversion>5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36</appversion><useragent>Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36</useragent><cookiesenabled>true</cookiesenabled><screenwidth>784</screenwidth><screenheight>758</screenheight><orientation>undefined</orientation><smt_browserid>Sat, 07 Oct 2021 08:56:50 GMT</smt_browserid><smt_sessionid>1633769810758</smt_sessionid></properties></capabilities></screen></fsxml>'
def _real_extract(self, url):
- id = self._match_id(url)
+ video_id = self._match_id(url)
args_for_js_request = self._download_webpage(
'https://euscreen.eu/lou/LouServlet/domain/euscreenxl/html5application/euscreenxlitem',
- id, data=self._payload, query={'actionlist': 'itempage', 'id': id})
+ video_id, data=self._payload, query={'actionlist': 'itempage', 'id': video_id})
info_js = self._download_webpage(
'https://euscreen.eu/lou/LouServlet/domain/euscreenxl/html5application/euscreenxlitem',
- id, data=args_for_js_request.replace('screenid', 'screenId').encode())
+ video_id, data=args_for_js_request.replace('screenid', 'screenId').encode())
video_json = self._parse_json(
self._search_regex(r'setVideo\(({.+})\)\(\$end\$\)put', info_js, 'Video JSON'),
- id, transform_source=js_to_json)
+ video_id, transform_source=js_to_json)
meta_json = self._parse_json(
self._search_regex(r'setData\(({.+})\)\(\$end\$\)', info_js, 'Metadata JSON'),
- id, transform_source=js_to_json)
+ video_id, transform_source=js_to_json)
formats = [{
'url': source['src'],
} for source in video_json.get('sources', [])]
return {
- 'id': id,
+ 'id': video_id,
'title': meta_json.get('originalTitle'),
'alt_title': meta_json.get('title'),
'duration': parse_duration(meta_json.get('duration')),
- 'description': '%s\n%s' % (meta_json.get('summaryOriginal', ''), meta_json.get('summaryEnglish', '')),
+ 'description': '{}\n{}'.format(meta_json.get('summaryOriginal', ''), meta_json.get('summaryEnglish', '')),
'series': meta_json.get('series') or meta_json.get('seriesEnglish'),
'episode': meta_json.get('episodeNumber'),
'uploader': meta_json.get('provider'),
diff --git a/yt_dlp/extractor/expressen.py b/yt_dlp/extractor/expressen.py
index b96f2e4cb..33b829845 100644
--- a/yt_dlp/extractor/expressen.py
+++ b/yt_dlp/extractor/expressen.py
@@ -58,7 +58,7 @@ class ExpressenIE(InfoExtractor):
def extract_data(name):
return self._parse_json(
self._search_regex(
- r'data-%s=(["\'])(?P<value>(?:(?!\1).)+)\1' % name,
+ rf'data-{name}=(["\'])(?P<value>(?:(?!\1).)+)\1',
webpage, 'info', group='value'),
display_id, transform_source=unescapeHTML)
diff --git a/yt_dlp/extractor/eyedotv.py b/yt_dlp/extractor/eyedotv.py
index 4a13ab08d..b8c67ce4c 100644
--- a/yt_dlp/extractor/eyedotv.py
+++ b/yt_dlp/extractor/eyedotv.py
@@ -18,13 +18,13 @@ class EyedoTVIE(InfoExtractor):
'description': 'md5:4abe07293b2f73efc6e1c37028d58c98',
'uploader': 'Afnic Live',
'uploader_id': '8023',
- }
+ },
}
_ROOT_URL = 'http://live.eyedo.net:1935/'
def _real_extract(self, url):
video_id = self._match_id(url)
- video_data = self._download_xml('http://eyedo.tv/api/live/GetLive/%s' % video_id, video_id)
+ video_data = self._download_xml(f'http://eyedo.tv/api/live/GetLive/{video_id}', video_id)
def _add_ns(path):
return self._xpath_ns(path, 'http://schemas.datacontract.org/2004/07/EyeDo.Core.Implementation.Web.ViewModels.Api')
@@ -33,7 +33,7 @@ class EyedoTVIE(InfoExtractor):
state_live_code = xpath_text(video_data, _add_ns('StateLiveCode'), 'title', True)
if state_live_code == 'avenir':
raise ExtractorError(
- '%s said: We\'re sorry, but this video is not yet available.' % self.IE_NAME,
+ f'{self.IE_NAME} said: We\'re sorry, but this video is not yet available.',
expected=True)
is_live = state_live_code == 'live'
@@ -41,11 +41,11 @@ class EyedoTVIE(InfoExtractor):
# http://eyedo.tv/Content/Html5/Scripts/html5view.js
if is_live:
if xpath_text(video_data, 'Cdn') == 'true':
- m3u8_url = 'http://rrr.sz.xlcdn.com/?account=eyedo&file=A%s&type=live&service=wowza&protocol=http&output=playlist.m3u8' % video_id
+ m3u8_url = f'http://rrr.sz.xlcdn.com/?account=eyedo&file=A{video_id}&type=live&service=wowza&protocol=http&output=playlist.m3u8'
else:
- m3u8_url = self._ROOT_URL + 'w/%s/eyedo_720p/playlist.m3u8' % video_id
+ m3u8_url = self._ROOT_URL + f'w/{video_id}/eyedo_720p/playlist.m3u8'
else:
- m3u8_url = self._ROOT_URL + 'replay-w/%s/mp4:%s.mp4/playlist.m3u8' % (video_id, video_id)
+ m3u8_url = self._ROOT_URL + f'replay-w/{video_id}/mp4:{video_id}.mp4/playlist.m3u8'
return {
'id': video_id,
diff --git a/yt_dlp/extractor/facebook.py b/yt_dlp/extractor/facebook.py
index b76407a5c..1d1e0770a 100644
--- a/yt_dlp/extractor/facebook.py
+++ b/yt_dlp/extractor/facebook.py
@@ -3,18 +3,13 @@ import re
import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_etree_fromstring,
- compat_str,
- compat_urllib_parse_unquote,
-)
+from ..compat import compat_etree_fromstring
from ..networking import Request
from ..networking.exceptions import network_exceptions
from ..utils import (
ExtractorError,
clean_html,
determine_ext,
- error_to_compat_str,
float_or_none,
format_field,
get_element_by_id,
@@ -209,7 +204,7 @@ class FacebookIE(InfoExtractor):
'skip_download': True,
},
}, {
- # FIXME
+ # FIXME: Cannot parse data error
'url': 'https://www.facebook.com/LaGuiaDelVaron/posts/1072691702860471',
'info_dict': {
'id': '1072691702860471',
@@ -415,7 +410,7 @@ class FacebookIE(InfoExtractor):
}]
_SUPPORTED_PAGLETS_REGEX = r'(?:pagelet_group_mall|permalink_video_pagelet|hyperfeed_story_id_[0-9a-f]+)'
_api_config = {
- 'graphURI': '/api/graphql/'
+ 'graphURI': '/api/graphql/',
}
def _perform_login(self, username, password):
@@ -450,7 +445,7 @@ class FacebookIE(InfoExtractor):
r'(?s)<div[^>]+class=(["\']).*?login_error_box.*?\1[^>]*><div[^>]*>.*?</div><div[^>]*>(?P<error>.+?)</div>',
login_results, 'login error', default=None, group='error')
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
self.report_warning('unable to log in: bad username/password, or exceeded login rate limit (~3/min). Check credentials or wait.')
return
@@ -474,7 +469,7 @@ class FacebookIE(InfoExtractor):
if re.search(r'id="checkpointSubmitButton"', check_response) is not None:
self.report_warning('Unable to confirm login, you have to login in your browser and authorize the login.')
except network_exceptions as err:
- self.report_warning('unable to log in: %s' % error_to_compat_str(err))
+ self.report_warning(f'unable to log in: {err}')
return
def _extract_from_url(self, url, video_id):
@@ -493,7 +488,7 @@ class FacebookIE(InfoExtractor):
page_title = title or self._html_search_regex((
r'<h2\s+[^>]*class="uiHeaderTitle"[^>]*>(?P<content>[^<]*)</h2>',
r'(?s)<span class="fbPhotosPhotoCaption".*?id="fbPhotoPageCaption"><span class="hasCaption">(?P<content>.*?)</span>',
- self._meta_regex('og:title'), self._meta_regex('twitter:title'), r'<title>(?P<content>.+?)</title>'
+ self._meta_regex('og:title'), self._meta_regex('twitter:title'), r'<title>(?P<content>.+?)</title>',
), webpage, 'title', default=None, group='content')
description = description or self._html_search_meta(
['description', 'og:description', 'twitter:description'],
@@ -525,7 +520,7 @@ class FacebookIE(InfoExtractor):
'timestamp': timestamp,
'thumbnail': thumbnail,
'view_count': parse_count(self._search_regex(
- (r'\bviewCount\s*:\s*["\']([\d,.]+)', r'video_view_count["\']\s*:\s*(\d+)',),
+ (r'\bviewCount\s*:\s*["\']([\d,.]+)', r'video_view_count["\']\s*:\s*(\d+)'),
webpage, 'view count', default=None)),
'concurrent_view_count': get_first(post, (
('video', (..., ..., 'attachments', ..., 'media')), 'liveViewerCount', {int_or_none})),
@@ -578,7 +573,7 @@ class FacebookIE(InfoExtractor):
def extract_relay_data(_filter):
return self._parse_json(self._search_regex(
- r'data-sjs>({.*?%s.*?})</script>' % _filter,
+ rf'data-sjs>({{.*?{_filter}.*?}})</script>',
webpage, 'replay data', default='{}'), video_id, fatal=False) or {}
def extract_relay_prefetched_data(_filter):
@@ -590,7 +585,7 @@ class FacebookIE(InfoExtractor):
if not video_data:
server_js_data = self._parse_json(self._search_regex([
r'bigPipe\.onPageletArrive\(({.+?})\)\s*;\s*}\s*\)\s*,\s*["\']onPageletArrive\s+' + self._SUPPORTED_PAGLETS_REGEX,
- r'bigPipe\.onPageletArrive\(({.*?id\s*:\s*"%s".*?})\);' % self._SUPPORTED_PAGLETS_REGEX
+ rf'bigPipe\.onPageletArrive\(({{.*?id\s*:\s*"{self._SUPPORTED_PAGLETS_REGEX}".*?}})\);',
], webpage, 'js data', default='{}'), video_id, js_to_json, False)
video_data = extract_from_jsmods_instances(server_js_data)
@@ -632,7 +627,7 @@ class FacebookIE(InfoExtractor):
for caption in traverse_obj(video, (
'video_available_captions_locales',
{lambda x: sorted(x, key=lambda c: c['locale'])},
- lambda _, v: url_or_none(v['captions_url'])
+ lambda _, v: url_or_none(v['captions_url']),
)):
lang = caption.get('localized_language') or 'und'
subs = {
@@ -670,7 +665,7 @@ class FacebookIE(InfoExtractor):
'description': description,
})
else:
- info['title'] = description or 'Facebook video #%s' % v_id
+ info['title'] = description or f'Facebook video #{v_id}'
entries.append(info)
def parse_attachment(attachment, key='media'):
@@ -699,7 +694,7 @@ class FacebookIE(InfoExtractor):
if video:
attachments = try_get(video, [
lambda x: x['story']['attachments'],
- lambda x: x['creation_story']['attachments']
+ lambda x: x['creation_story']['attachments'],
], list) or []
for attachment in attachments:
parse_attachment(attachment)
@@ -723,7 +718,7 @@ class FacebookIE(InfoExtractor):
m_msg = re.search(r'class="[^"]*uiInterstitialContent[^"]*"><div>(.*?)</div>', webpage)
if m_msg is not None:
raise ExtractorError(
- 'The video is not available, Facebook said: "%s"' % m_msg.group(1),
+ f'The video is not available, Facebook said: "{m_msg.group(1)}"',
expected=True)
elif any(p in webpage for p in (
'>You must log in to continue',
@@ -760,7 +755,7 @@ class FacebookIE(InfoExtractor):
v_id = video.get('id')
if not v_id:
continue
- v_id = compat_str(v_id)
+ v_id = str(v_id)
entries.append(self.url_result(
self._VIDEO_PAGE_TEMPLATE % v_id,
self.ie_key(), v_id, video.get('name')))
@@ -818,7 +813,7 @@ class FacebookIE(InfoExtractor):
continue
for quality in ('sd', 'hd'):
for src_type in ('src', 'src_no_ratelimit'):
- src = f[0].get('%s_%s' % (quality, src_type))
+ src = f[0].get(f'{quality}_{src_type}')
if src:
# sd, hd formats w/o resolution info should be deprioritized below DASH
# TODO: investigate if progressive or src formats still exist
@@ -826,10 +821,10 @@ class FacebookIE(InfoExtractor):
if quality == 'hd':
preference += 1
formats.append({
- 'format_id': '%s_%s_%s' % (format_id, quality, src_type),
+ 'format_id': f'{format_id}_{quality}_{src_type}',
'url': src,
'quality': preference,
- 'height': 720 if quality == 'hd' else None
+ 'height': 720 if quality == 'hd' else None,
})
extract_dash_manifest(f[0], formats)
subtitles_src = f[0].get('subtitles_src')
@@ -879,7 +874,7 @@ class FacebookPluginsVideoIE(InfoExtractor):
def _real_extract(self, url):
return self.url_result(
- compat_urllib_parse_unquote(self._match_id(url)),
+ urllib.parse.unquote(self._match_id(url)),
FacebookIE.ie_key())
@@ -940,7 +935,7 @@ class FacebookReelIE(InfoExtractor):
'timestamp': 1637502609,
'upload_date': '20211121',
'thumbnail': r're:^https?://.*',
- }
+ },
}]
def _real_extract(self, url):
@@ -966,7 +961,7 @@ class FacebookAdsIE(InfoExtractor):
'thumbnail': r're:^https?://.*',
'upload_date': '20231214',
'like_count': int,
- }
+ },
}, {
'url': 'https://www.facebook.com/ads/library/?id=893637265423481',
'info_dict': {
@@ -998,7 +993,7 @@ class FacebookAdsIE(InfoExtractor):
def _extract_formats(self, video_dict):
formats = []
for format_key, format_url in traverse_obj(video_dict, (
- {dict.items}, lambda _, v: v[0] in self._FORMATS_MAP and url_or_none(v[1])
+ {dict.items}, lambda _, v: v[0] in self._FORMATS_MAP and url_or_none(v[1]),
)):
formats.append({
'format_id': self._FORMATS_MAP[format_key][0],
@@ -1035,7 +1030,7 @@ class FacebookAdsIE(InfoExtractor):
entries = []
for idx, entry in enumerate(traverse_obj(
- data, (('videos', 'cards'), lambda _, v: any([url_or_none(v[f]) for f in self._FORMATS_MAP]))), 1
+ data, (('videos', 'cards'), lambda _, v: any(url_or_none(v[f]) for f in self._FORMATS_MAP))), 1,
):
entries.append({
'id': f'{video_id}_{idx}',
diff --git a/yt_dlp/extractor/fancode.py b/yt_dlp/extractor/fancode.py
index 1e80f9a37..1b1ed3956 100644
--- a/yt_dlp/extractor/fancode.py
+++ b/yt_dlp/extractor/fancode.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import ExtractorError, mimetype2ext, parse_iso8601, try_get
@@ -19,12 +18,12 @@ class FancodeVodIE(InfoExtractor):
'ext': 'mp4',
'title': 'Match Preview: PBKS vs MI',
'thumbnail': r're:^https?://.*\.jpg$',
- "timestamp": 1619081590,
+ 'timestamp': 1619081590,
'view_count': int,
'like_count': int,
'upload_date': '20210422',
- 'uploader_id': '6008340455001'
- }
+ 'uploader_id': '6008340455001',
+ },
}, {
'url': 'https://fancode.com/video/15043',
'only_matching': True,
@@ -53,14 +52,14 @@ class FancodeVodIE(InfoExtractor):
"refreshToken":"%s"
},
"operationName":"RefreshToken"
- }''' % password
+ }''' % password # noqa: UP031
- token_json = self.download_gql('refresh token', data, "Getting the Access token")
+ token_json = self.download_gql('refresh token', data, 'Getting the Access token')
self._ACCESS_TOKEN = try_get(token_json, lambda x: x['data']['refreshToken']['accessToken'])
if self._ACCESS_TOKEN is None:
self.report_warning('Failed to get Access token')
else:
- self.headers.update({'Authorization': 'Bearer %s' % self._ACCESS_TOKEN})
+ self.headers.update({'Authorization': f'Bearer {self._ACCESS_TOKEN}'})
def _check_login_required(self, is_available, is_premium):
msg = None
@@ -92,12 +91,12 @@ class FancodeVodIE(InfoExtractor):
}
},
"operationName":"Video"
- }''' % video_id
+ }''' % video_id # noqa: UP031
metadata_json = self.download_gql(video_id, data, note='Downloading metadata')
media = try_get(metadata_json, lambda x: x['data']['media'], dict) or {}
- brightcove_video_id = try_get(media, lambda x: x['mediaSource']['brightcove'], compat_str)
+ brightcove_video_id = try_get(media, lambda x: x['mediaSource']['brightcove'], str)
if brightcove_video_id is None:
raise ExtractorError('Unable to extract brightcove Video ID')
@@ -132,11 +131,11 @@ class FancodeLiveIE(FancodeVodIE): # XXX: Do not subclass from concrete IE
'id': '35328',
'ext': 'mp4',
'title': 'BUB vs BLB',
- "timestamp": 1624863600,
+ 'timestamp': 1624863600,
'is_live': True,
'upload_date': '20210628',
},
- 'skip': 'Ended'
+ 'skip': 'Ended',
}, {
'url': 'https://fancode.com/match/35328/',
'only_matching': True,
@@ -147,7 +146,7 @@ class FancodeLiveIE(FancodeVodIE): # XXX: Do not subclass from concrete IE
def _real_extract(self, url):
- id = self._match_id(url)
+ video_id = self._match_id(url)
data = '''{
"query":"query MatchResponse($id: Int\\u0021, $isLoggedIn: Boolean\\u0021) { match: matchWithScores(id: $id) { id matchDesc mediaId videoStreamId videoStreamUrl { ...VideoSource } liveStreams { videoStreamId videoStreamUrl { ...VideoSource } contentId } name startTime streamingStatus isPremium isUserEntitled @include(if: $isLoggedIn) status metaTags bgImage { src } sport { name slug } tour { id name } squads { name shortName } liveStreams { contentId } mediaId }}fragment VideoSource on VideoSource { title description posterUrl url deliveryType playerType}",
"variables":{
@@ -155,21 +154,21 @@ class FancodeLiveIE(FancodeVodIE): # XXX: Do not subclass from concrete IE
"isLoggedIn":true
},
"operationName":"MatchResponse"
- }''' % id
+ }''' % video_id # noqa: UP031
- info_json = self.download_gql(id, data, "Info json")
+ info_json = self.download_gql(video_id, data, 'Info json')
match_info = try_get(info_json, lambda x: x['data']['match'])
- if match_info.get('streamingStatus') != "STARTED":
+ if match_info.get('streamingStatus') != 'STARTED':
raise ExtractorError('The stream can\'t be accessed', expected=True)
self._check_login_required(match_info.get('isUserEntitled'), True) # all live streams are premium only
return {
- 'id': id,
+ 'id': video_id,
'title': match_info.get('name'),
- 'formats': self._extract_akamai_formats(try_get(match_info, lambda x: x['videoStreamUrl']['url']), id),
+ 'formats': self._extract_akamai_formats(try_get(match_info, lambda x: x['videoStreamUrl']['url']), video_id),
'ext': mimetype2ext(try_get(match_info, lambda x: x['videoStreamUrl']['deliveryType'])),
'is_live': True,
- 'release_timestamp': parse_iso8601(match_info.get('startTime'))
+ 'release_timestamp': parse_iso8601(match_info.get('startTime')),
}
diff --git a/yt_dlp/extractor/fc2.py b/yt_dlp/extractor/fc2.py
index bbc4b5693..eac70f6a9 100644
--- a/yt_dlp/extractor/fc2.py
+++ b/yt_dlp/extractor/fc2.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_parse_qs
from ..networking import Request
from ..utils import (
ExtractorError,
@@ -92,7 +92,7 @@ class FC2IE(InfoExtractor):
description = self._og_search_description(webpage, default=None)
vidplaylist = self._download_json(
- 'https://video.fc2.com/api/v3/videoplaylist/%s?sh=1&fs=0' % video_id, video_id,
+ f'https://video.fc2.com/api/v3/videoplaylist/{video_id}?sh=1&fs=0', video_id,
note='Downloading info page')
vid_url = traverse_obj(vidplaylist, ('playlist', 'nq'))
if not vid_url:
@@ -127,22 +127,22 @@ class FC2EmbedIE(InfoExtractor):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
- query = compat_parse_qs(mobj.group('query'))
+ query = urllib.parse.parse_qs(mobj.group('query'))
video_id = query['i'][-1]
- title = query.get('tl', ['FC2 video %s' % video_id])[0]
+ title = query.get('tl', [f'FC2 video {video_id}'])[0]
sj = query.get('sj', [None])[0]
thumbnail = None
if sj:
# See thumbnailImagePath() in ServerConst.as of flv2.swf
- thumbnail = 'http://video%s-thumbnail.fc2.com/up/pic/%s.jpg' % (
+ thumbnail = 'http://video{}-thumbnail.fc2.com/up/pic/{}.jpg'.format(
sj, '/'.join((video_id[:6], video_id[6:8], video_id[-2], video_id[-1], video_id)))
return {
'_type': 'url_transparent',
'ie_key': FC2IE.ie_key(),
- 'url': 'fc2:%s' % video_id,
+ 'url': f'fc2:{video_id}',
'title': title,
'thumbnail': thumbnail,
}
@@ -166,7 +166,7 @@ class FC2LiveIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- webpage = self._download_webpage('https://live.fc2.com/%s/' % video_id, video_id)
+ webpage = self._download_webpage(f'https://live.fc2.com/{video_id}/', video_id)
self._set_cookie('live.fc2.com', 'js-player_size', '1')
@@ -175,7 +175,7 @@ class FC2LiveIE(InfoExtractor):
'channel': '1',
'profile': '1',
'user': '1',
- 'streamid': video_id
+ 'streamid': video_id,
}), note='Requesting member info')
control_server = self._download_json(
@@ -224,7 +224,7 @@ class FC2LiveIE(InfoExtractor):
self.write_debug('Goodbye')
playlist_data = data
break
- self.write_debug('Server said: %s%s' % (recv[:100], '...' if len(recv) > 100 else ''))
+ self.write_debug('Server said: {}{}'.format(recv[:100], '...' if len(recv) > 100 else ''))
if not playlist_data:
raise ExtractorError('Unable to fetch HLS playlist info via WebSocket')
diff --git a/yt_dlp/extractor/filmon.py b/yt_dlp/extractor/filmon.py
index 69ca87c84..af1de7ac8 100644
--- a/yt_dlp/extractor/filmon.py
+++ b/yt_dlp/extractor/filmon.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -35,12 +34,12 @@ class FilmOnIE(InfoExtractor):
try:
response = self._download_json(
- 'https://www.filmon.com/api/vod/movie?id=%s' % video_id,
+ f'https://www.filmon.com/api/vod/movie?id={video_id}',
video_id)['response']
except ExtractorError as e:
if isinstance(e.cause, HTTPError):
errmsg = self._parse_json(e.cause.response.read().decode(), video_id)['reason']
- raise ExtractorError('%s said: %s' % (self.IE_NAME, errmsg), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {errmsg}', expected=True)
raise
title = response['title']
@@ -124,10 +123,10 @@ class FilmOnChannelIE(InfoExtractor):
except ExtractorError as e:
if isinstance(e.cause, HTTPError):
errmsg = self._parse_json(e.cause.response.read().decode(), channel_id)['message']
- raise ExtractorError('%s said: %s' % (self.IE_NAME, errmsg), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {errmsg}', expected=True)
raise
- channel_id = compat_str(channel_data['id'])
+ channel_id = str(channel_data['id'])
is_live = not channel_data.get('is_vod') and not channel_data.get('is_vox')
title = channel_data['title']
@@ -155,7 +154,7 @@ class FilmOnChannelIE(InfoExtractor):
for name, width, height in self._THUMBNAIL_RES:
thumbnails.append({
'id': name,
- 'url': 'http://static.filmon.com/assets/channels/%s/%s.png' % (channel_id, name),
+ 'url': f'http://static.filmon.com/assets/channels/{channel_id}/{name}.png',
'width': width,
'height': height,
})
diff --git a/yt_dlp/extractor/filmweb.py b/yt_dlp/extractor/filmweb.py
index cfea1f2fb..6dde6c3f0 100644
--- a/yt_dlp/extractor/filmweb.py
+++ b/yt_dlp/extractor/filmweb.py
@@ -14,7 +14,7 @@ class FilmwebIE(InfoExtractor):
'timestamp': 1458140101,
'uploader_id': '12639966',
'uploader': 'Live Roaldset',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/firsttv.py b/yt_dlp/extractor/firsttv.py
index f74bd132f..2d47ee561 100644
--- a/yt_dlp/extractor/firsttv.py
+++ b/yt_dlp/extractor/firsttv.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
int_or_none,
qualities,
@@ -60,12 +58,12 @@ class FirstTVIE(InfoExtractor):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id)
- playlist_url = compat_urlparse.urljoin(url, self._search_regex(
+ playlist_url = urllib.parse.urljoin(url, self._search_regex(
r'data-playlist-url=(["\'])(?P<url>(?:(?!\1).)+)\1',
webpage, 'playlist url', group='url'))
- parsed_url = compat_urlparse.urlparse(playlist_url)
- qs = compat_urlparse.parse_qs(parsed_url.query)
+ parsed_url = urllib.parse.urlparse(playlist_url)
+ qs = urllib.parse.parse_qs(parsed_url.query)
item_ids = qs.get('videos_ids[]') or qs.get('news_ids[]')
items = self._download_json(playlist_url, display_id)
@@ -73,12 +71,12 @@ class FirstTVIE(InfoExtractor):
if item_ids:
items = [
item for item in items
- if item.get('uid') and compat_str(item['uid']) in item_ids]
+ if item.get('uid') and str(item['uid']) in item_ids]
else:
items = [items[0]]
entries = []
- QUALITIES = ('ld', 'sd', 'hd', )
+ QUALITIES = ('ld', 'sd', 'hd')
for item in items:
title = item['title']
@@ -116,11 +114,10 @@ class FirstTVIE(InfoExtractor):
if len(formats) == 1:
m3u8_path = ','
else:
- tbrs = [compat_str(t) for t in sorted(f['tbr'] for f in formats)]
- m3u8_path = '_,%s,%s' % (','.join(tbrs), '.mp4')
+ tbrs = [str(t) for t in sorted(f['tbr'] for f in formats)]
+ m3u8_path = '_,{},{}'.format(','.join(tbrs), '.mp4')
formats.extend(self._extract_m3u8_formats(
- 'http://balancer-vod.1tv.ru/%s%s.urlset/master.m3u8'
- % (path, m3u8_path),
+ f'http://balancer-vod.1tv.ru/{path}{m3u8_path}.urlset/master.m3u8',
display_id, 'mp4',
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False))
@@ -131,12 +128,12 @@ class FirstTVIE(InfoExtractor):
'ya:ovs:upload_date', webpage, 'upload date', default=None))
entries.append({
- 'id': compat_str(item.get('id') or item['uid']),
+ 'id': str(item.get('id') or item['uid']),
'thumbnail': thumbnail,
'title': title,
'upload_date': upload_date,
'duration': int_or_none(duration),
- 'formats': formats
+ 'formats': formats,
})
title = self._html_search_regex(
diff --git a/yt_dlp/extractor/flickr.py b/yt_dlp/extractor/flickr.py
index 89a40d7e2..507bfe9d4 100644
--- a/yt_dlp/extractor/flickr.py
+++ b/yt_dlp/extractor/flickr.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_urlencode,
-)
from ..utils import (
ExtractorError,
format_field,
@@ -31,7 +29,7 @@ class FlickrIE(InfoExtractor):
'view_count': int,
'tags': list,
'license': 'Attribution-ShareAlike',
- }
+ },
}
_API_BASE_URL = 'https://api.flickr.com/services/rest?'
# https://help.yahoo.com/kb/flickr/SLN25525.html
@@ -52,14 +50,14 @@ class FlickrIE(InfoExtractor):
def _call_api(self, method, video_id, api_key, note, secret=None):
query = {
'photo_id': video_id,
- 'method': 'flickr.%s' % method,
+ 'method': f'flickr.{method}',
'api_key': api_key,
'format': 'json',
'nojsoncallback': 1,
}
if secret:
query['secret'] = secret
- data = self._download_json(self._API_BASE_URL + compat_urllib_parse_urlencode(query), video_id, note)
+ data = self._download_json(self._API_BASE_URL + urllib.parse.urlencode(query), video_id, note)
if data['stat'] != 'ok':
raise ExtractorError(data['message'])
return data
@@ -83,7 +81,7 @@ class FlickrIE(InfoExtractor):
formats = []
for stream in streams['stream']:
- stream_type = compat_str(stream.get('type'))
+ stream_type = str(stream.get('type'))
formats.append({
'format_id': stream_type,
'url': stream['_content'],
diff --git a/yt_dlp/extractor/floatplane.py b/yt_dlp/extractor/floatplane.py
index 8676d73f6..b7ee160a4 100644
--- a/yt_dlp/extractor/floatplane.py
+++ b/yt_dlp/extractor/floatplane.py
@@ -211,7 +211,7 @@ class FloatplaneIE(InfoExtractor):
stream = self._download_json(
'https://www.floatplane.com/api/v2/cdn/delivery', media_id, query={
'type': 'vod' if media_typ == 'video' else 'aod',
- 'guid': metadata['guid']
+ 'guid': metadata['guid'],
}, note=f'Downloading {media_typ} stream data')
path_template = traverse_obj(stream, ('resource', 'uri', {str}))
diff --git a/yt_dlp/extractor/folketinget.py b/yt_dlp/extractor/folketinget.py
index 55a11e591..95095701b 100644
--- a/yt_dlp/extractor/folketinget.py
+++ b/yt_dlp/extractor/folketinget.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_parse_qs
from ..utils import (
int_or_none,
parse_duration,
@@ -42,7 +43,7 @@ class FolketingetIE(InfoExtractor):
r'(?s)<div class="video-item-agenda"[^>]*>(.*?)<',
webpage, 'description', fatal=False)
- player_params = compat_parse_qs(self._search_regex(
+ player_params = urllib.parse.parse_qs(self._search_regex(
r'<embed src="http://ft\.arkena\.tv/flash/ftplayer\.swf\?([^"]+)"',
webpage, 'player params'))
xml_url = player_params['xml'][0]
diff --git a/yt_dlp/extractor/footyroom.py b/yt_dlp/extractor/footyroom.py
index 4a1316b50..797e894d6 100644
--- a/yt_dlp/extractor/footyroom.py
+++ b/yt_dlp/extractor/footyroom.py
@@ -19,7 +19,7 @@ class FootyRoomIE(InfoExtractor):
'title': 'VIDEO Georgia 0 - 2 Germany',
},
'playlist_count': 1,
- 'add_ie': ['Playwire']
+ 'add_ie': ['Playwire'],
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/fourtube.py b/yt_dlp/extractor/fourtube.py
index b6368b87d..ba94b5bdc 100644
--- a/yt_dlp/extractor/fourtube.py
+++ b/yt_dlp/extractor/fourtube.py
@@ -1,12 +1,8 @@
+import base64
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_b64decode,
- compat_str,
- compat_urllib_parse_unquote,
- compat_urlparse,
-)
from ..utils import (
int_or_none,
parse_duration,
@@ -21,21 +17,20 @@ from ..utils import (
class FourTubeBaseIE(InfoExtractor):
def _extract_formats(self, url, video_id, media_id, sources):
- token_url = 'https://%s/%s/desktop/%s' % (
+ token_url = 'https://{}/{}/desktop/{}'.format(
self._TKN_HOST, media_id, '+'.join(sources))
- parsed_url = compat_urlparse.urlparse(url)
+ parsed_url = urllib.parse.urlparse(url)
tokens = self._download_json(token_url, video_id, data=b'', headers={
- 'Origin': '%s://%s' % (parsed_url.scheme, parsed_url.hostname),
+ 'Origin': f'{parsed_url.scheme}://{parsed_url.hostname}',
'Referer': url,
})
- formats = [{
- 'url': tokens[format]['token'],
- 'format_id': format + 'p',
- 'resolution': format + 'p',
- 'quality': int(format),
- } for format in sources]
- return formats
+ return [{
+ 'url': tokens[res]['token'],
+ 'format_id': res + 'p',
+ 'resolution': res + 'p',
+ 'quality': int(res),
+ } for res in sources]
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -89,9 +84,9 @@ class FourTubeBaseIE(InfoExtractor):
params_js = self._search_regex(
r'\$\.ajax\(url,\ opts\);\s*\}\s*\}\)\(([0-9,\[\] ]+)\)',
player_js, 'initialization parameters')
- params = self._parse_json('[%s]' % params_js, video_id)
+ params = self._parse_json(f'[{params_js}]', video_id)
media_id = params[0]
- sources = ['%s' % p for p in params[2]]
+ sources = [f'{p}' for p in params[2]]
formats = self._extract_formats(url, video_id, media_id, sources)
@@ -234,20 +229,20 @@ class PornTubeIE(FourTubeBaseIE):
self._search_regex(
r'INITIALSTATE\s*=\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
webpage, 'data', group='value'), video_id,
- transform_source=lambda x: compat_urllib_parse_unquote(
- compat_b64decode(x).decode('utf-8')))['page']['video']
+ transform_source=lambda x: urllib.parse.unquote(
+ base64.b64decode(x).decode('utf-8')))['page']['video']
title = video['title']
media_id = video['mediaId']
- sources = [compat_str(e['height'])
+ sources = [str(e['height'])
for e in video['encodings'] if e.get('height')]
formats = self._extract_formats(url, video_id, media_id, sources)
thumbnail = url_or_none(video.get('masterThumb'))
- uploader = try_get(video, lambda x: x['user']['username'], compat_str)
+ uploader = try_get(video, lambda x: x['user']['username'], str)
uploader_id = str_or_none(try_get(
video, lambda x: x['user']['id'], int))
- channel = try_get(video, lambda x: x['channel']['name'], compat_str)
+ channel = try_get(video, lambda x: x['channel']['name'], str)
channel_id = str_or_none(try_get(
video, lambda x: x['channel']['id'], int))
like_count = int_or_none(video.get('likes'))
diff --git a/yt_dlp/extractor/fox.py b/yt_dlp/extractor/fox.py
index e00e977bd..e3cf22d4d 100644
--- a/yt_dlp/extractor/fox.py
+++ b/yt_dlp/extractor/fox.py
@@ -1,11 +1,8 @@
import json
+import urllib.parse
import uuid
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_unquote,
-)
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -59,7 +56,7 @@ class FOXIE(InfoExtractor):
_HOME_PAGE_URL = 'https://www.fox.com/'
_API_KEY = '6E9S4bmcoNnZwVLOHywOv8PJEdu76cM9'
_access_token = None
- _device_id = compat_str(uuid.uuid4())
+ _device_id = str(uuid.uuid4())
def _call_api(self, path, video_id, data=None):
headers = {
@@ -88,7 +85,7 @@ class FOXIE(InfoExtractor):
if not self._access_token:
mvpd_auth = self._get_cookies(self._HOME_PAGE_URL).get('mvpd-auth')
if mvpd_auth:
- self._access_token = (self._parse_json(compat_urllib_parse_unquote(
+ self._access_token = (self._parse_json(urllib.parse.unquote(
mvpd_auth.value), None, fatal=False) or {}).get('accessToken')
if not self._access_token:
self._access_token = self._call_api(
@@ -100,7 +97,7 @@ class FOXIE(InfoExtractor):
video_id = self._match_id(url)
self._access_token = self._call_api(
- 'previewpassmvpd?device_id=%s&mvpd_id=TempPass_fbcfox_60min' % self._device_id,
+ f'previewpassmvpd?device_id={self._device_id}&mvpd_id=TempPass_fbcfox_60min',
video_id)['accessToken']
video = self._call_api('watch', video_id, data=json.dumps({
@@ -113,13 +110,13 @@ class FOXIE(InfoExtractor):
'provider': {
'freewheel': {'did': self._device_id},
'vdms': {'rays': ''},
- 'dmp': {'kuid': '', 'seg': ''}
+ 'dmp': {'kuid': '', 'seg': ''},
},
'playlist': '',
'privacy': {'us': '1---'},
'siteSection': '',
'streamType': 'vod',
- 'streamId': video_id}).encode('utf-8'))
+ 'streamId': video_id}).encode())
title = video['name']
release_url = video['url']
diff --git a/yt_dlp/extractor/fptplay.py b/yt_dlp/extractor/fptplay.py
index 85613bafe..db9b2e153 100644
--- a/yt_dlp/extractor/fptplay.py
+++ b/yt_dlp/extractor/fptplay.py
@@ -84,7 +84,7 @@ class FptplayIE(InfoExtractor):
a = [0, 0, 0, 0]
s = len(e)
c = 0
- for z in range(s, 0, -1):
+ for _ in range(s, 0, -1):
if n <= 3:
i[n] = e[c]
n += 1
diff --git a/yt_dlp/extractor/francetv.py b/yt_dlp/extractor/francetv.py
index 7b8f7dd04..f732d5677 100644
--- a/yt_dlp/extractor/francetv.py
+++ b/yt_dlp/extractor/francetv.py
@@ -175,7 +175,7 @@ class FranceTVIE(InfoExtractor):
for f in formats:
if f.get('acodec') != 'none' and f.get('language') in ('qtz', 'qad'):
f['language_preference'] = -10
- f['format_note'] = 'audio description%s' % format_field(f, 'format_note', ', %s')
+ f['format_note'] = 'audio description{}'.format(format_field(f, 'format_note', ', %s'))
if spritesheets:
formats.append({
@@ -189,10 +189,10 @@ class FranceTVIE(InfoExtractor):
'fragments': [{
'url': sheet,
# XXX: not entirely accurate; each spritesheet seems to be
- # a 10×10 grid of thumbnails corresponding to approximately
+ # a 10x10 grid of thumbnails corresponding to approximately
# 2 seconds of the video; the last spritesheet may be shorter
'duration': 200,
- } for sheet in traverse_obj(spritesheets, (..., {url_or_none}))]
+ } for sheet in traverse_obj(spritesheets, (..., {url_or_none}))],
})
return {
diff --git a/yt_dlp/extractor/freesound.py b/yt_dlp/extractor/freesound.py
index fcde04469..b8b8fe82d 100644
--- a/yt_dlp/extractor/freesound.py
+++ b/yt_dlp/extractor/freesound.py
@@ -23,7 +23,7 @@ class FreesoundIE(InfoExtractor):
'uploader': 'miklovan',
'upload_date': '20130715',
'tags': list,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/freetv.py b/yt_dlp/extractor/freetv.py
index 757a10d01..518a6680f 100644
--- a/yt_dlp/extractor/freetv.py
+++ b/yt_dlp/extractor/freetv.py
@@ -23,7 +23,7 @@ class FreeTvMoviesIE(FreeTvBaseIE):
'title': 'Atrápame Si Puedes',
'description': 'md5:ca63bc00898aeb2f64ec87c6d3a5b982',
'ext': 'mp4',
- }
+ },
}, {
'url': 'https://www.freetv.com/peliculas/monstruoso/',
'md5': '509c15c68de41cb708d1f92d071f20aa',
@@ -32,7 +32,7 @@ class FreeTvMoviesIE(FreeTvBaseIE):
'title': 'Monstruoso',
'description': 'md5:333fc19ee327b457b980e54a911ea4a3',
'ext': 'mp4',
- }
+ },
}]
def _extract_video(self, content_id, action='olyott_video_play'):
@@ -72,7 +72,7 @@ class FreeTvIE(FreeTvBaseIE):
'info_dict': {
'id': 'el-detective-l',
'title': 'El Detective L',
- 'description': 'md5:f9f1143bc33e9856ecbfcbfb97a759be'
+ 'description': 'md5:f9f1143bc33e9856ecbfcbfb97a759be',
},
'playlist_count': 24,
}, {
@@ -80,7 +80,7 @@ class FreeTvIE(FreeTvBaseIE):
'info_dict': {
'id': 'esmeraldas',
'title': 'Esmeraldas',
- 'description': 'md5:43d7ec45bd931d8268a4f5afaf4c77bf'
+ 'description': 'md5:43d7ec45bd931d8268a4f5afaf4c77bf',
},
'playlist_count': 62,
}, {
@@ -88,7 +88,7 @@ class FreeTvIE(FreeTvBaseIE):
'info_dict': {
'id': 'las-aventuras-de-leonardo',
'title': 'Las Aventuras de Leonardo',
- 'description': 'md5:0c47130846c141120a382aca059288f6'
+ 'description': 'md5:0c47130846c141120a382aca059288f6',
},
'playlist_count': 13,
},
diff --git a/yt_dlp/extractor/frontendmasters.py b/yt_dlp/extractor/frontendmasters.py
index 3bae8add0..b5176aa9b 100644
--- a/yt_dlp/extractor/frontendmasters.py
+++ b/yt_dlp/extractor/frontendmasters.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
parse_duration,
@@ -22,7 +19,7 @@ class FrontendMastersBaseIE(InfoExtractor):
_QUALITIES = {
'low': {'width': 480, 'height': 360},
'mid': {'width': 1280, 'height': 720},
- 'high': {'width': 1920, 'height': 1080}
+ 'high': {'width': 1920, 'height': 1080},
}
def _perform_login(self, username, password):
@@ -33,7 +30,7 @@ class FrontendMastersBaseIE(InfoExtractor):
login_form.update({
'username': username,
- 'password': password
+ 'password': password,
})
post_url = self._search_regex(
@@ -41,7 +38,7 @@ class FrontendMastersBaseIE(InfoExtractor):
'post_url', default=self._LOGIN_URL, group='url')
if not post_url.startswith('http'):
- post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
+ post_url = urllib.parse.urljoin(self._LOGIN_URL, post_url)
response = self._download_webpage(
post_url, None, 'Logging in', data=urlencode_postdata(login_form),
@@ -56,14 +53,14 @@ class FrontendMastersBaseIE(InfoExtractor):
r'class=(["\'])(?:(?!\1).)*\bMessageAlert\b(?:(?!\1).)*\1[^>]*>(?P<error>[^<]+)<',
response, 'error message', default=None, group='error')
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
raise ExtractorError('Unable to log in')
class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
def _download_course(self, course_name, url):
return self._download_json(
- '%s/courses/%s' % (self._API_BASE, course_name), course_name,
+ f'{self._API_BASE}/courses/{course_name}', course_name,
'Downloading course JSON', headers={'Referer': url})
@staticmethod
@@ -92,7 +89,7 @@ class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
duration = None
timestamp = lesson.get('timestamp')
- if isinstance(timestamp, compat_str):
+ if isinstance(timestamp, str):
mobj = re.search(
r'(?P<start>\d{1,2}:\d{1,2}:\d{1,2})\s*-(?P<end>\s*\d{1,2}:\d{1,2}:\d{1,2})',
timestamp)
@@ -102,7 +99,7 @@ class FrontendMastersPageBaseIE(FrontendMastersBaseIE):
return {
'_type': 'url_transparent',
- 'url': 'frontendmasters:%s' % lesson_id,
+ 'url': f'frontendmasters:{lesson_id}',
'ie_key': FrontendMastersIE.ie_key(),
'id': lesson_id,
'display_id': display_id,
@@ -134,16 +131,16 @@ class FrontendMastersIE(FrontendMastersBaseIE):
def _real_extract(self, url):
lesson_id = self._match_id(url)
- source_url = '%s/video/%s/source' % (self._API_BASE, lesson_id)
+ source_url = f'{self._API_BASE}/video/{lesson_id}/source'
formats = []
for ext in ('webm', 'mp4'):
for quality in ('low', 'mid', 'high'):
resolution = self._QUALITIES[quality].copy()
- format_id = '%s-%s' % (ext, quality)
+ format_id = f'{ext}-{quality}'
format_url = self._download_json(
source_url, lesson_id,
- 'Downloading %s source JSON' % format_id, query={
+ f'Downloading {format_id} source JSON', query={
'f': ext,
'r': resolution['height'],
}, headers={
@@ -163,15 +160,15 @@ class FrontendMastersIE(FrontendMastersBaseIE):
subtitles = {
'en': [{
- 'url': '%s/transcripts/%s.vtt' % (self._API_BASE, lesson_id),
- }]
+ 'url': f'{self._API_BASE}/transcripts/{lesson_id}.vtt',
+ }],
}
return {
'id': lesson_id,
'title': lesson_id,
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
diff --git a/yt_dlp/extractor/fujitv.py b/yt_dlp/extractor/fujitv.py
index 77e826e2d..a2d1a828b 100644
--- a/yt_dlp/extractor/fujitv.py
+++ b/yt_dlp/extractor/fujitv.py
@@ -34,7 +34,7 @@ class FujiTVFODPlus7IE(InfoExtractor):
'series': 'ちびまる子ちゃん',
'series_id': '5d40',
'thumbnail': 'https://i.fod.fujitv.co.jp/img/program/5d40/episode/5d40810083_a.jpg'},
- 'skip': 'Video available only in one week'
+ 'skip': 'Video available only in one week',
}]
def _real_extract(self, url):
@@ -43,7 +43,9 @@ class FujiTVFODPlus7IE(InfoExtractor):
json_info = {}
token = self._get_cookies(url).get('CT')
if token:
- json_info = self._download_json('https://fod-sp.fujitv.co.jp/apps/api/episode/detail/?ep_id=%s&is_premium=false' % video_id, video_id, headers={'x-authorization': f'Bearer {token.value}'}, fatal=False)
+ json_info = self._download_json(
+ f'https://fod-sp.fujitv.co.jp/apps/api/episode/detail/?ep_id={video_id}&is_premium=false',
+ video_id, headers={'x-authorization': f'Bearer {token.value}'}, fatal=False)
else:
self.report_warning(f'The token cookie is needed to extract video metadata. {self._login_hint("cookies")}')
formats, subtitles = [], {}
@@ -67,5 +69,5 @@ class FujiTVFODPlus7IE(InfoExtractor):
'formats': formats,
'subtitles': subtitles,
'thumbnail': f'{self._BASE_URL}img/program/{series_id}/episode/{video_id}_a.jpg',
- '_format_sort_fields': ('tbr', )
+ '_format_sort_fields': ('tbr', ),
}
diff --git a/yt_dlp/extractor/funimation.py b/yt_dlp/extractor/funimation.py
index c32f005ba..d3e61c84f 100644
--- a/yt_dlp/extractor/funimation.py
+++ b/yt_dlp/extractor/funimation.py
@@ -96,7 +96,7 @@ class FunimationPageIE(FunimationBaseIE):
f'{show}_{episode}', query={
'deviceType': 'web',
'region': self._REGION,
- 'locale': locale or 'en'
+ 'locale': locale or 'en',
}), ('videoList', ..., 'id'), get_all=False)
return self.url_result(f'https://www.funimation.com/player/{video_id}', FunimationIE.ie_key(), video_id)
@@ -157,7 +157,7 @@ class FunimationIE(FunimationBaseIE):
yield lang, version.title(), f
def _get_episode(self, webpage, experience_id=None, episode_id=None, fatal=True):
- ''' Extract the episode, season and show objects given either episode/experience id '''
+ """ Extract the episode, season and show objects given either episode/experience id """
show = self._parse_json(
self._search_regex(
r'show\s*=\s*({.+?})\s*;', webpage, 'show data', fatal=fatal),
@@ -199,16 +199,16 @@ class FunimationIE(FunimationBaseIE):
continue
thumbnails.append({'url': fmt.get('poster')})
duration = max(duration, fmt.get('duration', 0))
- format_name = '%s %s (%s)' % (version, lang, experience_id)
+ format_name = f'{version} {lang} ({experience_id})'
self.extract_subtitles(
subtitles, experience_id, display_id=display_id, format_name=format_name,
episode=episode if experience_id == initial_experience_id else episode_id)
headers = {}
if self._TOKEN:
- headers['Authorization'] = 'Token %s' % self._TOKEN
+ headers['Authorization'] = f'Token {self._TOKEN}'
page = self._download_json(
- 'https://www.funimation.com/api/showexperience/%s/' % experience_id,
+ f'https://www.funimation.com/api/showexperience/{experience_id}/',
display_id, headers=headers, expected_status=403, query={
'pinst_id': ''.join(random.choices(string.digits + string.ascii_letters, k=8)),
}, note=f'Downloading {format_name} JSON')
@@ -216,7 +216,7 @@ class FunimationIE(FunimationBaseIE):
if not sources:
error = try_get(page, lambda x: x['errors'][0], dict)
if error:
- self.report_warning('%s said: Error %s - %s' % (
+ self.report_warning('{} said: Error {} - {}'.format(
self.IE_NAME, error.get('code'), error.get('detail') or error.get('title')))
else:
self.report_warning('No sources found for format')
@@ -227,11 +227,11 @@ class FunimationIE(FunimationBaseIE):
source_type = source.get('videoType') or determine_ext(source_url)
if source_type == 'm3u8':
current_formats.extend(self._extract_m3u8_formats(
- source_url, display_id, 'mp4', m3u8_id='%s-%s' % (experience_id, 'hls'), fatal=False,
+ source_url, display_id, 'mp4', m3u8_id='{}-{}'.format(experience_id, 'hls'), fatal=False,
note=f'Downloading {format_name} m3u8 information'))
else:
current_formats.append({
- 'format_id': '%s-%s' % (experience_id, source_type),
+ 'format_id': f'{experience_id}-{source_type}',
'url': source_url,
})
for f in current_formats:
@@ -284,7 +284,7 @@ class FunimationIE(FunimationBaseIE):
sub_type = sub_type if sub_type != 'FULL' else None
current_sub = {
'url': text_track['src'],
- 'name': join_nonempty(version, text_track.get('label'), sub_type, delim=' ')
+ 'name': join_nonempty(version, text_track.get('label'), sub_type, delim=' '),
}
lang = join_nonempty(text_track.get('language', 'und'),
version if version != 'Simulcast' else None,
@@ -302,7 +302,7 @@ class FunimationShowIE(FunimationBaseIE):
'url': 'https://www.funimation.com/en/shows/sk8-the-infinity',
'info_dict': {
'id': '1315000',
- 'title': 'SK8 the Infinity'
+ 'title': 'SK8 the Infinity',
},
'playlist_count': 13,
'params': {
@@ -313,7 +313,7 @@ class FunimationShowIE(FunimationBaseIE):
'url': 'https://www.funimation.com/shows/ouran-high-school-host-club/',
'info_dict': {
'id': '39643',
- 'title': 'Ouran High School Host Club'
+ 'title': 'Ouran High School Host Club',
},
'playlist_count': 26,
'params': {
@@ -329,11 +329,11 @@ class FunimationShowIE(FunimationBaseIE):
base_url, locale, display_id = self._match_valid_url(url).groups()
show_info = self._download_json(
- 'https://title-api.prd.funimationsvc.com/v2/shows/%s?region=%s&deviceType=web&locale=%s'
- % (display_id, self._REGION, locale or 'en'), display_id)
+ 'https://title-api.prd.funimationsvc.com/v2/shows/{}?region={}&deviceType=web&locale={}'.format(
+ display_id, self._REGION, locale or 'en'), display_id)
items_info = self._download_json(
- 'https://prod-api-funimationnow.dadcdigital.com/api/funimation/episodes/?limit=99999&title_id=%s'
- % show_info.get('id'), display_id)
+ 'https://prod-api-funimationnow.dadcdigital.com/api/funimation/episodes/?limit=99999&title_id={}'.format(
+ show_info.get('id')), display_id)
vod_items = traverse_obj(items_info, ('items', ..., lambda k, _: re.match(r'(?i)mostRecent[AS]vod', k), 'item'))
@@ -343,7 +343,7 @@ class FunimationShowIE(FunimationBaseIE):
'title': show_info['name'],
'entries': orderedSet(
self.url_result(
- '%s/%s' % (base_url, vod_item.get('episodeSlug')), FunimationPageIE.ie_key(),
+ '{}/{}'.format(base_url, vod_item.get('episodeSlug')), FunimationPageIE.ie_key(),
vod_item.get('episodeId'), vod_item.get('episodeName'))
for vod_item in sorted(vod_items, key=lambda x: x.get('episodeOrder', -1))),
}
diff --git a/yt_dlp/extractor/funker530.py b/yt_dlp/extractor/funker530.py
index 62fd7f6dd..5d59e9c98 100644
--- a/yt_dlp/extractor/funker530.py
+++ b/yt_dlp/extractor/funker530.py
@@ -25,7 +25,7 @@ class Funker530IE(InfoExtractor):
'timestamp': 1686241321,
'live_status': 'not_live',
'description': 'md5:bea2e1f458095414e04b5ac189c2f980',
- }
+ },
}, {
'url': 'https://funker530.com/video/my-friends-joined-the-russians-civdiv/',
'md5': 'a42c2933391210662e93e867d7124b70',
@@ -54,7 +54,7 @@ class Funker530IE(InfoExtractor):
'upload_date': '20230608',
'playable_in_embed': True,
'heatmap': 'count:100',
- }
+ },
}]
def _real_extract(self, url):
@@ -76,5 +76,5 @@ class Funker530IE(InfoExtractor):
'_type': 'url_transparent',
'description': strip_or_none(self._search_regex(
r'(?s)(.+)About the Author', clean_html(get_element_by_class('video-desc-paragraph', webpage)),
- 'description', default=None))
+ 'description', default=None)),
}
diff --git a/yt_dlp/extractor/fuyintv.py b/yt_dlp/extractor/fuyintv.py
index 197901d57..f46839bba 100644
--- a/yt_dlp/extractor/fuyintv.py
+++ b/yt_dlp/extractor/fuyintv.py
@@ -11,7 +11,7 @@ class FuyinTVIE(InfoExtractor):
'ext': 'mp4',
'title': '第1集',
'description': 'md5:21a3d238dc8d49608e1308e85044b9c3',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/gab.py b/yt_dlp/extractor/gab.py
index c10d290dc..024628e1c 100644
--- a/yt_dlp/extractor/gab.py
+++ b/yt_dlp/extractor/gab.py
@@ -22,29 +22,29 @@ class GabTVIE(InfoExtractor):
'uploader': 'Wurzelroot',
'uploader_id': '608fb0a85738fd1974984f7d',
'thumbnail': 'https://tv.gab.com/image/61217eacea5665de450d0488',
- }
+ },
}]
def _real_extract(self, url):
- id = self._match_id(url).split('-')[-1]
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url).split('-')[-1]
+ webpage = self._download_webpage(url, video_id)
channel_id = self._search_regex(r'data-channel-id=\"(?P<channel_id>[^\"]+)', webpage, 'channel_id')
channel_name = self._search_regex(r'data-channel-name=\"(?P<channel_id>[^\"]+)', webpage, 'channel_name')
title = self._search_regex(r'data-episode-title=\"(?P<channel_id>[^\"]+)', webpage, 'title')
view_key = self._search_regex(r'data-view-key=\"(?P<channel_id>[^\"]+)', webpage, 'view_key')
description = clean_html(
self._html_search_regex(self._meta_regex('description'), webpage, 'description', group='content')) or None
- available_resolutions = re.findall(r'<a\ data-episode-id=\"%s\"\ data-resolution=\"(?P<resolution>[^\"]+)' % id,
- webpage)
+ available_resolutions = re.findall(
+ rf'<a\ data-episode-id=\"{video_id}\"\ data-resolution=\"(?P<resolution>[^\"]+)', webpage)
formats = []
for resolution in available_resolutions:
frmt = {
- 'url': f'https://tv.gab.com/media/{id}?viewKey={view_key}&r={resolution}',
+ 'url': f'https://tv.gab.com/media/{video_id}?viewKey={view_key}&r={resolution}',
'format_id': resolution,
'vcodec': 'h264',
'acodec': 'aac',
- 'ext': 'mp4'
+ 'ext': 'mp4',
}
if 'audio-' in resolution:
frmt['abr'] = str_to_int(resolution.replace('audio-', ''))
@@ -55,13 +55,13 @@ class GabTVIE(InfoExtractor):
formats.append(frmt)
return {
- 'id': id,
+ 'id': video_id,
'title': title,
'formats': formats,
'description': description,
'uploader': channel_name,
'uploader_id': channel_id,
- 'thumbnail': f'https://tv.gab.com/image/{id}',
+ 'thumbnail': f'https://tv.gab.com/image/{video_id}',
}
@@ -79,7 +79,7 @@ class GabIE(InfoExtractor):
'description': 'md5:204055fafd5e1a519f5d6db953567ca3',
'timestamp': 1635192289,
'upload_date': '20211025',
- }
+ },
}, {
'url': 'https://gab.com/TheLonelyProud/posts/107045884469287653',
'md5': 'f9cefcfdff6418e392611a828d47839d',
@@ -91,7 +91,7 @@ class GabIE(InfoExtractor):
'timestamp': 1633390571,
'upload_date': '20211004',
'uploader': 'TheLonelyProud',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/gaia.py b/yt_dlp/extractor/gaia.py
index c84386f2c..048ea517b 100644
--- a/yt_dlp/extractor/gaia.py
+++ b/yt_dlp/extractor/gaia.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_unquote,
-)
from ..utils import (
ExtractorError,
int_or_none,
@@ -52,7 +50,7 @@ class GaiaIE(InfoExtractor):
def _real_initialize(self):
auth = self._get_cookies('https://www.gaia.com/').get('auth')
if auth:
- auth = self._parse_json(compat_urllib_parse_unquote(auth.value), None, fatal=False)
+ auth = self._parse_json(urllib.parse.unquote(auth.value), None, fatal=False)
self._jwt = auth.get('jwt')
def _perform_login(self, username, password):
@@ -62,7 +60,7 @@ class GaiaIE(InfoExtractor):
'https://auth.gaia.com/v1/login',
None, data=urlencode_postdata({
'username': username,
- 'password': password
+ 'password': password,
}))
if auth.get('success') is False:
raise ExtractorError(', '.join(auth['messages']), expected=True)
@@ -77,7 +75,7 @@ class GaiaIE(InfoExtractor):
node = self._download_json(
'https://brooklyn.gaia.com/node/%d' % node_id, node_id)
vdata = node[vtype]
- media_id = compat_str(vdata['nid'])
+ media_id = str(vdata['nid'])
title = node['title']
headers = None
@@ -115,7 +113,7 @@ class GaiaIE(InfoExtractor):
'like_count': int_or_none(try_get(fivestar, lambda x: x['up_count']['value'])),
'dislike_count': int_or_none(try_get(fivestar, lambda x: x['down_count']['value'])),
'comment_count': int_or_none(node.get('comment_count')),
- 'series': try_get(node, lambda x: x['series']['title'], compat_str),
+ 'series': try_get(node, lambda x: x['series']['title'], str),
'season_number': int_or_none(get_field_value('season')),
'season_id': str_or_none(get_field_value('series_nid', 'nid')),
'episode_number': int_or_none(get_field_value('episode')),
diff --git a/yt_dlp/extractor/gamejolt.py b/yt_dlp/extractor/gamejolt.py
index b284e1e28..01386c142 100644
--- a/yt_dlp/extractor/gamejolt.py
+++ b/yt_dlp/extractor/gamejolt.py
@@ -1,9 +1,9 @@
import itertools
import json
import math
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
determine_ext,
format_field,
@@ -45,7 +45,7 @@ class GameJoltBaseIE(InfoExtractor):
'comments/Fireside_Post/%s/%s?%s=%d' % (
post_num_id, sort_by,
'scroll_id' if is_scrolled else 'page', scroll_id if is_scrolled else page),
- post_hash_id, note='Downloading comments list page %d' % page)
+ post_hash_id, note=f'Downloading comments list page {page}')
if not comments_data.get('comments'):
break
for comment in traverse_obj(comments_data, (('comments', 'childComments'), ...), expected_type=dict):
@@ -87,7 +87,7 @@ class GameJoltBaseIE(InfoExtractor):
'uploader': user_data.get('display_name') or user_data.get('name'),
'uploader_id': user_data.get('username'),
'uploader_url': format_field(user_data, 'url', 'https://gamejolt.com%s'),
- 'categories': [try_get(category, lambda x: '%s - %s' % (x['community']['name'], x['channel'].get('display_title') or x['channel']['title']))
+ 'categories': [try_get(category, lambda x: '{} - {}'.format(x['community']['name'], x['channel'].get('display_title') or x['channel']['title']))
for category in post_data.get('communities') or []],
'tags': traverse_obj(
lead_content, ('content', ..., 'content', ..., 'marks', ..., 'attrs', 'tag'), expected_type=str_or_none),
@@ -95,7 +95,7 @@ class GameJoltBaseIE(InfoExtractor):
'comment_count': int_or_none(post_data.get('comment_count'), default=0),
'timestamp': int_or_none(post_data.get('added_on'), scale=1000),
'release_timestamp': int_or_none(post_data.get('published_on'), scale=1000),
- '__post_extractor': self.extract_comments(post_data.get('id'), post_id)
+ '__post_extractor': self.extract_comments(post_data.get('id'), post_id),
}
# TODO: Handle multiple videos/embeds?
@@ -152,7 +152,7 @@ class GameJoltBaseIE(InfoExtractor):
'height': media.get('height') if url_key == 'img_url' else None,
'filesize': media.get('filesize') if url_key == 'img_url' else None,
'acodec': 'none',
- } for url_key in ('img_url', 'mediaserver_url', 'mediaserver_url_mp4', 'mediaserver_url_webm') if media.get(url_key)]
+ } for url_key in ('img_url', 'mediaserver_url', 'mediaserver_url_mp4', 'mediaserver_url_webm') if media.get(url_key)],
})
if gif_entries:
return {
@@ -192,7 +192,7 @@ class GameJoltIE(GameJoltBaseIE):
'like_count': int,
'comment_count': int,
'view_count': int,
- }
+ },
}, {
# YouTube embed
'url': 'https://gamejolt.com/p/hey-hey-if-there-s-anyone-who-s-looking-to-get-into-learning-a-n6g4jzpq',
@@ -220,7 +220,7 @@ class GameJoltIE(GameJoltBaseIE):
'upload_date': '20211015',
'view_count': int,
'chapters': 'count:18',
- }
+ },
}, {
# Article
'url': 'https://gamejolt.com/p/i-fuckin-broke-chaos-d56h3eue',
@@ -243,7 +243,7 @@ class GameJoltIE(GameJoltBaseIE):
'like_count': int,
'comment_count': int,
'view_count': int,
- }
+ },
}, {
# Single GIF
'url': 'https://gamejolt.com/p/hello-everyone-i-m-developing-a-pixel-art-style-mod-for-fnf-and-i-vs4gdrd8',
@@ -267,7 +267,7 @@ class GameJoltIE(GameJoltBaseIE):
'id': 'dszyjnwi',
'ext': 'webm',
'title': 'gif-presentacion-mejorado-dszyjnwi',
- }
+ },
}],
'playlist_count': 1,
}, {
@@ -310,7 +310,7 @@ class GameJoltPostListBaseIE(GameJoltBaseIE):
endpoint, list_id, note=f'{note} page {page_num}', errnote=errnote, data=json.dumps({
'scrollDirection': 'from',
'scrollId': scroll_id,
- }).encode('utf-8')).get('items')
+ }).encode()).get('items')
class GameJoltUserIE(GameJoltPostListBaseIE):
@@ -348,7 +348,7 @@ class GameJoltGameIE(GameJoltPostListBaseIE):
'info_dict': {
'id': '655124',
'title': 'Friday Night Funkin\': Friday 4 Fun',
- 'description': 'md5:576a7dd87912a2dcf33c50d2bd3966d3'
+ 'description': 'md5:576a7dd87912a2dcf33c50d2bd3966d3',
},
'params': {
'ignore_no_formats_error': True,
@@ -383,7 +383,7 @@ class GameJoltGameSoundtrackIE(GameJoltBaseIE):
'url': r're:^https://.+vs-oswald-menu-music\.mp3$',
'release_timestamp': 1635190816,
'release_date': '20211025',
- }
+ },
}, {
'info_dict': {
'id': '184435',
@@ -392,7 +392,7 @@ class GameJoltGameSoundtrackIE(GameJoltBaseIE):
'url': r're:^https://.+rabbit-s-luck--full-version-\.mp3$',
'release_timestamp': 1635190841,
'release_date': '20211025',
- }
+ },
}, {
'info_dict': {
'id': '185228',
@@ -401,7 +401,7 @@ class GameJoltGameSoundtrackIE(GameJoltBaseIE):
'url': r're:^https://.+last-straw\.mp3$',
'release_timestamp': 1635881104,
'release_date': '20211102',
- }
+ },
}],
'playlist_count': 3,
}]
@@ -427,7 +427,7 @@ class GameJoltCommunityIE(GameJoltPostListBaseIE):
'info_dict': {
'id': 'fnf/videos',
'title': 'Friday Night Funkin\' - Videos',
- 'description': 'md5:6d8c06f27460f7d35c1554757ffe53c8'
+ 'description': 'md5:6d8c06f27460f7d35c1554757ffe53c8',
},
'params': {
'playlistend': 50,
@@ -440,7 +440,7 @@ class GameJoltCommunityIE(GameJoltPostListBaseIE):
'info_dict': {
'id': 'youtubers/featured',
'title': 'Youtubers - featured',
- 'description': 'md5:53e5582c93dcc467ab597bfca4db17d4'
+ 'description': 'md5:53e5582c93dcc467ab597bfca4db17d4',
},
'params': {
'playlistend': 50,
@@ -528,7 +528,7 @@ class GameJoltSearchIE(GameJoltPostListBaseIE):
def _real_extract(self, url):
filter_mode, query = self._match_valid_url(url).group('filter', 'id')
- display_query = compat_urllib_parse_unquote(query)
+ display_query = urllib.parse.unquote(query)
return self.playlist_result(
self._search_entries(query, filter_mode, display_query) if filter_mode else self._entries(
f'web/posts/fetch/search/{query}', display_query, initial_items=self._call_api(
diff --git a/yt_dlp/extractor/gamespot.py b/yt_dlp/extractor/gamespot.py
index 8dec2522c..cd3f9655d 100644
--- a/yt_dlp/extractor/gamespot.py
+++ b/yt_dlp/extractor/gamespot.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .once import OnceIE
-from ..compat import compat_urllib_parse_unquote
class GameSpotIE(OnceIE):
@@ -40,7 +41,7 @@ class GameSpotIE(OnceIE):
data_video = self._parse_json(self._html_search_regex(
r'data-video=(["\'])({.*?})\1', webpage,
'video data', group=2), page_id)
- title = compat_urllib_parse_unquote(data_video['title'])
+ title = urllib.parse.unquote(data_video['title'])
streams = data_video['videoStreams']
formats = []
diff --git a/yt_dlp/extractor/gamestar.py b/yt_dlp/extractor/gamestar.py
index e9966f532..8e3b8a5da 100644
--- a/yt_dlp/extractor/gamestar.py
+++ b/yt_dlp/extractor/gamestar.py
@@ -19,7 +19,7 @@ class GameStarIE(InfoExtractor):
'timestamp': 1406542380,
'upload_date': '20140728',
'duration': 17,
- }
+ },
}, {
'url': 'http://www.gamepro.de/videos/top-10-indie-spiele-fuer-nintendo-switch-video-tolle-nindies-games-zum-download,95316.html',
'only_matching': True,
@@ -42,7 +42,7 @@ class GameStarIE(InfoExtractor):
webpage, 'JSON-LD', group='json_ld'), video_id)
info_dict = self._json_ld(json_ld, video_id)
info_dict['title'] = remove_end(
- info_dict['title'], ' - Game%s' % site.title())
+ info_dict['title'], f' - Game{site.title()}')
view_count = int_or_none(json_ld.get('interactionCount'))
comment_count = int_or_none(self._html_search_regex(
@@ -54,7 +54,7 @@ class GameStarIE(InfoExtractor):
'url': 'http://gamestar.de/_misc/videos/portal/getVideoUrl.cfm?premium=0&videoId=' + video_id,
'ext': 'mp4',
'view_count': view_count,
- 'comment_count': comment_count
+ 'comment_count': comment_count,
})
return info_dict
diff --git a/yt_dlp/extractor/gaskrank.py b/yt_dlp/extractor/gaskrank.py
index 6403be8cf..beb5a8a82 100644
--- a/yt_dlp/extractor/gaskrank.py
+++ b/yt_dlp/extractor/gaskrank.py
@@ -22,7 +22,7 @@ class GaskrankIE(InfoExtractor):
'display_id': 'strike-einparken-durch-anfaenger-crash-mit-groesserem-flurschaden',
'uploader_id': 'Bikefun',
'upload_date': '20170110',
- }
+ },
}, {
'url': 'http://www.gaskrank.tv/tv/racing/isle-of-man-tt-2011-michael-du-15920.htm',
'md5': 'c33ee32c711bc6c8224bfcbe62b23095',
@@ -36,7 +36,7 @@ class GaskrankIE(InfoExtractor):
'uploader_id': 'IOM',
'upload_date': '20170523',
'uploader_url': 'www.iomtt.com',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/gazeta.py b/yt_dlp/extractor/gazeta.py
index 8925b69fd..b1b6ee771 100644
--- a/yt_dlp/extractor/gazeta.py
+++ b/yt_dlp/extractor/gazeta.py
@@ -33,7 +33,7 @@ class GazetaIE(InfoExtractor):
mobj = self._match_valid_url(url)
display_id = mobj.group('id')
- embed_url = '%s?p=embed' % mobj.group('url')
+ embed_url = '{}?p=embed'.format(mobj.group('url'))
embed_page = self._download_webpage(
embed_url, display_id, 'Downloading embed page')
@@ -41,4 +41,4 @@ class GazetaIE(InfoExtractor):
r'<div[^>]*?class="eagleplayer"[^>]*?data-id="([^"]+)"', embed_page, 'video id')
return self.url_result(
- 'eagleplatform:gazeta.media.eagleplatform.com:%s' % video_id, 'EaglePlatform')
+ f'eagleplatform:gazeta.media.eagleplatform.com:{video_id}', 'EaglePlatform')
diff --git a/yt_dlp/extractor/gbnews.py b/yt_dlp/extractor/gbnews.py
index bb1554eea..d65256637 100644
--- a/yt_dlp/extractor/gbnews.py
+++ b/yt_dlp/extractor/gbnews.py
@@ -1,5 +1,3 @@
-import functools
-
from .common import InfoExtractor
from ..utils import (
ExtractorError,
@@ -35,7 +33,7 @@ class GBNewsIE(InfoExtractor):
'display_id': 'prince-harry-in-love-with-kate-meghan-markle-jealous-royal',
'description': 'Ingrid Seward has published 17 books documenting the highs and lows of the Royal Family',
'title': 'Royal author claims Prince Harry was \'in love\' with Kate - Meghan was \'jealous\'',
- }
+ },
}, {
'url': 'https://www.gbnews.uk/watchlive',
'info_dict': {
@@ -48,13 +46,20 @@ class GBNewsIE(InfoExtractor):
},
'params': {'skip_download': 'm3u8'},
}]
+ _SS_ENDPOINTS = None
- @functools.lru_cache
def _get_ss_endpoint(self, data_id, data_env):
+ if not self._SS_ENDPOINTS:
+ self._SS_ENDPOINTS = {}
+
if not data_id:
data_id = 'GB003'
if not data_env:
data_env = 'production'
+ key = data_id, data_env
+ result = self._SS_ENDPOINTS.get(key)
+ if result:
+ return result
json_data = self._download_json(
self._SSMP_URL, None, 'Downloading Simplestream JSON metadata', query={
@@ -65,6 +70,7 @@ class GBNewsIE(InfoExtractor):
if not meta_url:
raise ExtractorError('No API host found')
+ self._SS_ENDPOINTS[key] = meta_url
return meta_url
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/gdcvault.py b/yt_dlp/extractor/gdcvault.py
index b4d81b2e8..5d4524065 100644
--- a/yt_dlp/extractor/gdcvault.py
+++ b/yt_dlp/extractor/gdcvault.py
@@ -18,8 +18,8 @@ class GDCVaultIE(InfoExtractor):
'id': '201311826596_AWNY',
'display_id': 'Doki-Doki-Universe-Sweet-Simple',
'ext': 'mp4',
- 'title': 'Doki-Doki Universe: Sweet, Simple and Genuine (GDC Next 10)'
- }
+ 'title': 'Doki-Doki Universe: Sweet, Simple and Genuine (GDC Next 10)',
+ },
},
{
'url': 'http://www.gdcvault.com/play/1015683/Embracing-the-Dark-Art-of',
@@ -27,11 +27,11 @@ class GDCVaultIE(InfoExtractor):
'id': '201203272_1330951438328RSXR',
'display_id': 'Embracing-the-Dark-Art-of',
'ext': 'flv',
- 'title': 'Embracing the Dark Art of Mathematical Modeling in AI'
+ 'title': 'Embracing the Dark Art of Mathematical Modeling in AI',
},
'params': {
'skip_download': True, # Requires rtmpdump
- }
+ },
},
{
'url': 'http://www.gdcvault.com/play/1015301/Thexder-Meets-Windows-95-or',
@@ -202,7 +202,7 @@ class GDCVaultIE(InfoExtractor):
'display_id': display_id,
})
return info
- embed_url = '%s/xml/%s' % (xml_root, xml_name)
+ embed_url = f'{xml_root}/xml/{xml_name}'
ie_key = 'DigitallySpeaking'
return {
diff --git a/yt_dlp/extractor/gedidigital.py b/yt_dlp/extractor/gedidigital.py
index 1878d636d..2ffa66030 100644
--- a/yt_dlp/extractor/gedidigital.py
+++ b/yt_dlp/extractor/gedidigital.py
@@ -109,7 +109,7 @@ class GediDigitalIE(InfoExtractor):
# add protocol if missing
for i, e in enumerate(urls):
if e.startswith('//'):
- urls[i] = 'https:%s' % e
+ urls[i] = f'https:{e}'
# clean iframes urls
for i, e in enumerate(urls):
urls[i] = urljoin(base_url(e), url_basename(e))
@@ -166,7 +166,7 @@ class GediDigitalIE(InfoExtractor):
'abr': abr,
'tbr': abr,
'acodec': ext,
- 'vcodec': 'none'
+ 'vcodec': 'none',
})
else:
mobj = re.match(r'^video-rrtv-(\d+)(?:-(\d+))?$', n)
diff --git a/yt_dlp/extractor/generic.py b/yt_dlp/extractor/generic.py
index 2818c718d..cc17890e7 100644
--- a/yt_dlp/extractor/generic.py
+++ b/yt_dlp/extractor/generic.py
@@ -62,7 +62,7 @@ class GenericIE(InfoExtractor):
'upload_date': '20100513',
'direct': True,
'timestamp': 1273772943.0,
- }
+ },
},
# Direct link to media delivered compressed (until Accept-Encoding is *)
{
@@ -75,7 +75,7 @@ class GenericIE(InfoExtractor):
'upload_date': '20140522',
},
'expected_warnings': [
- 'URL could be a direct video link, returning it as such.'
+ 'URL could be a direct video link, returning it as such.',
],
'skip': 'URL invalid',
},
@@ -109,8 +109,8 @@ class GenericIE(InfoExtractor):
'timestamp': 1416498816.0,
},
'expected_warnings': [
- 'URL could be a direct video link, returning it as such.'
- ]
+ 'URL could be a direct video link, returning it as such.',
+ ],
},
# RSS feed
{
@@ -118,7 +118,7 @@ class GenericIE(InfoExtractor):
'info_dict': {
'id': 'https://phihag.de/2014/youtube-dl/rss2.xml',
'title': 'Zero Punctuation',
- 'description': 're:.*groundbreaking video review series.*'
+ 'description': 're:.*groundbreaking video review series.*',
},
'playlist_mincount': 11,
},
@@ -334,7 +334,7 @@ class GenericIE(InfoExtractor):
},
'params': {
'skip_download': False,
- }
+ },
},
{
# redirect in Refresh HTTP header
@@ -360,7 +360,7 @@ class GenericIE(InfoExtractor):
'ext': 'mp4',
'uploader': 'www.hodiho.fr',
'title': 'R\u00e9gis plante sa Jeep',
- }
+ },
},
# bandcamp page with custom domain
{
@@ -438,19 +438,19 @@ class GenericIE(InfoExtractor):
'id': '370908',
'title': 'Госзаказ. День 3',
'ext': 'mp4',
- }
+ },
}, {
'info_dict': {
'id': '370905',
'title': 'Госзаказ. День 2',
'ext': 'mp4',
- }
+ },
}, {
'info_dict': {
'id': '370902',
'title': 'Госзаказ. День 1',
'ext': 'mp4',
- }
+ },
}],
'params': {
# m3u8 download
@@ -491,7 +491,7 @@ class GenericIE(InfoExtractor):
'title': 'Hidden miracles of the natural world',
'uploader': 'Louie Schwartzberg',
'description': 'md5:8145d19d320ff3e52f28401f4c4283b9',
- }
+ },
},
# nowvideo embed hidden behind percent encoding
{
@@ -516,7 +516,7 @@ class GenericIE(InfoExtractor):
'upload_date': '20140320',
},
'params': {
- 'skip_download': 'Requires rtmpdump'
+ 'skip_download': 'Requires rtmpdump',
},
'skip': 'video gone',
},
@@ -537,8 +537,8 @@ class GenericIE(InfoExtractor):
'skip_download': True,
},
'expected_warnings': [
- 'Forbidden'
- ]
+ 'Forbidden',
+ ],
},
# Condé Nast embed
{
@@ -548,7 +548,7 @@ class GenericIE(InfoExtractor):
'id': '53501be369702d3275860000',
'ext': 'mp4',
'title': 'Honda’s New Asimo Robot Is More Human Than Ever',
- }
+ },
},
# Dailymotion embed
{
@@ -595,7 +595,7 @@ class GenericIE(InfoExtractor):
'add_ie': ['Youtube'],
'params': {
'skip_download': True,
- }
+ },
},
# MTVServices embed
{
@@ -624,7 +624,7 @@ class GenericIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
},
# Flowplayer
{
@@ -636,7 +636,7 @@ class GenericIE(InfoExtractor):
'age_limit': 18,
'uploader': 'www.handjobhub.com',
'title': 'Busty Blonde Siri Tit Fuck While Wank at HandjobHub.com',
- }
+ },
},
# MLB embed
{
@@ -680,7 +680,7 @@ class GenericIE(InfoExtractor):
'uploader': 'Sophos Security',
'title': 'Chet Chat 171 - Oct 29, 2014',
'upload_date': '20141029',
- }
+ },
},
# Soundcloud multiple embeds
{
@@ -714,7 +714,7 @@ class GenericIE(InfoExtractor):
'ext': 'flv',
'upload_date': '20141112',
'title': 'Rosetta #CometLanding webcast HL 10',
- }
+ },
},
# Another Livestream embed, without 'new.' in URL
{
@@ -749,7 +749,7 @@ class GenericIE(InfoExtractor):
'title': 'Underground Wellness Radio - Jack Tips: 5 Steps to Permanent Gut Healing',
'thumbnail': 'https://assets.libsyn.com/secure/item/3793998/?height=90&width=90',
'duration': 3989.0,
- }
+ },
},
# Cinerama player
{
@@ -759,7 +759,7 @@ class GenericIE(InfoExtractor):
'ext': 'mp4',
'uploader': 'www.abc.net.au',
'title': 'Game of Thrones with dice - Dungeons and Dragons fantasy role-playing game gets new life - 19/01/2015',
- }
+ },
},
# embedded viddler video
{
@@ -876,7 +876,7 @@ class GenericIE(InfoExtractor):
'info_dict': {
'id': 'aanslagen-kopenhagen',
'title': 'Aanslagen Kopenhagen',
- }
+ },
},
# Zapiks embed
{
@@ -885,7 +885,7 @@ class GenericIE(InfoExtractor):
'id': '118046',
'ext': 'mp4',
'title': 'EP3S5 - Bon Appétit - Baqueira Mi Corazon !',
- }
+ },
},
# Kaltura embed (different embed code)
{
@@ -924,11 +924,11 @@ class GenericIE(InfoExtractor):
},
'add_ie': ['Kaltura'],
'expected_warnings': [
- 'Could not send HEAD request'
+ 'Could not send HEAD request',
],
'params': {
'skip_download': True,
- }
+ },
},
{
# Kaltura embedded, some fileExt broken (#11480)
@@ -1055,7 +1055,7 @@ class GenericIE(InfoExtractor):
'info_dict': {
'id': '8RUoRhRi',
'ext': 'mp4',
- 'title': "Fox & Friends Says Protecting Atheists From Discrimination Is Anti-Christian!",
+ 'title': 'Fox & Friends Says Protecting Atheists From Discrimination Is Anti-Christian!',
'description': 'md5:e1a46ad1650e3a5ec7196d432799127f',
'timestamp': 1428207000,
'upload_date': '20150405',
@@ -1131,7 +1131,7 @@ class GenericIE(InfoExtractor):
'uploader': 'clickhole',
'upload_date': '20150527',
'timestamp': 1432744860,
- }
+ },
},
# SnagFilms embed
{
@@ -1140,7 +1140,7 @@ class GenericIE(InfoExtractor):
'id': '74849a00-85a9-11e1-9660-123139220831',
'ext': 'mp4',
'title': '#whilewewatch',
- }
+ },
},
# AdobeTVVideo embed
{
@@ -1436,7 +1436,7 @@ class GenericIE(InfoExtractor):
'upload_date': '20211217',
'thumbnail': 'https://www.megatv.com/wp-content/uploads/2021/12/tsiodras-mitsotakis-1024x545.jpg',
},
- }]
+ }],
},
{
'url': 'https://www.ertnews.gr/video/manolis-goyalles-o-anthropos-piso-apo-ti-diadiktyaki-vasilopita/',
@@ -1547,7 +1547,7 @@ class GenericIE(InfoExtractor):
'id': '0f64ce6',
'title': 'vl14062007715967',
'ext': 'mp4',
- }
+ },
},
{
'url': 'http://www.heidelberg-laureate-forum.org/blog/video/lecture-friday-september-23-2016-sir-c-antony-r-hoare/',
@@ -1559,7 +1559,7 @@ class GenericIE(InfoExtractor):
'description': 'md5:5a51db84a62def7b7054df2ade403c6c',
'timestamp': 1474354800,
'upload_date': '20160920',
- }
+ },
},
{
'url': 'http://www.kidzworld.com/article/30935-trolls-the-beat-goes-on-interview-skylar-astin-and-amanda-leighton',
@@ -1651,7 +1651,7 @@ class GenericIE(InfoExtractor):
'info_dict': {
'id': '83645793',
'title': 'Lock up and get excited',
- 'ext': 'mp4'
+ 'ext': 'mp4',
},
'skip': 'TODO: fix nested playlists processing in tests',
},
@@ -1727,7 +1727,7 @@ class GenericIE(InfoExtractor):
'upload_date': '20220110',
'thumbnail': 'https://opentv-static.siliconweb.com/imgHandler/1920/70bc39fa-895b-4918-a364-c39d2135fc6d.jpg',
- }
+ },
},
{
# blogger embed
@@ -1897,8 +1897,8 @@ class GenericIE(InfoExtractor):
'timestamp': 1501941939.0,
'title': 'That small heart attack.',
'upload_date': '20170805',
- 'uploader': 'Antw87'
- }
+ 'uploader': 'Antw87',
+ },
},
{
# 1080p Reddit-hosted video that will redirect and be processed by RedditIE
@@ -1910,8 +1910,8 @@ class GenericIE(InfoExtractor):
'title': "The game Didn't want me to Knife that Guy I guess",
'uploader': 'paraf1ve',
'timestamp': 1636788683.0,
- 'upload_date': '20211113'
- }
+ 'upload_date': '20211113',
+ },
},
{
# MainStreaming player
@@ -1923,15 +1923,15 @@ class GenericIE(InfoExtractor):
'ext': 'mp4',
'live_status': 'not_live',
'thumbnail': r're:https?://[A-Za-z0-9-]*\.msvdn.net/image/\w+/poster',
- 'duration': 1512
- }
+ 'duration': 1512,
+ },
},
{
# Multiple gfycat iframe embeds
'url': 'https://www.gezip.net/bbs/board.php?bo_table=entertaine&wr_id=613422',
'info_dict': {
'title': '재이, 윤, 세은 황금 드레스를 입고 빛난다',
- 'id': 'board'
+ 'id': 'board',
},
'playlist_count': 8,
},
@@ -1940,18 +1940,18 @@ class GenericIE(InfoExtractor):
'url': 'https://www.gezip.net/bbs/board.php?bo_table=entertaine&wr_id=612199',
'info_dict': {
'title': '옳게 된 크롭 니트 스테이씨 아이사',
- 'id': 'board'
+ 'id': 'board',
},
- 'playlist_count': 6
+ 'playlist_count': 6,
},
{
# Multiple gfycat embeds, with uppercase "IFR" in urls
'url': 'https://kkzz.kr/?vid=2295',
'info_dict': {
'title': '지방시 앰버서더 에스파 카리나 움짤',
- 'id': '?vid=2295'
+ 'id': '?vid=2295',
},
- 'playlist_count': 9
+ 'playlist_count': 9,
},
{
# Panopto embeds
@@ -1984,9 +1984,9 @@ class GenericIE(InfoExtractor):
'url': 'https://www.hs.fi/kotimaa/art-2000008762560.html',
'info_dict': {
'title': 'Koronavirus | Epidemiahuippu voi olla Suomessa ohi, mutta koronaviruksen poistamista yleisvaarallisten tautien joukosta harkitaan vasta syksyllä',
- 'id': 'art-2000008762560'
+ 'id': 'art-2000008762560',
},
- 'playlist_count': 3
+ 'playlist_count': 3,
},
{
# Ruutu embed in hs.fi with a single video
@@ -2015,7 +2015,7 @@ class GenericIE(InfoExtractor):
'thumbnail': 'https://www.filmarkivet.se/wp-content/uploads/parisdmoll2.jpg',
'timestamp': 1652833414,
'age_limit': 0,
- }
+ },
},
{
'url': 'https://www.mollymovieclub.com/p/interstellar?s=r#details',
@@ -2055,7 +2055,7 @@ class GenericIE(InfoExtractor):
'thumbnail': 'https://cdn.jwplayer.com/v2/media/YTmgRiNU/poster.jpg?width=720',
'duration': 5688.0,
'upload_date': '20210111',
- }
+ },
},
{
'note': 'JSON LD with multiple @type',
@@ -2071,7 +2071,7 @@ class GenericIE(InfoExtractor):
'upload_date': '20200411',
'age_limit': 0,
'duration': 111.0,
- }
+ },
},
{
'note': 'JSON LD with unexpected data type',
@@ -2086,7 +2086,7 @@ class GenericIE(InfoExtractor):
'thumbnail': r're:^https://media.autoweek.nl/m/.+\.jpg$',
'age_limit': 0,
'direct': True,
- }
+ },
},
{
'note': 'server returns data in brotli compression by default if `accept-encoding: *` is specified.',
@@ -2148,7 +2148,7 @@ class GenericIE(InfoExtractor):
def report_following_redirect(self, new_url):
"""Report information extraction."""
- self._downloader.to_screen('[redirect] Following redirect to %s' % new_url)
+ self._downloader.to_screen(f'[redirect] Following redirect to {new_url}')
def report_detected(self, name, num=1, note=None):
if num > 1:
@@ -2244,29 +2244,29 @@ class GenericIE(InfoExtractor):
return video_url # not obfuscated
parsed = urllib.parse.urlparse(video_url[len('function/0/'):])
- license = cls._kvs_get_license_token(license_code)
+ license_token = cls._kvs_get_license_token(license_code)
urlparts = parsed.path.split('/')
HASH_LENGTH = 32
- hash = urlparts[3][:HASH_LENGTH]
+ hash_ = urlparts[3][:HASH_LENGTH]
indices = list(range(HASH_LENGTH))
# Swap indices of hash according to the destination calculated from the license token
accum = 0
for src in reversed(range(HASH_LENGTH)):
- accum += license[src]
+ accum += license_token[src]
dest = (src + accum) % HASH_LENGTH
indices[src], indices[dest] = indices[dest], indices[src]
- urlparts[3] = ''.join(hash[index] for index in indices) + urlparts[3][HASH_LENGTH:]
+ urlparts[3] = ''.join(hash_[index] for index in indices) + urlparts[3][HASH_LENGTH:]
return urllib.parse.urlunparse(parsed._replace(path='/'.join(urlparts)))
@staticmethod
- def _kvs_get_license_token(license):
- license = license.replace('$', '')
- license_values = [int(char) for char in license]
+ def _kvs_get_license_token(license_code):
+ license_code = license_code.replace('$', '')
+ license_values = [int(char) for char in license_code]
- modlicense = license.replace('0', '1')
+ modlicense = license_code.replace('0', '1')
center = len(modlicense) // 2
fronthalf = int(modlicense[:center + 1])
backhalf = int(modlicense[center:])
@@ -2338,18 +2338,17 @@ class GenericIE(InfoExtractor):
if default_search == 'auto_warning':
if re.match(r'^(?:url|URL)$', url):
raise ExtractorError(
- 'Invalid URL: %r . Call yt-dlp like this: yt-dlp -v "https://www.youtube.com/watch?v=BaW_jenozKc" ' % url,
+ f'Invalid URL: {url!r} . Call yt-dlp like this: yt-dlp -v "https://www.youtube.com/watch?v=BaW_jenozKc" ',
expected=True)
else:
self.report_warning(
- 'Falling back to youtube search for %s . Set --default-search "auto" to suppress this warning.' % url)
+ f'Falling back to youtube search for {url} . Set --default-search "auto" to suppress this warning.')
return self.url_result('ytsearch:' + url)
if default_search in ('error', 'fixup_error'):
raise ExtractorError(
- '%r is not a valid URL. '
- 'Set --default-search "ytsearch" (or run yt-dlp "ytsearch:%s" ) to search YouTube'
- % (url, url), expected=True)
+ f'{url!r} is not a valid URL. '
+ f'Set --default-search "ytsearch" (or run yt-dlp "ytsearch:{url}" ) to search YouTube', expected=True)
else:
if ':' not in default_search:
default_search += ':'
@@ -2387,7 +2386,7 @@ class GenericIE(InfoExtractor):
info_dict = {
'id': video_id,
'title': self._generic_title(url),
- 'timestamp': unified_timestamp(full_response.headers.get('Last-Modified'))
+ 'timestamp': unified_timestamp(full_response.headers.get('Last-Modified')),
}
# Check for direct link to a video
@@ -2401,7 +2400,7 @@ class GenericIE(InfoExtractor):
subtitles = {}
if format_id.endswith('mpegurl') or ext == 'm3u8':
formats, subtitles = self._extract_m3u8_formats_and_subtitles(url, video_id, 'mp4', headers=headers)
- elif format_id.endswith('mpd') or format_id.endswith('dash+xml') or ext == 'mpd':
+ elif format_id.endswith(('mpd', 'dash+xml')) or ext == 'mpd':
formats, subtitles = self._extract_mpd_formats_and_subtitles(url, video_id, headers=headers)
elif format_id == 'f4m' or ext == 'f4m':
formats = self._extract_f4m_formats(url, video_id, headers=headers)
@@ -2410,7 +2409,7 @@ class GenericIE(InfoExtractor):
'format_id': format_id,
'url': url,
'ext': ext,
- 'vcodec': 'none' if m.group('type') == 'audio' else None
+ 'vcodec': 'none' if m.group('type') == 'audio' else None,
}]
info_dict['direct'] = True
info_dict.update({
@@ -2458,7 +2457,7 @@ class GenericIE(InfoExtractor):
try:
doc = compat_etree_fromstring(webpage)
except xml.etree.ElementTree.ParseError:
- doc = compat_etree_fromstring(webpage.encode('utf-8'))
+ doc = compat_etree_fromstring(webpage.encode())
if doc.tag == 'rss':
self.report_detected('RSS feed')
return self._extract_rss(url, video_id, doc)
@@ -2606,7 +2605,7 @@ class GenericIE(InfoExtractor):
})
# https://docs.videojs.com/player#addRemoteTextTrack
# https://html.spec.whatwg.org/multipage/media.html#htmltrackelement
- for sub_match in re.finditer(rf'(?s){re.escape(varname)}' r'\.addRemoteTextTrack\(({.+?})\s*,\s*(?:true|false)\)', webpage):
+ for sub_match in re.finditer(rf'(?s){re.escape(varname)}' + r'\.addRemoteTextTrack\(({.+?})\s*,\s*(?:true|false)\)', webpage):
sub = self._parse_json(
sub_match.group(1), video_id, transform_source=js_to_json, fatal=False) or {}
sub_src = str_or_none(sub.get('src'))
@@ -2728,7 +2727,7 @@ class GenericIE(InfoExtractor):
REDIRECT_REGEX = r'[0-9]{,2};\s*(?:URL|url)=\'?([^\'"]+)'
found = re.search(
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
- r'(?:[a-z-]+="[^"]+"\s+)*?content="%s' % REDIRECT_REGEX,
+ rf'(?:[a-z-]+="[^"]+"\s+)*?content="{REDIRECT_REGEX}',
webpage)
if not found:
# Look also in Refresh HTTP header
@@ -2772,7 +2771,7 @@ class GenericIE(InfoExtractor):
video_id = os.path.splitext(video_id)[0]
headers = {
- 'referer': actual_url
+ 'referer': actual_url,
}
entry_info_dict = {
@@ -2829,5 +2828,5 @@ class GenericIE(InfoExtractor):
for num, e in enumerate(entries, start=1):
# 'url' results don't have a title
if e.get('title') is not None:
- e['title'] = '%s (%d)' % (e['title'], num)
+ e['title'] = '{} ({})'.format(e['title'], num)
return entries
diff --git a/yt_dlp/extractor/genericembeds.py b/yt_dlp/extractor/genericembeds.py
index 9b4f14dd1..359d38bc0 100644
--- a/yt_dlp/extractor/genericembeds.py
+++ b/yt_dlp/extractor/genericembeds.py
@@ -15,8 +15,8 @@ class HTML5MediaEmbedIE(InfoExtractor):
'title': 'HTML5 Media',
'description': 'md5:933b2d02ceffe7a7a0f3c8326d91cc2a',
},
- 'playlist_count': 2
- }
+ 'playlist_count': 2,
+ },
]
def _extract_from_webpage(self, url, webpage):
@@ -50,7 +50,7 @@ class QuotedHTMLIE(InfoExtractor):
'description': 'md5:6816e1e5a65304bd7898e4c7eb1b26f7',
'age_limit': 0,
},
- 'playlist_count': 2
+ 'playlist_count': 2,
}, {
# Generic iframe embed of TV24UAPlayerIE within data-html
'url': 'https://24tv.ua/harkivyani-zgaduyut-misto-do-viyni-shhemlive-video_n1887584',
@@ -60,7 +60,7 @@ class QuotedHTMLIE(InfoExtractor):
'title': 'Харків\'яни згадують місто до війни: щемливе відео',
'thumbnail': r're:^https?://.*\.jpe?g',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
# YouTube embeds on Squarespace (data-html): https://github.com/ytdl-org/youtube-dl/issues/21294
'url': 'https://www.harvardballetcompany.org/past-productions',
@@ -70,7 +70,7 @@ class QuotedHTMLIE(InfoExtractor):
'age_limit': 0,
'description': 'Past Productions',
},
- 'playlist_mincount': 26
+ 'playlist_mincount': 26,
}, {
# Squarespace video embed, 2019-08-28, data-html
'url': 'http://ootboxford.com',
diff --git a/yt_dlp/extractor/getcourseru.py b/yt_dlp/extractor/getcourseru.py
index 144321ad6..53b881011 100644
--- a/yt_dlp/extractor/getcourseru.py
+++ b/yt_dlp/extractor/getcourseru.py
@@ -17,7 +17,7 @@ class GetCourseRuPlayerIE(InfoExtractor):
'title': '190bdf93f1b29735309853a7a19e24b3',
'ext': 'mp4',
'thumbnail': 'https://preview-htz.kinescopecdn.net/preview/190bdf93f1b29735309853a7a19e24b3/preview.jpg?version=1702370546&host=vh-80',
- 'duration': 1693
+ 'duration': 1693,
},
'skip': 'JWT expired',
}]
@@ -38,7 +38,7 @@ class GetCourseRuPlayerIE(InfoExtractor):
}),
'id': video_id,
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
@@ -47,7 +47,7 @@ class GetCourseRuIE(InfoExtractor):
_DOMAINS = [
'academymel.online',
'marafon.mani-beauty.com',
- 'on.psbook.ru'
+ 'on.psbook.ru',
]
_BASE_URL_RE = rf'https?://(?:(?!player02\.)[^.]+\.getcourse\.(?:ru|io)|{"|".join(map(re.escape, _DOMAINS))})'
_VALID_URL = [
@@ -68,9 +68,9 @@ class GetCourseRuIE(InfoExtractor):
'ext': 'mp4',
'title': 'Промоуроки Академии МЕЛ',
'thumbnail': 'https://preview-htz.kinescopecdn.net/preview/190bdf93f1b29735309853a7a19e24b3/preview.jpg?version=1702370546&host=vh-80',
- 'duration': 1693
+ 'duration': 1693,
},
- }]
+ }],
}, {
'url': 'https://academymel.getcourse.ru/3video_1',
'info_dict': {
@@ -85,9 +85,9 @@ class GetCourseRuIE(InfoExtractor):
'ext': 'mp4',
'title': 'Промоуроки Академии МЕЛ',
'thumbnail': 'https://preview-htz.kinescopecdn.net/preview/190bdf93f1b29735309853a7a19e24b3/preview.jpg?version=1702370546&host=vh-80',
- 'duration': 1693
+ 'duration': 1693,
},
- }]
+ }],
}, {
'url': 'https://academymel.getcourse.ru/pl/teach/control/lesson/view?id=319141781&editMode=0',
'info_dict': {
@@ -101,10 +101,10 @@ class GetCourseRuIE(InfoExtractor):
'ext': 'mp4',
'title': '1. Разминка у стены',
'thumbnail': 'https://preview-htz.vhcdn.com/preview/5a521788e7dc25b4f70c3dff6512d90e/preview.jpg?version=1703223532&host=vh-81',
- 'duration': 704
+ 'duration': 704,
},
}],
- 'skip': 'paid lesson'
+ 'skip': 'paid lesson',
}, {
'url': 'https://manibeauty.getcourse.ru/pl/teach/control/lesson/view?id=272499894',
'info_dict': {
@@ -118,10 +118,10 @@ class GetCourseRuIE(InfoExtractor):
'ext': 'mp4',
'title': 'Мотивация к тренировкам',
'thumbnail': 'https://preview-htz.vhcdn.com/preview/70ed5b9f489dd03b4aff55bfdff71a26/preview.jpg?version=1685115787&host=vh-71',
- 'duration': 30
+ 'duration': 30,
},
}],
- 'skip': 'paid lesson'
+ 'skip': 'paid lesson',
}, {
'url': 'https://gaismasmandalas.getcourse.io/ATLAUTSEVBUT',
'only_matching': True,
diff --git a/yt_dlp/extractor/gettr.py b/yt_dlp/extractor/gettr.py
index b9dc7c63c..2a9d5e7e9 100644
--- a/yt_dlp/extractor/gettr.py
+++ b/yt_dlp/extractor/gettr.py
@@ -38,7 +38,7 @@ class GettrIE(GettrBaseIE):
'timestamp': 1632782451.058,
'duration': 58.5585,
'tags': ['hornofafrica', 'explorations'],
- }
+ },
}, {
'url': 'https://gettr.com/post/p4iahp',
'info_dict': {
@@ -53,7 +53,7 @@ class GettrIE(GettrBaseIE):
'timestamp': 1626594455.017,
'duration': 23,
'tags': 'count:12',
- }
+ },
}, {
# quote post
'url': 'https://gettr.com/post/pxn5b743a9',
@@ -76,7 +76,7 @@ class GettrIE(GettrBaseIE):
def _real_extract(self, url):
post_id = self._match_id(url)
webpage = self._download_webpage(url, post_id)
- api_data = self._call_api('post/%s?incl="poststats|userinfo"' % post_id, post_id)
+ api_data = self._call_api(f'post/{post_id}?incl="poststats|userinfo"', post_id)
post_data = api_data.get('data')
user_data = try_get(api_data, lambda x: x['aux']['uinf'][post_data['uid']], dict) or {}
@@ -106,7 +106,7 @@ class GettrIE(GettrBaseIE):
or self._search_regex(r'^(.+?) on GETTR', self._og_search_title(webpage, default=''), 'uploader', fatal=False))
if uploader:
- title = '%s - %s' % (uploader, title)
+ title = f'{uploader} - {title}'
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
urljoin(self._MEDIA_BASE_URL, vid), post_id, 'mp4',
@@ -157,7 +157,7 @@ class GettrStreamingIE(GettrBaseIE):
'title': 'Day 1: Opening Session of the Grand Jury Proceeding',
'timestamp': 1644080997.164,
'upload_date': '20220205',
- }
+ },
}, {
'url': 'https://gettr.com/streaming/psfmeefcc1',
'info_dict': {
@@ -172,12 +172,12 @@ class GettrStreamingIE(GettrBaseIE):
'duration': 21872.507,
'timestamp': 1643976662.858,
'upload_date': '20220204',
- }
+ },
}]
def _real_extract(self, url):
video_id = self._match_id(url)
- video_info = self._call_api('live/join/%s' % video_id, video_id, data={})
+ video_info = self._call_api(f'live/join/{video_id}', video_id, data={})
live_info = video_info['broadcast']
live_url = url_or_none(live_info.get('url'))
diff --git a/yt_dlp/extractor/giantbomb.py b/yt_dlp/extractor/giantbomb.py
index 112572366..259d39095 100644
--- a/yt_dlp/extractor/giantbomb.py
+++ b/yt_dlp/extractor/giantbomb.py
@@ -22,7 +22,7 @@ class GiantBombIE(InfoExtractor):
'description': 'md5:0aa3aaf2772a41b91d44c63f30dfad24',
'duration': 2399,
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'url': 'https://www.giantbomb.com/shows/ben-stranding/2970-20212',
'only_matching': True,
diff --git a/yt_dlp/extractor/gigya.py b/yt_dlp/extractor/gigya.py
index 7baf8de8d..cc18ee67c 100644
--- a/yt_dlp/extractor/gigya.py
+++ b/yt_dlp/extractor/gigya.py
@@ -15,5 +15,5 @@ class GigyaBaseIE(InfoExtractor):
error_message = auth_info.get('errorDetails') or auth_info.get('errorMessage')
if error_message:
raise ExtractorError(
- 'Unable to login: %s' % error_message, expected=True)
+ f'Unable to login: {error_message}', expected=True)
return auth_info
diff --git a/yt_dlp/extractor/glide.py b/yt_dlp/extractor/glide.py
index d114f3494..b4c868108 100644
--- a/yt_dlp/extractor/glide.py
+++ b/yt_dlp/extractor/glide.py
@@ -12,7 +12,7 @@ class GlideIE(InfoExtractor):
'ext': 'mp4',
'title': "Damon's Glide message",
'thumbnail': r're:^https?://.*?\.cloudfront\.net/.*\.jpg$',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/globalplayer.py b/yt_dlp/extractor/globalplayer.py
index e0c0d58fd..3d4a9304c 100644
--- a/yt_dlp/extractor/globalplayer.py
+++ b/yt_dlp/extractor/globalplayer.py
@@ -37,7 +37,7 @@ class GlobalPlayerBaseIE(InfoExtractor):
'url': 'streamUrl',
'timestamp': (('pubDate', 'startDate'), {unified_timestamp}),
'title': 'title',
- }, get_all=False)
+ }, get_all=False),
}
@@ -111,7 +111,7 @@ class GlobalPlayerLivePlaylistIE(GlobalPlayerBaseIE):
'live_status': 'is_live',
'description': 'md5:e10f5e10b01a7f2c14ba815509fbb38d',
'thumbnail': 'https://images.globalplayer.com/images/551379?width=450&signature=oMLPZIoi5_dBSHnTMREW0Xg76mA=',
- 'title': 're:^Classic FM Hall of Fame.+$'
+ 'title': 're:^Classic FM Hall of Fame.+$',
},
}]
diff --git a/yt_dlp/extractor/globo.py b/yt_dlp/extractor/globo.py
index df98f093c..d72296be6 100644
--- a/yt_dlp/extractor/globo.py
+++ b/yt_dlp/extractor/globo.py
@@ -5,9 +5,6 @@ import random
import re
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..networking import HEADRequest
from ..utils import (
ExtractorError,
@@ -88,7 +85,7 @@ class GloboIE(InfoExtractor):
video_id, 'Getting cookies')
video = self._download_json(
- 'http://api.globovideos.com/videos/%s/playlist' % video_id,
+ f'http://api.globovideos.com/videos/{video_id}/playlist',
video_id)['videos'][0]
if not self.get_param('allow_unplayable_formats') and video.get('encrypted') is True:
self.report_drm(video_id)
@@ -97,14 +94,14 @@ class GloboIE(InfoExtractor):
formats = []
security = self._download_json(
- 'https://playback.video.globo.com/v2/video-session', video_id, 'Downloading security hash for %s' % video_id,
+ 'https://playback.video.globo.com/v2/video-session', video_id, f'Downloading security hash for {video_id}',
headers={'content-type': 'application/json'}, data=json.dumps({
- "player_type": "desktop",
- "video_id": video_id,
- "quality": "max",
- "content_protection": "widevine",
- "vsid": "581b986b-4c40-71f0-5a58-803e579d5fa2",
- "tz": "-3.0:00"
+ 'player_type': 'desktop',
+ 'video_id': video_id,
+ 'quality': 'max',
+ 'content_protection': 'widevine',
+ 'vsid': '581b986b-4c40-71f0-5a58-803e579d5fa2',
+ 'tz': '-3.0:00',
}).encode())
self._request_webpage(HEADRequest(security['sources'][0]['url_template']), video_id, 'Getting locksession cookie')
@@ -114,7 +111,7 @@ class GloboIE(InfoExtractor):
message = security.get('message')
if message:
raise ExtractorError(
- '%s returned error: %s' % (self.IE_NAME, message), expected=True)
+ f'{self.IE_NAME} returned error: {message}', expected=True)
hash_code = security_hash[:2]
padding = '%010d' % random.randint(1, 10000000000)
@@ -128,13 +125,13 @@ class GloboIE(InfoExtractor):
padding += '1'
hash_prefix = '05' + security_hash[:22]
- padded_sign_time = compat_str(int(received_time) + 86400) + padding
+ padded_sign_time = str(int(received_time) + 86400) + padding
md5_data = (received_md5 + padded_sign_time + '0xAC10FD').encode()
signed_md5 = base64.urlsafe_b64encode(hashlib.md5(md5_data).digest()).decode().strip('=')
signed_hash = hash_prefix + padded_sign_time + signed_md5
source = security['sources'][0]['url_parts']
resource_url = source['scheme'] + '://' + source['domain'] + source['path']
- signed_url = '%s?h=%s&k=html5&a=%s' % (resource_url, signed_hash, 'F' if video.get('subscriber_only') else 'A')
+ signed_url = '{}?h={}&k=html5&a={}'.format(resource_url, signed_hash, 'F' if video.get('subscriber_only') else 'A')
fmts, subtitles = self._extract_m3u8_formats_and_subtitles(
signed_url, video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
@@ -230,7 +227,7 @@ class GloboArticleIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if GloboIE.suitable(url) else super(GloboArticleIE, cls).suitable(url)
+ return False if GloboIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
@@ -239,7 +236,7 @@ class GloboArticleIE(InfoExtractor):
for video_regex in self._VIDEOID_REGEXES:
video_ids.extend(re.findall(video_regex, webpage))
entries = [
- self.url_result('globo:%s' % video_id, GloboIE.ie_key())
+ self.url_result(f'globo:{video_id}', GloboIE.ie_key())
for video_id in orderedSet(video_ids)]
title = self._og_search_title(webpage).strip()
description = self._html_search_meta('description', webpage)
diff --git a/yt_dlp/extractor/glomex.py b/yt_dlp/extractor/glomex.py
index 515f3c567..35ffad56c 100644
--- a/yt_dlp/extractor/glomex.py
+++ b/yt_dlp/extractor/glomex.py
@@ -49,15 +49,15 @@ class GlomexBaseIE(InfoExtractor):
video_id_type = self._get_videoid_type(video_id)
return self._download_json(
self._API_URL,
- video_id, 'Downloading %s JSON' % video_id_type,
- 'Unable to download %s JSON' % video_id_type,
+ video_id, f'Downloading {video_id_type} JSON',
+ f'Unable to download {video_id_type} JSON',
query=query)
def _download_and_extract_api_data(self, video_id, integration, current_url):
api_data = self._download_api_data(video_id, integration, current_url)
videos = api_data['videos']
if not videos:
- raise ExtractorError('no videos found for %s' % video_id)
+ raise ExtractorError(f'no videos found for {video_id}')
videos = [self._extract_api_data(video, video_id) for video in videos]
return videos[0] if len(videos) == 1 else self.playlist_result(videos, video_id)
diff --git a/yt_dlp/extractor/gmanetwork.py b/yt_dlp/extractor/gmanetwork.py
index 62fff4ead..ecef1e16a 100644
--- a/yt_dlp/extractor/gmanetwork.py
+++ b/yt_dlp/extractor/gmanetwork.py
@@ -33,7 +33,7 @@ class GMANetworkVideoIE(InfoExtractor):
'channel': 'YoüLOL',
'availability': 'public',
'release_date': '20220919',
- }
+ },
}, {
'url': 'https://www.gmanetwork.com/fullepisodes/home/more_than_words/87059/more-than-words-full-episode-80/video?section=home',
'info_dict': {
@@ -60,7 +60,7 @@ class GMANetworkVideoIE(InfoExtractor):
'tags': 'count:29',
'view_count': int,
'uploader_url': 'http://www.youtube.com/user/GMANETWORK',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/go.py b/yt_dlp/extractor/go.py
index fba98d79f..bbb23ffc0 100644
--- a/yt_dlp/extractor/go.py
+++ b/yt_dlp/extractor/go.py
@@ -1,7 +1,6 @@
import re
from .adobepass import AdobePassIE
-from ..compat import compat_str
from ..utils import (
ExtractorError,
determine_ext,
@@ -50,14 +49,14 @@ class GoIE(AdobePassIE):
_VALID_URL = r'''(?x)
https?://
(?P<sub_domain>
- (?:%s\.)?go|fxnow\.fxnetworks|
+ (?:{}\.)?go|fxnow\.fxnetworks|
(?:www\.)?(?:abc|freeform|disneynow)
)\.com/
(?:
(?:[^/]+/)*(?P<id>[Vv][Dd][Kk][Aa]\w+)|
(?:[^/]+/)*(?P<display_id>[^/?\#]+)
)
- ''' % r'\.|'.join(list(_SITE_INFO.keys()))
+ '''.format(r'\.|'.join(list(_SITE_INFO.keys())))
_TESTS = [{
'url': 'http://abc.go.com/shows/designated-survivor/video/most-recent/VDKA3807643',
'info_dict': {
@@ -94,7 +93,7 @@ class GoIE(AdobePassIE):
'series': 'Shadowhunters',
'episode_number': 1,
'timestamp': 1483387200,
- 'ext': 'mp4'
+ 'ext': 'mp4',
},
'params': {
'geo_bypass_ip_block': '3.244.239.0/24',
@@ -168,7 +167,7 @@ class GoIE(AdobePassIE):
def _extract_videos(self, brand, video_id='-1', show_id='-1'):
display_id = video_id if video_id != '-1' else show_id
return self._download_json(
- 'http://api.contents.watchabc.go.com/vp2/ws/contents/3000/videos/%s/001/-1/%s/-1/%s/-1/-1.json' % (brand, show_id, video_id),
+ f'http://api.contents.watchabc.go.com/vp2/ws/contents/3000/videos/{brand}/001/-1/{show_id}/-1/{video_id}/-1/-1.json',
display_id)['video']
def _real_extract(self, url):
@@ -191,7 +190,7 @@ class GoIE(AdobePassIE):
video_id = try_get(
layout,
(lambda x: x['videoid'], lambda x: x['video']['id']),
- compat_str)
+ str)
if not video_id:
video_id = self._search_regex(
(
@@ -201,7 +200,7 @@ class GoIE(AdobePassIE):
# page.analytics.videoIdCode
r'\bvideoIdCode["\']\s*:\s*["\']((?:vdka|VDKA)\w+)',
# https://abc.com/shows/the-rookie/episode-guide/season-02/03-the-bet
- r'\b(?:video)?id["\']\s*:\s*["\'](VDKA\w+)'
+ r'\b(?:video)?id["\']\s*:\s*["\'](VDKA\w+)',
), webpage, 'video id', default=video_id)
if not site_info:
brand = self._search_regex(
@@ -266,7 +265,7 @@ class GoIE(AdobePassIE):
self.raise_geo_restricted(
error['message'], countries=['US'])
error_message = ', '.join([error['message'] for error in errors])
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error_message), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error_message}', expected=True)
asset_url += '?' + entitlement['uplynkData']['sessionKey']
fmts, subs = self._extract_m3u8_formats_and_subtitles(
asset_url, video_id, 'mp4', m3u8_id=format_id or 'hls', fatal=False)
@@ -280,7 +279,7 @@ class GoIE(AdobePassIE):
}
if re.search(r'(?:/mp4/source/|_source\.mp4)', asset_url):
f.update({
- 'format_id': ('%s-' % format_id if format_id else '') + 'SOURCE',
+ 'format_id': (f'{format_id}-' if format_id else '') + 'SOURCE',
'quality': 1,
})
else:
@@ -288,7 +287,7 @@ class GoIE(AdobePassIE):
if mobj:
height = int(mobj.group(2))
f.update({
- 'format_id': ('%s-' % format_id if format_id else '') + '%dP' % height,
+ 'format_id': (f'{format_id}-' if format_id else '') + f'{height}P',
'width': int(mobj.group(1)),
'height': height,
})
diff --git a/yt_dlp/extractor/godresource.py b/yt_dlp/extractor/godresource.py
index 276a6c7fe..35cc30cd8 100644
--- a/yt_dlp/extractor/godresource.py
+++ b/yt_dlp/extractor/godresource.py
@@ -24,7 +24,7 @@ class GodResourceIE(InfoExtractor):
'channel': 'Stedfast Baptist Church',
'upload_date': '20240320',
'title': 'GodResource video #A01mTKjyf6w',
- }
+ },
}, {
# mp4 link
'url': 'https://new.godresource.com/video/01DXmBbQv_X',
@@ -39,7 +39,7 @@ class GodResourceIE(InfoExtractor):
'channel': 'Documentaries',
'title': 'The Sodomite Deception',
'upload_date': '20230629',
- }
+ },
}]
def _real_extract(self, url):
@@ -56,7 +56,7 @@ class GodResourceIE(InfoExtractor):
elif ext == 'mp4':
formats, subtitles = [{
'url': video_url,
- 'ext': ext
+ 'ext': ext,
}], {}
else:
raise ExtractorError(f'Unexpected video format {ext}')
@@ -74,6 +74,6 @@ class GodResourceIE(InfoExtractor):
'channel': ('channelName', {str}),
'channel_id': ('channelId', {str_or_none}),
'timestamp': ('streamDateCreated', {unified_timestamp}),
- 'modified_timestamp': ('streamDataModified', {unified_timestamp})
- })
+ 'modified_timestamp': ('streamDataModified', {unified_timestamp}),
+ }),
}
diff --git a/yt_dlp/extractor/godtube.py b/yt_dlp/extractor/godtube.py
index 35fb7a9c9..f4496ac5d 100644
--- a/yt_dlp/extractor/godtube.py
+++ b/yt_dlp/extractor/godtube.py
@@ -30,7 +30,7 @@ class GodTubeIE(InfoExtractor):
video_id = mobj.group('id')
config = self._download_xml(
- 'http://www.godtube.com/resource/mediaplayer/%s.xml' % video_id.lower(),
+ f'http://www.godtube.com/resource/mediaplayer/{video_id.lower()}.xml',
video_id, 'Downloading player config XML')
video_url = config.find('file').text
@@ -40,7 +40,7 @@ class GodTubeIE(InfoExtractor):
thumbnail = config.find('image').text
media = self._download_xml(
- 'http://www.godtube.com/media/xml/?v=%s' % video_id, video_id, 'Downloading media XML')
+ f'http://www.godtube.com/media/xml/?v={video_id}', video_id, 'Downloading media XML')
title = media.find('title').text
diff --git a/yt_dlp/extractor/gofile.py b/yt_dlp/extractor/gofile.py
index fac088462..a9777a594 100644
--- a/yt_dlp/extractor/gofile.py
+++ b/yt_dlp/extractor/gofile.py
@@ -20,8 +20,8 @@ class GofileIE(InfoExtractor):
'title': 'nuuh',
'release_timestamp': 1638338704,
'release_date': '20211201',
- }
- }]
+ },
+ }],
}, {
'url': 'https://gofile.io/d/is8lKr',
'info_dict': {
@@ -63,7 +63,7 @@ class GofileIE(InfoExtractor):
query_params = {'wt': '4fd6sg89d7s6'} # From https://gofile.io/dist/js/alljs.js
password = self.get_param('videopassword')
if password:
- query_params['password'] = hashlib.sha256(password.encode('utf-8')).hexdigest()
+ query_params['password'] = hashlib.sha256(password.encode()).hexdigest()
files = self._download_json(
f'https://api.gofile.io/contents/{file_id}', file_id, 'Getting filelist',
query=query_params, headers={'Authorization': f'Bearer {self._TOKEN}'})
@@ -89,7 +89,7 @@ class GofileIE(InfoExtractor):
'title': file['name'].rsplit('.', 1)[0],
'url': file_url,
'filesize': file.get('size'),
- 'release_timestamp': file.get('createTime')
+ 'release_timestamp': file.get('createTime'),
}
if not found_files:
diff --git a/yt_dlp/extractor/golem.py b/yt_dlp/extractor/golem.py
index c33d95019..90d2fe6c2 100644
--- a/yt_dlp/extractor/golem.py
+++ b/yt_dlp/extractor/golem.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
determine_ext,
)
@@ -20,7 +18,7 @@ class GolemIE(InfoExtractor):
'title': 'iPhone 6 und 6 Plus - Test',
'duration': 300.44,
'filesize': 65309548,
- }
+ },
}
_PREFIX = 'http://video.golem.de'
@@ -29,7 +27,7 @@ class GolemIE(InfoExtractor):
video_id = self._match_id(url)
config = self._download_xml(
- 'https://video.golem.de/xml/{0}.xml'.format(video_id), video_id)
+ f'https://video.golem.de/xml/{video_id}.xml', video_id)
info = {
'id': video_id,
@@ -44,8 +42,8 @@ class GolemIE(InfoExtractor):
continue
formats.append({
- 'format_id': compat_str(e.tag),
- 'url': compat_urlparse.urljoin(self._PREFIX, url),
+ 'format_id': str(e.tag),
+ 'url': urllib.parse.urljoin(self._PREFIX, url),
'height': self._int(e.get('height'), 'height'),
'width': self._int(e.get('width'), 'width'),
'filesize': self._int(e.findtext('filesize'), 'filesize'),
@@ -59,7 +57,7 @@ class GolemIE(InfoExtractor):
if not url:
continue
thumbnails.append({
- 'url': compat_urlparse.urljoin(self._PREFIX, url),
+ 'url': urllib.parse.urljoin(self._PREFIX, url),
'width': self._int(e.get('width'), 'thumbnail width'),
'height': self._int(e.get('height'), 'thumbnail height'),
})
diff --git a/yt_dlp/extractor/googledrive.py b/yt_dlp/extractor/googledrive.py
index c19192cfa..dfba2d3ba 100644
--- a/yt_dlp/extractor/googledrive.py
+++ b/yt_dlp/extractor/googledrive.py
@@ -1,8 +1,8 @@
import re
+import urllib.parse
from .common import InfoExtractor
from .youtube import YoutubeIE
-from ..compat import compat_parse_qs
from ..utils import (
ExtractorError,
bug_reports_message,
@@ -39,7 +39,7 @@ class GoogleDriveIE(InfoExtractor):
'title': 'Big Buck Bunny.mp4',
'duration': 45,
'thumbnail': 'https://drive.google.com/thumbnail?id=0ByeS4oOUV-49Zzh4R1J6R09zazQ',
- }
+ },
}, {
# has itag 50 which is not in YoutubeIE._formats (royalty Free music from 1922)
'url': 'https://drive.google.com/uc?id=1IP0o8dHcQrIHGgVyp0Ofvx2cGfLzyO1x',
@@ -88,7 +88,7 @@ class GoogleDriveIE(InfoExtractor):
r'<iframe[^>]+src="https?://(?:video\.google\.com/get_player\?.*?docid=|(?:docs|drive)\.google\.com/file/d/)(?P<id>[a-zA-Z0-9_-]{28,})',
webpage)
if mobj:
- yield 'https://drive.google.com/file/d/%s' % mobj.group('id')
+ yield 'https://drive.google.com/file/d/{}'.format(mobj.group('id'))
def _download_subtitles_xml(self, video_id, subtitles_id, hl):
if self._captions_xml:
@@ -166,7 +166,7 @@ class GoogleDriveIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- video_info = compat_parse_qs(self._download_webpage(
+ video_info = urllib.parse.parse_qs(self._download_webpage(
'https://drive.google.com/get_video_info',
video_id, 'Downloading video webpage', query={'docid': video_id}))
@@ -218,8 +218,8 @@ class GoogleDriveIE(InfoExtractor):
def request_source_file(source_url, kind, data=None):
return self._request_webpage(
- source_url, video_id, note='Requesting %s file' % kind,
- errnote='Unable to request %s file' % kind, fatal=False, data=data)
+ source_url, video_id, note=f'Requesting {kind} file',
+ errnote=f'Unable to request {kind} file', fatal=False, data=data)
urlh = request_source_file(source_url, 'source')
if urlh:
def add_source_format(urlh):
@@ -270,7 +270,7 @@ class GoogleDriveIE(InfoExtractor):
if ttsurl:
# the video Id for subtitles will be the last value in the ttsurl
# query string
- subtitles_id = ttsurl.encode('utf-8').decode(
+ subtitles_id = ttsurl.encode().decode(
'unicode_escape').split('=')[-1]
self.cookiejar.clear(domain='.google.com', path='/', name='NID')
@@ -294,7 +294,7 @@ class GoogleDriveFolderIE(InfoExtractor):
'url': 'https://drive.google.com/drive/folders/1dQ4sx0-__Nvg65rxTSgQrl7VyW_FZ9QI',
'info_dict': {
'id': '1dQ4sx0-__Nvg65rxTSgQrl7VyW_FZ9QI',
- 'title': 'Forrest'
+ 'title': 'Forrest',
},
'playlist_count': 3,
}]
@@ -312,13 +312,13 @@ GET %s
def _call_api(self, folder_id, key, data, **kwargs):
response = self._download_webpage(
'https://clients6.google.com/batch/drive/v2beta',
- folder_id, data=data.encode('utf-8'),
+ folder_id, data=data.encode(),
headers={
'Content-Type': 'text/plain;charset=UTF-8;',
'Origin': 'https://drive.google.com',
}, query={
'$ct': f'multipart/mixed; boundary="{self._BOUNDARY}"',
- 'key': key
+ 'key': key,
}, **kwargs)
return self._search_json('', response, 'api response', folder_id, **kwargs) or {}
diff --git a/yt_dlp/extractor/googlepodcasts.py b/yt_dlp/extractor/googlepodcasts.py
index 8b2351ba8..8d1cc4fa1 100644
--- a/yt_dlp/extractor/googlepodcasts.py
+++ b/yt_dlp/extractor/googlepodcasts.py
@@ -48,7 +48,7 @@ class GooglePodcastsIE(GooglePodcastsBaseIE):
'timestamp': 1609606800,
'duration': 2901,
'series': "Wait Wait... Don't Tell Me!",
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/goplay.py b/yt_dlp/extractor/goplay.py
index 7a98e0f31..dfe5afe63 100644
--- a/yt_dlp/extractor/goplay.py
+++ b/yt_dlp/extractor/goplay.py
@@ -31,7 +31,7 @@ class GoPlayIE(InfoExtractor):
'episode': 'Episode 2',
'episode_number': 2,
},
- 'skip': 'This video is only available for registered users'
+ 'skip': 'This video is only available for registered users',
}, {
'url': 'https://www.goplay.be/video/a-family-for-thr-holidays-s1-aflevering-1#autoplay',
'info_dict': {
@@ -39,7 +39,7 @@ class GoPlayIE(InfoExtractor):
'ext': 'mp4',
'title': 'A Family for the Holidays',
},
- 'skip': 'This video is only available for registered users'
+ 'skip': 'This video is only available for registered users',
}, {
'url': 'https://www.goplay.be/video/de-mol/de-mol-s11/de-mol-s11-aflevering-1#autoplay',
'info_dict': {
@@ -50,12 +50,12 @@ class GoPlayIE(InfoExtractor):
'series': 'De Mol',
'season_number': 11,
'episode_number': 1,
- 'season': 'Season 11'
+ 'season': 'Season 11',
},
'params': {
- 'skip_download': True
+ 'skip_download': True,
},
- 'skip': 'This video is only available for registered users'
+ 'skip': 'This video is only available for registered users',
}]
_id_token = None
@@ -79,7 +79,7 @@ class GoPlayIE(InfoExtractor):
if movie:
video_id = movie['videoUuid']
info_dict = {
- 'title': movie.get('title')
+ 'title': movie.get('title'),
}
else:
episode = traverse_obj(video_data, ('playlists', ..., 'episodes', lambda _, v: v['pageInfo']['url'] == url), get_all=False)
@@ -94,7 +94,7 @@ class GoPlayIE(InfoExtractor):
api = self._download_json(
f'https://api.goplay.be/web/v1/videos/long-form/{video_id}',
video_id, headers={
- 'Authorization': 'Bearer %s' % self._id_token,
+ 'Authorization': f'Bearer {self._id_token}',
**self.geo_verification_headers(),
})
@@ -154,31 +154,32 @@ class AwsIdp:
self.ie = ie
self.pool_id = pool_id
- if "_" not in self.pool_id:
- raise ValueError("Invalid pool_id format. Should be <region>_<poolid>.")
+ if '_' not in self.pool_id:
+ raise ValueError('Invalid pool_id format. Should be <region>_<poolid>.')
self.client_id = client_id
- self.region = self.pool_id.split("_")[0]
- self.url = "https://cognito-idp.%s.amazonaws.com/" % (self.region,)
+ self.region = self.pool_id.split('_')[0]
+ self.url = f'https://cognito-idp.{self.region}.amazonaws.com/'
# Initialize the values
# https://github.com/aws/amazon-cognito-identity-js/blob/master/src/AuthenticationHelper.js#L22
- self.n_hex = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1' + \
- '29024E088A67CC74020BBEA63B139B22514A08798E3404DD' + \
- 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245' + \
- 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' + \
- 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D' + \
- 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F' + \
- '83655D23DCA3AD961C62F356208552BB9ED529077096966D' + \
- '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' + \
- 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9' + \
- 'DE2BCBF6955817183995497CEA956AE515D2261898FA0510' + \
- '15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64' + \
- 'ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' + \
- 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B' + \
- 'F12FFA06D98A0864D87602733EC86A64521F2B18177B200C' + \
- 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31' + \
- '43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF'
+ self.n_hex = (
+ 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1'
+ '29024E088A67CC74020BBEA63B139B22514A08798E3404DD'
+ 'EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245'
+ 'E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED'
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D'
+ 'C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F'
+ '83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+ '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B'
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9'
+ 'DE2BCBF6955817183995497CEA956AE515D2261898FA0510'
+ '15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64'
+ 'ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7'
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B'
+ 'F12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+ 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31'
+ '43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF')
# https://github.com/aws/amazon-cognito-identity-js/blob/master/src/AuthenticationHelper.js#L49
self.g_hex = '2'
@@ -194,26 +195,26 @@ class AwsIdp:
""" Authenticate with a username and password. """
# Step 1: First initiate an authentication request
auth_data_dict = self.__get_authentication_request(username)
- auth_data = json.dumps(auth_data_dict).encode("utf-8")
+ auth_data = json.dumps(auth_data_dict).encode()
auth_headers = {
- "X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
- "Accept-Encoding": "identity",
- "Content-Type": "application/x-amz-json-1.1"
+ 'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
+ 'Accept-Encoding': 'identity',
+ 'Content-Type': 'application/x-amz-json-1.1',
}
auth_response_json = self.ie._download_json(
self.url, None, data=auth_data, headers=auth_headers,
note='Authenticating username', errnote='Invalid username')
- challenge_parameters = auth_response_json.get("ChallengeParameters")
+ challenge_parameters = auth_response_json.get('ChallengeParameters')
- if auth_response_json.get("ChallengeName") != "PASSWORD_VERIFIER":
- raise AuthenticationException(auth_response_json["message"])
+ if auth_response_json.get('ChallengeName') != 'PASSWORD_VERIFIER':
+ raise AuthenticationException(auth_response_json['message'])
# Step 2: Respond to the Challenge with a valid ChallengeResponse
challenge_request = self.__get_challenge_response_request(challenge_parameters, password)
- challenge_data = json.dumps(challenge_request).encode("utf-8")
+ challenge_data = json.dumps(challenge_request).encode()
challenge_headers = {
- "X-Amz-Target": "AWSCognitoIdentityProviderService.RespondToAuthChallenge",
- "Content-Type": "application/x-amz-json-1.1"
+ 'X-Amz-Target': 'AWSCognitoIdentityProviderService.RespondToAuthChallenge',
+ 'Content-Type': 'application/x-amz-json-1.1',
}
auth_response_json = self.ie._download_json(
self.url, None, data=challenge_data, headers=challenge_headers,
@@ -223,7 +224,7 @@ class AwsIdp:
raise InvalidLoginException(auth_response_json['message'])
return (
auth_response_json['AuthenticationResult']['IdToken'],
- auth_response_json['AuthenticationResult']['RefreshToken']
+ auth_response_json['AuthenticationResult']['RefreshToken'],
)
def __get_authentication_request(self, username):
@@ -234,15 +235,14 @@ class AwsIdp:
:return: A full Authorization request.
:rtype: dict
"""
- auth_request = {
- "AuthParameters": {
- "USERNAME": username,
- "SRP_A": self.__long_to_hex(self.large_a_value)
+ return {
+ 'AuthParameters': {
+ 'USERNAME': username,
+ 'SRP_A': self.__long_to_hex(self.large_a_value),
},
- "AuthFlow": "USER_SRP_AUTH",
- "ClientId": self.client_id
+ 'AuthFlow': 'USER_SRP_AUTH',
+ 'ClientId': self.client_id,
}
- return auth_request
def __get_challenge_response_request(self, challenge_parameters, password):
""" Create a Challenge Response Request object.
@@ -253,11 +253,11 @@ class AwsIdp:
:return: A valid and full request data object to use as a response for a challenge.
:rtype: dict
"""
- user_id = challenge_parameters["USERNAME"]
- user_id_for_srp = challenge_parameters["USER_ID_FOR_SRP"]
- srp_b = challenge_parameters["SRP_B"]
- salt = challenge_parameters["SALT"]
- secret_block = challenge_parameters["SECRET_BLOCK"]
+ user_id = challenge_parameters['USERNAME']
+ user_id_for_srp = challenge_parameters['USER_ID_FOR_SRP']
+ srp_b = challenge_parameters['SRP_B']
+ salt = challenge_parameters['SALT']
+ secret_block = challenge_parameters['SECRET_BLOCK']
timestamp = self.__get_current_timestamp()
@@ -266,7 +266,7 @@ class AwsIdp:
user_id_for_srp,
password,
self.__hex_to_long(srp_b),
- salt
+ salt,
)
secret_block_bytes = base64.standard_b64decode(secret_block)
@@ -278,17 +278,16 @@ class AwsIdp:
bytearray(timestamp, 'utf-8')
hmac_obj = hmac.new(hkdf, msg, digestmod=hashlib.sha256)
signature_string = base64.standard_b64encode(hmac_obj.digest()).decode('utf-8')
- challenge_request = {
- "ChallengeResponses": {
- "USERNAME": user_id,
- "TIMESTAMP": timestamp,
- "PASSWORD_CLAIM_SECRET_BLOCK": secret_block,
- "PASSWORD_CLAIM_SIGNATURE": signature_string
+ return {
+ 'ChallengeResponses': {
+ 'USERNAME': user_id,
+ 'TIMESTAMP': timestamp,
+ 'PASSWORD_CLAIM_SECRET_BLOCK': secret_block,
+ 'PASSWORD_CLAIM_SIGNATURE': signature_string,
},
- "ChallengeName": "PASSWORD_VERIFIER",
- "ClientId": self.client_id
+ 'ChallengeName': 'PASSWORD_VERIFIER',
+ 'ClientId': self.client_id,
}
- return challenge_request
def __get_hkdf_key_for_password(self, username, password, server_b_value, salt):
""" Calculates the final hkdf based on computed S value, and computed U value and the key.
@@ -305,18 +304,17 @@ class AwsIdp:
u_value = self.__calculate_u(self.large_a_value, server_b_value)
if u_value == 0:
raise ValueError('U cannot be zero.')
- username_password = '%s%s:%s' % (self.pool_id.split('_')[1], username, password)
- username_password_hash = self.__hash_sha256(username_password.encode('utf-8'))
+ username_password = '{}{}:{}'.format(self.pool_id.split('_')[1], username, password)
+ username_password_hash = self.__hash_sha256(username_password.encode())
x_value = self.__hex_to_long(self.__hex_hash(self.__pad_hex(salt) + username_password_hash))
g_mod_pow_xn = pow(self.g, x_value, self.big_n)
int_value2 = server_b_value - self.k * g_mod_pow_xn
s_value = pow(int_value2, self.small_a_value + u_value * x_value, self.big_n)
- hkdf = self.__compute_hkdf(
+ return self.__compute_hkdf(
bytearray.fromhex(self.__pad_hex(s_value)),
- bytearray.fromhex(self.__pad_hex(self.__long_to_hex(u_value)))
+ bytearray.fromhex(self.__pad_hex(self.__long_to_hex(u_value))),
)
- return hkdf
def __compute_hkdf(self, ikm, salt):
""" Standard hkdf algorithm
@@ -368,7 +366,7 @@ class AwsIdp:
@staticmethod
def __long_to_hex(long_num):
- return '%x' % long_num
+ return f'{long_num:x}'
@staticmethod
def __hex_to_long(hex_string):
@@ -399,9 +397,9 @@ class AwsIdp:
else:
hash_str = long_int
if len(hash_str) % 2 == 1:
- hash_str = '0%s' % hash_str
+ hash_str = f'0{hash_str}'
elif hash_str[0] in '89ABCDEFabcdef':
- hash_str = '00%s' % hash_str
+ hash_str = f'00{hash_str}'
return hash_str
@staticmethod
@@ -423,11 +421,10 @@ class AwsIdp:
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
time_now = dt.datetime.now(dt.timezone.utc)
- format_string = "{} {} {} %H:%M:%S UTC %Y".format(days[time_now.weekday()], months[time_now.month], time_now.day)
- time_string = time_now.strftime(format_string)
- return time_string
+ format_string = f'{days[time_now.weekday()]} {months[time_now.month]} {time_now.day} %H:%M:%S UTC %Y'
+ return time_now.strftime(format_string)
def __str__(self):
- return "AWS IDP Client for:\nRegion: %s\nPoolId: %s\nAppId: %s" % (
- self.region, self.pool_id.split("_")[1], self.client_id
+ return 'AWS IDP Client for:\nRegion: {}\nPoolId: {}\nAppId: {}'.format(
+ self.region, self.pool_id.split('_')[1], self.client_id,
)
diff --git a/yt_dlp/extractor/gopro.py b/yt_dlp/extractor/gopro.py
index ec1595bc5..9142566c3 100644
--- a/yt_dlp/extractor/gopro.py
+++ b/yt_dlp/extractor/gopro.py
@@ -23,7 +23,7 @@ class GoProIE(InfoExtractor):
'upload_date': '20210919',
'uploader_id': 'fireydive30018',
'duration': 396062,
- }
+ },
}, {
'url': 'https://gopro.com/v/KRm6Vgp2peg4e',
'info_dict': {
@@ -36,7 +36,7 @@ class GoProIE(InfoExtractor):
'uploader_id': 'dc9bcb8b-47d2-47c6-afbc-4c48f9a3769e',
'duration': 45187,
'track': 'The Sky Machine',
- }
+ },
}, {
'url': 'https://gopro.com/v/kVrK9wlJvBMwn',
'info_dict': {
@@ -50,7 +50,7 @@ class GoProIE(InfoExtractor):
'duration': 313075,
'track': 'Battery (Live)',
'artist': 'Metallica',
- }
+ },
}]
def _real_extract(self, url):
@@ -62,7 +62,7 @@ class GoProIE(InfoExtractor):
video_info = metadata['collectionMedia'][0]
media_data = self._download_json(
- 'https://api.gopro.com/media/%s/download' % video_info['id'], video_id)
+ 'https://api.gopro.com/media/{}/download'.format(video_info['id']), video_id)
formats = []
for fmt in try_get(media_data, lambda x: x['_embedded']['variations']) or []:
diff --git a/yt_dlp/extractor/goshgay.py b/yt_dlp/extractor/goshgay.py
index 9a1f32b7e..7bcac9bde 100644
--- a/yt_dlp/extractor/goshgay.py
+++ b/yt_dlp/extractor/goshgay.py
@@ -1,7 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
-)
from ..utils import (
parse_duration,
)
@@ -19,7 +18,7 @@ class GoshgayIE(InfoExtractor):
'thumbnail': r're:^http://.*\.jpg$',
'duration': 80,
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
@@ -32,7 +31,7 @@ class GoshgayIE(InfoExtractor):
r'<span class="duration">\s*-?\s*(.*?)</span>',
webpage, 'duration', fatal=False))
- flashvars = compat_parse_qs(self._html_search_regex(
+ flashvars = urllib.parse.parse_qs(self._html_search_regex(
r'<embed.+?id="flash-player-embed".+?flashvars="([^"]+)"',
webpage, 'flashvars'))
thumbnail = flashvars.get('url_bigthumb', [None])[0]
diff --git a/yt_dlp/extractor/gotostage.py b/yt_dlp/extractor/gotostage.py
index 9c1a6cb91..e47a8eabc 100644
--- a/yt_dlp/extractor/gotostage.py
+++ b/yt_dlp/extractor/gotostage.py
@@ -1,7 +1,6 @@
import json
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import try_get, url_or_none
@@ -15,8 +14,8 @@ class GoToStageIE(InfoExtractor):
'ext': 'mp4',
'title': 'What is GoToStage?',
'thumbnail': r're:^https?://.*\.jpg$',
- 'duration': 93.924711
- }
+ 'duration': 93.924711,
+ },
}, {
'url': 'https://www.gotostage.com/channel/bacc3d3535b34bafacc3f4ef8d4df78a/recording/831e74cd3e0042be96defba627b6f676/watch?source=HOMEPAGE',
'only_matching': True,
@@ -25,7 +24,7 @@ class GoToStageIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
metadata = self._download_json(
- 'https://api.gotostage.com/contents?ids=%s' % video_id,
+ f'https://api.gotostage.com/contents?ids={video_id}',
video_id,
note='Downloading video metadata',
errnote='Unable to download video metadata')[0]
@@ -36,7 +35,7 @@ class GoToStageIE(InfoExtractor):
'productReferenceKey': metadata['productRefKey'],
'firstName': 'foo',
'lastName': 'bar',
- 'email': 'foobar@example.com'
+ 'email': 'foobar@example.com',
}
registration_response = self._download_json(
@@ -49,7 +48,7 @@ class GoToStageIE(InfoExtractor):
errnote='Unable to register user')
content_response = self._download_json(
- 'https://api.gotostage.com/contents/%s/asset' % video_id,
+ f'https://api.gotostage.com/contents/{video_id}/asset',
video_id,
headers={'x-registrantkey': registration_response['registrationKey']},
note='Get download url',
@@ -57,11 +56,11 @@ class GoToStageIE(InfoExtractor):
return {
'id': video_id,
- 'title': try_get(metadata, lambda x: x['title'], compat_str),
- 'url': try_get(content_response, lambda x: x['cdnLocation'], compat_str),
+ 'title': try_get(metadata, lambda x: x['title'], str),
+ 'url': try_get(content_response, lambda x: x['cdnLocation'], str),
'ext': 'mp4',
'thumbnail': url_or_none(try_get(metadata, lambda x: x['thumbnail']['location'])),
'duration': try_get(metadata, lambda x: x['duration'], float),
- 'categories': [try_get(metadata, lambda x: x['category'], compat_str)],
- 'is_live': False
+ 'categories': [try_get(metadata, lambda x: x['category'], str)],
+ 'is_live': False,
}
diff --git a/yt_dlp/extractor/gputechconf.py b/yt_dlp/extractor/gputechconf.py
index 2d13bf491..f31791a71 100644
--- a/yt_dlp/extractor/gputechconf.py
+++ b/yt_dlp/extractor/gputechconf.py
@@ -11,7 +11,7 @@ class GPUTechConfIE(InfoExtractor):
'ext': 'mp4',
'title': 'Coordinating More Than 3 Million CUDA Threads for Social Network Analysis',
'duration': 1219,
- }
+ },
}
def _real_extract(self, url):
@@ -27,6 +27,6 @@ class GPUTechConfIE(InfoExtractor):
return {
'_type': 'url_transparent',
'id': video_id,
- 'url': '%sxml/%s.xml' % (root_path, xml_file_id),
+ 'url': f'{root_path}xml/{xml_file_id}.xml',
'ie_key': 'DigitallySpeaking',
}
diff --git a/yt_dlp/extractor/gronkh.py b/yt_dlp/extractor/gronkh.py
index 1ae0a6893..166890037 100644
--- a/yt_dlp/extractor/gronkh.py
+++ b/yt_dlp/extractor/gronkh.py
@@ -24,7 +24,7 @@ class GronkhIE(InfoExtractor):
'chapters': 'count:3',
'duration': 31463,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://gronkh.tv/stream/536',
'info_dict': {
@@ -36,24 +36,24 @@ class GronkhIE(InfoExtractor):
'upload_date': '20211001',
'duration': 32058,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://gronkh.tv/watch/stream/546',
'only_matching': True,
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://api.gronkh.tv/v1/video/info?episode={id}', id)
- m3u8_url = self._download_json(f'https://api.gronkh.tv/v1/video/playlist?episode={id}', id)['playlist_url']
- formats, subtitles = self._extract_m3u8_formats_and_subtitles(m3u8_url, id)
+ video_id = self._match_id(url)
+ data_json = self._download_json(f'https://api.gronkh.tv/v1/video/info?episode={video_id}', video_id)
+ m3u8_url = self._download_json(f'https://api.gronkh.tv/v1/video/playlist?episode={video_id}', video_id)['playlist_url']
+ formats, subtitles = self._extract_m3u8_formats_and_subtitles(m3u8_url, video_id)
if data_json.get('vtt_url'):
subtitles.setdefault('en', []).append({
'url': data_json['vtt_url'],
'ext': 'vtt',
})
return {
- 'id': id,
+ 'id': video_id,
'title': data_json.get('title'),
'view_count': data_json.get('views'),
'thumbnail': data_json.get('preview_url'),
diff --git a/yt_dlp/extractor/groupon.py b/yt_dlp/extractor/groupon.py
index c1cbda35f..a05fab1f1 100644
--- a/yt_dlp/extractor/groupon.py
+++ b/yt_dlp/extractor/groupon.py
@@ -50,8 +50,7 @@ class GrouponIE(InfoExtractor):
url_pattern, ie_key = self._PROVIDERS.get(provider.lower())
if not url_pattern:
self.report_warning(
- '%s: Unsupported video provider %s, skipping video' %
- (playlist_id, provider))
+ f'{playlist_id}: Unsupported video provider {provider}, skipping video')
continue
entries.append(self.url_result(url_pattern % video_id, ie_key))
diff --git a/yt_dlp/extractor/harpodeon.py b/yt_dlp/extractor/harpodeon.py
index 46eaddb32..aa3b2ca34 100644
--- a/yt_dlp/extractor/harpodeon.py
+++ b/yt_dlp/extractor/harpodeon.py
@@ -15,7 +15,7 @@ class HarpodeonIE(InfoExtractor):
'description': 'md5:47e16bdb41fc8a79c83ab83af11c8b77',
'creator': 'Vitagraph Company of America',
'release_year': 1915,
- }
+ },
}, {
'url': 'https://www.harpodeon.com/preview/The_Smoking_Out_of_Bella_Butts/268068288',
'md5': '6dfea5412845f690c7331be703f884db',
@@ -26,7 +26,7 @@ class HarpodeonIE(InfoExtractor):
'description': 'md5:47e16bdb41fc8a79c83ab83af11c8b77',
'creator': 'Vitagraph Company of America',
'release_year': 1915,
- }
+ },
}, {
'url': 'https://www.harpodeon.com/preview/Behind_the_Screen/421838710',
'md5': '7979df9ca04637282cb7d172ab3a9c3b',
@@ -37,7 +37,7 @@ class HarpodeonIE(InfoExtractor):
'description': 'md5:008972a3dc51fba3965ee517d2ba9155',
'creator': 'Lone Star Corporation',
'release_year': 1916,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/hbo.py b/yt_dlp/extractor/hbo.py
index 2551cfffd..146d8a23d 100644
--- a/yt_dlp/extractor/hbo.py
+++ b/yt_dlp/extractor/hbo.py
@@ -56,7 +56,7 @@ class HBOBaseIE(InfoExtractor):
episode_title = title = xpath_text(video_data, 'title', fatal=True)
series = xpath_text(video_data, 'program')
if series:
- title = '%s - %s' % (series, title)
+ title = f'{series} - {title}'
formats = []
for source in xpath_element(video_data, 'videos', 'sources', True):
@@ -69,7 +69,7 @@ class HBOBaseIE(InfoExtractor):
height = format_info.get('height')
fmt = {
'url': path,
- 'format_id': 'http%s' % ('-%dp' % height if height else ''),
+ 'format_id': 'http{}'.format(f'-{height}p' if height else ''),
'width': format_info.get('width'),
'height': height,
}
@@ -107,7 +107,7 @@ class HBOBaseIE(InfoExtractor):
else:
format_info = self._FORMATS_INFO.get(source.tag, {})
formats.append({
- 'format_id': 'http-%s' % source.tag,
+ 'format_id': f'http-{source.tag}',
'url': video_url,
'width': format_info.get('width'),
'height': format_info.get('height'),
@@ -133,7 +133,7 @@ class HBOBaseIE(InfoExtractor):
subtitles = {
'en': [{
'url': caption_url,
- 'ext': 'ttml'
+ 'ext': 'ttml',
}],
}
diff --git a/yt_dlp/extractor/heise.py b/yt_dlp/extractor/heise.py
index 27d737c04..01b700b15 100644
--- a/yt_dlp/extractor/heise.py
+++ b/yt_dlp/extractor/heise.py
@@ -105,7 +105,7 @@ class HeiseIE(InfoExtractor):
'description': 'md5:fa164d8c8707dff124a9626d39205f5d',
'timestamp': 1414825200,
'upload_date': '20141101',
- }
+ },
}, {
'url': 'http://www.heise.de/ct/artikel/c-t-uplink-3-3-Owncloud-Tastaturen-Peilsender-Smartphone-2403911.html',
'only_matching': True,
@@ -156,7 +156,7 @@ class HeiseIE(InfoExtractor):
r'entry-id=(["\'])(?P<id>(?:(?!\1).)+)\1', webpage, 'kaltura id',
default=None, group='id')
if kaltura_id:
- return _make_kaltura_result('kaltura:2238431:%s' % kaltura_id)
+ return _make_kaltura_result(f'kaltura:2238431:{kaltura_id}')
yt_urls = tuple(YoutubeIE._extract_embed_urls(url, webpage))
if yt_urls:
@@ -191,7 +191,7 @@ class HeiseIE(InfoExtractor):
formats.append({
'url': video_url,
'format_note': label,
- 'format_id': '%s_%s' % (ext, label),
+ 'format_id': f'{ext}_{label}',
'height': height,
})
diff --git a/yt_dlp/extractor/hidive.py b/yt_dlp/extractor/hidive.py
index df6868df6..0cbe9913c 100644
--- a/yt_dlp/extractor/hidive.py
+++ b/yt_dlp/extractor/hidive.py
@@ -57,7 +57,7 @@ class HiDiveIE(InfoExtractor):
'profileId': profile_id,
'hash': self._search_regex(
r'\<button [^>]+?data-hash="(\w+)"', login_webpage, 'profile id hash'),
- 'returnUrl': '/dashboard'
+ 'returnUrl': '/dashboard',
}))
def _call_api(self, video_id, title, key, data={}, **kwargs):
@@ -80,7 +80,7 @@ class HiDiveIE(InfoExtractor):
self.raise_geo_restricted()
if restriction and restriction != 'None':
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, restriction), expected=True)
+ f'{self.IE_NAME} said: {restriction}', expected=True)
formats, parsed_urls = [], {None}
for rendition_id, rendition in settings['renditions'].items():
@@ -115,5 +115,5 @@ class HiDiveIE(InfoExtractor):
self._search_regex(r's(\d+)', key, 'season number', default=None)),
'episode_number': int_or_none(
self._search_regex(r'e(\d+)', key, 'episode number', default=None)),
- 'http_headers': {'Referer': url}
+ 'http_headers': {'Referer': url},
}
diff --git a/yt_dlp/extractor/historicfilms.py b/yt_dlp/extractor/historicfilms.py
index c428feede..714f65114 100644
--- a/yt_dlp/extractor/historicfilms.py
+++ b/yt_dlp/extractor/historicfilms.py
@@ -33,7 +33,7 @@ class HistoricFilmsIE(InfoExtractor):
duration = parse_duration(self._html_search_meta(
'duration', webpage, 'duration'))
- video_url = 'http://www.historicfilms.com/video/%s_%s_web.mov' % (tape_id, video_id)
+ video_url = f'http://www.historicfilms.com/video/{tape_id}_{video_id}_web.mov'
return {
'id': video_id,
diff --git a/yt_dlp/extractor/hitrecord.py b/yt_dlp/extractor/hitrecord.py
index 902af44fa..3c3d7f942 100644
--- a/yt_dlp/extractor/hitrecord.py
+++ b/yt_dlp/extractor/hitrecord.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
clean_html,
float_or_none,
@@ -27,14 +26,14 @@ class HitRecordIE(InfoExtractor):
'like_count': int,
'comment_count': int,
'tags': list,
- }
+ },
}
def _real_extract(self, url):
video_id = self._match_id(url)
video = self._download_json(
- 'https://hitrecord.org/api/web/records/%s' % video_id, video_id)
+ f'https://hitrecord.org/api/web/records/{video_id}', video_id)
title = video['title']
video_url = video['source_url']['mp4_url']
@@ -46,7 +45,7 @@ class HitRecordIE(InfoExtractor):
t['text']
for t in tags_list
if isinstance(t, dict) and t.get('text')
- and isinstance(t['text'], compat_str)]
+ and isinstance(t['text'], str)]
return {
'id': video_id,
@@ -56,9 +55,9 @@ class HitRecordIE(InfoExtractor):
'duration': float_or_none(video.get('duration'), 1000),
'timestamp': int_or_none(video.get('created_at_i')),
'uploader': try_get(
- video, lambda x: x['user']['username'], compat_str),
+ video, lambda x: x['user']['username'], str),
'uploader_id': try_get(
- video, lambda x: compat_str(x['user']['id'])),
+ video, lambda x: str(x['user']['id'])),
'view_count': int_or_none(video.get('total_views_count')),
'like_count': int_or_none(video.get('hearts_count')),
'comment_count': int_or_none(video.get('comments_count')),
diff --git a/yt_dlp/extractor/hketv.py b/yt_dlp/extractor/hketv.py
index 099c2a175..bffb6afe0 100644
--- a/yt_dlp/extractor/hketv.py
+++ b/yt_dlp/extractor/hketv.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
clean_html,
@@ -144,7 +143,7 @@ class HKETVIE(InfoExtractor):
if not isinstance(track, dict):
continue
track_kind = str_or_none(track.get('kind'))
- if not track_kind or not isinstance(track_kind, compat_str):
+ if not track_kind or not isinstance(track_kind, str):
continue
if track_kind.lower() not in ('captions', 'subtitles'):
continue
diff --git a/yt_dlp/extractor/hollywoodreporter.py b/yt_dlp/extractor/hollywoodreporter.py
index 1f7eb89bc..52db5e5c1 100644
--- a/yt_dlp/extractor/hollywoodreporter.py
+++ b/yt_dlp/extractor/hollywoodreporter.py
@@ -53,7 +53,7 @@ class HollywoodReporterPlaylistIE(InfoExtractor):
'info_dict': {
'id': '57822',
'title': 'heat-vision-breakdown',
- }
+ },
}]
def _fetch_page(self, slug, pl_id, page):
diff --git a/yt_dlp/extractor/holodex.py b/yt_dlp/extractor/holodex.py
index a2b73ecc1..00b045ee7 100644
--- a/yt_dlp/extractor/holodex.py
+++ b/yt_dlp/extractor/holodex.py
@@ -46,7 +46,7 @@ class HolodexIE(InfoExtractor):
'url': 'https://holodex.net/watch/_m2mQyaofjI?foo=bar&playlist=69',
'info_dict': {
'id': '69',
- 'title': '拿著金斧頭的藍髮大姊姊'
+ 'title': '拿著金斧頭的藍髮大姊姊',
},
'playlist_count': 3,
}, {
diff --git a/yt_dlp/extractor/hotnewhiphop.py b/yt_dlp/extractor/hotnewhiphop.py
index 4f506cde7..8573e8947 100644
--- a/yt_dlp/extractor/hotnewhiphop.py
+++ b/yt_dlp/extractor/hotnewhiphop.py
@@ -1,5 +1,6 @@
+import base64
+
from .common import InfoExtractor
-from ..compat import compat_b64decode
from ..networking import HEADRequest, Request
from ..utils import ExtractorError, urlencode_postdata
@@ -13,8 +14,8 @@ class HotNewHipHopIE(InfoExtractor):
'info_dict': {
'id': '1435540',
'ext': 'mp3',
- 'title': 'Freddie Gibbs - Lay It Down'
- }
+ 'title': 'Freddie Gibbs - Lay It Down',
+ },
}
def _real_extract(self, url):
@@ -42,7 +43,7 @@ class HotNewHipHopIE(InfoExtractor):
if 'mediaKey' not in mkd:
raise ExtractorError('Did not get a media key')
- redirect_url = compat_b64decode(video_url_base64).decode('utf-8')
+ redirect_url = base64.b64decode(video_url_base64).decode('utf-8')
redirect_req = HEADRequest(redirect_url)
req = self._request_webpage(
redirect_req, video_id,
diff --git a/yt_dlp/extractor/hotstar.py b/yt_dlp/extractor/hotstar.py
index a3a3c20c9..e97740c90 100644
--- a/yt_dlp/extractor/hotstar.py
+++ b/yt_dlp/extractor/hotstar.py
@@ -6,7 +6,6 @@ import time
import uuid
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -32,7 +31,7 @@ class HotStarBaseIE(InfoExtractor):
def _call_api_impl(self, path, video_id, query, st=None, cookies=None):
st = int_or_none(st) or int(time.time())
exp = st + 6000
- auth = 'st=%d~exp=%d~acl=/*' % (st, exp)
+ auth = f'st={st}~exp={exp}~acl=/*'
auth += '~hmac=' + hmac.new(self._AKAMAI_ENCRYPTION_KEY, auth.encode(), hashlib.sha256).hexdigest()
if cookies and cookies.get('userUP'):
@@ -41,7 +40,7 @@ class HotStarBaseIE(InfoExtractor):
token = self._download_json(
f'{self._API_URL}/um/v3/users',
video_id, note='Downloading token',
- data=json.dumps({"device_ids": [{"id": compat_str(uuid.uuid4()), "type": "device_id"}]}).encode('utf-8'),
+ data=json.dumps({'device_ids': [{'id': str(uuid.uuid4()), 'type': 'device_id'}]}).encode(),
headers={
'hotstarauth': auth,
'x-hs-platform': 'PCTV', # or 'web'
@@ -66,7 +65,7 @@ class HotStarBaseIE(InfoExtractor):
return self._call_api_impl(
f'{path}/content/{video_id}', video_id, st=st, cookies=cookies, query={
'desired-config': 'audio_channel:stereo|container:fmp4|dynamic_range:hdr|encryption:plain|ladder:tv|package:dash|resolution:fhd|subs-tag:HotstarVIP|video_codec:h265',
- 'device-id': cookies.get('device_id').value if cookies.get('device_id') else compat_str(uuid.uuid4()),
+ 'device-id': cookies.get('device_id').value if cookies.get('device_id') else str(uuid.uuid4()),
'os-name': 'Windows',
'os-version': '10',
})
@@ -122,7 +121,7 @@ class HotStarIE(HotStarBaseIE):
'season_id': '6771',
'episode': 'Janhvi Targets Suman',
'episode_number': 8,
- }
+ },
}, {
'url': 'https://www.hotstar.com/in/shows/anupama/1260022017/anupama-anuj-share-a-moment/1000282843',
'info_dict': {
@@ -257,7 +256,6 @@ class HotStarIE(HotStarBaseIE):
for key, prefix in self._IGNORE_MAP.items()
for ignore in self._configuration_arg(key)):
continue
- tag_dict = dict((t.split(':', 1) + [None])[:2] for t in tags.split(';'))
format_url = url_or_none(playback_set.get('playbackUrl'))
if not format_url:
@@ -286,6 +284,7 @@ class HotStarIE(HotStarBaseIE):
geo_restricted = True
continue
+ tag_dict = dict((*t.split(':', 1), None)[:2] for t in tags.split(';'))
if tag_dict.get('encryption') not in ('plain', None):
for f in current_formats:
f['has_drm'] = True
diff --git a/yt_dlp/extractor/hrfensehen.py b/yt_dlp/extractor/hrfensehen.py
index 35e9f67c4..17673d5b8 100644
--- a/yt_dlp/extractor/hrfensehen.py
+++ b/yt_dlp/extractor/hrfensehen.py
@@ -24,17 +24,17 @@ class HRFernsehenIE(InfoExtractor):
'Sterbehilfe: Die Lage in Hessen / Miss Hessen leitet zwei eigene Unternehmen / '
'Pop-Up Museum zeigt Schwarze Unterhaltung und Black Music',
'subtitles': {'de': [{
- 'url': 'https://hr-a.akamaihd.net/video/as/hessenschau/2020_08/hrLogo_200826200407_L385592_512x288-25p-500kbit.vtt'
+ 'url': 'https://hr-a.akamaihd.net/video/as/hessenschau/2020_08/hrLogo_200826200407_L385592_512x288-25p-500kbit.vtt',
}]},
'timestamp': 1598400000,
'upload_date': '20200826',
'thumbnail': 'https://www.hessenschau.de/tv-sendung/hs_ganz-1554~_t-1598465545029_v-16to9.jpg',
'title': 'hessenschau vom 26.08.2020',
- 'duration': 1654
- }
+ 'duration': 1654,
+ },
}, {
'url': 'https://www.hr-fernsehen.de/sendungen-a-z/mex/sendungen/fair-und-gut---was-hinter-aldis-eigenem-guetesiegel-steckt,video-130544.html',
- 'only_matching': True
+ 'only_matching': True,
}]
_GEO_COUNTRIES = ['DE']
@@ -74,7 +74,7 @@ class HRFernsehenIE(InfoExtractor):
subtitle = traverse_obj(loader_data, ('mediaCollection', 'subTitles', 0, 'sources', 0, 'url'))
- info = {
+ return {
'id': video_id,
'title': title,
'description': description,
@@ -86,5 +86,3 @@ class HRFernsehenIE(InfoExtractor):
loader_data, ('playerConfig', 'pluginData', 'trackingAti@all', 'richMedia', 'duration'))),
'thumbnail': self._search_regex(r'thumbnailUrl\W*([^"]+)', webpage, 'thumbnail', default=None),
}
-
- return info
diff --git a/yt_dlp/extractor/hrti.py b/yt_dlp/extractor/hrti.py
index 41d50d000..84e3867d3 100644
--- a/yt_dlp/extractor/hrti.py
+++ b/yt_dlp/extractor/hrti.py
@@ -28,21 +28,21 @@ class HRTiBaseIE(InfoExtractor):
def _initialize_pre_login(self):
init_data = {
- 'application_publication_id': self._APP_PUBLICATION_ID
+ 'application_publication_id': self._APP_PUBLICATION_ID,
}
uuid = self._download_json(
self._API_URL, None, note='Downloading uuid',
errnote='Unable to download uuid',
- data=json.dumps(init_data).encode('utf-8'))['uuid']
+ data=json.dumps(init_data).encode())['uuid']
app_data = {
'uuid': uuid,
'application_publication_id': self._APP_PUBLICATION_ID,
- 'application_version': self._APP_VERSION
+ 'application_version': self._APP_VERSION,
}
- req = Request(self._API_URL, data=json.dumps(app_data).encode('utf-8'))
+ req = Request(self._API_URL, data=json.dumps(app_data).encode())
req.get_method = lambda: 'PUT'
resources = self._download_json(
@@ -71,17 +71,17 @@ class HRTiBaseIE(InfoExtractor):
try:
auth_info = self._download_json(
self._login_url, None, note='Logging in', errnote='Unable to log in',
- data=json.dumps(auth_data).encode('utf-8'))
+ data=json.dumps(auth_data).encode())
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 406:
- auth_info = self._parse_json(e.cause.response.read().encode('utf-8'), None)
+ auth_info = self._parse_json(e.cause.response.read().encode(), None)
else:
raise
error_message = auth_info.get('error', {}).get('message')
if error_message:
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, error_message),
+ f'{self.IE_NAME} said: {error_message}',
expected=True)
self._token = auth_info['secure_streaming_token']
@@ -133,7 +133,7 @@ class HRTiIE(HRTiBaseIE):
display_id = mobj.group('display_id') or video_id
video = self._download_json(
- '%s/video_id/%s/format/json' % (self._search_url, video_id),
+ f'{self._search_url}/video_id/{video_id}/format/json',
display_id, 'Downloading video metadata JSON')['video'][0]
title_info = video['title']
@@ -188,13 +188,13 @@ class HRTiPlaylistIE(HRTiBaseIE):
display_id = mobj.group('display_id') or category_id
response = self._download_json(
- '%s/category_id/%s/format/json' % (self._search_url, category_id),
+ f'{self._search_url}/category_id/{category_id}/format/json',
display_id, 'Downloading video metadata JSON')
video_ids = try_get(
response, lambda x: x['video_listings'][0]['alternatives'][0]['list'],
list) or [video['id'] for video in response.get('videos', []) if video.get('id')]
- entries = [self.url_result('hrti:%s' % video_id) for video_id in video_ids]
+ entries = [self.url_result(f'hrti:{video_id}') for video_id in video_ids]
return self.playlist_result(entries, category_id, display_id)
diff --git a/yt_dlp/extractor/hse.py b/yt_dlp/extractor/hse.py
index 3cb21d2dd..d9004293f 100644
--- a/yt_dlp/extractor/hse.py
+++ b/yt_dlp/extractor/hse.py
@@ -39,7 +39,7 @@ class HSEShowIE(HSEShowBaseInfoExtractor):
'timestamp': 1638810000,
'upload_date': '20211206',
'channel': 'HSE24',
- 'uploader': 'Arina Pirayesh'
+ 'uploader': 'Arina Pirayesh',
},
'params': {'skip_download': 'm3u8'},
}]
@@ -72,7 +72,7 @@ class HSEProductIE(HSEShowBaseInfoExtractor):
'id': '408630',
'ext': 'mp4',
'title': 'Hose im Ponte-Mix',
- 'uploader': 'Judith Williams'
+ 'uploader': 'Judith Williams',
},
'params': {'skip_download': 'm3u8'},
}]
diff --git a/yt_dlp/extractor/huajiao.py b/yt_dlp/extractor/huajiao.py
index c498fa330..093ce7dc2 100644
--- a/yt_dlp/extractor/huajiao.py
+++ b/yt_dlp/extractor/huajiao.py
@@ -22,7 +22,7 @@ class HuajiaoIE(InfoExtractor):
'upload_date': '20161007',
'uploader': 'Penny_余姿昀',
'uploader_id': '75206005',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/huffpost.py b/yt_dlp/extractor/huffpost.py
index 69fdc34ef..156ddebf7 100644
--- a/yt_dlp/extractor/huffpost.py
+++ b/yt_dlp/extractor/huffpost.py
@@ -40,7 +40,7 @@ class HuffPostIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- api_url = 'http://embed.live.huffingtonpost.com/api/segments/%s.json' % video_id
+ api_url = f'http://embed.live.huffingtonpost.com/api/segments/{video_id}.json'
data = self._download_json(api_url, video_id)['data']
video_title = data['title']
diff --git a/yt_dlp/extractor/hungama.py b/yt_dlp/extractor/hungama.py
index 7da8aad7a..a687b12dc 100644
--- a/yt_dlp/extractor/hungama.py
+++ b/yt_dlp/extractor/hungama.py
@@ -98,7 +98,7 @@ class HungamaIE(HungamaBaseIE):
'en': [{
'url': video_json['sub_title'],
'ext': 'vtt',
- }]
+ }],
} if video_json.get('sub_title') else None,
}
@@ -136,7 +136,7 @@ class HungamaSongIE(InfoExtractor):
audio_id = self._match_id(url)
data = self._download_json(
- 'https://www.hungama.com/audio-player-data/track/%s' % audio_id,
+ f'https://www.hungama.com/audio-player-data/track/{audio_id}',
audio_id, query={'_country': 'IN'})[0]
track = data['song_name']
artist = data.get('singer_name')
@@ -153,7 +153,7 @@ class HungamaSongIE(InfoExtractor):
'acodec': media_type,
})
- title = '%s - %s' % (artist, track) if artist else track
+ title = f'{artist} - {track}' if artist else track
thumbnail = data.get('img_src') or data.get('album_image')
return {
diff --git a/yt_dlp/extractor/huya.py b/yt_dlp/extractor/huya.py
index 5379b5410..5663a78a3 100644
--- a/yt_dlp/extractor/huya.py
+++ b/yt_dlp/extractor/huya.py
@@ -1,9 +1,10 @@
+import base64
import hashlib
import random
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_b64decode, compat_urlparse
from ..utils import (
ExtractorError,
int_or_none,
@@ -32,7 +33,7 @@ class HuyaLiveIE(InfoExtractor):
},
}, {
'url': 'https://www.huya.com/xiaoyugame',
- 'only_matching': True
+ 'only_matching': True,
}]
_RESOLUTION = {
@@ -46,8 +47,8 @@ class HuyaLiveIE(InfoExtractor):
},
'流畅': {
'width': 800,
- 'height': 480
- }
+ 'height': 480,
+ },
}
def _real_extract(self, url):
@@ -70,7 +71,7 @@ class HuyaLiveIE(InfoExtractor):
continue
stream_name = stream_info.get('sStreamName')
re_secret = not screen_type and live_source_type in (0, 8, 13)
- params = dict(compat_urlparse.parse_qsl(unescapeHTML(stream_info['sFlvAntiCode'])))
+ params = dict(urllib.parse.parse_qsl(unescapeHTML(stream_info['sFlvAntiCode'])))
fm, ss = '', ''
if re_secret:
fm, ss = self.encrypt(params, stream_info, stream_name)
@@ -127,6 +128,6 @@ class HuyaLiveIE(InfoExtractor):
'uuid': int_or_none(ct % 1e7 * 1e6 % 0xffffffff),
't': '100',
})
- fm = compat_b64decode(params['fm']).decode().split('_', 1)[0]
+ fm = base64.b64decode(params['fm']).decode().split('_', 1)[0]
ss = hashlib.md5('|'.join([params['seqid'], params['ctype'], params['t']]))
return fm, ss
diff --git a/yt_dlp/extractor/hypem.py b/yt_dlp/extractor/hypem.py
index 54db7b3eb..204a72e2d 100644
--- a/yt_dlp/extractor/hypem.py
+++ b/yt_dlp/extractor/hypem.py
@@ -14,7 +14,7 @@ class HypemIE(InfoExtractor):
'uploader': 'BODYWORK',
'timestamp': 1371810457,
'upload_date': '20130621',
- }
+ },
}
def _real_extract(self, url):
@@ -30,9 +30,9 @@ class HypemIE(InfoExtractor):
title = track['song']
final_url = self._download_json(
- 'http://hypem.com/serve/source/%s/%s' % (track_id, track['key']),
+ 'http://hypem.com/serve/source/{}/{}'.format(track_id, track['key']),
track_id, 'Downloading metadata', headers={
- 'Content-Type': 'application/json'
+ 'Content-Type': 'application/json',
})['url']
return {
diff --git a/yt_dlp/extractor/hypergryph.py b/yt_dlp/extractor/hypergryph.py
index 96e452a51..1fb2e9a98 100644
--- a/yt_dlp/extractor/hypergryph.py
+++ b/yt_dlp/extractor/hypergryph.py
@@ -12,7 +12,7 @@ class MonsterSirenHypergryphMusicIE(InfoExtractor):
'artists': ['塞壬唱片-MSR'],
'album': 'Flame Shadow',
'title': 'Flame Shadow',
- }
+ },
}]
def _real_extract(self, url):
@@ -28,5 +28,5 @@ class MonsterSirenHypergryphMusicIE(InfoExtractor):
'ext': 'wav',
'vcodec': 'none',
'artists': traverse_obj(json_data, ('player', 'songDetail', 'artists', ...)),
- 'album': traverse_obj(json_data, ('musicPlay', 'albumDetail', 'name'))
+ 'album': traverse_obj(json_data, ('musicPlay', 'albumDetail', 'name')),
}
diff --git a/yt_dlp/extractor/hytale.py b/yt_dlp/extractor/hytale.py
index e8cd21a64..6956c4e5d 100644
--- a/yt_dlp/extractor/hytale.py
+++ b/yt_dlp/extractor/hytale.py
@@ -21,8 +21,8 @@ class HytaleIE(InfoExtractor):
'ext': 'mp4',
'title': 'Avatar Personalization',
'thumbnail': r're:https://videodelivery\.net/\w+/thumbnails/thumbnail\.jpg',
- }
- }]
+ },
+ }],
}, {
'url': 'https://www.hytale.com/news/2019/11/hytale-graphics-update',
'info_dict': {
diff --git a/yt_dlp/extractor/icareus.py b/yt_dlp/extractor/icareus.py
index d081cf42e..3d6e1f94d 100644
--- a/yt_dlp/extractor/icareus.py
+++ b/yt_dlp/extractor/icareus.py
@@ -65,19 +65,19 @@ class IcareusIE(InfoExtractor):
},
}, {
'url': 'https://asahitv.fi/fi/web/asahi/player/vod?assetId=89415818',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://hyvinvointitv.fi/fi/web/hyvinvointitv/player/vod?assetId=89149730',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://inez.fi/fi/web/inez-media/player/vod?assetId=71328822',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.permanto.fi/fi/web/alfatv/player/vod?assetId=135497515',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://videos.minifiddlers.org/web/international-minifiddlers/player/vod?assetId=1982759',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -166,7 +166,7 @@ class IcareusIE(InfoExtractor):
}
thumbnails = info.get('thumbnails') or [{
- 'url': url_or_none(info.get('thumbnail') or assets.get('thumbnail'))
+ 'url': url_or_none(info.get('thumbnail') or assets.get('thumbnail')),
}]
return merge_dicts({
diff --git a/yt_dlp/extractor/ichinanalive.py b/yt_dlp/extractor/ichinanalive.py
index c28d09f34..a37cfe77b 100644
--- a/yt_dlp/extractor/ichinanalive.py
+++ b/yt_dlp/extractor/ichinanalive.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import ExtractorError, str_or_none, traverse_obj, unified_strdate
@@ -27,21 +26,21 @@ class IchinanaLiveIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return not IchinanaLiveClipIE.suitable(url) and super(IchinanaLiveIE, cls).suitable(url)
+ return not IchinanaLiveClipIE.suitable(url) and super().suitable(url)
def _real_extract(self, url):
video_id = self._match_id(url)
- url = 'https://17.live/live/%s' % video_id
+ url = f'https://17.live/live/{video_id}'
enter = self._download_json(
- 'https://api-dsa.17app.co/api/v1/lives/%s/enter' % video_id, video_id,
+ f'https://api-dsa.17app.co/api/v1/lives/{video_id}/enter', video_id,
headers={'Referer': url}, fatal=False, expected_status=420,
data=b'\0')
if enter and enter.get('message') == 'ended':
raise ExtractorError('This live has ended.', expected=True)
view_data = self._download_json(
- 'https://api-dsa.17app.co/api/v1/lives/%s' % video_id, video_id,
+ f'https://api-dsa.17app.co/api/v1/lives/{video_id}', video_id,
headers={'Referer': url})
uploader = traverse_obj(
@@ -52,7 +51,7 @@ class IchinanaLiveIE(InfoExtractor):
raise ExtractorError('unable to extract live URL information')
formats = []
for (name, value) in video_urls[0].items():
- if not isinstance(value, compat_str):
+ if not isinstance(value, str):
continue
if not value.startswith('http'):
continue
@@ -106,10 +105,10 @@ class IchinanaLiveClipIE(InfoExtractor):
def _real_extract(self, url):
uploader_id, video_id = self._match_valid_url(url).groups()
- url = 'https://17.live/profile/r/%s/clip/%s' % (uploader_id, video_id)
+ url = f'https://17.live/profile/r/{uploader_id}/clip/{video_id}'
view_data = self._download_json(
- 'https://api-dsa.17app.co/api/v1/clips/%s' % video_id, video_id,
+ f'https://api-dsa.17app.co/api/v1/clips/{video_id}', video_id,
headers={'Referer': url})
uploader = traverse_obj(
diff --git a/yt_dlp/extractor/ign.py b/yt_dlp/extractor/ign.py
index 1c4f105e9..771c18501 100644
--- a/yt_dlp/extractor/ign.py
+++ b/yt_dlp/extractor/ign.py
@@ -2,12 +2,10 @@ import re
import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_parse_qs
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
determine_ext,
- error_to_compat_str,
extract_attributes,
int_or_none,
merge_dicts,
@@ -22,7 +20,7 @@ from ..utils import (
class IGNBaseIE(InfoExtractor):
def _call_api(self, slug):
return self._download_json(
- 'http://apis.ign.com/{0}/v3/{0}s/slug/{1}'.format(self._PAGE_TYPE, slug), slug)
+ f'http://apis.ign.com/{self._PAGE_TYPE}/v3/{self._PAGE_TYPE}s/slug/{slug}', slug)
def _checked_call_api(self, slug):
try:
@@ -106,8 +104,7 @@ class IGNIE(IGNBaseIE):
_VIDEO_PATH_RE = r'/(?:\d{4}/\d{2}/\d{2}/)?(?P<id>.+?)'
_PLAYLIST_PATH_RE = r'(?:/?\?(?P<filt>[^&#]+))?'
_VALID_URL = (
- r'https?://(?:.+?\.ign|www\.pcmag)\.com/videos(?:%s)'
- % '|'.join((_VIDEO_PATH_RE + r'(?:[/?&#]|$)', _PLAYLIST_PATH_RE)))
+ r'https?://(?:.+?\.ign|www\.pcmag)\.com/videos(?:{})'.format('|'.join((_VIDEO_PATH_RE + r'(?:[/?&#]|$)', _PLAYLIST_PATH_RE))))
IE_NAME = 'ign.com'
_PAGE_TYPE = 'video'
@@ -152,10 +149,10 @@ class IGNIE(IGNBaseIE):
grids = re.findall(
r'''(?s)<section\b[^>]+\bclass\s*=\s*['"](?:[\w-]+\s+)*?content-feed-grid(?!\B|-)[^>]+>(.+?)</section[^>]*>''',
webpage)
- return filter(None,
- (urljoin(url, m.group('path')) for m in re.finditer(
- r'''<a\b[^>]+\bhref\s*=\s*('|")(?P<path>/videos%s)\1'''
- % cls._VIDEO_PATH_RE, grids[0] if grids else '')))
+ return filter(
+ None, (urljoin(url, m.group('path')) for m in re.finditer(
+ rf'''<a\b[^>]+\bhref\s*=\s*('|")(?P<path>/videos{cls._VIDEO_PATH_RE})\1''',
+ grids[0] if grids else '')))
def _real_extract(self, url):
display_id, filt = self._match_valid_url(url).group('id', 'filt')
@@ -224,7 +221,7 @@ class IGNVideoIE(IGNBaseIE):
webpage, urlh = self._download_webpage_handle(embed_url, video_id)
new_url = urlh.url
- ign_url = compat_parse_qs(
+ ign_url = urllib.parse.parse_qs(
urllib.parse.urlparse(new_url).query).get('url', [None])[-1]
if ign_url:
return self.url_result(ign_url, IGNIE.ie_key())
@@ -328,7 +325,7 @@ class IGNArticleIE(IGNBaseIE):
'Content not found: expired?', cause=e.cause,
expected=True)
elif e.cause.status == 503:
- self.report_warning(error_to_compat_str(e.cause))
+ self.report_warning(str(e.cause))
return
raise
@@ -367,7 +364,7 @@ class IGNArticleIE(IGNBaseIE):
flashvars = self._search_regex(
r'''(<param\b[^>]+\bname\s*=\s*("|')flashvars\2[^>]*>)''',
m.group('params'), 'flashvars', default='')
- flashvars = compat_parse_qs(extract_attributes(flashvars).get('value') or '')
+ flashvars = urllib.parse.parse_qs(extract_attributes(flashvars).get('value') or '')
v_url = url_or_none((flashvars.get('url') or [None])[-1])
if v_url:
yield self.url_result(v_url)
diff --git a/yt_dlp/extractor/iheart.py b/yt_dlp/extractor/iheart.py
index fb6f51e2c..21870ca04 100644
--- a/yt_dlp/extractor/iheart.py
+++ b/yt_dlp/extractor/iheart.py
@@ -35,7 +35,7 @@ class IHeartRadioIE(IHeartRadioBaseIE):
'description': 'md5:96cc7297b3a5a9ebae28643801c96fae',
'timestamp': 1597741200,
'upload_date': '20200818',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/ilpost.py b/yt_dlp/extractor/ilpost.py
index ae98399ee..2868f0c62 100644
--- a/yt_dlp/extractor/ilpost.py
+++ b/yt_dlp/extractor/ilpost.py
@@ -28,7 +28,7 @@ class IlPostIE(InfoExtractor):
'availability': 'public',
'series_id': '235598',
'description': '',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/iltalehti.py b/yt_dlp/extractor/iltalehti.py
index 0e7e82c9c..37aa47165 100644
--- a/yt_dlp/extractor/iltalehti.py
+++ b/yt_dlp/extractor/iltalehti.py
@@ -47,5 +47,5 @@ class IltalehtiIE(InfoExtractor):
'state', 'articles', ..., 'items', (('main_media', 'properties'), ('body', ..., 'properties'))))
video_ids = traverse_obj(props, (lambda _, v: v['provider'] == 'jwplayer', 'id'))
return self.playlist_from_matches(
- video_ids, article_id, ie='JWPlatform', getter=lambda id: f'jwplatform:{id}',
+ video_ids, article_id, ie='JWPlatform', getter=lambda video_id: f'jwplatform:{video_id}',
title=traverse_obj(info, ('state', 'articles', ..., 'items', 'canonical_title'), get_all=False))
diff --git a/yt_dlp/extractor/imdb.py b/yt_dlp/extractor/imdb.py
index 557a3b7b7..a786ce32e 100644
--- a/yt_dlp/extractor/imdb.py
+++ b/yt_dlp/extractor/imdb.py
@@ -28,7 +28,7 @@ class ImdbIE(InfoExtractor):
'description': 'md5:87bd0bdc61e351f21f20d2d7441cb4e7',
'duration': 152,
'thumbnail': r're:^https?://.+\.jpg',
- }
+ },
}, {
'url': 'https://www.imdb.com/video/vi3516832537',
'info_dict': {
@@ -38,7 +38,7 @@ class ImdbIE(InfoExtractor):
'description': 'md5:17fcc4fe11ec29b4399be9d4c5ef126c',
'duration': 153,
'thumbnail': r're:^https?://.+\.jpg',
- }
+ },
}, {
'url': 'http://www.imdb.com/video/_/vi2524815897',
'only_matching': True,
@@ -73,7 +73,7 @@ class ImdbIE(InfoExtractor):
'key': base64.b64encode(json.dumps({
'type': 'VIDEO_PLAYER',
'subType': 'FORCE_LEGACY',
- 'id': 'vi%s' % video_id,
+ 'id': f'vi{video_id}',
}).encode()).decode(),
}), lambda x: x[0]['videoLegacyEncodings'])
quality = qualities(('SD', '480p', '720p', '1080p'))
@@ -132,7 +132,7 @@ class ImdbListIE(InfoExtractor):
webpage = self._download_webpage(url, list_id)
entries = [
self.url_result('http://www.imdb.com' + m, 'Imdb')
- for m in re.findall(r'href="(/list/ls%s/videoplayer/vi[^"]+)"' % list_id, webpage)]
+ for m in re.findall(rf'href="(/list/ls{list_id}/videoplayer/vi[^"]+)"', webpage)]
list_title = self._html_search_regex(
r'<h1[^>]+class="[^"]*header[^"]*"[^>]*>(.*?)</h1>',
diff --git a/yt_dlp/extractor/imggaming.py b/yt_dlp/extractor/imggaming.py
index a40aa2176..3a7b5bd5e 100644
--- a/yt_dlp/extractor/imggaming.py
+++ b/yt_dlp/extractor/imggaming.py
@@ -73,7 +73,7 @@ class ImgGamingBaseIE(InfoExtractor):
if not video_id:
continue
entries.append(self.url_result(
- 'https://%s/video/%s' % (domain, video_id),
+ f'https://{domain}/video/{video_id}',
self.ie_key(), video_id))
return self.playlist_result(
entries, media_id, playlist.get('title'),
diff --git a/yt_dlp/extractor/imgur.py b/yt_dlp/extractor/imgur.py
index f32c116bb..f0c3419d4 100644
--- a/yt_dlp/extractor/imgur.py
+++ b/yt_dlp/extractor/imgur.py
@@ -228,21 +228,18 @@ class ImgurGalleryBaseIE(ImgurBaseIE):
if traverse_obj(data, 'is_album'):
- def yield_media_ids():
- for m_id in traverse_obj(data, (
- 'media', lambda _, v: v.get('type') == 'video' or v['metadata']['is_animated'],
- 'id', {lambda x: str_or_none(x) or None})):
- yield m_id
+ items = traverse_obj(data, (
+ 'media', lambda _, v: v.get('type') == 'video' or v['metadata']['is_animated'],
+ 'id', {lambda x: str_or_none(x) or None}))
# if a gallery with exactly one video, apply album metadata to video
- media_id = (
- self._GALLERY
- and traverse_obj(data, ('image_count', {lambda c: c == 1}))
- and next(yield_media_ids(), None))
+ media_id = None
+ if self._GALLERY and len(items) == 1:
+ media_id = items[0]
if not media_id:
result = self.playlist_result(
- map(self._imgur_result, yield_media_ids()), gallery_id)
+ map(self._imgur_result, items), gallery_id)
result.update(info)
return result
gallery_id = media_id
@@ -372,13 +369,13 @@ class ImgurAlbumIE(ImgurGalleryBaseIE):
'url': 'https://imgur.com/a/iX265HX',
'info_dict': {
'id': 'iX265HX',
- 'title': 'enen-no-shouboutai'
+ 'title': 'enen-no-shouboutai',
},
'playlist_count': 2,
}, {
'url': 'https://imgur.com/a/8pih2Ed',
'info_dict': {
- 'id': '8pih2Ed'
+ 'id': '8pih2Ed',
},
'playlist_mincount': 1,
}]
diff --git a/yt_dlp/extractor/ina.py b/yt_dlp/extractor/ina.py
index 857013df3..ba8220176 100644
--- a/yt_dlp/extractor/ina.py
+++ b/yt_dlp/extractor/ina.py
@@ -14,7 +14,7 @@ class InaIE(InfoExtractor):
'description': 'md5:19f61e2b4844ed4bb2e3df9ab9f527ff',
'upload_date': '20070712',
'thumbnail': 'https://cdn-hub.ina.fr/notice/690x517/3c4/I12055569.jpeg',
- }
+ },
}, {
'url': 'https://www.ina.fr/video/S806544_001/don-d-organes-des-avancees-mais-d-importants-besoins-video.html',
'only_matching': True,
diff --git a/yt_dlp/extractor/inc.py b/yt_dlp/extractor/inc.py
index 9b3fe9ac1..f47b8e1cc 100644
--- a/yt_dlp/extractor/inc.py
+++ b/yt_dlp/extractor/inc.py
@@ -54,4 +54,4 @@ class IncIE(InfoExtractor):
display_id)['vid_kaltura_id']
return self.url_result(
- 'kaltura:%s:%s' % (partner_id, kaltura_id), KalturaIE.ie_key())
+ f'kaltura:{partner_id}:{kaltura_id}', KalturaIE.ie_key())
diff --git a/yt_dlp/extractor/indavideo.py b/yt_dlp/extractor/indavideo.py
index 564bf8a02..85e388e0d 100644
--- a/yt_dlp/extractor/indavideo.py
+++ b/yt_dlp/extractor/indavideo.py
@@ -80,7 +80,7 @@ class IndavideoEmbedIE(InfoExtractor):
height = int_or_none(self._search_regex(
r'\.(\d{3,4})\.mp4(?:\?|$)', video_url, 'height', default=None))
if not height and len(filesh) == 1:
- height = int_or_none(list(filesh.keys())[0])
+ height = int_or_none(next(iter(filesh.keys())))
token = filesh.get(str(height))
if token is None:
continue
@@ -95,7 +95,7 @@ class IndavideoEmbedIE(InfoExtractor):
timestamp = parse_iso8601(timestamp + ' +0200', ' ')
thumbnails = [{
- 'url': self._proto_relative_url(thumbnail)
+ 'url': self._proto_relative_url(thumbnail),
} for thumbnail in video.get('thumbnails', [])]
tags = [tag['title'] for tag in video.get('tags') or []]
diff --git a/yt_dlp/extractor/infoq.py b/yt_dlp/extractor/infoq.py
index 2bb48508c..5274c9339 100644
--- a/yt_dlp/extractor/infoq.py
+++ b/yt_dlp/extractor/infoq.py
@@ -1,9 +1,7 @@
+import base64
+import urllib.parse
+
from .bokecc import BokeCCBaseIE
-from ..compat import (
- compat_b64decode,
- compat_urllib_parse_unquote,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
determine_ext,
@@ -59,7 +57,7 @@ class InfoQIE(BokeCCBaseIE):
encoded_id = self._search_regex(
r"jsclassref\s*=\s*'([^']*)'", webpage, 'encoded id', default=None)
- real_id = compat_urllib_parse_unquote(compat_b64decode(encoded_id).decode('utf-8'))
+ real_id = urllib.parse.unquote(base64.b64decode(encoded_id).decode('utf-8'))
playpath = 'mp4:' + real_id
return [{
@@ -98,7 +96,7 @@ class InfoQIE(BokeCCBaseIE):
# base URL is found in the Location header in the response returned by
# GET https://www.infoq.com/mp3download.action?filename=... when logged in.
- http_audio_url = compat_urlparse.urljoin('http://ress.infoq.com/downloads/mp3downloads/', http_audio_url)
+ http_audio_url = urllib.parse.urljoin('http://ress.infoq.com/downloads/mp3downloads/', http_audio_url)
http_audio_url = update_url_query(http_audio_url, self._extract_cf_auth(webpage))
# audio file seem to be missing some times even if there is a download link
diff --git a/yt_dlp/extractor/instagram.py b/yt_dlp/extractor/instagram.py
index 46f9cd681..c8bf7e9c4 100644
--- a/yt_dlp/extractor/instagram.py
+++ b/yt_dlp/extractor/instagram.py
@@ -26,9 +26,9 @@ from ..utils import (
_ENCODING_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
-def _pk_to_id(id):
+def _pk_to_id(media_id):
"""Source: https://stackoverflow.com/questions/24437823/getting-instagram-post-url-from-media-id"""
- return encode_base_n(int(id.split('_')[0]), table=_ENCODING_CHARS)
+ return encode_base_n(int(media_id.split('_')[0]), table=_ENCODING_CHARS)
def _id_to_pk(shortcode):
@@ -113,7 +113,7 @@ class InstagramBaseIE(InfoExtractor):
'height': self._get_dimension('height', node),
'http_headers': {
'Referer': 'https://www.instagram.com/',
- }
+ },
}
elif not video_id:
continue
@@ -148,25 +148,25 @@ class InstagramBaseIE(InfoExtractor):
return {}
formats = [{
- 'format_id': format.get('id'),
- 'url': format.get('url'),
- 'width': format.get('width'),
- 'height': format.get('height'),
+ 'format_id': fmt.get('id'),
+ 'url': fmt.get('url'),
+ 'width': fmt.get('width'),
+ 'height': fmt.get('height'),
'vcodec': vcodec,
- } for format in videos_list or []]
+ } for fmt in videos_list or []]
if dash_manifest_raw:
formats.extend(self._parse_mpd_formats(self._parse_xml(dash_manifest_raw, media_id), mpd_id='dash'))
thumbnails = [{
'url': thumbnail.get('url'),
'width': thumbnail.get('width'),
- 'height': thumbnail.get('height')
+ 'height': thumbnail.get('height'),
} for thumbnail in traverse_obj(product_media, ('image_versions2', 'candidates')) or []]
return {
'id': media_id,
'duration': float_or_none(product_media.get('video_duration')),
'formats': formats,
- 'thumbnails': thumbnails
+ 'thumbnails': thumbnails,
}
def _extract_product(self, product_info):
@@ -188,7 +188,7 @@ class InstagramBaseIE(InfoExtractor):
'__post_extractor': self.extract_comments(_pk_to_id(product_info.get('pk'))),
'http_headers': {
'Referer': 'https://www.instagram.com/',
- }
+ },
}
carousel_media = product_info.get('carousel_media')
if carousel_media:
@@ -204,7 +204,7 @@ class InstagramBaseIE(InfoExtractor):
return {
**info_dict,
- **self._extract_product_media(product_info)
+ **self._extract_product_media(product_info),
}
def _get_comments(self, video_id):
@@ -246,7 +246,7 @@ class InstagramIOSIE(InfoExtractor):
'comment_count': int,
'comments': list,
},
- 'add_ie': ['Instagram']
+ 'add_ie': ['Instagram'],
}]
def _real_extract(self, url):
@@ -520,7 +520,7 @@ class InstagramIE(InstagramBaseIE):
return {
'id': video_id,
'formats': formats,
- 'title': media.get('title') or 'Video by %s' % username,
+ 'title': media.get('title') or f'Video by {username}',
'description': description,
'duration': float_or_none(media.get('video_duration')),
'timestamp': traverse_obj(media, 'taken_at_timestamp', 'date', expected_type=int_or_none),
@@ -534,7 +534,7 @@ class InstagramIE(InstagramBaseIE):
'thumbnails': thumbnails,
'http_headers': {
'Referer': 'https://www.instagram.com/',
- }
+ },
}
@@ -567,10 +567,10 @@ class InstagramPlaylistBaseIE(InstagramBaseIE):
gis_tmpls = [self._gis_tmpl]
else:
gis_tmpls = [
- '%s' % rhx_gis,
+ f'{rhx_gis}',
'',
- '%s:%s' % (rhx_gis, csrf_token),
- '%s:%s:%s' % (rhx_gis, csrf_token, self.get_param('http_headers')['User-Agent']),
+ f'{rhx_gis}:{csrf_token}',
+ '{}:{}:{}'.format(rhx_gis, csrf_token, self.get_param('http_headers')['User-Agent']),
]
# try all of the ways to generate a GIS query, and not only use the
@@ -579,10 +579,10 @@ class InstagramPlaylistBaseIE(InstagramBaseIE):
try:
json_data = self._download_json(
'https://www.instagram.com/graphql/query/', uploader_id,
- 'Downloading JSON page %d' % page_num, headers={
+ f'Downloading JSON page {page_num}', headers={
'X-Requested-With': 'XMLHttpRequest',
'X-Instagram-GIS': hashlib.md5(
- ('%s:%s' % (gis_tmpl, variables)).encode('utf-8')).hexdigest(),
+ (f'{gis_tmpl}:{variables}').encode()).hexdigest(),
}, query={
'query_hash': self._QUERY_HASH,
'variables': variables,
@@ -635,10 +635,10 @@ class InstagramUserIE(InstagramPlaylistBaseIE):
'extract_flat': True,
'skip_download': True,
'playlistend': 5,
- }
+ },
}]
- _QUERY_HASH = '42323d64886122307be10013ad2dcc44',
+ _QUERY_HASH = ('42323d64886122307be10013ad2dcc44',)
@staticmethod
def _parse_timeline_from(data):
@@ -650,7 +650,7 @@ class InstagramUserIE(InstagramPlaylistBaseIE):
# returns a dictionary of variables to add to the timeline query based
# on the GraphQL of the original page
return {
- 'id': data['entry_data']['ProfilePage'][0]['graphql']['user']['id']
+ 'id': data['entry_data']['ProfilePage'][0]['graphql']['user']['id'],
}
@@ -669,10 +669,10 @@ class InstagramTagIE(InstagramPlaylistBaseIE):
'extract_flat': True,
'skip_download': True,
'playlistend': 50,
- }
+ },
}]
- _QUERY_HASH = 'f92f56d47dc7a55b606908374b43a314',
+ _QUERY_HASH = ('f92f56d47dc7a55b606908374b43a314',)
@staticmethod
def _parse_timeline_from(data):
@@ -685,7 +685,7 @@ class InstagramTagIE(InstagramPlaylistBaseIE):
# on the GraphQL of the original page
return {
'tag_name':
- data['entry_data']['TagPage'][0]['graphql']['hashtag']['name']
+ data['entry_data']['TagPage'][0]['graphql']['hashtag']['name'],
}
@@ -699,7 +699,7 @@ class InstagramStoryIE(InstagramBaseIE):
'id': '18090946048123978',
'title': 'Rare',
},
- 'playlist_mincount': 50
+ 'playlist_mincount': 50,
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/internazionale.py b/yt_dlp/extractor/internazionale.py
index 1b1cb574a..3c3ad7ea3 100644
--- a/yt_dlp/extractor/internazionale.py
+++ b/yt_dlp/extractor/internazionale.py
@@ -52,8 +52,8 @@ class InternazionaleIE(InfoExtractor):
'video available aboard', default='1', group='value')
video_available_abroad = video_available_abroad == '1'
- video_base = 'https://video%s.internazionale.it/%s/%s.' % \
- ('' if video_available_abroad else '-ita', video_path, video_id)
+ video_base = 'https://video{}.internazionale.it/{}/{}.'.format(
+ '' if video_available_abroad else '-ita', video_path, video_id)
formats = self._extract_m3u8_formats(
video_base + 'm3u8', display_id, 'mp4',
diff --git a/yt_dlp/extractor/iprima.py b/yt_dlp/extractor/iprima.py
index d5a3d8095..ab26dc5ef 100644
--- a/yt_dlp/extractor/iprima.py
+++ b/yt_dlp/extractor/iprima.py
@@ -200,8 +200,8 @@ class IPrimaCNNIE(InfoExtractor):
'title': 'md5:277c6b1ed0577e51b40ddd35602ff43e',
},
'params': {
- 'skip_download': 'm3u8'
- }
+ 'skip_download': 'm3u8',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/iqiyi.py b/yt_dlp/extractor/iqiyi.py
index 85ed549de..735b44637 100644
--- a/yt_dlp/extractor/iqiyi.py
+++ b/yt_dlp/extractor/iqiyi.py
@@ -2,10 +2,10 @@ import hashlib
import itertools
import re
import time
+import urllib.parse
from .common import InfoExtractor
from .openload import PhantomJSwrapper
-from ..compat import compat_str, compat_urllib_parse_unquote, compat_urllib_parse_urlencode
from ..utils import (
ExtractorError,
clean_html,
@@ -30,7 +30,7 @@ from ..utils import (
def md5_text(text):
- return hashlib.md5(text.encode('utf-8')).hexdigest()
+ return hashlib.md5(text.encode()).hexdigest()
class IqiyiSDK:
@@ -41,17 +41,17 @@ class IqiyiSDK:
@staticmethod
def split_sum(data):
- return compat_str(sum(map(lambda p: int(p, 16), list(data))))
+ return str(sum(int(p, 16) for p in data))
@staticmethod
def digit_sum(num):
if isinstance(num, int):
- num = compat_str(num)
- return compat_str(sum(map(int, num)))
+ num = str(num)
+ return str(sum(map(int, num)))
def even_odd(self):
- even = self.digit_sum(compat_str(self.timestamp)[::2])
- odd = self.digit_sum(compat_str(self.timestamp)[1::2])
+ even = self.digit_sum(str(self.timestamp)[::2])
+ odd = self.digit_sum(str(self.timestamp)[1::2])
return even, odd
def preprocess(self, chunksize):
@@ -65,7 +65,7 @@ class IqiyiSDK:
def mod(self, modulus):
chunks, ip = self.preprocess(32)
- self.target = chunks[0] + ''.join(map(lambda p: compat_str(p % modulus), ip))
+ self.target = chunks[0] + ''.join(str(p % modulus) for p in ip)
def split(self, chunksize):
modulus_map = {
@@ -77,7 +77,7 @@ class IqiyiSDK:
chunks, ip = self.preprocess(chunksize)
ret = ''
for i in range(len(chunks)):
- ip_part = compat_str(ip[i] % modulus_map[chunksize]) if i < 4 else ''
+ ip_part = str(ip[i] % modulus_map[chunksize]) if i < 4 else ''
if chunksize == 8:
ret += ip_part + chunks[i]
else:
@@ -104,11 +104,11 @@ class IqiyiSDK:
self.target = md5_text(self.target)
d = time.localtime(self.timestamp)
strings = {
- 'y': compat_str(d.tm_year),
+ 'y': str(d.tm_year),
'm': '%02d' % d.tm_mon,
'd': '%02d' % d.tm_mday,
}
- self.target += ''.join(map(lambda c: strings[c], list(scheme)))
+ self.target += ''.join(strings[c] for c in scheme)
def split_time_even_odd(self):
even, odd = self.even_odd()
@@ -120,11 +120,11 @@ class IqiyiSDK:
def split_ip_time_sum(self):
chunks, ip = self.preprocess(32)
- self.target = compat_str(sum(ip)) + chunks[0] + self.digit_sum(self.timestamp)
+ self.target = str(sum(ip)) + chunks[0] + self.digit_sum(self.timestamp)
def split_time_ip_sum(self):
chunks, ip = self.preprocess(32)
- self.target = self.digit_sum(self.timestamp) + chunks[0] + compat_str(sum(ip))
+ self.target = self.digit_sum(self.timestamp) + chunks[0] + str(sum(ip))
class IqiyiSDKInterpreter:
@@ -157,7 +157,7 @@ class IqiyiSDKInterpreter:
elif function in other_functions:
other_functions[function]()
else:
- raise ExtractorError('Unknown function %s' % function)
+ raise ExtractorError(f'Unknown function {function}')
return sdk.target
@@ -177,7 +177,7 @@ class IqiyiIE(InfoExtractor):
'id': '9c1fb1b99d192b21c559e5a1a2cb3c73',
'ext': 'mp4',
'title': '美国德州空中惊现奇异云团 酷似UFO',
- }
+ },
}, {
'url': 'http://www.iqiyi.com/v_19rrhnnclk.html',
'md5': 'b7dc800a4004b1b57749d9abae0472da',
@@ -249,8 +249,9 @@ class IqiyiIE(InfoExtractor):
note='Get token for logging', errnote='Unable to get token for logging')
sdk = data['sdk']
timestamp = int(time.time())
- target = '/apis/reglogin/login.action?lang=zh_TW&area_code=null&email=%s&passwd=%s&agenttype=1&from=undefined&keeplogin=0&piccode=&fromurl=&_pos=1' % (
- username, self._rsa_fun(password.encode('utf-8')))
+ target = (
+ f'/apis/reglogin/login.action?lang=zh_TW&area_code=null&email={username}'
+ f'&passwd={self._rsa_fun(password.encode())}&agenttype=1&from=undefined&keeplogin=0&piccode=&fromurl=&_pos=1')
interp = IqiyiSDKInterpreter(sdk)
sign = interp.run(target, data['ip'], timestamp)
@@ -264,7 +265,7 @@ class IqiyiIE(InfoExtractor):
'bird_t': timestamp,
}
validation_result = self._download_json(
- 'http://kylin.iqiyi.com/validate?' + compat_urllib_parse_urlencode(validation_params), None,
+ 'http://kylin.iqiyi.com/validate?' + urllib.parse.urlencode(validation_params), None,
note='Validate credentials', errnote='Unable to validate credentials')
MSG_MAP = {
@@ -276,7 +277,7 @@ class IqiyiIE(InfoExtractor):
if code != 'A00000':
msg = MSG_MAP.get(code)
if not msg:
- msg = 'error %s' % code
+ msg = f'error {code}'
if validation_result.get('msg'):
msg += ': ' + validation_result['msg']
self.report_warning('unable to log in: ' + msg)
@@ -288,7 +289,7 @@ class IqiyiIE(InfoExtractor):
tm = int(time.time() * 1000)
key = 'd5fb4bd9d50c4be6948c97edd7254b0e'
- sc = md5_text(compat_str(tm) + key + tvid)
+ sc = md5_text(str(tm) + key + tvid)
params = {
'tvid': tvid,
'vid': video_id,
@@ -298,7 +299,7 @@ class IqiyiIE(InfoExtractor):
}
return self._download_json(
- 'http://cache.m.iqiyi.com/jp/tmts/%s/%s/' % (tvid, video_id),
+ f'http://cache.m.iqiyi.com/jp/tmts/{tvid}/{video_id}/',
video_id, transform_source=lambda s: remove_start(s, 'var tvInfoJs='),
query=params, headers=self.geo_verification_headers())
@@ -321,10 +322,10 @@ class IqiyiIE(InfoExtractor):
# Start from 2 because links in the first page are already on webpage
for page_num in itertools.count(2):
pagelist_page = self._download_webpage(
- 'http://cache.video.qiyi.com/jp/avlist/%s/%d/%d/' % (album_id, page_num, PAGE_SIZE),
+ f'http://cache.video.qiyi.com/jp/avlist/{album_id}/{page_num}/{PAGE_SIZE}/',
album_id,
- note='Download playlist page %d' % page_num,
- errnote='Failed to download playlist page %d' % page_num)
+ note=f'Download playlist page {page_num}',
+ errnote=f'Failed to download playlist page {page_num}')
pagelist = self._parse_json(
remove_start(pagelist_page, 'var tvInfoJs='), album_id)
vlist = pagelist['data']['vlist']
@@ -367,7 +368,7 @@ class IqiyiIE(InfoExtractor):
for stream in data['vidl']:
if 'm3utx' not in stream:
continue
- vd = compat_str(stream['vd'])
+ vd = str(stream['vd'])
formats.append({
'url': stream['m3utx'],
'format_id': vd,
@@ -416,11 +417,11 @@ class IqIE(InfoExtractor):
'params': {
'format': '500',
},
- 'expected_warnings': ['format is restricted']
+ 'expected_warnings': ['format is restricted'],
}, {
# VIP-restricted video
'url': 'https://www.iq.com/play/mermaid-in-the-fog-2021-gbdpx13bs4',
- 'only_matching': True
+ 'only_matching': True,
}]
_BID_TAGS = {
'100': '240P',
@@ -562,7 +563,7 @@ class IqIE(InfoExtractor):
return
self._BID_TAGS = {
bid: traverse_obj(extracted_bid_tags, (bid, 'value'), expected_type=str, default=self._BID_TAGS.get(bid))
- for bid in extracted_bid_tags.keys()
+ for bid in extracted_bid_tags
}
def _get_cookie(self, name, default=None):
@@ -580,7 +581,7 @@ class IqIE(InfoExtractor):
uid = traverse_obj(
self._parse_json(
- self._get_cookie('I00002', '{}'), video_id, transform_source=compat_urllib_parse_unquote, fatal=False),
+ self._get_cookie('I00002', '{}'), video_id, transform_source=urllib.parse.unquote, fatal=False),
('data', 'uid'), default=0)
if uid:
@@ -590,7 +591,7 @@ class IqIE(InfoExtractor):
'platformId': 3,
'modeCode': self._get_cookie('mod', 'intl'),
'langCode': self._get_cookie('lang', 'en_us'),
- 'deviceId': self._get_cookie('QC005', '')
+ 'deviceId': self._get_cookie('QC005', ''),
}, fatal=False)
ut_list = traverse_obj(vip_data, ('data', 'all_vip', ..., 'vipType'), expected_type=str_or_none)
else:
@@ -621,7 +622,7 @@ class IqIE(InfoExtractor):
preview_time = traverse_obj(
initial_format_data, ('boss_ts', (None, 'data'), ('previewTime', 'rtime')), expected_type=float_or_none, get_all=False)
if traverse_obj(initial_format_data, ('boss_ts', 'data', 'prv'), expected_type=int_or_none):
- self.report_warning('This preview video is limited%s' % format_field(preview_time, None, ' to %s seconds'))
+ self.report_warning('This preview video is limited{}'.format(format_field(preview_time, None, ' to %s seconds')))
# TODO: Extract audio-only formats
for bid in set(traverse_obj(initial_format_data, ('program', 'video', ..., 'bid'), expected_type=str_or_none)):
@@ -672,7 +673,7 @@ class IqIE(InfoExtractor):
f.update({
'quality': qualities(list(self._BID_TAGS.keys()))(bid),
'format_note': self._BID_TAGS[bid],
- **parse_resolution(video_format.get('scrsz'))
+ **parse_resolution(video_format.get('scrsz')),
})
formats.extend(extracted_formats)
@@ -680,7 +681,7 @@ class IqIE(InfoExtractor):
lang = self._LID_TAGS.get(str_or_none(sub_format.get('lid')), sub_format.get('_name'))
subtitles.setdefault(lang, []).extend([{
'ext': format_ext,
- 'url': urljoin(initial_format_data.get('dstl', 'http://meta.video.iqiyi.com'), sub_format[format_key])
+ 'url': urljoin(initial_format_data.get('dstl', 'http://meta.video.iqiyi.com'), sub_format[format_key]),
} for format_key, format_ext in [('srt', 'srt'), ('webvtt', 'vtt')] if sub_format.get(format_key)])
extra_metadata = page_data.get('albumInfo') if video_info.get('albumId') and page_data.get('albumInfo') else video_info
@@ -709,9 +710,9 @@ class IqAlbumIE(InfoExtractor):
'info_dict': {
'id': '1bk9icvr331',
'title': 'One Piece',
- 'description': 'Subtitle available on Sunday 4PM(GMT+8).'
+ 'description': 'Subtitle available on Sunday 4PM(GMT+8).',
},
- 'playlist_mincount': 238
+ 'playlist_mincount': 238,
}, {
# Movie/single video
'url': 'https://www.iq.com/album/九龙城寨-2021-22yjnij099k',
@@ -728,7 +729,7 @@ class IqAlbumIE(InfoExtractor):
'age_limit': 13,
'average_rating': float,
},
- 'expected_warnings': ['format is restricted']
+ 'expected_warnings': ['format is restricted'],
}]
def _entries(self, album_id_num, page_ranges, album_id=None, mode_code='intl', lang_code='en_us'):
@@ -741,7 +742,7 @@ class IqAlbumIE(InfoExtractor):
'modeCode': mode_code,
'langCode': lang_code,
'endOrder': page_range['to'],
- 'startOrder': page_range['from']
+ 'startOrder': page_range['from'],
})
for video in page['data']['epg']:
yield self.url_result('https://www.iq.com/play/%s' % (video.get('playLocSuffix') or video['qipuIdStr']),
@@ -754,7 +755,7 @@ class IqAlbumIE(InfoExtractor):
album_data = next_data['props']['initialState']['album']['videoAlbumInfo']
if album_data.get('videoType') == 'singleVideo':
- return self.url_result('https://www.iq.com/play/%s' % album_id, IqIE.ie_key())
+ return self.url_result(f'https://www.iq.com/play/{album_id}', IqIE.ie_key())
return self.playlist_result(
self._entries(album_data['albumId'], album_data['totalPageRange'], album_id,
traverse_obj(next_data, ('props', 'initialProps', 'pageProps', 'modeCode')),
diff --git a/yt_dlp/extractor/islamchannel.py b/yt_dlp/extractor/islamchannel.py
index 253a846b7..f70c3add4 100644
--- a/yt_dlp/extractor/islamchannel.py
+++ b/yt_dlp/extractor/islamchannel.py
@@ -14,7 +14,7 @@ class IslamChannelIE(InfoExtractor):
'description': 'md5:5cc7ddecef064ea7afe52eb5e0e33b55',
'thumbnail': r're:https?://.+',
'ext': 'mp4',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/israelnationalnews.py b/yt_dlp/extractor/israelnationalnews.py
index 35040f576..76e54d9cf 100644
--- a/yt_dlp/extractor/israelnationalnews.py
+++ b/yt_dlp/extractor/israelnationalnews.py
@@ -7,7 +7,7 @@ class IsraelNationalNewsIE(InfoExtractor):
_TESTS = [{
'url': 'https://www.israelnationalnews.com/news/354520',
'info_dict': {
- 'id': '354520'
+ 'id': '354520',
},
'playlist': [{
'info_dict': {
@@ -34,8 +34,8 @@ class IsraelNationalNewsIE(InfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UCJdKr0Bgd_5saZYqLCa9mng',
'upload_date': '20220606',
'uploader': 'The Rubin Report',
- }
- }]
+ },
+ }],
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/itprotv.py b/yt_dlp/extractor/itprotv.py
index 5d6fbaa01..71001c469 100644
--- a/yt_dlp/extractor/itprotv.py
+++ b/yt_dlp/extractor/itprotv.py
@@ -12,7 +12,7 @@ from ..utils import (
class ITProTVBaseIE(InfoExtractor):
_ENDPOINTS = {
'course': 'course?url={}&brand=00002560-0000-3fa9-0000-1d61000035f3',
- 'episode': 'brand/00002560-0000-3fa9-0000-1d61000035f3/episode?url={}'
+ 'episode': 'brand/00002560-0000-3fa9-0000-1d61000035f3/episode?url={}',
}
def _call_api(self, ep, item_id, webpage):
@@ -46,7 +46,7 @@ class ITProTVIE(ITProTVBaseIE):
'availability': 'needs_auth',
'chapter': 'ITProTV 101',
'chapter_number': 1,
- 'chapter_id': '5dbb3de426b46c0010b5d1b6'
+ 'chapter_id': '5dbb3de426b46c0010b5d1b6',
},
},
{
@@ -64,7 +64,7 @@ class ITProTVIE(ITProTVBaseIE):
'availability': 'needs_auth',
'chapter': 'Job Development',
'chapter_number': 2,
- 'chapter_id': '5f7c78d424330c000edf04d9'
+ 'chapter_id': '5f7c78d424330c000edf04d9',
},
}]
@@ -95,7 +95,7 @@ class ITProTVIE(ITProTVBaseIE):
'chapter_number': chapter_number,
'chapter_id': str_or_none(chapter.get('id')),
'subtitles': {
- 'en': [{'ext': 'vtt', 'data': episode['enCaptionData']}]
+ 'en': [{'ext': 'vtt', 'data': episode['enCaptionData']}],
} if episode.get('enCaptionData') else None,
}
@@ -110,16 +110,16 @@ class ITProTVCourseIE(ITProTVBaseIE):
'description': 'md5:b175c2c3061ce35a4dd33865b2c1da4e',
'title': 'ITProTV 101',
},
- 'playlist_count': 6
+ 'playlist_count': 6,
},
{
'url': 'https://app.itpro.tv/course/beyond-tech',
'info_dict': {
'id': 'beyond-tech',
'description': 'md5:44cd99855e7f81a15ce1269bd0621fed',
- 'title': 'Beyond Tech'
+ 'title': 'Beyond Tech',
},
- 'playlist_count': 15
+ 'playlist_count': 15,
},
]
diff --git a/yt_dlp/extractor/itv.py b/yt_dlp/extractor/itv.py
index 55c416521..89e6f189c 100644
--- a/yt_dlp/extractor/itv.py
+++ b/yt_dlp/extractor/itv.py
@@ -2,7 +2,6 @@ import json
from .brightcove import BrightcoveNewIE
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
JSON_LD_RE,
ExtractorError,
@@ -34,7 +33,7 @@ class ITVIE(InfoExtractor):
'series': 'Plebs',
'season_number': 1,
'episode_number': 1,
- 'thumbnail': r're:https?://hubimages\.itv\.com/episode/2_1873_0002'
+ 'thumbnail': r're:https?://hubimages\.itv\.com/episode/2_1873_0002',
},
'params': {
# m3u8 download
@@ -50,7 +49,7 @@ class ITVIE(InfoExtractor):
'series': 'The Jonathan Ross Show',
'episode_number': 8,
'season_number': 17,
- 'thumbnail': r're:https?://hubimages\.itv\.com/episode/2_1873_0002'
+ 'thumbnail': r're:https?://hubimages\.itv\.com/episode/2_1873_0002',
},
'params': {
# m3u8 download
@@ -83,7 +82,7 @@ class ITVIE(InfoExtractor):
'user': {
'itvUserId': '',
'entitlements': [],
- 'token': ''
+ 'token': '',
},
'device': {
'manufacturer': 'Safari',
@@ -91,20 +90,20 @@ class ITVIE(InfoExtractor):
'os': {
'name': 'Windows NT',
'version': '6.1',
- 'type': 'desktop'
- }
+ 'type': 'desktop',
+ },
},
'client': {
'version': '4.1',
- 'id': 'browser'
+ 'id': 'browser',
},
'variantAvailability': {
'featureset': {
'min': featureset,
- 'max': featureset
+ 'max': featureset,
},
- 'platformTag': platform_tag
- }
+ 'platformTag': platform_tag,
+ },
}).encode(), headers=headers, fatal=fatal)
def _get_subtitles(self, video_id, variants, ios_playlist_url, headers, *args, **kwargs):
@@ -136,7 +135,7 @@ class ITVIE(InfoExtractor):
params = extract_attributes(self._search_regex(
r'(?s)(<[^>]+id="video"[^>]*>)', webpage, 'params'))
variants = self._parse_json(
- try_get(params, lambda x: x['data-video-variants'], compat_str) or '{}',
+ try_get(params, lambda x: x['data-video-variants'], str) or '{}',
video_id, fatal=False)
# Prefer last matching featureset
# See: https://github.com/yt-dlp/yt-dlp/issues/986
@@ -185,7 +184,7 @@ class ITVIE(InfoExtractor):
break
thumbnails = []
- thumbnail_url = try_get(params, lambda x: x['data-video-posterframe'], compat_str)
+ thumbnail_url = try_get(params, lambda x: x['data-video-posterframe'], str)
if thumbnail_url:
thumbnails.extend([{
'url': thumbnail_url.format(width=1920, height=1080, quality=100, blur=0, bg='false'),
@@ -193,7 +192,7 @@ class ITVIE(InfoExtractor):
'height': 1080,
}, {
'url': urljoin(base_url(thumbnail_url), url_basename(thumbnail_url)),
- 'preference': -2
+ 'preference': -2,
}])
thumbnail_url = self._html_search_meta(['og:image', 'twitter:image'], webpage, default=None)
@@ -210,7 +209,7 @@ class ITVIE(InfoExtractor):
'subtitles': self.extract_subtitles(video_id, variants, ios_playlist_url, headers),
'duration': parse_duration(video_data.get('Duration')),
'description': clean_html(get_element_by_class('episode-info__synopsis', webpage)),
- 'thumbnails': thumbnails
+ 'thumbnails': thumbnails,
}, info)
@@ -227,9 +226,9 @@ class ITVBTCCIE(InfoExtractor):
'url': 'https://www.itv.com/news/2021-10-27/i-have-to-protect-the-country-says-rishi-sunak-as-uk-faces-interest-rate-hike',
'info_dict': {
'id': 'i-have-to-protect-the-country-says-rishi-sunak-as-uk-faces-interest-rate-hike',
- 'title': 'md5:6ef054dd9f069330db3dcc66cb772d32'
+ 'title': 'md5:6ef054dd9f069330db3dcc66cb772d32',
},
- 'playlist_count': 4
+ 'playlist_count': 4,
}]
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_default/index.html?videoId=%s'
@@ -254,7 +253,7 @@ class ITVBTCCIE(InfoExtractor):
# ITV does not like some GB IP ranges, so here are some
# IP blocks it accepts
'geo_ip_blocks': [
- '193.113.0.0/16', '54.36.162.0/23', '159.65.16.0/21'
+ '193.113.0.0/16', '54.36.162.0/23', '159.65.16.0/21',
],
'referrer': url,
}),
diff --git a/yt_dlp/extractor/ivi.py b/yt_dlp/extractor/ivi.py
index fa5ceec95..57c276a67 100644
--- a/yt_dlp/extractor/ivi.py
+++ b/yt_dlp/extractor/ivi.py
@@ -82,10 +82,10 @@ class IviIE(InfoExtractor):
'params': [
video_id, {
'site': 's%d',
- 'referrer': 'http://www.ivi.ru/watch/%s' % video_id,
- 'contentid': video_id
- }
- ]
+ 'referrer': f'http://www.ivi.ru/watch/{video_id}',
+ 'contentid': video_id,
+ },
+ ],
})
for site in (353, 183):
@@ -98,7 +98,7 @@ class IviIE(InfoExtractor):
self._LIGHT_URL, video_id,
'Downloading timestamp JSON', data=json.dumps({
'method': 'da.timestamp.get',
- 'params': []
+ 'params': [],
}).encode(), fatal=False) or {}).get('result')
if not timestamp:
continue
@@ -158,7 +158,7 @@ class IviIE(InfoExtractor):
compilation = result.get('compilation')
episode = title if compilation else None
- title = '%s - %s' % (compilation, title) if compilation is not None else title
+ title = f'{compilation} - {title}' if compilation is not None else title
thumbnails = [{
'url': preview['url'],
@@ -219,9 +219,9 @@ class IviCompilationIE(InfoExtractor):
def _extract_entries(self, html, compilation_id):
return [
self.url_result(
- 'http://www.ivi.ru/watch/%s/%s' % (compilation_id, serie), IviIE.ie_key())
+ f'http://www.ivi.ru/watch/{compilation_id}/{serie}', IviIE.ie_key())
for serie in re.findall(
- r'<a\b[^>]+\bhref=["\']/watch/%s/(\d+)["\']' % compilation_id, html)]
+ rf'<a\b[^>]+\bhref=["\']/watch/{compilation_id}/(\d+)["\']', html)]
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -230,8 +230,8 @@ class IviCompilationIE(InfoExtractor):
if season_id is not None: # Season link
season_page = self._download_webpage(
- url, compilation_id, 'Downloading season %s web page' % season_id)
- playlist_id = '%s/season%s' % (compilation_id, season_id)
+ url, compilation_id, f'Downloading season {season_id} web page')
+ playlist_id = f'{compilation_id}/season{season_id}'
playlist_title = self._html_search_meta('title', season_page, 'title')
entries = self._extract_entries(season_page, compilation_id)
else: # Compilation link
@@ -239,15 +239,15 @@ class IviCompilationIE(InfoExtractor):
playlist_id = compilation_id
playlist_title = self._html_search_meta('title', compilation_page, 'title')
seasons = re.findall(
- r'<a href="/watch/%s/season(\d+)' % compilation_id, compilation_page)
+ rf'<a href="/watch/{compilation_id}/season(\d+)', compilation_page)
if not seasons: # No seasons in this compilation
entries = self._extract_entries(compilation_page, compilation_id)
else:
entries = []
for season_id in seasons:
season_page = self._download_webpage(
- 'http://www.ivi.ru/watch/%s/season%s' % (compilation_id, season_id),
- compilation_id, 'Downloading season %s web page' % season_id)
+ f'http://www.ivi.ru/watch/{compilation_id}/season{season_id}',
+ compilation_id, f'Downloading season {season_id} web page')
entries.extend(self._extract_entries(season_page, compilation_id))
return self.playlist_result(entries, playlist_id, playlist_title)
diff --git a/yt_dlp/extractor/ivideon.py b/yt_dlp/extractor/ivideon.py
index 7d1e554c2..eb860c7a6 100644
--- a/yt_dlp/extractor/ivideon.py
+++ b/yt_dlp/extractor/ivideon.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_urllib_parse_urlencode,
- compat_urlparse,
-)
from ..utils import qualities
@@ -21,7 +19,7 @@ class IvideonIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.ivideon.com/tv/camera/100-c4ee4cb9ede885cf62dfbe93d7b53783/589824/?lang=ru',
'only_matching': True,
@@ -36,8 +34,8 @@ class IvideonIE(InfoExtractor):
mobj = self._match_valid_url(url)
server_id, camera_id = mobj.group('id'), mobj.group('camera_id')
camera_name, description = None, None
- camera_url = compat_urlparse.urljoin(
- url, '/tv/camera/%s/%s/' % (server_id, camera_id))
+ camera_url = urllib.parse.urljoin(
+ url, f'/tv/camera/{server_id}/{camera_id}/')
webpage = self._download_webpage(camera_url, server_id, fatal=False)
if webpage:
@@ -57,12 +55,12 @@ class IvideonIE(InfoExtractor):
quality = qualities(self._QUALITIES)
formats = [{
- 'url': 'https://streaming.ivideon.com/flv/live?%s' % compat_urllib_parse_urlencode({
+ 'url': 'https://streaming.ivideon.com/flv/live?{}'.format(urllib.parse.urlencode({
'server': server_id,
'camera': camera_id,
'sessionId': 'demo',
'q': quality(format_id),
- }),
+ })),
'format_id': format_id,
'ext': 'flv',
'quality': quality(format_id),
diff --git a/yt_dlp/extractor/iwara.py b/yt_dlp/extractor/iwara.py
index a11f3f11d..df2088f9e 100644
--- a/yt_dlp/extractor/iwara.py
+++ b/yt_dlp/extractor/iwara.py
@@ -41,7 +41,7 @@ class IwaraBaseIE(InfoExtractor):
'https://api.iwara.tv/user/login', None, note='Logging in',
headers={'Content-Type': 'application/json'}, data=json.dumps({
'email': username,
- 'password': password
+ 'password': password,
}).encode(), expected_status=lambda x: True)
user_token = traverse_obj(response, ('token', {str}))
if not user_token:
@@ -65,7 +65,7 @@ class IwaraBaseIE(InfoExtractor):
'https://api.iwara.tv/user/token', None, note='Fetching media token',
data=b'', headers={
'Authorization': f'Bearer {IwaraBaseIE._USERTOKEN}',
- 'Content-Type': 'application/json'
+ 'Content-Type': 'application/json',
})['accessToken']
return {'Authorization': f'Bearer {IwaraBaseIE._MEDIATOKEN}'}
@@ -107,7 +107,7 @@ class IwaraIE(IwaraBaseIE):
'uploader': 'Lyu ya',
'uploader_id': 'user792540',
'tags': [
- 'uncategorized'
+ 'uncategorized',
],
'like_count': int,
'view_count': int,
@@ -129,7 +129,7 @@ class IwaraIE(IwaraBaseIE):
'uploader': 'Fe_Kurosabi',
'uploader_id': 'fekurosabi',
'tags': [
- 'pee'
+ 'pee',
],
'like_count': int,
'view_count': int,
diff --git a/yt_dlp/extractor/ixigua.py b/yt_dlp/extractor/ixigua.py
index 1f086d2bd..2868c2fc7 100644
--- a/yt_dlp/extractor/ixigua.py
+++ b/yt_dlp/extractor/ixigua.py
@@ -29,7 +29,7 @@ class IxiguaIE(InfoExtractor):
'thumbnail': r're:^https?://.+\.(avif|webp)',
'timestamp': 1629088414,
'duration': 1030,
- }
+ },
}]
def _get_json_data(self, webpage, video_id):
diff --git a/yt_dlp/extractor/izlesene.py b/yt_dlp/extractor/izlesene.py
index 5cdf8709d..cf2a269c3 100644
--- a/yt_dlp/extractor/izlesene.py
+++ b/yt_dlp/extractor/izlesene.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_unquote,
-)
from ..utils import (
determine_ext,
float_or_none,
@@ -33,7 +31,7 @@ class IzleseneIE(InfoExtractor):
'upload_date': '20140702',
'duration': 95.395,
'age_limit': 0,
- }
+ },
},
{
'url': 'http://www.izlesene.com/video/tarkan-dortmund-2006-konseri/17997',
@@ -48,14 +46,14 @@ class IzleseneIE(InfoExtractor):
'upload_date': '20061112',
'duration': 253.666,
'age_limit': 0,
- }
+ },
},
]
def _real_extract(self, url):
video_id = self._match_id(url)
- webpage = self._download_webpage('http://www.izlesene.com/video/%s' % video_id, video_id)
+ webpage = self._download_webpage(f'http://www.izlesene.com/video/{video_id}', video_id)
video = self._parse_json(
self._search_regex(
@@ -67,14 +65,14 @@ class IzleseneIE(InfoExtractor):
formats = []
for stream in video['media']['level']:
source_url = stream.get('source')
- if not source_url or not isinstance(source_url, compat_str):
+ if not source_url or not isinstance(source_url, str):
continue
ext = determine_ext(url, 'mp4')
quality = stream.get('value')
height = int_or_none(quality)
formats.append({
- 'format_id': '%sp' % quality if quality else 'sd',
- 'url': compat_urllib_parse_unquote(source_url),
+ 'format_id': f'{quality}p' if quality else 'sd',
+ 'url': urllib.parse.unquote(source_url),
'ext': ext,
'height': height,
})
diff --git a/yt_dlp/extractor/jamendo.py b/yt_dlp/extractor/jamendo.py
index 8557a81ad..16540c414 100644
--- a/yt_dlp/extractor/jamendo.py
+++ b/yt_dlp/extractor/jamendo.py
@@ -2,7 +2,6 @@ import hashlib
import random
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
clean_html,
int_or_none,
@@ -40,20 +39,20 @@ class JamendoIE(InfoExtractor):
'like_count': int,
'average_rating': int,
'tags': ['piano', 'peaceful', 'newage', 'strings', 'upbeat'],
- }
+ },
}, {
'url': 'https://licensing.jamendo.com/en/track/1496667/energetic-rock',
'only_matching': True,
}]
def _call_api(self, resource, resource_id, fatal=True):
- path = '/api/%ss' % resource
- rand = compat_str(random.random())
+ path = f'/api/{resource}s'
+ rand = str(random.random())
return self._download_json(
'https://www.jamendo.com' + path, resource_id, fatal=fatal, query={
'id[]': resource_id,
}, headers={
- 'X-Jam-Call': '$%s*%s~' % (hashlib.sha1((path + rand).encode()).hexdigest(), rand)
+ 'X-Jam-Call': f'${hashlib.sha1((path + rand).encode()).hexdigest()}*{rand}~',
})[0]
def _real_extract(self, url):
@@ -72,12 +71,11 @@ class JamendoIE(InfoExtractor):
# if artist_name:
# title = '%s - %s' % (artist_name, title)
# album = get_model('album')
- artist = self._call_api("artist", track.get('artistId'), fatal=False)
- album = self._call_api("album", track.get('albumId'), fatal=False)
+ artist = self._call_api('artist', track.get('artistId'), fatal=False)
+ album = self._call_api('album', track.get('albumId'), fatal=False)
formats = [{
- 'url': 'https://%s.jamendo.com/?trackid=%s&format=%s&from=app-97dab294'
- % (sub_domain, track_id, format_id),
+ 'url': f'https://{sub_domain}.jamendo.com/?trackid={track_id}&format={format_id}&from=app-97dab294',
'format_id': format_id,
'ext': ext,
'quality': quality,
@@ -111,7 +109,7 @@ class JamendoIE(InfoExtractor):
tags.append(tag_name)
stats = track.get('stats') or {}
- license = track.get('licenseCC') or []
+ video_license = track.get('licenseCC') or []
return {
'id': track_id,
@@ -124,7 +122,7 @@ class JamendoIE(InfoExtractor):
'track': track_name,
'album': album.get('name'),
'formats': formats,
- 'license': '-'.join(license) if license else None,
+ 'license': '-'.join(video_license) if video_license else None,
'timestamp': int_or_none(track.get('dateCreated')),
'view_count': int_or_none(stats.get('listenedAll')),
'like_count': int_or_none(stats.get('favorited')),
@@ -160,7 +158,7 @@ class JamendoAlbumIE(JamendoIE): # XXX: Do not subclass from concrete IE
'average_rating': 4,
'tags': ['rock', 'drums', 'bass', 'world', 'punk', 'neutral'],
'like_count': int,
- }
+ },
}, {
'md5': '1f358d7b2f98edfe90fd55dac0799d50',
'info_dict': {
@@ -179,11 +177,11 @@ class JamendoAlbumIE(JamendoIE): # XXX: Do not subclass from concrete IE
'average_rating': 4,
'license': 'by',
'like_count': int,
- }
+ },
}],
'params': {
- 'playlistend': 2
- }
+ 'playlistend': 2,
+ },
}]
def _real_extract(self, url):
@@ -196,7 +194,7 @@ class JamendoAlbumIE(JamendoIE): # XXX: Do not subclass from concrete IE
track_id = track.get('id')
if not track_id:
continue
- track_id = compat_str(track_id)
+ track_id = str(track_id)
entries.append({
'_type': 'url_transparent',
'url': 'https://www.jamendo.com/track/' + track_id,
@@ -207,4 +205,4 @@ class JamendoAlbumIE(JamendoIE): # XXX: Do not subclass from concrete IE
return self.playlist_result(
entries, album_id, album_name,
- clean_html(try_get(album, lambda x: x['description']['en'], compat_str)))
+ clean_html(try_get(album, lambda x: x['description']['en'], str)))
diff --git a/yt_dlp/extractor/japandiet.py b/yt_dlp/extractor/japandiet.py
index 19d2b923b..2ef091aff 100644
--- a/yt_dlp/extractor/japandiet.py
+++ b/yt_dlp/extractor/japandiet.py
@@ -41,7 +41,7 @@ def _parse_japanese_duration(text):
mobj = re.search(r'(?:(\d+)日間?)?(?:(\d+)時間?)?(?:(\d+)分)?(?:(\d+)秒)?', re.sub(r'[\s\u3000]+', '', text or ''))
if not mobj:
return
- days, hours, mins, secs = [int_or_none(x, default=0) for x in mobj.groups()]
+ days, hours, mins, secs = (int_or_none(x, default=0) for x in mobj.groups())
return secs + mins * 60 + hours * 60 * 60 + days * 24 * 60 * 60
@@ -142,10 +142,10 @@ class ShugiinItvVodIE(ShugiinItvBaseIE):
'title': 'ウクライナ大統領国会演説(オンライン)',
'release_date': '20220323',
'chapters': 'count:4',
- }
+ },
}, {
'url': 'https://www.shugiintv.go.jp/en/index.php?ex=VL&media_type=&deli_id=53846',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -232,7 +232,7 @@ class SangiinIE(InfoExtractor):
'is_live': True,
},
'skip': 'this live is turned into archive after it ends',
- }, ]
+ }]
def _real_extract(self, url):
video_id = self._match_id(url)
diff --git a/yt_dlp/extractor/jiocinema.py b/yt_dlp/extractor/jiocinema.py
index e7186d75c..5898e1f49 100644
--- a/yt_dlp/extractor/jiocinema.py
+++ b/yt_dlp/extractor/jiocinema.py
@@ -157,11 +157,11 @@ class JioCinemaBaseIE(InfoExtractor):
'info': {
'platform': {'name': 'iPhone OS' if is_iphone else 'Android'},
'androidId': self._DEVICE_ID,
- 'type': 'iOS' if is_iphone else 'Android'
- }
+ 'type': 'iOS' if is_iphone else 'Android',
+ },
},
**initial_data,
- 'otp': self._get_tfa_info('the one-time password sent to your phone')
+ 'otp': self._get_tfa_info('the one-time password sent to your phone'),
}, 'Submitting OTP')
if traverse_obj(response, 'code') == 1043:
raise ExtractorError('Wrong OTP', expected=True)
@@ -276,12 +276,12 @@ class JioCinemaIE(JioCinemaBaseIE):
'aesSupport': 'yes',
'fairPlayDrmSupport': 'none',
'playreadyDrmSupport': 'none',
- 'widevineDRMSupport': 'none'
+ 'widevineDRMSupport': 'none',
},
'frameRateCapability': [{
'frameRateSupport': '30fps',
- 'videoQuality': '1440p'
- }]
+ 'videoQuality': '1440p',
+ }],
},
'continueWatchingRequired': False,
'dolby': False,
@@ -293,7 +293,7 @@ class JioCinemaIE(JioCinemaBaseIE):
'multiAudioRequired': True,
'osVersion': '10',
'parentalPinValid': True,
- 'x-apisignatures': self._API_SIGNATURES
+ 'x-apisignatures': self._API_SIGNATURES,
})
status_code = traverse_obj(playback, ('code', {int}))
diff --git a/yt_dlp/extractor/jiosaavn.py b/yt_dlp/extractor/jiosaavn.py
index 35fb3fd6b..542e41b80 100644
--- a/yt_dlp/extractor/jiosaavn.py
+++ b/yt_dlp/extractor/jiosaavn.py
@@ -27,7 +27,7 @@ class JioSaavnBaseIE(InfoExtractor):
if invalid_bitrates := set(requested_bitrates) - self._VALID_BITRATES:
raise ValueError(
f'Invalid bitrate(s): {", ".join(invalid_bitrates)}. '
- + f'Valid bitrates are: {", ".join(sorted(self._VALID_BITRATES, key=int))}')
+ f'Valid bitrates are: {", ".join(sorted(self._VALID_BITRATES, key=int))}')
return requested_bitrates
def _extract_formats(self, song_data):
diff --git a/yt_dlp/extractor/joj.py b/yt_dlp/extractor/joj.py
index ea4604240..0c8e999cd 100644
--- a/yt_dlp/extractor/joj.py
+++ b/yt_dlp/extractor/joj.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
format_field,
int_or_none,
@@ -25,7 +24,7 @@ class JojIE(InfoExtractor):
'title': 'NOVÉ BÝVANIE',
'thumbnail': r're:^https?://.*?$',
'duration': 3118,
- }
+ },
}, {
'url': 'https://media.joj.sk/embed/CSM0Na0l0p1',
'info_dict': {
@@ -35,7 +34,7 @@ class JojIE(InfoExtractor):
'title': 'Extrémne rodiny 2 - POKRAČOVANIE (2012/04/09 21:30:00)',
'duration': 3937,
'thumbnail': r're:^https?://.*?$',
- }
+ },
}, {
'url': 'https://media.joj.sk/embed/9i1cxv',
'only_matching': True,
@@ -51,7 +50,7 @@ class JojIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'https://media.joj.sk/embed/%s' % video_id, video_id)
+ f'https://media.joj.sk/embed/{video_id}', video_id)
title = (self._search_json(r'videoTitle\s*:', webpage, 'title', video_id,
contains_pattern=r'["\'].+["\']', default=None)
@@ -66,7 +65,7 @@ class JojIE(InfoExtractor):
formats = []
for format_url in try_get(bitrates, lambda x: x['mp4'], list) or []:
- if isinstance(format_url, compat_str):
+ if isinstance(format_url, str):
height = self._search_regex(
r'(\d+)[pP]|(pal)\.', format_url, 'height', default=None)
if height == 'pal':
@@ -78,7 +77,7 @@ class JojIE(InfoExtractor):
})
if not formats:
playlist = self._download_xml(
- 'https://media.joj.sk/services/Video.php?clip=%s' % video_id,
+ f'https://media.joj.sk/services/Video.php?clip={video_id}',
video_id)
for file_el in playlist.findall('./files/file'):
path = file_el.get('path')
@@ -86,8 +85,8 @@ class JojIE(InfoExtractor):
continue
format_id = file_el.get('id') or file_el.get('label')
formats.append({
- 'url': 'http://n16.joj.sk/storage/%s' % path.replace(
- 'dat/', '', 1),
+ 'url': 'http://n16.joj.sk/storage/{}'.format(path.replace(
+ 'dat/', '', 1)),
'format_id': format_id,
'height': int_or_none(self._search_regex(
r'(\d+)[pP]', format_id or path, 'height',
diff --git a/yt_dlp/extractor/jove.py b/yt_dlp/extractor/jove.py
index 8069fea4c..6b37ccfdc 100644
--- a/yt_dlp/extractor/jove.py
+++ b/yt_dlp/extractor/jove.py
@@ -16,7 +16,7 @@ class JoveIE(InfoExtractor):
'description': 'md5:015dd4509649c0908bc27f049e0262c6',
'thumbnail': r're:^https?://.*\.png$',
'upload_date': '20110523',
- }
+ },
},
{
'url': 'http://www.jove.com/video/51796/culturing-caenorhabditis-elegans-axenic-liquid-media-creation',
@@ -28,7 +28,7 @@ class JoveIE(InfoExtractor):
'description': 'md5:35ff029261900583970c4023b70f1dc9',
'thumbnail': r're:^https?://.*\.png$',
'upload_date': '20140802',
- }
+ },
},
]
diff --git a/yt_dlp/extractor/jwplatform.py b/yt_dlp/extractor/jwplatform.py
index bc47aa6d3..7d5a931b5 100644
--- a/yt_dlp/extractor/jwplatform.py
+++ b/yt_dlp/extractor/jwplatform.py
@@ -18,7 +18,7 @@ class JWPlatformIE(InfoExtractor):
'timestamp': 1227796140,
'duration': 32.0,
'thumbnail': 'https://cdn.jwplayer.com/v2/media/nPripu9l/poster.jpg?width=720',
- }
+ },
}, {
'url': 'https://cdn.jwplayer.com/players/nPripu9l-ALJ3XQCI.js',
'only_matching': True,
@@ -72,7 +72,7 @@ class JWPlatformIE(InfoExtractor):
# <input value=URL> is used by hyland.com
# if we find <iframe>, dont look for <input>
ret = re.findall(
- r'<%s[^>]+?%s=\\?["\']?((?:https?:)?//(?:content\.jwplatform|cdn\.jwplayer)\.com/players/[a-zA-Z0-9]{8})' % (tag, key),
+ rf'<{tag}[^>]+?{key}=\\?["\']?((?:https?:)?//(?:content\.jwplatform|cdn\.jwplayer)\.com/players/[a-zA-Z0-9]{{8}})',
webpage)
if ret:
return ret
diff --git a/yt_dlp/extractor/kakao.py b/yt_dlp/extractor/kakao.py
index 563aa2d72..6f3459db8 100644
--- a/yt_dlp/extractor/kakao.py
+++ b/yt_dlp/extractor/kakao.py
@@ -33,7 +33,7 @@ class KakaoIE(InfoExtractor):
'view_count': int,
'duration': 1503,
'comment_count': int,
- }
+ },
}, {
'url': 'http://tv.kakao.com/channel/2653210/cliplink/300103180',
'md5': 'a8917742069a4dd442516b86e7d66529',
@@ -52,7 +52,7 @@ class KakaoIE(InfoExtractor):
'view_count': int,
'duration': 184,
'comment_count': int,
- }
+ },
}, {
# geo restricted
'url': 'https://tv.kakao.com/channel/3643855/cliplink/412069491',
@@ -76,7 +76,7 @@ class KakaoIE(InfoExtractor):
'description', 'channelId', 'createTime', 'duration', 'playCount',
'likeCount', 'commentCount', 'tagList', 'channel', 'name',
'clipChapterThumbnailList', 'thumbnailUrl', 'timeInSec', 'isDefault',
- 'videoOutputList', 'width', 'height', 'kbps', 'profile', 'label'])
+ 'videoOutputList', 'width', 'height', 'kbps', 'profile', 'label']),
}
api_json = self._download_json(
@@ -99,7 +99,7 @@ class KakaoIE(InfoExtractor):
try:
fmt_url_json = self._download_json(
cdn_api_base, video_id, query=query,
- note='Downloading video URL for profile %s' % profile_name)
+ note=f'Downloading video URL for profile {profile_name}')
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
resp = self._parse_json(e.cause.response.read().decode(), video_id)
@@ -126,7 +126,7 @@ class KakaoIE(InfoExtractor):
thumbs.append({
'url': thumb.get('thumbnailUrl'),
'id': str(thumb.get('timeInSec')),
- 'preference': -1 if thumb.get('isDefault') else 0
+ 'preference': -1 if thumb.get('isDefault') else 0,
})
top_thumbnail = clip.get('thumbnailUrl')
if top_thumbnail:
diff --git a/yt_dlp/extractor/kaltura.py b/yt_dlp/extractor/kaltura.py
index 4752d5a55..e5737b1e9 100644
--- a/yt_dlp/extractor/kaltura.py
+++ b/yt_dlp/extractor/kaltura.py
@@ -1,12 +1,10 @@
import base64
+import contextlib
import json
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
clean_html,
@@ -57,7 +55,7 @@ class KalturaIE(InfoExtractor):
'thumbnail': 're:^https?://.*/thumbnail/.*',
'timestamp': int,
},
- 'skip': 'The access to this service is forbidden since the specified partner is blocked'
+ 'skip': 'The access to this service is forbidden since the specified partner is blocked',
},
{
'url': 'http://www.kaltura.com/index.php/kwidget/cache_st/1300318621/wid/_269692/uiconf_id/3873291/entry_id/1_1jc2y3e4',
@@ -124,14 +122,14 @@ class KalturaIE(InfoExtractor):
'view_count': int,
'upload_date': '20140815',
'thumbnail': 'http://cfvod.kaltura.com/p/691292/sp/69129200/thumbnail/entry_id/0_c076mna6/version/100022',
- }
+ },
},
{
# html5lib playlist URL using kwidget player
'url': 'https://cdnapisec.kaltura.com/html5/html5lib/v2.89/mwEmbedFrame.php/p/2019031/uiconf_id/40436601?wid=1_4j3m32cv&iframeembed=true&playerId=kaltura_player_&flashvars[playlistAPI.kpl0Id]=1_jovey5nu&flashvars[ks]=&&flashvars[imageDefaultDuration]=30&flashvars[localizationCode]=en&flashvars[leadWithHTML5]=true&flashvars[forceMobileHTML5]=true&flashvars[nextPrevBtn.plugin]=true&flashvars[hotspots.plugin]=true&flashvars[sideBarContainer.plugin]=true&flashvars[sideBarContainer.position]=left&flashvars[sideBarContainer.clickToClose]=true&flashvars[chapters.plugin]=true&flashvars[chapters.layout]=vertical&flashvars[chapters.thumbnailRotator]=false&flashvars[streamSelector.plugin]=true&flashvars[EmbedPlayer.SpinnerTarget]=videoHolder&flashvars[dualScreen.plugin]=true&flashvars[playlistAPI.playlistUrl]=https://canvasgatechtest.kaf.kaltura.com/playlist/details/{playlistAPI.kpl0Id}/categoryid/126428551',
'info_dict': {
'id': '1_jovey5nu',
- 'title': '00-00 Introduction'
+ 'title': '00-00 Introduction',
},
'playlist': [
{
@@ -145,7 +143,7 @@ class KalturaIE(InfoExtractor):
'timestamp': 1533154447,
'upload_date': '20180801',
'uploader_id': 'djoyner3',
- }
+ },
}, {
'info_dict': {
'id': '1_jfb7mdpn',
@@ -157,7 +155,7 @@ class KalturaIE(InfoExtractor):
'timestamp': 1533154489,
'upload_date': '20180801',
'uploader_id': 'djoyner3',
- }
+ },
}, {
'info_dict': {
'id': '1_8xflxdp7',
@@ -169,7 +167,7 @@ class KalturaIE(InfoExtractor):
'timestamp': 1533154512,
'upload_date': '20180801',
'uploader_id': 'djoyner3',
- }
+ },
}, {
'info_dict': {
'id': '1_3hqew8kn',
@@ -181,10 +179,10 @@ class KalturaIE(InfoExtractor):
'timestamp': 1533154536,
'upload_date': '20180801',
'uploader_id': 'djoyner3',
- }
- }
- ]
- }
+ },
+ },
+ ],
+ },
]
@classmethod
@@ -192,14 +190,14 @@ class KalturaIE(InfoExtractor):
# Embed codes: https://knowledge.kaltura.com/embedding-kaltura-media-players-your-site
finditer = (
list(re.finditer(
- r"""(?xs)
+ r'''(?xs)
kWidget\.(?:thumb)?[Ee]mbed\(
\{.*?
(?P<q1>['"])wid(?P=q1)\s*:\s*
(?P<q2>['"])_?(?P<partner_id>(?:(?!(?P=q2)).)+)(?P=q2),.*?
(?P<q3>['"])entry_?[Ii]d(?P=q3)\s*:\s*
(?P<q4>['"])(?P<id>(?:(?!(?P=q4)).)+)(?P=q4)(?:,|\s*\})
- """, webpage))
+ ''', webpage))
or list(re.finditer(
r'''(?xs)
(?P<q1>["'])
@@ -230,34 +228,34 @@ class KalturaIE(InfoExtractor):
for k, v in embed_info.items():
if v:
embed_info[k] = v.strip()
- embed_url = 'kaltura:%(partner_id)s:%(id)s' % embed_info
+ embed_url = 'kaltura:{partner_id}:{id}'.format(**embed_info)
escaped_pid = re.escape(embed_info['partner_id'])
service_mobj = re.search(
- r'<script[^>]+src=(["\'])(?P<id>(?:https?:)?//(?:(?!\1).)+)/p/%s/sp/%s00/embedIframeJs' % (escaped_pid, escaped_pid),
+ rf'<script[^>]+src=(["\'])(?P<id>(?:https?:)?//(?:(?!\1).)+)/p/{escaped_pid}/sp/{escaped_pid}00/embedIframeJs',
webpage)
if service_mobj:
embed_url = smuggle_url(embed_url, {'service_url': service_mobj.group('id')})
urls.append(embed_url)
return urls
- def _kaltura_api_call(self, video_id, actions, service_url=None, *args, **kwargs):
+ def _kaltura_api_call(self, video_id, actions, service_url=None, **kwargs):
params = actions[0]
- params.update({i: a for i, a in enumerate(actions[1:], start=1)})
+ params.update(dict(enumerate(actions[1:], start=1)))
data = self._download_json(
(service_url or self._SERVICE_URL) + self._SERVICE_BASE,
- video_id, data=json.dumps(params).encode('utf-8'),
+ video_id, data=json.dumps(params).encode(),
headers={
'Content-Type': 'application/json',
'Accept-Encoding': 'gzip, deflate, br',
- }, *args, **kwargs)
+ }, **kwargs)
for idx, status in enumerate(data):
if not isinstance(status, dict):
continue
if status.get('objectType') == 'KalturaAPIException':
raise ExtractorError(
- '%s said: %s (%d)' % (self.IE_NAME, status['message'], idx))
+ '{} said: {} ({})'.format(self.IE_NAME, status['message'], idx))
data[1] = traverse_obj(data, (1, 'objects', 0))
@@ -342,7 +340,7 @@ class KalturaIE(InfoExtractor):
'apiVersion': '3.1',
'clientTag': 'kwidget:v2.89',
'ignoreNull': 1,
- 'ks': '{1:result:ks}'
+ 'ks': '{1:result:ks}',
},
# info
{
@@ -397,10 +395,10 @@ class KalturaIE(InfoExtractor):
raise ExtractorError('Invalid URL', expected=True)
params = {}
if query:
- params = compat_parse_qs(query)
+ params = urllib.parse.parse_qs(query)
if path:
splitted_path = path.split('/')
- params.update(dict((zip(splitted_path[::2], [[v] for v in splitted_path[1::2]]))))
+ params.update(dict(zip(splitted_path[::2], [[v] for v in splitted_path[1::2]])))
if 'wid' in params:
partner_id = remove_start(params['wid'][0], '_')
elif 'p' in params:
@@ -423,14 +421,11 @@ class KalturaIE(InfoExtractor):
# Unfortunately, data returned in kalturaIframePackageData lacks
# captions so we will try requesting the complete data using
# regular approach since we now know the entry_id
- try:
+ # Even if this fails we already have everything extracted
+ # apart from captions and can process at least with this
+ with contextlib.suppress(ExtractorError):
_, info, flavor_assets, captions = self._get_video_info(
entry_id, partner_id, player_type=player_type)
- except ExtractorError:
- # Regular scenario failed but we already have everything
- # extracted apart from captions and can process at least
- # with this
- pass
elif 'uiconf_id' in params and 'flashvars[playlistAPI.kpl0Id]' in params:
playlist_id = params['flashvars[playlistAPI.kpl0Id]'][0]
webpage = self._download_webpage(url, playlist_id)
@@ -451,16 +446,16 @@ class KalturaIE(InfoExtractor):
source_url = smuggled_data.get('source_url')
if source_url:
referrer = base64.b64encode(
- '://'.join(compat_urlparse.urlparse(source_url)[:2])
- .encode('utf-8')).decode('utf-8')
+ '://'.join(urllib.parse.urlparse(source_url)[:2])
+ .encode()).decode('utf-8')
else:
referrer = None
def sign_url(unsigned_url):
if ks:
- unsigned_url += '/ks/%s' % ks
+ unsigned_url += f'/ks/{ks}'
if referrer:
- unsigned_url += '?referrer=%s' % referrer
+ unsigned_url += f'?referrer={referrer}'
return unsigned_url
data_url = info['dataUrl']
@@ -487,8 +482,8 @@ class KalturaIE(InfoExtractor):
else:
f['fileExt'] = 'mp4'
video_url = sign_url(
- '%s/flavorId/%s' % (data_url, f['id']))
- format_id = '%(fileExt)s-%(bitrate)s' % f
+ '{}/flavorId/{}'.format(data_url, f['id']))
+ format_id = '{fileExt}-{bitrate}'.format(**f)
# Source format may not be available (e.g. kaltura:513551:1_66x4rg7o)
if f.get('isOriginal') is True and not self._is_valid_url(
video_url, entry_id, format_id):
@@ -527,7 +522,7 @@ class KalturaIE(InfoExtractor):
continue
caption_format = int_or_none(caption.get('format'))
subtitles.setdefault(caption.get('languageCode') or caption.get('language'), []).append({
- 'url': '%s/api_v3/service/caption_captionasset/action/serve/captionAssetId/%s' % (self._SERVICE_URL, caption['id']),
+ 'url': '{}/api_v3/service/caption_captionasset/action/serve/captionAssetId/{}'.format(self._SERVICE_URL, caption['id']),
'ext': caption.get('fileExt') or self._CAPTION_TYPES.get(caption_format) or 'ttml',
})
diff --git a/yt_dlp/extractor/kankanews.py b/yt_dlp/extractor/kankanews.py
index 3d74c745c..a39ff78ce 100644
--- a/yt_dlp/extractor/kankanews.py
+++ b/yt_dlp/extractor/kankanews.py
@@ -19,7 +19,7 @@ class KankaNewsIE(InfoExtractor):
'ext': 'mp4',
'title': '视频|第23个中国记者节,我们在进博切蛋糕',
'thumbnail': r're:^https?://.*\.jpg*',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/karaoketv.py b/yt_dlp/extractor/karaoketv.py
index 381dc00ad..8168b1a49 100644
--- a/yt_dlp/extractor/karaoketv.py
+++ b/yt_dlp/extractor/karaoketv.py
@@ -13,7 +13,7 @@ class KaraoketvIE(InfoExtractor):
'params': {
# rtmp download
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
@@ -45,7 +45,7 @@ class KaraoketvIE(InfoExtractor):
servers = ('wowzail.video-cdn.com:80/vodcdn', )
formats = [{
- 'url': 'rtmp://%s' % server if not server.startswith('rtmp') else server,
+ 'url': f'rtmp://{server}' if not server.startswith('rtmp') else server,
'play_path': play_path,
'app': 'vodcdn',
'page_url': video_cdn_url,
diff --git a/yt_dlp/extractor/kelbyone.py b/yt_dlp/extractor/kelbyone.py
index bba527e29..0ac0c5eab 100644
--- a/yt_dlp/extractor/kelbyone.py
+++ b/yt_dlp/extractor/kelbyone.py
@@ -24,7 +24,7 @@ class KelbyOneIE(InfoExtractor):
'duration': 90,
'upload_date': '20201001',
},
- }]
+ }],
}]
def _entries(self, playlist):
diff --git a/yt_dlp/extractor/kicker.py b/yt_dlp/extractor/kicker.py
index a2c7dd4e8..4ab675178 100644
--- a/yt_dlp/extractor/kicker.py
+++ b/yt_dlp/extractor/kicker.py
@@ -20,8 +20,8 @@ class KickerIE(InfoExtractor):
'age_limit': 0,
'thumbnail': r're:https://s\d+\.dmcdn\.net/v/T-x741YeYAx8aSZ0Z/x1080',
'tags': ['published', 'category.InternationalSoccer'],
- 'upload_date': '20220608'
- }
+ 'upload_date': '20220608',
+ },
}, {
'url': 'https://www.kicker.de/ex-unioner-in-der-bezirksliga-felix-kroos-vereinschallenge-in-pankow-902825/video',
'info_dict': {
@@ -39,7 +39,7 @@ class KickerIE(InfoExtractor):
'uploader': 'kicker.de',
'description': 'md5:0c2060c899a91c8bf40f578f78c5846f',
'like_count': int,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/kinja.py b/yt_dlp/extractor/kinja.py
index f4e5c4c47..99c8a1224 100644
--- a/yt_dlp/extractor/kinja.py
+++ b/yt_dlp/extractor/kinja.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_unquote,
-)
from ..utils import (
int_or_none,
parse_iso8601,
@@ -32,7 +30,7 @@ class KinjaEmbedIE(InfoExtractor):
ajax/inset|
embed/video
)/iframe\?.*?\bid='''
- _VALID_URL = r'''(?x)https?://%s%s
+ _VALID_URL = rf'''(?x)https?://{_DOMAIN_REGEX}{_COMMON_REGEX}
(?P<type>
fb|
imgur|
@@ -49,7 +47,7 @@ class KinjaEmbedIE(InfoExtractor):
vimeo|
vine|
youtube-(?:list|video)
- )-(?P<id>[^&]+)''' % (_DOMAIN_REGEX, _COMMON_REGEX)
+ )-(?P<id>[^&]+)'''
_EMBED_REGEX = [rf'(?x)<iframe[^>]+?src=(?P<q>["\'])(?P<url>(?:(?:https?:)?//{_DOMAIN_REGEX})?{_COMMON_REGEX}(?:(?!\1).)+)\1']
_TESTS = [{
'url': 'https://kinja.com/ajax/inset/iframe?id=fb-10103303356633621',
@@ -116,7 +114,7 @@ class KinjaEmbedIE(InfoExtractor):
provider = self._PROVIDER_MAP.get(video_type)
if provider:
- video_id = compat_urllib_parse_unquote(video_id)
+ video_id = urllib.parse.unquote(video_id)
if video_type == 'tumblr-post':
video_id, blog = video_id.split('-', 1)
result_url = provider[0] % (blog, video_id)
@@ -145,7 +143,7 @@ class KinjaEmbedIE(InfoExtractor):
poster = data.get('poster') or {}
poster_id = poster.get('id')
if poster_id:
- thumbnail = 'https://i.kinja-img.com/gawker-media/image/upload/%s.%s' % (poster_id, poster.get('format') or 'jpg')
+ thumbnail = 'https://i.kinja-img.com/gawker-media/image/upload/{}.{}'.format(poster_id, poster.get('format') or 'jpg')
return {
'id': video_id,
@@ -190,10 +188,10 @@ class KinjaEmbedIE(InfoExtractor):
return {
'id': video_id,
'title': title,
- 'thumbnail': try_get(iptc, lambda x: x['cloudinaryLink']['link'], compat_str),
+ 'thumbnail': try_get(iptc, lambda x: x['cloudinaryLink']['link'], str),
'uploader': fmg.get('network'),
'duration': int_or_none(iptc.get('fileDuration')),
'formats': formats,
- 'description': try_get(iptc, lambda x: x['description']['en'], compat_str),
+ 'description': try_get(iptc, lambda x: x['description']['en'], str),
'timestamp': parse_iso8601(iptc.get('dateReleased')),
}
diff --git a/yt_dlp/extractor/kommunetv.py b/yt_dlp/extractor/kommunetv.py
index 432816cd8..5ec33a9ff 100644
--- a/yt_dlp/extractor/kommunetv.py
+++ b/yt_dlp/extractor/kommunetv.py
@@ -10,16 +10,16 @@ class KommunetvIE(InfoExtractor):
'info_dict': {
'id': '921',
'title': 'Bystyremøte',
- 'ext': 'mp4'
- }
+ 'ext': 'mp4',
+ },
}
def _real_extract(self, url):
video_id = self._match_id(url)
headers = {
- 'Accept': 'application/json'
+ 'Accept': 'application/json',
}
- data = self._download_json('https://oslo.kommunetv.no/api/streams?streamType=1&id=%s' % video_id, video_id, headers=headers)
+ data = self._download_json(f'https://oslo.kommunetv.no/api/streams?streamType=1&id={video_id}', video_id, headers=headers)
title = data['stream']['title']
file = data['playlist'][0]['playlist'][0]['file']
url = update_url(file, query=None, fragment=None)
@@ -27,5 +27,5 @@ class KommunetvIE(InfoExtractor):
return {
'id': video_id,
'formats': formats,
- 'title': title
+ 'title': title,
}
diff --git a/yt_dlp/extractor/kompas.py b/yt_dlp/extractor/kompas.py
index 8bad96190..2ef076c1e 100644
--- a/yt_dlp/extractor/kompas.py
+++ b/yt_dlp/extractor/kompas.py
@@ -16,7 +16,7 @@ class KompasVideoIE(JixieBaseIE):
'categories': ['news'],
'thumbnail': 'https://video.jixie.media/1001/164474/164474_1280x720.jpg',
'tags': 'count:9',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/koo.py b/yt_dlp/extractor/koo.py
index c78a7b9ca..6ec5b59f9 100644
--- a/yt_dlp/extractor/koo.py
+++ b/yt_dlp/extractor/koo.py
@@ -19,9 +19,9 @@ class KooIE(InfoExtractor):
'uploader_id': 'ytdlpTestAccount',
'uploader': 'yt-dlpTestAccount',
'duration': 7000,
- 'upload_date': '20210921'
+ 'upload_date': '20210921',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, { # Test for koo with long title
'url': 'https://www.kooapp.com/koo/laxman_kumarDBFEC/33decbf7-5e1e-4bb8-bfd7-04744a064361',
'info_dict': {
@@ -33,9 +33,9 @@ class KooIE(InfoExtractor):
'uploader_id': 'laxman_kumarDBFEC',
'uploader': 'Laxman Kumar 🇮🇳',
'duration': 46000,
- 'upload_date': '20210920'
+ 'upload_date': '20210920',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, { # Test for audio
'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/a2a9c88e-ce4b-4d2d-952f-d06361c5b602',
'info_dict': {
@@ -47,9 +47,9 @@ class KooIE(InfoExtractor):
'uploader_id': 'ytdlpTestAccount',
'uploader': 'yt-dlpTestAccount',
'duration': 214000,
- 'upload_date': '20210921'
+ 'upload_date': '20210921',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, { # Test for video
'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/a3e56c53-c1ed-4ac9-ac02-ed1630e6b1d1',
'info_dict': {
@@ -61,9 +61,9 @@ class KooIE(InfoExtractor):
'uploader_id': 'ytdlpTestAccount',
'uploader': 'yt-dlpTestAccount',
'duration': 14000,
- 'upload_date': '20210921'
+ 'upload_date': '20210921',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, { # Test for link
'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/01bf5b94-81a5-4d8e-a387-5f732022e15a',
'skip': 'No video/audio found at the provided url.',
@@ -83,10 +83,11 @@ class KooIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://www.kooapp.com/apiV1/ku/{id}?limit=20&offset=0&showSimilarKoos=true', id)['parentContent']
+ video_id = self._match_id(url)
+ data_json = self._download_json(
+ f'https://www.kooapp.com/apiV1/ku/{video_id}?limit=20&offset=0&showSimilarKoos=true', video_id)['parentContent']
item_json = next(content['items'][0] for content in data_json
- if try_get(content, lambda x: x['items'][0]['id']) == id)
+ if try_get(content, lambda x: x['items'][0]['id']) == video_id)
media_json = item_json['mediaMap']
formats = []
@@ -98,12 +99,12 @@ class KooIE(InfoExtractor):
'ext': 'mp4',
})
if video_m3u8_url:
- formats.extend(self._extract_m3u8_formats(video_m3u8_url, id, fatal=False, ext='mp4'))
+ formats.extend(self._extract_m3u8_formats(video_m3u8_url, video_id, fatal=False, ext='mp4'))
if not formats:
self.raise_no_formats('No video/audio found at the provided url.', expected=True)
return {
- 'id': id,
+ 'id': video_id,
'title': clean_html(item_json.get('title')),
'description': f'{clean_html(item_json.get("title"))}\n\n{clean_html(item_json.get("enTransliteration"))}',
'timestamp': item_json.get('createdAt'),
diff --git a/yt_dlp/extractor/kth.py b/yt_dlp/extractor/kth.py
index e17c6db91..76899fdb8 100644
--- a/yt_dlp/extractor/kth.py
+++ b/yt_dlp/extractor/kth.py
@@ -16,13 +16,12 @@ class KTHIE(InfoExtractor):
'timestamp': 1647345358,
'upload_date': '20220315',
'uploader_id': 'md5:0ec23e33a89e795a4512930c8102509f',
- }
+ },
}
def _real_extract(self, url):
video_id = self._match_id(url)
- result = self.url_result(
- smuggle_url('kaltura:308:%s' % video_id, {
+ return self.url_result(
+ smuggle_url(f'kaltura:308:{video_id}', {
'service_url': 'https://api.kaltura.nordu.net'}),
'Kaltura')
- return result
diff --git a/yt_dlp/extractor/ku6.py b/yt_dlp/extractor/ku6.py
index 31b4ea0c6..00e814cc0 100644
--- a/yt_dlp/extractor/ku6.py
+++ b/yt_dlp/extractor/ku6.py
@@ -10,7 +10,7 @@ class Ku6IE(InfoExtractor):
'id': 'JG-8yS14xzBr4bCn1pu0xw',
'ext': 'f4v',
'title': 'techniques test',
- }
+ },
}
def _real_extract(self, url):
@@ -19,12 +19,12 @@ class Ku6IE(InfoExtractor):
title = self._html_search_regex(
r'<h1 title=.*>(.*?)</h1>', webpage, 'title')
- dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
- jsonData = self._download_json(dataUrl, video_id)
- downloadUrl = jsonData['data']['f']
+ data_url = f'http://v.ku6.com/fetchVideo4Player/{video_id}.html'
+ json_data = self._download_json(data_url, video_id)
+ download_url = json_data['data']['f']
return {
'id': video_id,
'title': title,
- 'url': downloadUrl
+ 'url': download_url,
}
diff --git a/yt_dlp/extractor/kuwo.py b/yt_dlp/extractor/kuwo.py
index b77667160..80b6b55f1 100644
--- a/yt_dlp/extractor/kuwo.py
+++ b/yt_dlp/extractor/kuwo.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
ExtractorError,
InAdvancePagedList,
@@ -18,7 +18,7 @@ class KuwoBaseIE(InfoExtractor):
{'format': 'mp3-192', 'ext': 'mp3', 'br': '192kmp3', 'abr': 192, 'preference': 70},
{'format': 'mp3-128', 'ext': 'mp3', 'br': '128kmp3', 'abr': 128, 'preference': 60},
{'format': 'wma', 'ext': 'wma', 'preference': 20},
- {'format': 'aac', 'ext': 'aac', 'abr': 48, 'preference': 10}
+ {'format': 'aac', 'ext': 'aac', 'abr': 48, 'preference': 10},
]
def _get_formats(self, song_id, tolerate_ip_deny=False):
@@ -27,21 +27,21 @@ class KuwoBaseIE(InfoExtractor):
query = {
'format': file_format['ext'],
'br': file_format.get('br', ''),
- 'rid': 'MUSIC_%s' % song_id,
+ 'rid': f'MUSIC_{song_id}',
'type': 'convert_url',
- 'response': 'url'
+ 'response': 'url',
}
song_url = self._download_webpage(
'http://antiserver.kuwo.cn/anti.s',
- song_id, note='Download %s url info' % file_format['format'],
+ song_id, note='Download {} url info'.format(file_format['format']),
query=query, headers=self.geo_verification_headers(),
)
if song_url == 'IPDeny' and not tolerate_ip_deny:
raise ExtractorError('This song is blocked in this region', expected=True)
- if song_url.startswith('http://') or song_url.startswith('https://'):
+ if song_url.startswith(('http://', 'https://')):
formats.append({
'url': song_url,
'format_id': file_format['format'],
@@ -66,7 +66,7 @@ class KuwoIE(KuwoBaseIE):
'title': '爱我别走',
'creator': '张震岳',
'upload_date': '20080122',
- 'description': 'md5:ed13f58e3c3bf3f7fd9fbc4e5a7aa75c'
+ 'description': 'md5:ed13f58e3c3bf3f7fd9fbc4e5a7aa75c',
},
'skip': 'this song has been offline because of copyright issues',
}, {
@@ -113,7 +113,7 @@ class KuwoIE(KuwoBaseIE):
publish_time = None
if album_id is not None:
album_info_page = self._download_webpage(
- 'http://www.kuwo.cn/album/%s/' % album_id, song_id,
+ f'http://www.kuwo.cn/album/{album_id}/', song_id,
note='Download album detail info',
errnote='Unable to get album detail info')
@@ -160,7 +160,7 @@ class KuwoAlbumIE(InfoExtractor):
'album name')
album_intro = remove_start(
clean_html(get_element_by_id('intro', webpage)),
- '%s简介:' % album_name)
+ f'{album_name}简介:')
entries = [
self.url_result(song_url, 'Kuwo') for song_url in re.findall(
@@ -238,12 +238,12 @@ class KuwoSingerIE(InfoExtractor):
def page_func(page_num):
webpage = self._download_webpage(
'http://www.kuwo.cn/artist/contentMusicsAjax',
- singer_id, note='Download song list page #%d' % (page_num + 1),
- errnote='Unable to get song list page #%d' % (page_num + 1),
+ singer_id, note=f'Download song list page #{page_num + 1}',
+ errnote=f'Unable to get song list page #{page_num + 1}',
query={'artistId': artist_id, 'pn': page_num, 'rn': self.PAGE_SIZE})
return [
- self.url_result(compat_urlparse.urljoin(url, song_url), 'Kuwo')
+ self.url_result(urllib.parse.urljoin(url, song_url), 'Kuwo')
for song_url in re.findall(
r'<div[^>]+class="name"><a[^>]+href="(/yinyue/\d+)',
webpage)
@@ -280,7 +280,7 @@ class KuwoCategoryIE(InfoExtractor):
category_desc = remove_start(
get_element_by_id('intro', webpage).strip(),
- '%s简介:' % category_name)
+ f'{category_name}简介:')
if category_desc == '暂无':
category_desc = None
@@ -288,7 +288,7 @@ class KuwoCategoryIE(InfoExtractor):
r'var\s+jsonm\s*=\s*([^;]+);', webpage, 'category songs'), category_id)
entries = [
- self.url_result('http://www.kuwo.cn/yinyue/%s/' % song['musicrid'], 'Kuwo')
+ self.url_result('http://www.kuwo.cn/yinyue/{}/'.format(song['musicrid']), 'Kuwo')
for song in jsonm['musiclist']
]
return self.playlist_result(entries, category_id, category_name, category_desc)
@@ -314,16 +314,16 @@ class KuwoMvIE(KuwoBaseIE):
'format': 'mv',
},
}
- _FORMATS = KuwoBaseIE._FORMATS + [
+ _FORMATS = [
+ *KuwoBaseIE._FORMATS,
{'format': 'mkv', 'ext': 'mkv', 'preference': 250},
- {'format': 'mp4', 'ext': 'mp4', 'preference': 200},
- ]
+ {'format': 'mp4', 'ext': 'mp4', 'preference': 200}]
def _real_extract(self, url):
song_id = self._match_id(url)
webpage = self._download_webpage(
- url, song_id, note='Download mv detail info: %s' % song_id,
- errnote='Unable to get mv detail info: %s' % song_id)
+ url, song_id, note=f'Download mv detail info: {song_id}',
+ errnote=f'Unable to get mv detail info: {song_id}')
mobj = re.search(
r'<h1[^>]+title="(?P<song>[^"]+)">[^<]+<span[^>]+title="(?P<singer>[^"]+)"',
@@ -337,8 +337,8 @@ class KuwoMvIE(KuwoBaseIE):
formats = self._get_formats(song_id, tolerate_ip_deny=True)
mv_url = self._download_webpage(
- 'http://www.kuwo.cn/yy/st/mvurl?rid=MUSIC_%s' % song_id,
- song_id, note='Download %s MV URL' % song_id)
+ f'http://www.kuwo.cn/yy/st/mvurl?rid=MUSIC_{song_id}',
+ song_id, note=f'Download {song_id} MV URL')
formats.append({
'url': mv_url,
'format_id': 'mv',
diff --git a/yt_dlp/extractor/la7.py b/yt_dlp/extractor/la7.py
index f5fd24134..20a5235f2 100644
--- a/yt_dlp/extractor/la7.py
+++ b/yt_dlp/extractor/la7.py
@@ -93,7 +93,7 @@ class LA7IE(InfoExtractor):
'description': self._og_search_description(webpage, default=None),
'thumbnail': self._og_search_thumbnail(webpage, default=None),
'formats': formats,
- 'upload_date': unified_strdate(self._search_regex(r'datetime="(.+?)"', webpage, 'upload_date', fatal=False))
+ 'upload_date': unified_strdate(self._search_regex(r'datetime="(.+?)"', webpage, 'upload_date', fatal=False)),
}
diff --git a/yt_dlp/extractor/laxarxames.py b/yt_dlp/extractor/laxarxames.py
index e157f7c08..f6d515b21 100644
--- a/yt_dlp/extractor/laxarxames.py
+++ b/yt_dlp/extractor/laxarxames.py
@@ -58,7 +58,7 @@ class LaXarxaMesIE(InfoExtractor):
'https://api.laxarxames.cat/Media/GetMediaPlayInfo', video_id,
data=json.dumps({
'MediaId': int(video_id),
- 'StreamType': 'MAIN'
+ 'StreamType': 'MAIN',
}).encode(), headers={
'Authorization': f'Bearer {self._TOKEN}',
'X-Tenantorigin': 'https://laxarxames.cat',
diff --git a/yt_dlp/extractor/lbry.py b/yt_dlp/extractor/lbry.py
index dcb44d07f..c764d4961 100644
--- a/yt_dlp/extractor/lbry.py
+++ b/yt_dlp/extractor/lbry.py
@@ -24,7 +24,7 @@ from ..utils import (
class LBRYBaseIE(InfoExtractor):
_BASE_URL_REGEX = r'(?x)(?:https?://(?:www\.)?(?:lbry\.tv|odysee\.com)/|lbry://)'
_CLAIM_ID_REGEX = r'[0-9a-f]{1,40}'
- _OPT_CLAIM_ID = '[^$@:/?#&]+(?:[:#]%s)?' % _CLAIM_ID_REGEX
+ _OPT_CLAIM_ID = f'[^$@:/?#&]+(?:[:#]{_CLAIM_ID_REGEX})?'
_SUPPORTED_STREAM_TYPES = ['video', 'audio']
_PAGE_SIZE = 50
@@ -35,7 +35,7 @@ class LBRYBaseIE(InfoExtractor):
headers['x-lbry-auth-token'] = token
response = self._download_json(
'https://api.lbry.tv/api/v1/proxy',
- display_id, 'Downloading %s JSON metadata' % resource,
+ display_id, f'Downloading {resource} JSON metadata',
headers=headers,
data=json.dumps({
'method': method,
@@ -54,7 +54,7 @@ class LBRYBaseIE(InfoExtractor):
def _permanent_url(self, url, claim_name, claim_id):
return urljoin(
url.replace('lbry://', 'https://lbry.tv/'),
- '/%s:%s' % (claim_name, claim_id))
+ f'/{claim_name}:{claim_id}')
def _parse_stream(self, stream, url):
stream_type = traverse_obj(stream, ('value', 'stream_type', {str}))
@@ -169,9 +169,9 @@ class LBRYIE(LBRYBaseIE):
'lbc',
'lbry',
'start',
- 'tutorial'
+ 'tutorial',
],
- }
+ },
}, {
# Audio
'url': 'https://lbry.tv/@LBRYFoundation:0/Episode-1:e',
@@ -194,7 +194,7 @@ class LBRYIE(LBRYBaseIE):
'thumbnail': 'https://spee.ch/d/0bc63b0e6bf1492d.png',
'license': 'None',
'uploader_id': '@LBRYFoundation',
- }
+ },
}, {
'url': 'https://odysee.com/@gardeningincanada:b/plants-i-will-never-grow-again.-the:e',
'md5': 'c35fac796f62a14274b4dc2addb5d0ba',
@@ -216,7 +216,7 @@ class LBRYIE(LBRYBaseIE):
'formats': 'mincount:3',
'thumbnail': 'https://thumbnails.lbry.com/AgHSc_HzrrE',
'license': 'Copyrighted (contact publisher)',
- }
+ },
}, {
# HLS live stream (might expire)
'url': 'https://odysee.com/@RT:fd/livestream_RT:d',
@@ -239,7 +239,7 @@ class LBRYIE(LBRYBaseIE):
'license': 'None',
'uploader_id': '@RT',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
# original quality format w/higher resolution than HLS formats
'url': 'https://odysee.com/@wickedtruths:2/Biotechnological-Invasion-of-Skin-(April-2023):4',
diff --git a/yt_dlp/extractor/lcp.py b/yt_dlp/extractor/lcp.py
index 62874195f..69148be22 100644
--- a/yt_dlp/extractor/lcp.py
+++ b/yt_dlp/extractor/lcp.py
@@ -66,7 +66,7 @@ class LcpIE(InfoExtractor):
webpage = self._download_webpage(url, display_id)
play_url = self._search_regex(
- r'<iframe[^>]+src=(["\'])(?P<url>%s?(?:(?!\1).)*)\1' % LcpPlayIE._VALID_URL,
+ rf'<iframe[^>]+src=(["\'])(?P<url>{LcpPlayIE._VALID_URL}?(?:(?!\1).)*)\1',
webpage, 'play iframe', default=None, group='url')
if not play_url:
diff --git a/yt_dlp/extractor/lecture2go.py b/yt_dlp/extractor/lecture2go.py
index 1a3ada1e5..6157f3da3 100644
--- a/yt_dlp/extractor/lecture2go.py
+++ b/yt_dlp/extractor/lecture2go.py
@@ -25,7 +25,7 @@ class Lecture2GoIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/lecturio.py b/yt_dlp/extractor/lecturio.py
index 90f0268d7..4cfb87224 100644
--- a/yt_dlp/extractor/lecturio.py
+++ b/yt_dlp/extractor/lecturio.py
@@ -49,7 +49,7 @@ class LecturioBaseIE(InfoExtractor):
r'(?s)<ul[^>]+class=["\']error_list[^>]+>(.+?)</ul>', response,
'errors', default=None)
if errors:
- raise ExtractorError('Unable to login: %s' % errors, expected=True)
+ raise ExtractorError(f'Unable to login: {errors}', expected=True)
raise ExtractorError('Unable to log in')
@@ -130,7 +130,7 @@ class LecturioIE(LecturioBaseIE):
f = {
'url': file_url,
'format_id': label,
- 'filesize': float_or_none(filesize, invscale=1000)
+ 'filesize': float_or_none(filesize, invscale=1000),
}
if label:
mobj = re.match(r'(\d+)p\s*\(([^)]+)\)', label)
@@ -200,7 +200,7 @@ class LecturioCourseIE(LecturioBaseIE):
if lecture_url:
lecture_url = urljoin(url, lecture_url)
else:
- lecture_url = 'https://app.lecturio.com/#/lecture/c/%s/%s' % (course_id, lecture_id)
+ lecture_url = f'https://app.lecturio.com/#/lecture/c/{course_id}/{lecture_id}'
entries.append(self.url_result(
lecture_url, ie=LecturioIE.ie_key(), video_id=lecture_id))
return self.playlist_result(
diff --git a/yt_dlp/extractor/leeco.py b/yt_dlp/extractor/leeco.py
index a113b3d0d..58baa3fea 100644
--- a/yt_dlp/extractor/leeco.py
+++ b/yt_dlp/extractor/leeco.py
@@ -1,15 +1,12 @@
+import base64
import datetime as dt
import hashlib
import re
import time
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_b64decode,
- compat_ord,
- compat_str,
- compat_urllib_parse_urlencode,
-)
+from ..compat import compat_ord
from ..utils import (
ExtractorError,
determine_ext,
@@ -140,7 +137,7 @@ class LeIE(InfoExtractor):
def get_flash_urls(media_url, format_id):
nodes_data = self._download_json(
media_url, media_id,
- 'Download JSON metadata for format %s' % format_id,
+ f'Download JSON metadata for format {format_id}',
query={
'm3v': 1,
'format': 1,
@@ -150,7 +147,7 @@ class LeIE(InfoExtractor):
req = self._request_webpage(
nodes_data['nodelist'][0]['location'], media_id,
- note='Downloading m3u8 information for format %s' % format_id)
+ note=f'Downloading m3u8 information for format {format_id}')
m3u8_data = self.decrypt_m3u8(req.read())
@@ -173,7 +170,7 @@ class LeIE(InfoExtractor):
f = {
'url': format_url,
'ext': determine_ext(format_data[1]),
- 'format_id': '%s-%s' % (protocol, format_id),
+ 'format_id': f'{protocol}-{format_id}',
'protocol': 'm3u8_native' if protocol == 'hls' else 'http',
'quality': int_or_none(format_id),
}
@@ -207,18 +204,18 @@ class LePlaylistIE(InfoExtractor):
'info_dict': {
'id': '46177',
'title': '美人天下',
- 'description': 'md5:395666ff41b44080396e59570dbac01c'
+ 'description': 'md5:395666ff41b44080396e59570dbac01c',
},
- 'playlist_count': 35
+ 'playlist_count': 35,
}, {
'url': 'http://tv.le.com/izt/wuzetian/index.html',
'info_dict': {
'id': 'wuzetian',
'title': '武媚娘传奇',
- 'description': 'md5:e12499475ab3d50219e5bba00b3cb248'
+ 'description': 'md5:e12499475ab3d50219e5bba00b3cb248',
},
# This playlist contains some extra videos other than the drama itself
- 'playlist_mincount': 96
+ 'playlist_mincount': 96,
}, {
'url': 'http://tv.le.com/pzt/lswjzzjc/index.shtml',
# This series is moved to http://www.le.com/tv/10005297.html
@@ -233,7 +230,7 @@ class LePlaylistIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if LeIE.suitable(url) else super(LePlaylistIE, cls).suitable(url)
+ return False if LeIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
playlist_id = self._match_id(url)
@@ -294,7 +291,7 @@ class LetvCloudIE(InfoExtractor):
salt = 'fbeh5player12c43eccf2bec3300344'
items = ['cf', 'ran', 'uu', 'bver', 'vu']
input_data = ''.join([item + obj[item] for item in items]) + salt
- obj['sign'] = hashlib.md5(input_data.encode('utf-8')).hexdigest()
+ obj['sign'] = hashlib.md5(input_data.encode()).hexdigest()
def _get_formats(self, cf, uu, vu, media_id):
def get_play_json(cf, timestamp):
@@ -305,12 +302,12 @@ class LetvCloudIE(InfoExtractor):
'format': 'json',
'uu': uu,
'vu': vu,
- 'ran': compat_str(timestamp),
+ 'ran': str(timestamp),
}
self.sign_data(data)
return self._download_json(
- 'http://api.letvcloud.com/gpc.php?' + compat_urllib_parse_urlencode(data),
- media_id, 'Downloading playJson data for type %s' % cf)
+ 'http://api.letvcloud.com/gpc.php?' + urllib.parse.urlencode(data),
+ media_id, f'Downloading playJson data for type {cf}')
play_json = get_play_json(cf, time.time())
# The server time may be different from local time
@@ -319,14 +316,14 @@ class LetvCloudIE(InfoExtractor):
if not play_json.get('data'):
if play_json.get('message'):
- raise ExtractorError('Letv cloud said: %s' % play_json['message'], expected=True)
+ raise ExtractorError('Letv cloud said: {}'.format(play_json['message']), expected=True)
elif play_json.get('code'):
raise ExtractorError('Letv cloud returned error %d' % play_json['code'], expected=True)
else:
raise ExtractorError('Letv cloud returned an unknown error')
def b64decode(s):
- return compat_b64decode(s).decode('utf-8')
+ return base64.b64decode(s).decode('utf-8')
formats = []
for media in play_json['data']['video_info']['media'].values():
@@ -349,7 +346,7 @@ class LetvCloudIE(InfoExtractor):
vu_mobj = re.search(r'vu=([\w]+)', url)
if not uu_mobj or not vu_mobj:
- raise ExtractorError('Invalid URL: %s' % url, expected=True)
+ raise ExtractorError(f'Invalid URL: {url}', expected=True)
uu = uu_mobj.group(1)
vu = vu_mobj.group(1)
@@ -359,6 +356,6 @@ class LetvCloudIE(InfoExtractor):
return {
'id': media_id,
- 'title': 'Video %s' % media_id,
+ 'title': f'Video {media_id}',
'formats': formats,
}
diff --git a/yt_dlp/extractor/lego.py b/yt_dlp/extractor/lego.py
index 46fc7a9b6..5a98cc749 100644
--- a/yt_dlp/extractor/lego.py
+++ b/yt_dlp/extractor/lego.py
@@ -72,7 +72,7 @@ class LEGOIE(InfoExtractor):
# https://contentfeed.services.lego.com/api/v2/item/[VIDEO_ID]?culture=[LOCALE]&contentType=Video
'https://services.slingshot.lego.com/mediaplayer/v2',
video_id, query={
- 'videoId': '%s_%s' % (uuid.UUID(video_id), locale),
+ 'videoId': f'{uuid.UUID(video_id)}_{locale}',
}, headers=self.geo_verification_headers())
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 451:
@@ -111,7 +111,7 @@ class LEGOIE(InfoExtractor):
'abr': quality[0],
'height': quality[1],
'width': quality[2],
- }),
+ })
formats.append(f)
subtitles = {}
@@ -123,7 +123,7 @@ class LEGOIE(InfoExtractor):
video_version = video.get('VideoVersion')
if net_storage_path and invariant_id and video_file_id and video_version:
subtitles.setdefault(locale[:2], []).append({
- 'url': 'https://lc-mediaplayerns-live-s.legocdn.com/public/%s/%s_%s_%s_%s_sub.srt' % (net_storage_path, invariant_id, video_file_id, locale, video_version),
+ 'url': f'https://lc-mediaplayerns-live-s.legocdn.com/public/{net_storage_path}/{invariant_id}_{video_file_id}_{locale}_{video_version}_sub.srt',
})
return {
diff --git a/yt_dlp/extractor/lenta.py b/yt_dlp/extractor/lenta.py
index fe01bda1c..105ec371e 100644
--- a/yt_dlp/extractor/lenta.py
+++ b/yt_dlp/extractor/lenta.py
@@ -45,7 +45,7 @@ class LentaIE(InfoExtractor):
default=None)
if video_id:
return self.url_result(
- 'eagleplatform:lentaru.media.eagleplatform.com:%s' % video_id,
+ f'eagleplatform:lentaru.media.eagleplatform.com:{video_id}',
ie='EaglePlatform', video_id=video_id)
return self.url_result(url, ie='Generic')
diff --git a/yt_dlp/extractor/libraryofcongress.py b/yt_dlp/extractor/libraryofcongress.py
index 297993939..618560574 100644
--- a/yt_dlp/extractor/libraryofcongress.py
+++ b/yt_dlp/extractor/libraryofcongress.py
@@ -73,7 +73,7 @@ class LibraryOfCongressIE(InfoExtractor):
webpage, 'media id', group='id')
data = self._download_json(
- 'https://media.loc.gov/services/v1/media?id=%s&context=json' % media_id,
+ f'https://media.loc.gov/services/v1/media?id={media_id}&context=json',
media_id)['mediaObject']
derivative = data['derivatives'][0]
diff --git a/yt_dlp/extractor/libsyn.py b/yt_dlp/extractor/libsyn.py
index 29bbb03de..4ca521a10 100644
--- a/yt_dlp/extractor/libsyn.py
+++ b/yt_dlp/extractor/libsyn.py
@@ -18,7 +18,7 @@ class LibsynIE(InfoExtractor):
'info_dict': {
'id': '6385796',
'ext': 'mp3',
- 'title': "Champion Minded - Developing a Growth Mindset",
+ 'title': 'Champion Minded - Developing a Growth Mindset',
# description fetched using another request:
# http://html5-player.libsyn.com/embed/getitemdetails?item_id=6385796
# 'description': 'In this episode, Allistair talks about the importance of developing a growth mindset, not only in sports, but in life too.',
@@ -34,7 +34,7 @@ class LibsynIE(InfoExtractor):
'title': 'Clients From Hell Podcast - How a Sex Toy Company Kickstarted my Freelance Career',
'upload_date': '20150818',
'thumbnail': 're:^https?://.*',
- }
+ },
}]
def _real_extract(self, url):
@@ -56,7 +56,7 @@ class LibsynIE(InfoExtractor):
r'<h3>([^<]+)</h3>', webpage, 'podcast title',
default=None) or get_element_by_class('podcast-title', webpage)))
- title = '%s - %s' % (podcast_title, episode_title) if podcast_title else episode_title
+ title = f'{podcast_title} - {episode_title}' if podcast_title else episode_title
formats = []
for k, format_id in (('media_url_libsyn', 'libsyn'), ('media_url', 'main'), ('download_link', 'download')):
diff --git a/yt_dlp/extractor/lifenews.py b/yt_dlp/extractor/lifenews.py
index ea150a58b..60d50b1d1 100644
--- a/yt_dlp/extractor/lifenews.py
+++ b/yt_dlp/extractor/lifenews.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
determine_ext,
@@ -31,7 +28,7 @@ class LifeNewsIE(InfoExtractor):
'timestamp': 1344154740,
'upload_date': '20120805',
'view_count': int,
- }
+ },
}, {
# single video embedded via iframe
'url': 'https://life.ru/t/новости/152125',
@@ -44,7 +41,7 @@ class LifeNewsIE(InfoExtractor):
'timestamp': 1427961840,
'upload_date': '20150402',
'view_count': int,
- }
+ },
}, {
# two videos embedded via iframe
'url': 'https://life.ru/t/новости/153461',
@@ -100,7 +97,7 @@ class LifeNewsIE(InfoExtractor):
webpage)
if not video_urls and not iframe_links:
- raise ExtractorError('No media links available for %s' % video_id)
+ raise ExtractorError(f'No media links available for {video_id}')
title = remove_end(
self._og_search_title(webpage),
@@ -125,14 +122,14 @@ class LifeNewsIE(InfoExtractor):
def make_entry(video_id, video_url, index=None):
cur_info = dict(common_info)
cur_info.update({
- 'id': video_id if not index else '%s-video%s' % (video_id, index),
+ 'id': video_id if not index else f'{video_id}-video{index}',
'url': video_url,
- 'title': title if not index else '%s (Видео %s)' % (title, index),
+ 'title': title if not index else f'{title} (Видео {index})',
})
return cur_info
def make_video_entry(video_id, video_url, index=None):
- video_url = compat_urlparse.urljoin(url, video_url)
+ video_url = urllib.parse.urljoin(url, video_url)
return make_entry(video_id, video_url, index)
def make_iframe_entry(video_id, video_url, index=None):
@@ -174,7 +171,7 @@ class LifeEmbedIE(InfoExtractor):
'ext': 'mp4',
'title': 'e50c2dec2867350528e2574c899b8291',
'thumbnail': r're:http://.*\.jpg',
- }
+ },
}, {
# with 1080p
'url': 'https://embed.life.ru/video/e50c2dec2867350528e2574c899b8291',
@@ -207,17 +204,17 @@ class LifeEmbedIE(InfoExtractor):
video_id).get('playlist', {})
if playlist:
master = playlist.get('master')
- if isinstance(master, compat_str) and determine_ext(master) == 'm3u8':
- extract_m3u8(compat_urlparse.urljoin(url, master))
+ if isinstance(master, str) and determine_ext(master) == 'm3u8':
+ extract_m3u8(urllib.parse.urljoin(url, master))
original = playlist.get('original')
- if isinstance(original, compat_str):
+ if isinstance(original, str):
extract_original(original)
thumbnail = playlist.get('image')
# Old rendition fallback
if not formats:
for video_url in re.findall(r'"file"\s*:\s*"([^"]+)', webpage):
- video_url = compat_urlparse.urljoin(url, video_url)
+ video_url = urllib.parse.urljoin(url, video_url)
if determine_ext(video_url) == 'm3u8':
extract_m3u8(video_url)
else:
diff --git a/yt_dlp/extractor/likee.py b/yt_dlp/extractor/likee.py
index 324463136..f6a51c8ee 100644
--- a/yt_dlp/extractor/likee.py
+++ b/yt_dlp/extractor/likee.py
@@ -162,7 +162,7 @@ class LikeeUserIE(InfoExtractor):
'count': self._PAGE_SIZE,
'lastPostId': last_post_id,
'tabType': 0,
- }).encode('utf-8'),
+ }).encode(),
headers={'content-type': 'application/json'},
note=f'Get user info with lastPostId #{last_post_id}')
items = traverse_obj(user_videos, ('data', 'videoList'))
diff --git a/yt_dlp/extractor/limelight.py b/yt_dlp/extractor/limelight.py
index 1ff091ddb..763a01448 100644
--- a/yt_dlp/extractor/limelight.py
+++ b/yt_dlp/extractor/limelight.py
@@ -32,8 +32,8 @@ class LimelightBaseIE(InfoExtractor):
r'LimelightPlayer\.doLoad(Media|Channel|ChannelList)\(["\'](?P<id>[a-z0-9]{32})',
webpage):
entries.append(cls.url_result(
- smuggle('limelight:%s:%s' % (lm[kind], video_id)),
- 'Limelight%s' % kind, video_id))
+ smuggle(f'limelight:{lm[kind]}:{video_id}'),
+ f'Limelight{kind}', video_id))
for mobj in re.finditer(
# As per [1] class attribute should be exactly equal to
# LimelightEmbeddedPlayerFlash but numerous examples seen
@@ -48,14 +48,14 @@ class LimelightBaseIE(InfoExtractor):
''', webpage):
kind, video_id = mobj.group('kind'), mobj.group('id')
entries.append(cls.url_result(
- smuggle('limelight:%s:%s' % (kind, video_id)),
- 'Limelight%s' % kind.capitalize(), video_id))
+ smuggle(f'limelight:{kind}:{video_id}'),
+ f'Limelight{kind.capitalize()}', video_id))
# http://support.3playmedia.com/hc/en-us/articles/115009517327-Limelight-Embedding-the-Audio-Description-Plugin-with-the-Limelight-Player-on-Your-Web-Page)
for video_id in re.findall(
r'(?s)LimelightPlayerUtil\.embed\s*\(\s*{.*?\bmediaId["\']\s*:\s*["\'](?P<id>[a-z0-9]{32})',
webpage):
entries.append(cls.url_result(
- smuggle('limelight:media:%s' % video_id),
+ smuggle(f'limelight:media:{video_id}'),
LimelightMediaIE.ie_key(), video_id))
return entries
@@ -66,7 +66,7 @@ class LimelightBaseIE(InfoExtractor):
try:
return self._download_json(
self._PLAYLIST_SERVICE_URL % (self._PLAYLIST_SERVICE_PATH, item_id, method),
- item_id, 'Downloading PlaylistService %s JSON' % method,
+ item_id, f'Downloading PlaylistService {method} JSON',
fatal=fatal, headers=headers)
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
@@ -134,7 +134,7 @@ class LimelightBaseIE(InfoExtractor):
for cdn_host, http_host in CDN_HOSTS:
if cdn_host not in rtmp.group('host').lower():
continue
- http_url = 'http://%s/%s' % (http_host, rtmp.group('playpath')[4:])
+ http_url = 'http://{}/{}'.format(http_host, rtmp.group('playpath')[4:])
urls.append(http_url)
if self._is_valid_url(http_url, video_id, http_format_id):
http_fmt = fmt.copy()
@@ -351,7 +351,7 @@ class LimelightChannelListIE(LimelightBaseIE):
channel_list_id, 'getMobileChannelListById')
entries = [
- self.url_result('limelight:channel:%s' % channel['id'], 'LimelightChannel')
+ self.url_result('limelight:channel:{}'.format(channel['id']), 'LimelightChannel')
for channel in channel_list['channelList']]
return self.playlist_result(
diff --git a/yt_dlp/extractor/linkedin.py b/yt_dlp/extractor/linkedin.py
index 2a7c6f0e0..c8c8ae52a 100644
--- a/yt_dlp/extractor/linkedin.py
+++ b/yt_dlp/extractor/linkedin.py
@@ -59,14 +59,14 @@ class LinkedInLearningBaseIE(LinkedInBaseIE):
if video_slug:
query.update({
'videoSlug': video_slug,
- 'resolution': '_%s' % resolution,
+ 'resolution': f'_{resolution}',
})
sub = ' %dp' % resolution
api_url = 'https://www.linkedin.com/learning-api/detailedCourses'
if not self._get_cookies(api_url).get('JSESSIONID'):
self.raise_login_required()
return self._download_json(
- api_url, video_slug, 'Downloading%s JSON metadata' % sub, headers={
+ api_url, video_slug, f'Downloading{sub} JSON metadata', headers={
'Csrf-Token': self._get_cookies(api_url)['JSESSIONID'].value,
}, query=query)['elements'][0]
@@ -78,7 +78,7 @@ class LinkedInLearningBaseIE(LinkedInBaseIE):
return mobj.group(1)
def _get_video_id(self, video_data, course_slug, video_slug):
- return self._get_urn_id(video_data) or '%s/%s' % (course_slug, video_slug)
+ return self._get_urn_id(video_data) or f'{course_slug}/{video_slug}'
class LinkedInIE(LinkedInBaseIE):
@@ -92,7 +92,7 @@ class LinkedInIE(LinkedInBaseIE):
'description': 'md5:2998a31f6f479376dd62831f53a80f71',
'uploader': 'Mishal K.',
'thumbnail': 're:^https?://media.licdn.com/dms/image/.*$',
- 'like_count': int
+ 'like_count': int,
},
}, {
'url': 'https://www.linkedin.com/posts/the-mathworks_2_what-is-mathworks-cloud-center-activity-7151241570371948544-4Gu7',
@@ -104,7 +104,7 @@ class LinkedInIE(LinkedInBaseIE):
'uploader': 'MathWorks',
'thumbnail': 're:^https?://media.licdn.com/dms/image/.*$',
'like_count': int,
- 'subtitles': 'mincount:1'
+ 'subtitles': 'mincount:1',
},
}]
@@ -159,9 +159,10 @@ class LinkedInLearningIE(LinkedInLearningBaseIE):
for line, (line_dict, next_dict) in enumerate(itertools.zip_longest(transcript_lines, transcript_lines[1:])):
start_time, caption = line_dict['transcriptStartAt'] / 1000, line_dict['caption']
end_time = next_dict['transcriptStartAt'] / 1000 if next_dict else duration or start_time + 1
- srt_data += '%d\n%s --> %s\n%s\n\n' % (line + 1, srt_subtitles_timecode(start_time),
- srt_subtitles_timecode(end_time),
- caption)
+ srt_data += (
+ f'{line + 1}\n'
+ f'{srt_subtitles_timecode(start_time)} --> {srt_subtitles_timecode(end_time)}\n'
+ f'{caption}\n\n')
return srt_data
def _real_extract(self, url):
@@ -176,7 +177,7 @@ class LinkedInLearningIE(LinkedInLearningBaseIE):
progressive_url = video_url_data.get('progressiveUrl')
if progressive_url:
formats.append({
- 'format_id': 'progressive-%dp' % height,
+ 'format_id': f'progressive-{height}p',
'url': progressive_url,
'ext': 'mp4',
'height': height,
@@ -208,7 +209,7 @@ class LinkedInLearningIE(LinkedInLearningBaseIE):
if transcript_lines:
subtitles['en'] = [{
'ext': 'srt',
- 'data': self.json2srt(transcript_lines, duration)
+ 'data': self.json2srt(transcript_lines, duration),
}]
return {
@@ -222,7 +223,7 @@ class LinkedInLearningIE(LinkedInLearningBaseIE):
# It seems like this would be correctly handled by default
# However, unless someone can confirm this, the old
# behaviour is being kept as-is
- '_format_sort_fields': ('res', 'source_preference')
+ '_format_sort_fields': ('res', 'source_preference'),
}
@@ -241,7 +242,7 @@ class LinkedInLearningCourseIE(LinkedInLearningBaseIE):
@classmethod
def suitable(cls, url):
- return False if LinkedInLearningIE.suitable(url) else super(LinkedInLearningCourseIE, cls).suitable(url)
+ return False if LinkedInLearningIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
course_slug = self._match_id(url)
@@ -259,7 +260,7 @@ class LinkedInLearningCourseIE(LinkedInLearningBaseIE):
'_type': 'url_transparent',
'id': self._get_video_id(video, course_slug, video_slug),
'title': video.get('title'),
- 'url': 'https://www.linkedin.com/learning/%s/%s' % (course_slug, video_slug),
+ 'url': f'https://www.linkedin.com/learning/{course_slug}/{video_slug}',
'chapter': chapter_title,
'chapter_number': chapter_number,
'chapter_id': chapter_id,
diff --git a/yt_dlp/extractor/liputan6.py b/yt_dlp/extractor/liputan6.py
index c4477b93e..a29234a3a 100644
--- a/yt_dlp/extractor/liputan6.py
+++ b/yt_dlp/extractor/liputan6.py
@@ -25,8 +25,8 @@ class Liputan6IE(InfoExtractor):
'tags': ['perawat indonesia', 'rumah sakit', 'Medan', 'viral hari ini', 'viral', 'enamplus'],
'channel': 'Default Channel',
'dislike_count': int,
- 'upload_date': '20220707'
- }
+ 'upload_date': '20220707',
+ },
}, {
'url': 'https://www.liputan6.com/tv/read/5007719/video-program-minyakita-minyak-goreng-kemasan-sederhana-seharga-rp-14-ribu',
'info_dict': {
@@ -49,7 +49,7 @@ class Liputan6IE(InfoExtractor):
'thumbnail': 'https://thumbor.prod.vidiocdn.com/AAIOjz-64hKojjdw5hr0oNNEeJg=/640x360/filters:quality(70)/vidio-web-prod-video/uploads/video/image/7082543/program-minyakita-minyak-goreng-kemasan-sederhana-seharga-rp14-ribu-_-liputan-6-7d9fbb.jpg',
'channel': 'Liputan 6 Pagi',
'view_count': int,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/listennotes.py b/yt_dlp/extractor/listennotes.py
index 4ebc9be4d..61eae95ed 100644
--- a/yt_dlp/extractor/listennotes.py
+++ b/yt_dlp/extractor/listennotes.py
@@ -31,7 +31,7 @@ class ListenNotesIE(InfoExtractor):
'thumbnail': 'https://production.listennotes.com/podcasts/thriving-on-overload-ross-dawson-1wb_KospA3P-ed84wITivxF.300x300.jpg',
'channel_url': 'https://www.listennotes.com/podcasts/thriving-on-overload-ross-dawson-ed84wITivxF/',
'cast': ['Tim O’Reilly', 'Cookie Monster', 'Lao Tzu', 'Wallace Steven', 'Eric Raymond', 'Christine Peterson', 'John Maynard Keyne', 'Ross Dawson'],
- }
+ },
}, {
'url': 'https://www.listennotes.com/podcasts/ask-noah-show/episode-177-wireguard-with-lwEA3154JzG/',
'md5': '62fb4ffe7fc525632a1138bf72a5ce53',
@@ -47,7 +47,7 @@ class ListenNotesIE(InfoExtractor):
'channel_url': 'https://www.listennotes.com/podcasts/ask-noah-show-noah-j-chelliah-4DQTzdS5-j7/',
'thumbnail': 'https://production.listennotes.com/podcasts/ask-noah-show-noah-j-chelliah-cfbRUw9Gs3F-4DQTzdS5-j7.300x300.jpg',
'cast': ['noah showlink', 'noah show', 'noah dashboard', 'jason donenfeld'],
- }
+ },
}]
def _clean_description(self, description):
@@ -82,5 +82,5 @@ class ListenNotesIE(InfoExtractor):
'cast': ('nlp_entities', ..., 'name'),
'channel_url': 'channel_url',
'channel_id': 'channel_short_uuid',
- })
+ }),
}
diff --git a/yt_dlp/extractor/litv.py b/yt_dlp/extractor/litv.py
index 1003fb2fd..93f926a9f 100644
--- a/yt_dlp/extractor/litv.py
+++ b/yt_dlp/extractor/litv.py
@@ -113,7 +113,7 @@ class LiTVIE(InfoExtractor):
endpoint = 'getMainUrlNoAuth'
video_data = self._download_json(
f'https://www.litv.tv/vod/ajax/{endpoint}', video_id,
- data=json.dumps(payload).encode('utf-8'),
+ data=json.dumps(payload).encode(),
headers={'Content-Type': 'application/json'})
if not video_data.get('fullpath'):
@@ -121,8 +121,8 @@ class LiTVIE(InfoExtractor):
if error_msg == 'vod.error.outsideregionerror':
self.raise_geo_restricted('This video is available in Taiwan only')
if error_msg:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error_msg), expected=True)
- raise ExtractorError('Unexpected result from %s' % self.IE_NAME)
+ raise ExtractorError(f'{self.IE_NAME} said: {error_msg}', expected=True)
+ raise ExtractorError(f'Unexpected result from {self.IE_NAME}')
formats = self._extract_m3u8_formats(
video_data['fullpath'], video_id, ext='mp4',
diff --git a/yt_dlp/extractor/livejournal.py b/yt_dlp/extractor/livejournal.py
index 96bd8b233..c61f9bec7 100644
--- a/yt_dlp/extractor/livejournal.py
+++ b/yt_dlp/extractor/livejournal.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import int_or_none
@@ -14,7 +13,7 @@ class LiveJournalIE(InfoExtractor):
'title': 'Истребители против БПЛА',
'upload_date': '20190624',
'timestamp': 1561406715,
- }
+ },
}
def _real_extract(self, url):
@@ -23,7 +22,7 @@ class LiveJournalIE(InfoExtractor):
record = self._parse_json(self._search_regex(
r'Site\.page\s*=\s*({.+?});', webpage,
'page data'), video_id)['video']['record']
- storage_id = compat_str(record['storageid'])
+ storage_id = str(record['storageid'])
title = record.get('name')
if title:
# remove filename extension(.mp4, .mov, etc...)
diff --git a/yt_dlp/extractor/livestream.py b/yt_dlp/extractor/livestream.py
index a05a0fa9e..7f7947ee7 100644
--- a/yt_dlp/extractor/livestream.py
+++ b/yt_dlp/extractor/livestream.py
@@ -1,8 +1,8 @@
import itertools
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_str, compat_urlparse
from ..utils import (
determine_ext,
find_xpath_attr,
@@ -41,13 +41,13 @@ class LivestreamIE(InfoExtractor):
'like_count': int,
'view_count': int,
'comment_count': int,
- 'thumbnail': r're:^http://.*\.jpg$'
- }
+ 'thumbnail': r're:^http://.*\.jpg$',
+ },
}, {
'url': 'https://livestream.com/coheedandcambria/websterhall',
'info_dict': {
'id': '1585861',
- 'title': 'Live From Webster Hall'
+ 'title': 'Live From Webster Hall',
},
'playlist_mincount': 1,
}, {
@@ -69,8 +69,8 @@ class LivestreamIE(InfoExtractor):
'timestamp': 1331042383,
'thumbnail': 'http://img.new.livestream.com/videos/0000000000000372/cacbeed6-fb68-4b5e-ad9c-e148124e68a9_640x427.jpg',
'duration': 15.332,
- 'ext': 'mp4'
- }
+ 'ext': 'mp4',
+ },
}, {
'url': 'https://new.livestream.com/accounts/362/events/3557232/videos/67864563/player?autoPlay=false&height=360&mute=false&width=640',
'only_matching': True,
@@ -92,7 +92,7 @@ class LivestreamIE(InfoExtractor):
for vn in video_nodes:
tbr = int_or_none(vn.attrib.get('system-bitrate'), 1000)
furl = (
- update_url_query(compat_urlparse.urljoin(base, vn.attrib['src']), {
+ update_url_query(urllib.parse.urljoin(base, vn.attrib['src']), {
'v': '3.0.3',
'fp': 'WIN% 14,0,0,145',
}))
@@ -108,7 +108,7 @@ class LivestreamIE(InfoExtractor):
return formats, {}
def _extract_video_info(self, video_data):
- video_id = compat_str(video_data['id'])
+ video_id = str(video_data['id'])
FORMAT_KEYS = (
('sd', 'progressive_url'),
@@ -123,7 +123,7 @@ class LivestreamIE(InfoExtractor):
if ext == 'm3u8':
continue
bitrate = int_or_none(self._search_regex(
- r'(\d+)\.%s' % ext, video_url, 'bitrate', default=None))
+ rf'(\d+)\.{ext}', video_url, 'bitrate', default=None))
formats.append({
'url': video_url,
'format_id': format_id,
@@ -169,7 +169,7 @@ class LivestreamIE(InfoExtractor):
}
def _extract_stream_info(self, stream_info):
- broadcast_id = compat_str(stream_info['broadcast_id'])
+ broadcast_id = str(stream_info['broadcast_id'])
is_live = stream_info.get('is_live')
formats = []
@@ -199,8 +199,8 @@ class LivestreamIE(InfoExtractor):
}
def _generate_event_playlist(self, event_data):
- event_id = compat_str(event_data['id'])
- account_id = compat_str(event_data['owner_account_id'])
+ event_id = str(event_data['id'])
+ account_id = str(event_data['owner_account_id'])
feed_root_url = self._API_URL_TEMPLATE % (account_id, event_id) + '/feed.json'
stream_info = event_data.get('stream_info')
@@ -212,15 +212,14 @@ class LivestreamIE(InfoExtractor):
if last_video is None:
info_url = feed_root_url
else:
- info_url = '{root}?&id={id}&newer=-1&type=video'.format(
- root=feed_root_url, id=last_video)
+ info_url = f'{feed_root_url}?&id={last_video}&newer=-1&type=video'
videos_info = self._download_json(
info_url, event_id, f'Downloading page {i}')['data']
videos_info = [v['data'] for v in videos_info if v['type'] == 'video']
if not videos_info:
break
for v in videos_info:
- v_id = compat_str(v['id'])
+ v_id = str(v['id'])
yield self.url_result(
f'http://livestream.com/accounts/{account_id}/events/{event_id}/videos/{v_id}',
LivestreamIE, v_id, v.get('caption'))
@@ -278,7 +277,7 @@ class LivestreamOriginalIE(InfoExtractor):
}]
def _extract_video_info(self, user, video_id):
- api_url = 'http://x%sx.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id=%s' % (user, video_id)
+ api_url = f'http://x{user}x.api.channel.livestream.com/2.0/clipdetails?extendedInfo=true&id={video_id}'
info = self._download_xml(api_url, video_id)
item = info.find('channel').find('item')
@@ -335,7 +334,7 @@ class LivestreamOriginalIE(InfoExtractor):
entries = [{
'_type': 'url',
- 'url': compat_urlparse.urljoin(url, p),
+ 'url': urllib.parse.urljoin(url, p),
} for p in paths]
return self.playlist_result(entries, folder_id)
@@ -349,10 +348,10 @@ class LivestreamOriginalIE(InfoExtractor):
return self._extract_folder(url, content_id)
else:
# this url is used on mobile devices
- stream_url = 'http://x%sx.api.channel.livestream.com/3.0/getstream.json' % user
+ stream_url = f'http://x{user}x.api.channel.livestream.com/3.0/getstream.json'
info = {}
if content_id:
- stream_url += '?id=%s' % content_id
+ stream_url += f'?id={content_id}'
info = self._extract_video_info(user, content_id)
else:
content_id = user
@@ -381,8 +380,7 @@ class LivestreamShortenerIE(InfoExtractor):
_VALID_URL = r'https?://livestre\.am/(?P<id>.+)'
def _real_extract(self, url):
- mobj = self._match_valid_url(url)
- id = mobj.group('id')
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
return self.url_result(self._og_search_url(webpage))
diff --git a/yt_dlp/extractor/livestreamfails.py b/yt_dlp/extractor/livestreamfails.py
index 0df638422..c18d05d50 100644
--- a/yt_dlp/extractor/livestreamfails.py
+++ b/yt_dlp/extractor/livestreamfails.py
@@ -16,7 +16,7 @@ class LivestreamfailsIE(InfoExtractor):
'thumbnail': r're:^https?://.+',
'timestamp': 1656271785,
'upload_date': '20220626',
- }
+ },
}, {
'url': 'https://livestreamfails.com/post/139200',
'only_matching': True,
@@ -33,5 +33,5 @@ class LivestreamfailsIE(InfoExtractor):
'url': f'https://livestreamfails-video-prod.b-cdn.net/video/{api_response["videoId"]}',
'title': api_response.get('label'),
'creator': traverse_obj(api_response, ('streamer', 'label')),
- 'thumbnail': format_field(api_response, 'imageId', 'https://livestreamfails-image-prod.b-cdn.net/image/%s')
+ 'thumbnail': format_field(api_response, 'imageId', 'https://livestreamfails-image-prod.b-cdn.net/image/%s'),
}
diff --git a/yt_dlp/extractor/lnkgo.py b/yt_dlp/extractor/lnkgo.py
index 6282d2eaf..31a7cefd8 100644
--- a/yt_dlp/extractor/lnkgo.py
+++ b/yt_dlp/extractor/lnkgo.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
clean_html,
format_field,
@@ -58,10 +57,10 @@ class LnkGoIE(InfoExtractor):
display_id, video_id = self._match_valid_url(url).groups()
video_info = self._download_json(
- 'https://lnk.lt/api/main/video-page/%s/%s/false' % (display_id, video_id or '0'),
+ 'https://lnk.lt/api/main/video-page/{}/{}/false'.format(display_id, video_id or '0'),
display_id)['videoConfig']['videoInfo']
- video_id = compat_str(video_info['id'])
+ video_id = str(video_info['id'])
title = video_info['title']
prefix = 'smil' if video_info.get('isQualityChangeAvailable') else 'mp4'
formats = self._extract_m3u8_formats(
@@ -98,9 +97,9 @@ class LnkIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'episode_number': 13431,
'series': 'Naujausi žinių reportažai',
- 'episode': 'Episode 13431'
+ 'episode': 'Episode 13431',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://lnk.lt/istorijos-trumpai/152546',
'info_dict': {
@@ -114,9 +113,9 @@ class LnkIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'episode_number': 1036,
'series': 'Istorijos trumpai',
- 'episode': 'Episode 1036'
+ 'episode': 'Episode 1036',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://lnk.lt/gyvunu-pasaulis/151549',
'info_dict': {
@@ -130,26 +129,26 @@ class LnkIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'episode_number': 16,
'series': 'Gyvūnų pasaulis',
- 'episode': 'Episode 16'
+ 'episode': 'Episode 16',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- video_json = self._download_json(f'https://lnk.lt/api/video/video-config/{id}', id)['videoInfo']
+ video_id = self._match_id(url)
+ video_json = self._download_json(f'https://lnk.lt/api/video/video-config/{video_id}', video_id)['videoInfo']
formats, subtitles = [], {}
if video_json.get('videoUrl'):
- fmts, subs = self._extract_m3u8_formats_and_subtitles(video_json['videoUrl'], id)
+ fmts, subs = self._extract_m3u8_formats_and_subtitles(video_json['videoUrl'], video_id)
formats.extend(fmts)
subtitles = self._merge_subtitles(subtitles, subs)
if video_json.get('videoFairplayUrl') and not video_json.get('drm'):
- fmts, subs = self._extract_m3u8_formats_and_subtitles(video_json['videoFairplayUrl'], id)
+ fmts, subs = self._extract_m3u8_formats_and_subtitles(video_json['videoFairplayUrl'], video_id)
formats.extend(fmts)
subtitles = self._merge_subtitles(subtitles, subs)
return {
- 'id': id,
+ 'id': video_id,
'title': video_json.get('title'),
'description': video_json.get('description'),
'view_count': video_json.get('viewsCount'),
diff --git a/yt_dlp/extractor/lovehomeporn.py b/yt_dlp/extractor/lovehomeporn.py
index ba5a13acd..63b75a3d3 100644
--- a/yt_dlp/extractor/lovehomeporn.py
+++ b/yt_dlp/extractor/lovehomeporn.py
@@ -15,7 +15,7 @@ class LoveHomePornIE(NuevoBaseIE):
},
'params': {
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
@@ -24,10 +24,10 @@ class LoveHomePornIE(NuevoBaseIE):
display_id = mobj.group('display_id')
info = self._extract_nuevo(
- 'http://lovehomeporn.com/media/nuevo/config.php?key=%s' % video_id,
+ f'http://lovehomeporn.com/media/nuevo/config.php?key={video_id}',
video_id)
info.update({
'display_id': display_id,
- 'age_limit': 18
+ 'age_limit': 18,
})
return info
diff --git a/yt_dlp/extractor/lrt.py b/yt_dlp/extractor/lrt.py
index 80d4d1cdb..1a0b6da23 100644
--- a/yt_dlp/extractor/lrt.py
+++ b/yt_dlp/extractor/lrt.py
@@ -22,8 +22,8 @@ class LRTStreamIE(LRTBaseIE):
'id': 'lrt-opus',
'live_status': 'is_live',
'title': 're:^LRT Opus.+$',
- 'ext': 'mp4'
- }
+ 'ext': 'mp4',
+ },
}]
def _real_extract(self, url):
@@ -44,7 +44,7 @@ class LRTStreamIE(LRTBaseIE):
'formats': formats,
'subtitles': subtitles,
'is_live': True,
- 'title': f'{self._og_search_title(webpage)} - {stream_title}'
+ 'title': f'{self._og_search_title(webpage)} - {stream_title}',
}
@@ -62,7 +62,7 @@ class LRTVODIE(LRTBaseIE):
'timestamp': 1604079000,
'upload_date': '20201030',
'tags': ['LRT TELEVIZIJA', 'Beatos virtuvė', 'Beata Nicholson', 'Makaronai', 'Baklažanai', 'Vakarienė', 'Receptas'],
- 'thumbnail': 'https://www.lrt.lt/img/2020/10/30/764041-126478-1287x836.jpg'
+ 'thumbnail': 'https://www.lrt.lt/img/2020/10/30/764041-126478-1287x836.jpg',
},
}, {
# direct mp3 download
diff --git a/yt_dlp/extractor/lsm.py b/yt_dlp/extractor/lsm.py
index 35a831fa2..f5be08f97 100644
--- a/yt_dlp/extractor/lsm.py
+++ b/yt_dlp/extractor/lsm.py
@@ -33,7 +33,7 @@ class LSMLREmbedIE(InfoExtractor):
'duration': 1823,
'title': '#138 Nepilnīgā kompensējamo zāļu sistēma pat mēnešiem dzenā pacientus pa aptiekām',
'thumbnail': 'https://pic.latvijasradio.lv/public/assets/media/9/d/gallery_fd4675ac.jpg',
- }
+ },
}, {
'url': 'https://radioteatris.lsm.lv/lv/embed/?id=&show=1270&theme=white&size=16x9',
'info_dict': {
@@ -59,7 +59,7 @@ class LSMLREmbedIE(InfoExtractor):
'title': 'Jens Ahlboms "Spārni". Radioizrāde ar Mārtiņa Freimaņa mūziku',
'thumbnail': 'https://radioteatris.lsm.lv/public/assets/shows/62f13023a457c.jpg',
'duration': 1788,
- }
+ },
}, {
'url': 'https://lr1.lsm.lv/lv/embed/?id=166557&show=0&theme=white&size=16x9',
'info_dict': {
@@ -168,7 +168,7 @@ class LSMLTVEmbedIE(InfoExtractor):
'upload_date': '20231121',
'title': 'D23-6000-105_cetstud',
'thumbnail': 'https://store.cloudycdn.services/tmsp00060/assets/media/660858/placeholder1700589200.jpg',
- }
+ },
}, {
'url': 'https://ltv.lsm.lv/embed?enablesdkjs=1&c=eyJpdiI6IncwVzZmUFk2MU12enVWK1I3SUcwQ1E9PSIsInZhbHVlIjoid3FhV29vamc3T2sxL1RaRmJ5Rm1GTXozU0o2dVczdUtLK0cwZEZJMDQ2a3ZIRG5DK2pneGlnbktBQy9uazVleHN6VXhxdWIweWNvcHRDSnlISlNYOHlVZ1lpcTUrcWZSTUZPQW14TVdkMW9aOUtRWVNDcFF4eWpHNGcrT0VZbUNFQStKQk91cGpndW9FVjJIa0lpbkh3PT0iLCJtYWMiOiIyZGI1NDJlMWRlM2QyMGNhOGEwYTM2MmNlN2JlOGRhY2QyYjdkMmEzN2RlOTEzYTVkNzI1ODlhZDlhZjU4MjQ2IiwidGFnIjoiIn0=',
'md5': 'a1711e190fe680fdb68fd8413b378e87',
@@ -198,7 +198,7 @@ class LSMLTVEmbedIE(InfoExtractor):
'uploader_url': 'https://www.youtube.com/@LTV16plus',
'like_count': int,
'description': 'md5:7ff0c42ba971e3c13e4b8a2ff03b70b5',
- }
+ },
}]
def _real_extract(self, url):
@@ -239,7 +239,7 @@ class LSMReplayIE(InfoExtractor):
'upload_date': '20231121',
'title': '4. studija. Zolitūdes traģēdija un Inčupes stacija',
'thumbnail': 'https://ltv.lsm.lv/storage/media/8/7/large/5/1f9604e1.jpg',
- }
+ },
}, {
'url': 'https://replay.lsm.lv/lv/ieraksts/lr/183522/138-nepilniga-kompensejamo-zalu-sistema-pat-menesiem-dzena-pacientus-pa-aptiekam',
'md5': '719b33875cd1429846eeeaeec6df2830',
@@ -252,7 +252,7 @@ class LSMReplayIE(InfoExtractor):
'upload_date': '20231102',
'timestamp': 1698921060,
'description': 'md5:7bac3b2dd41e44325032943251c357b1',
- }
+ },
}, {
'url': 'https://replay.lsm.lv/ru/statja/ltv/311130/4-studija-zolitudes-tragedija-un-incupes-stacija',
'only_matching': True,
diff --git a/yt_dlp/extractor/lumni.py b/yt_dlp/extractor/lumni.py
index 5a9538336..8c26f5b47 100644
--- a/yt_dlp/extractor/lumni.py
+++ b/yt_dlp/extractor/lumni.py
@@ -12,7 +12,7 @@ class LumniIE(FranceTVBaseInfoExtractor):
'title': "L'homme et son environnement dans la révolution industrielle - L'ère de l'homme",
'thumbnail': 'https://assets.webservices.francetelevisions.fr/v1/assets/images/a7/17/9f/a7179f5f-63a5-4e11-8d4d-012ab942d905.jpg',
'duration': 230,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/lynda.py b/yt_dlp/extractor/lynda.py
index 768ce913e..bfd461933 100644
--- a/yt_dlp/extractor/lynda.py
+++ b/yt_dlp/extractor/lynda.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
int_or_none,
@@ -21,11 +18,11 @@ class LyndaBaseIE(InfoExtractor):
@staticmethod
def _check_error(json_string, key_or_keys):
- keys = [key_or_keys] if isinstance(key_or_keys, compat_str) else key_or_keys
+ keys = [key_or_keys] if isinstance(key_or_keys, str) else key_or_keys
for key in keys:
error = json_string.get(key)
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
def _perform_login_step(self, form_html, fallback_action_url, extra_form_data, note, referrer_url):
action_url = self._search_regex(
@@ -33,7 +30,7 @@ class LyndaBaseIE(InfoExtractor):
'post url', default=fallback_action_url, group='url')
if not action_url.startswith('http'):
- action_url = compat_urlparse.urljoin(self._SIGNIN_URL, action_url)
+ action_url = urllib.parse.urljoin(self._SIGNIN_URL, action_url)
form_data = self._hidden_inputs(form_html)
form_data.update(extra_form_data)
@@ -44,7 +41,7 @@ class LyndaBaseIE(InfoExtractor):
headers={
'Referer': referrer_url,
'X-Requested-With': 'XMLHttpRequest',
- }, expected_status=(418, 500, ))
+ }, expected_status=(418, 500))
self._check_error(response, ('email', 'password', 'ErrorMessage'))
@@ -97,8 +94,8 @@ class LyndaIE(LyndaBaseIE):
'id': '114408',
'ext': 'mp4',
'title': 'Using the exercise files',
- 'duration': 68
- }
+ 'duration': 68,
+ },
}, {
'url': 'https://www.lynda.com/player/embed/133770?tr=foo=1;bar=g;fizz=rt&fs=0',
'only_matching': True,
@@ -116,7 +113,7 @@ class LyndaIE(LyndaBaseIE):
def _raise_unavailable(self, video_id):
self.raise_login_required(
- 'Video %s is only available for members' % video_id)
+ f'Video {video_id} is only available for members')
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -137,8 +134,7 @@ class LyndaIE(LyndaBaseIE):
query['courseId'] = course_id
play = self._download_json(
- 'https://www.lynda.com/ajax/course/%s/%s/play'
- % (course_id, video_id), video_id, 'Downloading play JSON')
+ f'https://www.lynda.com/ajax/course/{course_id}/{video_id}/play', video_id, 'Downloading play JSON')
if not play:
self._raise_unavailable(video_id)
@@ -154,7 +150,7 @@ class LyndaIE(LyndaBaseIE):
continue
formats.append({
'url': format_url,
- 'format_id': '%s-%s' % (cdn, format_id) if cdn else format_id,
+ 'format_id': f'{cdn}-{format_id}' if cdn else format_id,
'height': int_or_none(format_id),
})
@@ -174,12 +170,12 @@ class LyndaIE(LyndaBaseIE):
if 'Status' in video:
raise ExtractorError(
- 'lynda returned error: %s' % video['Message'], expected=True)
+ 'lynda returned error: {}'.format(video['Message']), expected=True)
if video.get('HasAccess') is False:
self._raise_unavailable(video_id)
- video_id = compat_str(video.get('ID') or video_id)
+ video_id = str(video.get('ID') or video_id)
duration = int_or_none(video.get('DurationInSeconds'))
title = video['Title']
@@ -193,7 +189,7 @@ class LyndaIE(LyndaBaseIE):
'width': int_or_none(f.get('Width')),
'height': int_or_none(f.get('Height')),
'filesize': int_or_none(f.get('FileSize')),
- 'format_id': compat_str(f.get('Resolution')) if f.get('Resolution') else None,
+ 'format_id': str(f.get('Resolution')) if f.get('Resolution') else None,
} for f in fmts if f.get('Url')])
prioritized_streams = video.get('PrioritizedStreams')
@@ -202,7 +198,7 @@ class LyndaIE(LyndaBaseIE):
formats.extend([{
'url': video_url,
'height': int_or_none(format_id),
- 'format_id': '%s-%s' % (prioritized_stream_id, format_id),
+ 'format_id': f'{prioritized_stream_id}-{format_id}',
} for format_id, video_url in prioritized_stream.items()])
self._check_formats(formats, video_id)
@@ -214,18 +210,16 @@ class LyndaIE(LyndaBaseIE):
'title': title,
'duration': duration,
'subtitles': subtitles,
- 'formats': formats
+ 'formats': formats,
}
def _fix_subtitles(self, subs):
srt = ''
seq_counter = 0
- for pos in range(0, len(subs) - 1):
- seq_current = subs[pos]
+ for seq_current, seq_next in zip(subs, subs[1:]):
m_current = re.match(self._TIMECODE_REGEX, seq_current['Timecode'])
if m_current is None:
continue
- seq_next = subs[pos + 1]
m_next = re.match(self._TIMECODE_REGEX, seq_next['Timecode'])
if m_next is None:
continue
@@ -234,12 +228,12 @@ class LyndaIE(LyndaBaseIE):
text = seq_current['Caption'].strip()
if text:
seq_counter += 1
- srt += '%s\r\n%s --> %s\r\n%s\r\n\r\n' % (seq_counter, appear_time, disappear_time, text)
+ srt += f'{seq_counter}\r\n{appear_time} --> {disappear_time}\r\n{text}\r\n\r\n'
if srt:
return srt
def _get_subtitles(self, video_id):
- url = 'https://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
+ url = f'https://www.lynda.com/ajax/player?videoId={video_id}&type=transcript'
subs = self._download_webpage(
url, video_id, 'Downloading subtitles JSON', fatal=False)
if not subs or 'Status="NotFound"' in subs:
@@ -274,10 +268,10 @@ class LyndaCourseIE(LyndaBaseIE):
course_path = mobj.group('coursepath')
course_id = mobj.group('courseid')
- item_template = 'https://www.lynda.com/%s/%%s-4.html' % course_path
+ item_template = f'https://www.lynda.com/{course_path}/%s-4.html'
course = self._download_json(
- 'https://www.lynda.com/ajax/player?courseId=%s&type=course' % course_id,
+ f'https://www.lynda.com/ajax/player?courseId={course_id}&type=course',
course_id, 'Downloading course JSON', fatal=False)
if not course:
@@ -295,7 +289,7 @@ class LyndaCourseIE(LyndaBaseIE):
if course.get('Status') == 'NotFound':
raise ExtractorError(
- 'Course %s does not exist' % course_id, expected=True)
+ f'Course {course_id} does not exist', expected=True)
unaccessible_videos = 0
entries = []
@@ -316,13 +310,13 @@ class LyndaCourseIE(LyndaBaseIE):
'ie_key': LyndaIE.ie_key(),
'chapter': chapter.get('Title'),
'chapter_number': int_or_none(chapter.get('ChapterIndex')),
- 'chapter_id': compat_str(chapter.get('ID')),
+ 'chapter_id': str(chapter.get('ID')),
})
if unaccessible_videos > 0:
self.report_warning(
- '%s videos are only available for members (or paid members) and will not be downloaded. '
- % unaccessible_videos + self._ACCOUNT_CREDENTIALS_HINT)
+ f'{unaccessible_videos} videos are only available for members (or paid members) '
+ f'and will not be downloaded. {self._ACCOUNT_CREDENTIALS_HINT}')
course_title = course.get('Title')
course_description = course.get('Description')
diff --git a/yt_dlp/extractor/magentamusik.py b/yt_dlp/extractor/magentamusik.py
index 9d86a1b21..5bfc0a154 100644
--- a/yt_dlp/extractor/magentamusik.py
+++ b/yt_dlp/extractor/magentamusik.py
@@ -19,7 +19,7 @@ class MagentaMusikIE(InfoExtractor):
'categories': ['Musikkonzert'],
'release_year': 2023,
'location': 'Deutschland',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/mailru.py b/yt_dlp/extractor/mailru.py
index 0f0550c92..cca678f14 100644
--- a/yt_dlp/extractor/mailru.py
+++ b/yt_dlp/extractor/mailru.py
@@ -4,7 +4,6 @@ import re
import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
int_or_none,
parse_duration,
@@ -100,7 +99,7 @@ class MailRuIE(InfoExtractor):
{
'url': 'https://videoapi.my.mail.ru/videos/embed/mail/cloud-strife/Games/2009.html',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
@@ -109,7 +108,7 @@ class MailRuIE(InfoExtractor):
video_id = None
if meta_id:
- meta_url = 'https://my.mail.ru/+/video/meta/%s' % meta_id
+ meta_url = f'https://my.mail.ru/+/video/meta/{meta_id}'
else:
video_id = mobj.group('idv1')
if not video_id:
@@ -138,7 +137,7 @@ class MailRuIE(InfoExtractor):
# Fallback old approach
if not video_data:
video_data = self._download_json(
- 'http://api.video.mail.ru/videos/%s.json?new=1' % video_id,
+ f'http://api.video.mail.ru/videos/{video_id}.json?new=1',
video_id, 'Downloading video JSON')
video_key = self._get_cookies('https://my.mail.ru').get('video_key')
@@ -169,7 +168,7 @@ class MailRuIE(InfoExtractor):
acc_id = meta_data.get('accId')
item_id = meta_data.get('itemId')
- content_id = '%s_%s' % (acc_id, item_id) if acc_id and item_id else video_id
+ content_id = f'{acc_id}_{item_id}' if acc_id and item_id else video_id
thumbnail = meta_data.get('poster')
duration = int_or_none(meta_data.get('duration'))
@@ -192,7 +191,7 @@ class MailRuMusicSearchBaseIE(InfoExtractor):
def _search(self, query, url, audio_id, limit=100, offset=0):
search = self._download_json(
'https://my.mail.ru/cgi-bin/my/ajax', audio_id,
- 'Downloading songs JSON page %d' % (offset // limit + 1),
+ f'Downloading songs JSON page {offset // limit + 1}',
headers={
'Referer': url,
'X-Requested-With': 'XMLHttpRequest',
@@ -236,7 +235,7 @@ class MailRuMusicSearchBaseIE(InfoExtractor):
artist = t.get('Author') or t.get('Author_Text_HTML')
if track:
- title = '%s - %s' % (artist, track) if artist else track
+ title = f'{artist} - {track}' if artist else track
else:
title = audio_id
@@ -307,7 +306,7 @@ class MailRuMusicSearchIE(MailRuMusicSearchBaseIE):
}]
def _real_extract(self, url):
- query = compat_urllib_parse_unquote(self._match_id(url))
+ query = urllib.parse.unquote(self._match_id(url))
entries = []
diff --git a/yt_dlp/extractor/mainstreaming.py b/yt_dlp/extractor/mainstreaming.py
index fa12a6a8d..fb9350584 100644
--- a/yt_dlp/extractor/mainstreaming.py
+++ b/yt_dlp/extractor/mainstreaming.py
@@ -30,9 +30,9 @@ class MainStreamingIE(InfoExtractor):
},
'expected_warnings': [
'Ignoring alternative content ID: WDAF1KOWUpH3',
- 'MainStreaming said: Live event is OFFLINE'
+ 'MainStreaming said: Live event is OFFLINE',
],
- 'skip': 'live stream offline'
+ 'skip': 'live stream offline',
}, {
# playlist
'url': 'https://webtools-e18da6642b684f8aa9ae449862783a56.msvdn.net/embed/WDAF1KOWUpH3',
@@ -40,7 +40,7 @@ class MainStreamingIE(InfoExtractor):
'id': 'WDAF1KOWUpH3',
'title': 'Playlist homepage',
},
- 'playlist_mincount': 2
+ 'playlist_mincount': 2,
}, {
# livestream
'url': 'https://webtools-859c1818ed614cc5b0047439470927b0.msvdn.net/embed/tDoFkZD3T1Lw',
@@ -51,7 +51,7 @@ class MainStreamingIE(InfoExtractor):
'ext': 'mp4',
'thumbnail': r're:https?://[A-Za-z0-9-]*\.msvdn.net/image/\w+/poster',
},
- 'skip': 'live stream'
+ 'skip': 'live stream',
}, {
'url': 'https://webtools-f5842579ff984c1c98d63b8d789673eb.msvdn.net/embed/EUlZfGWkGpOd?autoPlay=false',
'info_dict': {
@@ -61,8 +61,8 @@ class MainStreamingIE(InfoExtractor):
'ext': 'mp4',
'live_status': 'not_live',
'thumbnail': r're:https?://[A-Za-z0-9-]*\.msvdn.net/image/\w+/poster',
- 'duration': 1512
- }
+ 'duration': 1512,
+ },
}, {
# video without webtools- prefix
'url': 'https://f5842579ff984c1c98d63b8d789673eb.msvdn.net/embed/MfuWmzL2lGkA?autoplay=false&T=1635860445',
@@ -73,8 +73,8 @@ class MainStreamingIE(InfoExtractor):
'ext': 'mp4',
'live_status': 'not_live',
'thumbnail': r're:https?://[A-Za-z0-9-]*\.msvdn.net/image/\w+/poster',
- 'duration': 789.04
- }
+ 'duration': 789.04,
+ },
}, {
# always-on livestream with DVR
'url': 'https://webtools-f5842579ff984c1c98d63b8d789673eb.msvdn.net/embed/HVvPMzy',
@@ -92,14 +92,14 @@ class MainStreamingIE(InfoExtractor):
}, {
# no host
'url': 'https://webtools.msvdn.net/embed/MfuWmzL2lGkA',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://859c1818ed614cc5b0047439470927b0.msvdn.net/amp_embed/tDoFkZD3T1Lw',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://859c1818ed614cc5b0047439470927b0.msvdn.net/content/tDoFkZD3T1Lw#',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
def _playlist_entries(self, host, playlist_content):
@@ -111,7 +111,7 @@ class MainStreamingIE(InfoExtractor):
'id': content_id,
'duration': int_or_none(traverse_obj(entry, ('duration', 'totalSeconds'))),
'title': entry.get('title'),
- 'url': f'https://{host}/embed/{content_id}'
+ 'url': f'https://{host}/embed/{content_id}',
}
@staticmethod
@@ -205,5 +205,5 @@ class MainStreamingIE(InfoExtractor):
'duration': parse_duration(content_info.get('duration')),
'tags': content_info.get('tags'),
'subtitles': subtitles,
- 'thumbnail': urljoin(self._get_webtools_base_url(host), f'image/{video_id}/poster')
+ 'thumbnail': urljoin(self._get_webtools_base_url(host), f'image/{video_id}/poster'),
}
diff --git a/yt_dlp/extractor/mangomolo.py b/yt_dlp/extractor/mangomolo.py
index efaf66fa2..2231f71e8 100644
--- a/yt_dlp/extractor/mangomolo.py
+++ b/yt_dlp/extractor/mangomolo.py
@@ -1,8 +1,7 @@
+import base64
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_b64decode,
- compat_urllib_parse_unquote,
-)
from ..utils import classproperty, int_or_none
@@ -33,14 +32,14 @@ class MangomoloBaseIE(InfoExtractor):
def _real_extract(self, url):
page_id = self._get_real_id(self._match_id(url))
webpage = self._download_webpage(
- 'https://player.mangomolo.com/v1/%s?%s' % (self._TYPE, url.split('?')[1]), page_id)
+ 'https://player.mangomolo.com/v1/{}?{}'.format(self._TYPE, url.split('?')[1]), page_id)
hidden_inputs = self._hidden_inputs(webpage)
m3u8_entry_protocol = 'm3u8' if self._IS_LIVE else 'm3u8_native'
format_url = self._html_search_regex(
[
r'(?:file|src)\s*:\s*"(https?://[^"]+?/playlist\.m3u8)',
- r'<a[^>]+href="(rtsp://[^"]+)"'
+ r'<a[^>]+href="(rtsp://[^"]+)"',
], webpage, 'format url')
formats = self._extract_wowza_formats(
format_url, page_id, m3u8_entry_protocol, ['smil'])
@@ -70,4 +69,4 @@ class MangomoloLiveIE(MangomoloBaseIE):
_IS_LIVE = True
def _get_real_id(self, page_id):
- return compat_b64decode(compat_urllib_parse_unquote(page_id)).decode()
+ return base64.b64decode(urllib.parse.unquote(page_id)).decode()
diff --git a/yt_dlp/extractor/manoto.py b/yt_dlp/extractor/manoto.py
index 44c321c26..1dd0b15c1 100644
--- a/yt_dlp/extractor/manoto.py
+++ b/yt_dlp/extractor/manoto.py
@@ -20,11 +20,11 @@ class ManotoTVIE(InfoExtractor):
'title': 'کارول و جان',
'description': 'md5:d0fff1f8ba5c6775d312a00165d1a97e',
'thumbnail': r're:^https?://.*\.(jpeg|png|jpg)$',
- 'ext': 'mp4'
+ 'ext': 'mp4',
},
'params': {
'skip_download': 'm3u8',
- }
+ },
}, {
'url': 'https://www.manototv.com/episode/12576',
'info_dict': {
@@ -37,11 +37,11 @@ class ManotoTVIE(InfoExtractor):
'title': 'سه ماه تعطیلی',
'description': 'سه ماه تعطیلی فیلمی به کارگردانی و نویسندگی شاپور قریب ساختهٔ سال ۱۳۵۶ است.',
'thumbnail': r're:^https?://.*\.(jpeg|png|jpg)$',
- 'ext': 'mp4'
+ 'ext': 'mp4',
},
'params': {
'skip_download': 'm3u8',
- }
+ },
}]
def _real_extract(self, url):
@@ -93,7 +93,7 @@ class ManotoTVShowIE(InfoExtractor):
entries = [
self.url_result(
- 'https://www.manototv.com/episode/%s' % item['slideID'], ie=ManotoTVIE.ie_key(), video_id=item['slideID'])
+ 'https://www.manototv.com/episode/{}'.format(item['slideID']), ie=ManotoTVIE.ie_key(), video_id=item['slideID'])
for item in playlist]
return self.playlist_result(entries, show_id, title, description)
@@ -111,7 +111,7 @@ class ManotoTVLiveIE(InfoExtractor):
},
'params': {
'skip_download': 'm3u8',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/manyvids.py b/yt_dlp/extractor/manyvids.py
index 2aa3a3c93..8caa8f87f 100644
--- a/yt_dlp/extractor/manyvids.py
+++ b/yt_dlp/extractor/manyvids.py
@@ -44,7 +44,7 @@ class ManyVidsIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- real_url = 'https://www.manyvids.com/video/%s/gtm.js' % (video_id, )
+ real_url = f'https://www.manyvids.com/video/{video_id}/gtm.js'
try:
webpage = self._download_webpage(real_url, video_id)
except Exception:
@@ -75,7 +75,7 @@ class ManyVidsIE(InfoExtractor):
def mung_title(s):
if uploader:
- s = re.sub(r'^\s*%s\s+[|-]' % (re.escape(uploader), ), '', s)
+ s = re.sub(rf'^\s*{re.escape(uploader)}\s+[|-]', '', s)
return txt_or_none(s)
title = (
@@ -106,7 +106,7 @@ class ManyVidsIE(InfoExtractor):
'vid': video_id,
}), headers={
'Referer': url,
- 'X-Requested-With': 'XMLHttpRequest'
+ 'X-Requested-With': 'XMLHttpRequest',
})
formats = []
@@ -138,7 +138,7 @@ class ManyVidsIE(InfoExtractor):
def get_likes():
likes = self._search_regex(
- r'''(<a\b[^>]*\bdata-id\s*=\s*(['"])%s\2[^>]*>)''' % (video_id, ),
+ rf'''(<a\b[^>]*\bdata-id\s*=\s*(['"]){video_id}\2[^>]*>)''',
webpage, 'likes', default='')
likes = extract_attributes(likes)
return int_or_none(likes.get('data-likes'))
diff --git a/yt_dlp/extractor/markiza.py b/yt_dlp/extractor/markiza.py
index ca465eae9..088b60d55 100644
--- a/yt_dlp/extractor/markiza.py
+++ b/yt_dlp/extractor/markiza.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
orderedSet,
parse_duration,
@@ -60,11 +59,11 @@ class MarkizaIE(InfoExtractor):
info.update({
'id': video_id,
'title': try_get(
- data, lambda x: x['details']['name'], compat_str),
+ data, lambda x: x['details']['name'], str),
})
else:
info['duration'] = parse_duration(
- try_get(data, lambda x: x['details']['duration'], compat_str))
+ try_get(data, lambda x: x['details']['duration'], str))
return info
@@ -104,7 +103,7 @@ class MarkizaPageIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if MarkizaIE.suitable(url) else super(MarkizaPageIE, cls).suitable(url)
+ return False if MarkizaIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
playlist_id = self._match_id(url)
@@ -116,7 +115,7 @@ class MarkizaPageIE(InfoExtractor):
url, playlist_id, expected_status=500)
entries = [
- self.url_result('http://videoarchiv.markiza.sk/video/%s' % video_id)
+ self.url_result(f'http://videoarchiv.markiza.sk/video/{video_id}')
for video_id in orderedSet(re.findall(
r'(?:initPlayer_|data-entity=["\']|id=["\']player_)(\d+)',
webpage))]
diff --git a/yt_dlp/extractor/massengeschmacktv.py b/yt_dlp/extractor/massengeschmacktv.py
index 1490e9b21..43c087334 100644
--- a/yt_dlp/extractor/massengeschmacktv.py
+++ b/yt_dlp/extractor/massengeschmacktv.py
@@ -22,7 +22,7 @@ class MassengeschmackTVIE(InfoExtractor):
'id': 'fktv202',
'ext': 'mp4',
'title': 'Fernsehkritik-TV #202',
- 'thumbnail': 'https://cache.massengeschmack.tv/img/mag/fktv202.jpg'
+ 'thumbnail': 'https://cache.massengeschmack.tv/img/mag/fktv202.jpg',
},
}
diff --git a/yt_dlp/extractor/masters.py b/yt_dlp/extractor/masters.py
index c3c58d7d0..4aa2c989a 100644
--- a/yt_dlp/extractor/masters.py
+++ b/yt_dlp/extractor/masters.py
@@ -15,7 +15,7 @@ class MastersIE(InfoExtractor):
'title': 'Sungjae Im: Thursday Interview 2022',
'upload_date': '20220407',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/mdr.py b/yt_dlp/extractor/mdr.py
index 49f5b49a4..46097fa20 100644
--- a/yt_dlp/extractor/mdr.py
+++ b/yt_dlp/extractor/mdr.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
determine_ext,
int_or_none,
@@ -104,7 +105,7 @@ class MDRIE(InfoExtractor):
webpage, 'data url', group='url').replace(r'\/', '/')
doc = self._download_xml(
- compat_urlparse.urljoin(url, data_url), video_id)
+ urllib.parse.urljoin(url, data_url), video_id)
title = xpath_text(doc, ['./title', './broadcast/broadcastName'], 'title', fatal=True)
@@ -118,7 +119,7 @@ class MDRIE(InfoExtractor):
'progressiveDownload',
'dynamicHttpStreamingRedirector',
'adaptiveHttpStreamingRedirector'):
- url_el = asset.find('./%sUrl' % source)
+ url_el = asset.find(f'./{source}Url')
if url_el is None:
continue
diff --git a/yt_dlp/extractor/medaltv.py b/yt_dlp/extractor/medaltv.py
index d040fb48f..d64dbfe63 100644
--- a/yt_dlp/extractor/medaltv.py
+++ b/yt_dlp/extractor/medaltv.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
float_or_none,
@@ -31,7 +30,7 @@ class MedalTVIE(InfoExtractor):
'view_count': int,
'like_count': int,
'duration': 13,
- }
+ },
}, {
'url': 'https://medal.tv/games/cod-cold-war/clips/2mA60jWAGQCBH',
'md5': 'fc7a3e4552ae8993c1c4006db46be447',
@@ -50,7 +49,7 @@ class MedalTVIE(InfoExtractor):
'view_count': int,
'like_count': int,
'duration': 23,
- }
+ },
}, {
'url': 'https://medal.tv/games/cod-cold-war/clips/2um24TWdty0NA',
'md5': 'b6dc76b78195fff0b4f8bf4a33ec2148',
@@ -69,7 +68,7 @@ class MedalTVIE(InfoExtractor):
'view_count': int,
'like_count': int,
'duration': 9,
- }
+ },
}, {
'url': 'https://medal.tv/games/valorant/clips/37rMeFpryCC-9',
'only_matching': True,
@@ -108,13 +107,13 @@ class MedalTVIE(InfoExtractor):
'url': item_url,
id_key: item_id,
'width': width,
- 'height': height
+ 'height': height,
})
formats = []
thumbnails = []
for k, v in clip.items():
- if not (v and isinstance(v, compat_str)):
+ if not (v and isinstance(v, str)):
continue
mobj = re.match(r'(contentUrl|thumbnail)(?:(\d+)p)?$', k)
if not mobj:
@@ -136,7 +135,7 @@ class MedalTVIE(InfoExtractor):
expected=True, video_id=video_id)
else:
self.raise_no_formats(
- 'An unknown error occurred ({0}).'.format(error),
+ f'An unknown error occurred ({error}).',
video_id=video_id)
# Necessary because the id of the author is not known in advance.
diff --git a/yt_dlp/extractor/mediaite.py b/yt_dlp/extractor/mediaite.py
index 32887cbde..b3fa6a197 100644
--- a/yt_dlp/extractor/mediaite.py
+++ b/yt_dlp/extractor/mediaite.py
@@ -15,7 +15,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1631630185,
'upload_date': '20210914',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.mediaite.com/tv/joe-scarborough-goes-off-on-tax-breaks-for-super-wealthy-largest-income-redistribution-scam-in-american-history/',
'info_dict': {
@@ -28,7 +28,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1631618057,
'upload_date': '20210914',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.mediaite.com/politics/watch-rudy-giuliani-impersonates-queen-elizabeth-calls-mark-milley-an-asshle-in-bizarre-9-11-speech/',
'info_dict': {
@@ -41,7 +41,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1631536476,
'upload_date': '20210913',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.mediaite.com/podcasts/clarissa-ward-says-she-decided-to-become-a-journalist-on-9-11/',
'info_dict': {
@@ -54,7 +54,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1631311188,
'upload_date': '20210910',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.mediaite.com/opinion/mainstream-media-ignores-rose-mcgowans-bombshell-allegation-that-newsoms-wife-tried-to-silence-her-on-weinstein/',
'info_dict': {
@@ -67,7 +67,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1631553328,
'upload_date': '20210913',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.mediaite.com/news/watch-cnbcs-jim-cramer-says-nobody-wants-to-die-getting-infected-by-unvaccinated-coworker-even-for-22-an-hour/',
'info_dict': {
@@ -80,7 +80,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1633014214,
'upload_date': '20210930',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.mediaite.com/politics/i-cant-read-it-fast-enough-while-defending-trump-larry-kudlow-overwhelmed-by-volume-of-ex-presidents-legal-troubles/',
'info_dict': {
@@ -93,7 +93,7 @@ class MediaiteIE(InfoExtractor):
'timestamp': 1691015535,
'upload_date': '20230802',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/mediaklikk.py b/yt_dlp/extractor/mediaklikk.py
index c01597762..bd1a27fcc 100644
--- a/yt_dlp/extractor/mediaklikk.py
+++ b/yt_dlp/extractor/mediaklikk.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_str, compat_urllib_parse_unquote
from ..utils import (
ExtractorError,
traverse_obj,
@@ -22,7 +23,7 @@ class MediaKlikkIE(InfoExtractor):
'title': 'Hazajáró, DÉLNYUGAT-BÁCSKA – A Duna mentén Palánkától Doroszlóig',
'ext': 'mp4',
'upload_date': '20210901',
- 'thumbnail': 'http://mediaklikk.hu/wp-content/uploads/sites/4/2014/02/hazajarouj_JO.jpg'
+ 'thumbnail': 'http://mediaklikk.hu/wp-content/uploads/sites/4/2014/02/hazajarouj_JO.jpg',
},
'skip': 'Webpage redirects to 404 page',
}, {
@@ -34,8 +35,8 @@ class MediaKlikkIE(InfoExtractor):
'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'
- }
+ 'thumbnail': 'https://mediaklikk.hu/wp-content/uploads/sites/4/2014/02/hazajarouj_JO.jpg',
+ },
}, {
# (old) m4sport
'url': 'https://m4sport.hu/video/2021/08/30/gyemant-liga-parizs/',
@@ -44,7 +45,7 @@ class MediaKlikkIE(InfoExtractor):
'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'
+ 'thumbnail': 'http://m4sport.hu/wp-content/uploads/sites/4/2021/08/vlcsnap-2021-08-30-18h21m20s10-1024x576.jpg',
},
'skip': 'Webpage redirects to 404 page',
}, {
@@ -56,8 +57,8 @@ class MediaKlikkIE(InfoExtractor):
'display_id': 'atletika-gyemant-liga-brusszel',
'ext': 'mp4',
'upload_date': '20230908',
- 'thumbnail': 'https://m4sport.hu/wp-content/uploads/sites/4/2023/09/vlcsnap-2023-09-08-22h43m18s691.jpg'
- }
+ 'thumbnail': 'https://m4sport.hu/wp-content/uploads/sites/4/2023/09/vlcsnap-2023-09-08-22h43m18s691.jpg',
+ },
}, {
# m4sport with *video/ url and no date
'url': 'https://m4sport.hu/bl-video/real-madrid-chelsea-1-1/',
@@ -66,8 +67,8 @@ class MediaKlikkIE(InfoExtractor):
'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'
- }
+ 'thumbnail': 'https://m4sport.hu/wp-content/uploads/sites/4/2021/04/Sequence-01.Still001-1024x576.png',
+ },
}, {
# (old) hirado
'url': 'https://hirado.hu/videok/felteteleket-szabott-a-fovaros/',
@@ -75,7 +76,7 @@ class MediaKlikkIE(InfoExtractor):
'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'
+ 'thumbnail': 'http://hirado.hu/wp-content/uploads/sites/4/2021/09/vlcsnap-2021-09-01-20h20m37s165.jpg',
},
'skip': 'Webpage redirects to video list page',
}, {
@@ -87,8 +88,8 @@ class MediaKlikkIE(InfoExtractor):
'display_id': 'marad-az-eves-elszamolas-a-napelemekre-beruhazo-csaladoknal',
'ext': 'mp4',
'upload_date': '20230911',
- 'thumbnail': 'https://hirado.hu/wp-content/uploads/sites/4/2023/09/vlcsnap-2023-09-11-09h16m09s882.jpg'
- }
+ 'thumbnail': 'https://hirado.hu/wp-content/uploads/sites/4/2023/09/vlcsnap-2023-09-11-09h16m09s882.jpg',
+ },
}, {
# (old) petofilive
'url': 'https://petofilive.hu/video/2021/06/07/tha-shudras-az-akusztikban/',
@@ -97,7 +98,7 @@ class MediaKlikkIE(InfoExtractor):
'title': 'Tha Shudras az Akusztikban',
'ext': 'mp4',
'upload_date': '20210607',
- 'thumbnail': 'http://petofilive.hu/wp-content/uploads/sites/4/2021/06/vlcsnap-2021-06-07-22h14m23s915-1024x576.jpg'
+ 'thumbnail': 'http://petofilive.hu/wp-content/uploads/sites/4/2021/06/vlcsnap-2021-06-07-22h14m23s915-1024x576.jpg',
},
'skip': 'Webpage redirects to empty page',
}, {
@@ -109,8 +110,8 @@ class MediaKlikkIE(InfoExtractor):
'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'
- }
+ 'thumbnail': 'https://petofilive.hu/wp-content/uploads/sites/4/2023/09/Clipboard11-2.jpg',
+ },
}]
def _real_extract(self, url):
@@ -120,13 +121,13 @@ class MediaKlikkIE(InfoExtractor):
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, compat_urllib_parse_unquote)
- video_id = compat_str(player_data['contentId'])
+ player_data = self._parse_json(player_data_str, display_id, urllib.parse.unquote)
+ 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')
upload_date = unified_strdate(
- '%s-%s-%s' % (mobj.group('year'), mobj.group('month'), mobj.group('day')))
+ '{}-{}-{}'.format(mobj.group('year'), mobj.group('month'), mobj.group('day')))
if not upload_date:
upload_date = unified_strdate(self._html_search_regex(
r'<p+\b[^>]+\bclass="article_date">([^<]+)<', webpage, 'upload date', default=None))
@@ -149,5 +150,5 @@ class MediaKlikkIE(InfoExtractor):
'display_id': display_id,
'formats': formats,
'upload_date': upload_date,
- 'thumbnail': player_data.get('bgImage') or self._og_search_thumbnail(webpage)
+ 'thumbnail': player_data.get('bgImage') or self._og_search_thumbnail(webpage),
}
diff --git a/yt_dlp/extractor/mediaset.py b/yt_dlp/extractor/mediaset.py
index b7df5c75a..8cb18e609 100644
--- a/yt_dlp/extractor/mediaset.py
+++ b/yt_dlp/extractor/mediaset.py
@@ -29,7 +29,7 @@ class MediasetIE(ThePlatformBaseIE):
'''
_EMBED_REGEX = [
- rf'<iframe[^>]+src=[\'"](?P<url>(?:https?:)?//(?:\w+\.)+mediaset\.it/player/(?:v\d+/)?index\.html\?\S*?programGuid={_GUID_RE})[\'"&]'
+ rf'<iframe[^>]+src=[\'"](?P<url>(?:https?:)?//(?:\w+\.)+mediaset\.it/player/(?:v\d+/)?index\.html\?\S*?programGuid={_GUID_RE})[\'"&]',
]
_TESTS = [{
# full episode
@@ -154,14 +154,14 @@ class MediasetIE(ThePlatformBaseIE):
},
'params': {
'skip_download': True,
- }
+ },
}]
def _parse_smil_formats_and_subtitles(
self, smil, smil_url, video_id, namespace=None, f4m_params=None, transform_rtmp_url=None):
for video in smil.findall(self._xpath_ns('.//video', namespace)):
video.attrib['src'] = re.sub(r'(https?://vod05)t(-mediaset-it\.akamaized\.net/.+?.mpd)\?.+', r'\1\2', video.attrib['src'])
- return super(MediasetIE, self)._parse_smil_formats_and_subtitles(
+ return super()._parse_smil_formats_and_subtitles(
smil, smil_url, video_id, namespace, f4m_params, transform_rtmp_url)
def _check_drm_formats(self, tp_formats, video_id):
diff --git a/yt_dlp/extractor/mediasite.py b/yt_dlp/extractor/mediasite.py
index d3fec4ec2..beb12f8a4 100644
--- a/yt_dlp/extractor/mediasite.py
+++ b/yt_dlp/extractor/mediasite.py
@@ -1,11 +1,8 @@
import json
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
float_or_none,
@@ -23,8 +20,8 @@ _ID_RE = r'(?:[0-9a-f]{32,34}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0
class MediasiteIE(InfoExtractor):
- _VALID_URL = r'(?xi)https?://[^/]+/Mediasite/(?:Play|Showcase/[^/#?]+/Presentation)/(?P<id>%s)(?P<query>\?[^#]+|)' % _ID_RE
- _EMBED_REGEX = [r'(?xi)<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:(?:https?:)?//[^/]+)?/Mediasite/Play/%s(?:\?.*?)?)\1' % _ID_RE]
+ _VALID_URL = rf'(?xi)https?://[^/]+/Mediasite/(?:Play|Showcase/[^/#?]+/Presentation)/(?P<id>{_ID_RE})(?P<query>\?[^#]+|)'
+ _EMBED_REGEX = [rf'(?xi)<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:(?:https?:)?//[^/]+)?/Mediasite/Play/{_ID_RE}(?:\?.*?)?)\1']
_TESTS = [
{
'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271681e4f199af3c60d1f82869b1d',
@@ -86,7 +83,7 @@ class MediasiteIE(InfoExtractor):
'upload_date': '20120409',
'timestamp': 1333983600,
'duration': 7794,
- }
+ },
},
{
'url': 'https://collegerama.tudelft.nl/Mediasite/Showcase/livebroadcast/Presentation/ada7020854f743c49fbb45c9ec7dbb351d',
@@ -100,7 +97,7 @@ class MediasiteIE(InfoExtractor):
# dashed id
'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271-681e-4f19-9af3-c60d1f82869b1d',
'only_matching': True,
- }
+ },
]
# look in Mediasite.Core.js (Mediasite.ContentStreamType[*])
@@ -117,16 +114,16 @@ class MediasiteIE(InfoExtractor):
for embed_url in super()._extract_embed_urls(url, webpage):
yield smuggle_url(embed_url, {'UrlReferrer': url})
- def __extract_slides(self, *, stream_id, snum, Stream, duration, images):
- slide_base_url = Stream['SlideBaseUrl']
+ def __extract_slides(self, *, stream_id, snum, stream, duration, images):
+ slide_base_url = stream['SlideBaseUrl']
- fname_template = Stream['SlideImageFileNameTemplate']
+ fname_template = stream['SlideImageFileNameTemplate']
if fname_template != 'slide_{0:D4}.jpg':
self.report_warning('Unusual slide file name template; report a bug if slide downloading fails')
fname_template = re.sub(r'\{0:D([0-9]+)\}', r'{0:0\1}', fname_template)
fragments = []
- for i, slide in enumerate(Stream['Slides']):
+ for i, slide in enumerate(stream['Slides']):
if i == 0:
if slide['Time'] > 0:
default_slide = images.get('DefaultSlide')
@@ -141,18 +138,18 @@ class MediasiteIE(InfoExtractor):
})
next_time = try_call(
- lambda: Stream['Slides'][i + 1]['Time'],
+ lambda: stream['Slides'][i + 1]['Time'],
lambda: duration,
lambda: slide['Time'],
expected_type=(int, float))
fragments.append({
'path': fname_template.format(slide.get('Number', i + 1)),
- 'duration': (next_time - slide['Time']) / 1000
+ 'duration': (next_time - slide['Time']) / 1000,
})
return {
- 'format_id': '%s-%u.slides' % (stream_id, snum),
+ 'format_id': f'{stream_id}-{snum}.slides',
'ext': 'mhtml',
'url': slide_base_url,
'protocol': 'mhtml',
@@ -173,12 +170,12 @@ class MediasiteIE(InfoExtractor):
redirect_url = urlh.url
# XXX: might have also extracted UrlReferrer and QueryString from the html
- service_path = compat_urlparse.urljoin(redirect_url, self._html_search_regex(
+ service_path = urllib.parse.urljoin(redirect_url, self._html_search_regex(
r'<div[^>]+\bid=["\']ServicePath[^>]+>(.+?)</div>', webpage, resource_id,
default='/Mediasite/PlayerService/PlayerService.svc/json'))
player_options = self._download_json(
- '%s/GetPlayerOptions' % service_path, resource_id,
+ f'{service_path}/GetPlayerOptions', resource_id,
headers={
'Content-type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
@@ -189,25 +186,25 @@ class MediasiteIE(InfoExtractor):
'QueryString': query,
'UrlReferrer': data.get('UrlReferrer', ''),
'UseScreenReader': False,
- }
- }).encode('utf-8'))['d']
+ },
+ }).encode())['d']
presentation = player_options['Presentation']
title = presentation['Title']
if presentation is None:
raise ExtractorError(
- 'Mediasite says: %s' % player_options['PlayerPresentationStatusMessage'],
+ 'Mediasite says: {}'.format(player_options['PlayerPresentationStatusMessage']),
expected=True)
thumbnails = []
formats = []
- for snum, Stream in enumerate(presentation['Streams']):
- stream_type = Stream.get('StreamType')
+ for snum, stream in enumerate(presentation['Streams']):
+ stream_type = stream.get('StreamType')
if stream_type is None:
continue
- video_urls = Stream.get('VideoUrls')
+ video_urls = stream.get('VideoUrls')
if not isinstance(video_urls, list):
video_urls = []
@@ -215,36 +212,36 @@ class MediasiteIE(InfoExtractor):
stream_type, 'type%u' % stream_type)
stream_formats = []
- for unum, VideoUrl in enumerate(video_urls):
- video_url = url_or_none(VideoUrl.get('Location'))
+ for unum, video_url in enumerate(video_urls):
+ video_url = url_or_none(video_url.get('Location'))
if not video_url:
continue
# XXX: if Stream.get('CanChangeScheme', False), switch scheme to HTTP/HTTPS
- media_type = VideoUrl.get('MediaType')
+ media_type = video_url.get('MediaType')
if media_type == 'SS':
stream_formats.extend(self._extract_ism_formats(
video_url, resource_id,
- ism_id='%s-%u.%u' % (stream_id, snum, unum),
+ ism_id=f'{stream_id}-{snum}.{unum}',
fatal=False))
elif media_type == 'Dash':
stream_formats.extend(self._extract_mpd_formats(
video_url, resource_id,
- mpd_id='%s-%u.%u' % (stream_id, snum, unum),
+ mpd_id=f'{stream_id}-{snum}.{unum}',
fatal=False))
else:
stream_formats.append({
- 'format_id': '%s-%u.%u' % (stream_id, snum, unum),
+ 'format_id': f'{stream_id}-{snum}.{unum}',
'url': video_url,
- 'ext': mimetype2ext(VideoUrl.get('MimeType')),
+ 'ext': mimetype2ext(video_url.get('MimeType')),
})
- if Stream.get('HasSlideContent', False):
+ if stream.get('HasSlideContent', False):
images = player_options['PlayerLayoutOptions']['Images']
stream_formats.append(self.__extract_slides(
stream_id=stream_id,
snum=snum,
- Stream=Stream,
+ stream=stream,
duration=presentation.get('Duration'),
images=images,
))
@@ -254,10 +251,10 @@ class MediasiteIE(InfoExtractor):
for fmt in stream_formats:
fmt['quality'] = -10
- thumbnail_url = Stream.get('ThumbnailUrl')
+ thumbnail_url = stream.get('ThumbnailUrl')
if thumbnail_url:
thumbnails.append({
- 'id': '%s-%u' % (stream_id, snum),
+ 'id': f'{stream_id}-{snum}',
'url': urljoin(redirect_url, thumbnail_url),
'preference': -1 if stream_type != 0 else 0,
})
@@ -278,15 +275,15 @@ class MediasiteIE(InfoExtractor):
class MediasiteCatalogIE(InfoExtractor):
- _VALID_URL = r'''(?xi)
+ _VALID_URL = rf'''(?xi)
(?P<url>https?://[^/]+/Mediasite)
/Catalog/Full/
- (?P<catalog_id>{0})
+ (?P<catalog_id>{_ID_RE})
(?:
- /(?P<current_folder_id>{0})
- /(?P<root_dynamic_folder_id>{0})
+ /(?P<current_folder_id>{_ID_RE})
+ /(?P<root_dynamic_folder_id>{_ID_RE})
)?
- '''.format(_ID_RE)
+ '''
_TESTS = [{
'url': 'http://events7.mediasite.com/Mediasite/Catalog/Full/631f9e48530d454381549f955d08c75e21',
'info_dict': {
@@ -368,7 +365,7 @@ class MediasiteCatalogIE(InfoExtractor):
headers[anti_forgery_header] = anti_forgery_token
catalog = self._download_json(
- '%s/Catalog/Data/GetPresentationsForFolder' % mediasite_url,
+ f'{mediasite_url}/Catalog/Data/GetPresentationsForFolder',
catalog_id, data=json.dumps(data).encode(), headers=headers)
entries = []
@@ -379,13 +376,13 @@ class MediasiteCatalogIE(InfoExtractor):
if not video_id:
continue
entries.append(self.url_result(
- '%s/Play/%s' % (mediasite_url, video_id),
+ f'{mediasite_url}/Play/{video_id}',
ie=MediasiteIE.ie_key(), video_id=video_id))
title = try_get(
- catalog, lambda x: x['CurrentFolder']['Name'], compat_str)
+ catalog, lambda x: x['CurrentFolder']['Name'], str)
- return self.playlist_result(entries, catalog_id, title,)
+ return self.playlist_result(entries, catalog_id, title)
class MediasiteNamedCatalogIE(InfoExtractor):
@@ -403,8 +400,8 @@ class MediasiteNamedCatalogIE(InfoExtractor):
webpage = self._download_webpage(url, catalog_name)
catalog_id = self._search_regex(
- r'CatalogId\s*:\s*["\'](%s)' % _ID_RE, webpage, 'catalog id')
+ rf'CatalogId\s*:\s*["\']({_ID_RE})', webpage, 'catalog id')
return self.url_result(
- '%s/Catalog/Full/%s' % (mediasite_url, catalog_id),
+ f'{mediasite_url}/Catalog/Full/{catalog_id}',
ie=MediasiteCatalogIE.ie_key(), video_id=catalog_id)
diff --git a/yt_dlp/extractor/mediaworksnz.py b/yt_dlp/extractor/mediaworksnz.py
index 62e37d24a..be67b631e 100644
--- a/yt_dlp/extractor/mediaworksnz.py
+++ b/yt_dlp/extractor/mediaworksnz.py
@@ -24,8 +24,8 @@ class MediaWorksNZVODIE(InfoExtractor):
'timestamp': 1604268608,
'upload_date': '20201101',
'thumbnail': r're:^https?://.*\.jpg$',
- 'channel': 'George FM'
- }
+ 'channel': 'George FM',
+ },
}, {
# has audio-only format
'url': 'https://vodupload-api.mediaworks.nz/library/asset/published/VID02627',
@@ -40,7 +40,7 @@ class MediaWorksNZVODIE(InfoExtractor):
'upload_date': '20220822',
'timestamp': 1661152289,
},
- 'params': {'format': 'ba[ext=mp3]'}
+ 'params': {'format': 'ba[ext=mp3]'},
}]
_WEBPAGE_TESTS = [{
@@ -55,7 +55,7 @@ class MediaWorksNZVODIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'description': 'Socrates Walks Into A Bar Podcast Episode 1',
'upload_date': '20220720',
- }
+ },
}]
@classmethod
@@ -63,7 +63,7 @@ class MediaWorksNZVODIE(InfoExtractor):
for mobj in re.finditer(
rf'''(?x)<div\s+\bid=["']Player-Attributes-JWID[^>]+\b
data-request-url=["']{cls._VALID_URL_BASE_RE}["'][^>]+\b
- data-asset-id=["']{cls._VALID_URL_ID_RE}["']''', webpage
+ data-asset-id=["']{cls._VALID_URL_ID_RE}["']''', webpage,
):
yield f'https://vodupload-api.mediaworks.nz/library/asset/published/{mobj.group("id")}'
diff --git a/yt_dlp/extractor/meipai.py b/yt_dlp/extractor/meipai.py
index 1a6f3cd74..e4c145c72 100644
--- a/yt_dlp/extractor/meipai.py
+++ b/yt_dlp/extractor/meipai.py
@@ -25,7 +25,7 @@ class MeipaiIE(InfoExtractor):
'view_count': 35511,
'creator': '她她-TATA',
'tags': ['葉子', '阿桑', '余姿昀', '超級女聲'],
- }
+ },
}, {
# record of live streaming
'url': 'http://www.meipai.com/media/585526361',
@@ -41,7 +41,7 @@ class MeipaiIE(InfoExtractor):
'upload_date': '20160919',
'view_count': 1215,
'creator': '她她-TATA',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/melonvod.py b/yt_dlp/extractor/melonvod.py
index 1d3fff856..05d9de8dc 100644
--- a/yt_dlp/extractor/melonvod.py
+++ b/yt_dlp/extractor/melonvod.py
@@ -20,7 +20,7 @@ class MelonVODIE(InfoExtractor):
},
'params': {
'skip_download': 'm3u8 download',
- }
+ },
}
def _real_extract(self, url):
@@ -64,5 +64,5 @@ class MelonVODIE(InfoExtractor):
'thumbnail': thumbnail,
'upload_date': upload_date,
'duration': duration,
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/metacritic.py b/yt_dlp/extractor/metacritic.py
index 14410549a..41e20a58e 100644
--- a/yt_dlp/extractor/metacritic.py
+++ b/yt_dlp/extractor/metacritic.py
@@ -40,9 +40,9 @@ class MetacriticIE(InfoExtractor):
clip = next(c for c in info.findall('playList/clip') if c.find('id').text == video_id)
formats = []
- for videoFile in clip.findall('httpURI/videoFile'):
- rate_str = videoFile.find('rate').text
- video_url = videoFile.find('filePath').text
+ for video_file in clip.findall('httpURI/videoFile'):
+ rate_str = video_file.find('rate').text
+ video_url = video_file.find('filePath').text
formats.append({
'url': video_url,
'ext': 'mp4',
diff --git a/yt_dlp/extractor/mgtv.py b/yt_dlp/extractor/mgtv.py
index 31ccf004e..d5dda06f9 100644
--- a/yt_dlp/extractor/mgtv.py
+++ b/yt_dlp/extractor/mgtv.py
@@ -77,13 +77,13 @@ class MGTVIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
tk2 = base64.urlsafe_b64encode(
- f'did={str(uuid.uuid4())}|pno=1030|ver=0.3.0301|clit={int(time.time())}'.encode())[::-1]
+ f'did={uuid.uuid4()}|pno=1030|ver=0.3.0301|clit={int(time.time())}'.encode())[::-1]
try:
api_data = self._download_json(
'https://pcweb.api.mgtv.com/player/video', video_id, query={
'tk2': tk2,
'video_id': video_id,
- 'type': 'pch5'
+ 'type': 'pch5',
}, headers=self.geo_verification_headers())['data']
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 401:
@@ -160,6 +160,6 @@ class MGTVIE(InfoExtractor):
subtitles.setdefault(locale.lower(), []).append({
'url': sub_url,
'name': sub.get('name'),
- 'ext': 'srt'
+ 'ext': 'srt',
})
return subtitles
diff --git a/yt_dlp/extractor/microsoftembed.py b/yt_dlp/extractor/microsoftembed.py
index f71ab3e92..98d50b18a 100644
--- a/yt_dlp/extractor/microsoftembed.py
+++ b/yt_dlp/extractor/microsoftembed.py
@@ -15,8 +15,8 @@ class MicrosoftEmbedIE(InfoExtractor):
'thumbnail': 'http://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RWL7Ju?ver=cae5',
'age_limit': 0,
'timestamp': 1631658316,
- 'upload_date': '20210914'
- }
+ 'upload_date': '20210914',
+ },
}]
_API_URL = 'https://prod-video-cms-rt-microsoft-com.akamaized.net/vhs/api/videos/'
diff --git a/yt_dlp/extractor/microsoftstream.py b/yt_dlp/extractor/microsoftstream.py
index f6a0b416d..b13881083 100644
--- a/yt_dlp/extractor/microsoftstream.py
+++ b/yt_dlp/extractor/microsoftstream.py
@@ -37,11 +37,11 @@ class MicrosoftStreamIE(InfoExtractor):
sub_dict = automatic_captions if track.get('autoGenerated') else subtitles
sub_dict.setdefault(track['language'], []).append({
'ext': 'vtt',
- 'url': track.get('url')
+ 'url': track.get('url'),
})
return {
'subtitles': subtitles,
- 'automatic_captions': automatic_captions
+ 'automatic_captions': automatic_captions,
}
def extract_all_subtitles(self, *args, **kwargs):
@@ -66,7 +66,7 @@ class MicrosoftStreamIE(InfoExtractor):
f'{api_url}/videos/{video_id}', video_id,
headers=headers, query={
'$expand': 'creator,tokens,status,liveEvent,extensions',
- 'api-version': '1.4-private'
+ 'api-version': '1.4-private',
})
video_id = video_data.get('id') or video_id
language = video_data.get('language')
diff --git a/yt_dlp/extractor/microsoftvirtualacademy.py b/yt_dlp/extractor/microsoftvirtualacademy.py
index b759b1860..e354d8a50 100644
--- a/yt_dlp/extractor/microsoftvirtualacademy.py
+++ b/yt_dlp/extractor/microsoftvirtualacademy.py
@@ -13,7 +13,7 @@ from ..utils import (
class MicrosoftVirtualAcademyBaseIE(InfoExtractor):
def _extract_base_url(self, course_id, display_id):
return self._download_json(
- 'https://api-mlxprod.microsoft.com/services/products/anonymous/%s' % course_id,
+ f'https://api-mlxprod.microsoft.com/services/products/anonymous/{course_id}',
display_id, 'Downloading course base URL')
def _extract_chapter_and_title(self, title):
@@ -26,7 +26,7 @@ class MicrosoftVirtualAcademyBaseIE(InfoExtractor):
class MicrosoftVirtualAcademyIE(MicrosoftVirtualAcademyBaseIE):
IE_NAME = 'mva'
IE_DESC = 'Microsoft Virtual Academy videos'
- _VALID_URL = r'(?:%s:|https?://(?:mva\.microsoft|(?:www\.)?microsoftvirtualacademy)\.com/[^/]+/training-courses/[^/?#&]+-)(?P<course_id>\d+)(?::|\?l=)(?P<id>[\da-zA-Z]+_\d+)' % IE_NAME
+ _VALID_URL = rf'(?:{IE_NAME}:|https?://(?:mva\.microsoft|(?:www\.)?microsoftvirtualacademy)\.com/[^/]+/training-courses/[^/?#&]+-)(?P<course_id>\d+)(?::|\?l=)(?P<id>[\da-zA-Z]+_\d+)'
_TESTS = [{
'url': 'https://mva.microsoft.com/en-US/training-courses/microsoft-azure-fundamentals-virtual-machines-11788?l=gfVXISmEB_6804984382',
@@ -41,7 +41,7 @@ class MicrosoftVirtualAcademyIE(MicrosoftVirtualAcademyBaseIE):
'ext': 'ttml',
}],
},
- }
+ },
}, {
'url': 'mva:11788:gfVXISmEB_6804984382',
'only_matching': True,
@@ -57,7 +57,7 @@ class MicrosoftVirtualAcademyIE(MicrosoftVirtualAcademyBaseIE):
base_url = smuggled_data.get('base_url') or self._extract_base_url(course_id, video_id)
settings = self._download_xml(
- '%s/content/content_%s/videosettings.xml?v=1' % (base_url, video_id),
+ f'{base_url}/content/content_{video_id}/videosettings.xml?v=1',
video_id, 'Downloading video settings XML')
_, title = self._extract_chapter_and_title(xpath_text(
@@ -100,7 +100,7 @@ class MicrosoftVirtualAcademyIE(MicrosoftVirtualAcademyBaseIE):
if not subtitle_url:
continue
subtitles.setdefault('en', []).append({
- 'url': '%s/%s' % (base_url, subtitle_url),
+ 'url': f'{base_url}/{subtitle_url}',
'ext': source.get('type'),
})
@@ -108,14 +108,14 @@ class MicrosoftVirtualAcademyIE(MicrosoftVirtualAcademyBaseIE):
'id': video_id,
'title': title,
'subtitles': subtitles,
- 'formats': formats
+ 'formats': formats,
}
class MicrosoftVirtualAcademyCourseIE(MicrosoftVirtualAcademyBaseIE):
IE_NAME = 'mva:course'
IE_DESC = 'Microsoft Virtual Academy courses'
- _VALID_URL = r'(?:%s:|https?://(?:mva\.microsoft|(?:www\.)?microsoftvirtualacademy)\.com/[^/]+/training-courses/(?P<display_id>[^/?#&]+)-)(?P<id>\d+)' % IE_NAME
+ _VALID_URL = rf'(?:{IE_NAME}:|https?://(?:mva\.microsoft|(?:www\.)?microsoftvirtualacademy)\.com/[^/]+/training-courses/(?P<display_id>[^/?#&]+)-)(?P<id>\d+)'
_TESTS = [{
'url': 'https://mva.microsoft.com/en-US/training-courses/microsoft-azure-fundamentals-virtual-machines-11788',
@@ -142,8 +142,7 @@ class MicrosoftVirtualAcademyCourseIE(MicrosoftVirtualAcademyBaseIE):
@classmethod
def suitable(cls, url):
- return False if MicrosoftVirtualAcademyIE.suitable(url) else super(
- MicrosoftVirtualAcademyCourseIE, cls).suitable(url)
+ return False if MicrosoftVirtualAcademyIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -153,7 +152,7 @@ class MicrosoftVirtualAcademyCourseIE(MicrosoftVirtualAcademyBaseIE):
base_url = self._extract_base_url(course_id, display_id)
manifest = self._download_json(
- '%s/imsmanifestlite.json' % base_url,
+ f'{base_url}/imsmanifestlite.json',
display_id, 'Downloading course manifest JSON')['manifest']
organization = manifest['organizations']['organization'][0]
@@ -175,7 +174,7 @@ class MicrosoftVirtualAcademyCourseIE(MicrosoftVirtualAcademyBaseIE):
entries.append({
'_type': 'url_transparent',
'url': smuggle_url(
- 'mva:%s:%s' % (course_id, item_id), {'base_url': base_url}),
+ f'mva:{course_id}:{item_id}', {'base_url': base_url}),
'title': title,
'description': description,
'duration': duration,
diff --git a/yt_dlp/extractor/mildom.py b/yt_dlp/extractor/mildom.py
index caf60c805..88a2b9e89 100644
--- a/yt_dlp/extractor/mildom.py
+++ b/yt_dlp/extractor/mildom.py
@@ -18,7 +18,7 @@ class MildomBaseIE(InfoExtractor):
def _call_api(self, url, video_id, query=None, note='Downloading JSON metadata', body=None):
if not self._GUEST_ID:
- self._GUEST_ID = f'pc-gp-{str(uuid.uuid4())}'
+ self._GUEST_ID = f'pc-gp-{uuid.uuid4()}'
content = self._download_json(
url, video_id, note=note, data=json.dumps(body).encode() if body else None,
@@ -150,18 +150,18 @@ class MildomVodIE(MildomBaseIE):
'protocol': 'm3u8_native',
'vcodec': 'none',
'acodec': 'aac',
- 'ext': 'm4a'
+ 'ext': 'm4a',
}]
for fmt in autoplay['video_link']:
formats.append({
- 'format_id': 'video-%s' % fmt['name'],
+ 'format_id': 'video-{}'.format(fmt['name']),
'url': fmt['url'],
'protocol': 'm3u8_native',
'width': fmt['level'] * autoplay['video_width'] // autoplay['video_height'],
'height': fmt['level'],
'vcodec': 'h264',
'acodec': 'aac',
- 'ext': 'mp4'
+ 'ext': 'mp4',
})
return {
@@ -280,7 +280,7 @@ class MildomUserVodIE(MildomBaseIE):
def _real_extract(self, url):
user_id = self._match_id(url)
- self.to_screen('This will download all VODs belonging to user. To download ongoing live video, use "https://www.mildom.com/%s" instead' % user_id)
+ self.to_screen(f'This will download all VODs belonging to user. To download ongoing live video, use "https://www.mildom.com/{user_id}" instead')
profile = self._call_api(
'https://cloudac.mildom.com/nonolive/gappserv/user/profileV2', user_id,
diff --git a/yt_dlp/extractor/minds.py b/yt_dlp/extractor/minds.py
index 27a6e3805..71c82f208 100644
--- a/yt_dlp/extractor/minds.py
+++ b/yt_dlp/extractor/minds.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
clean_html,
format_field,
@@ -16,7 +15,7 @@ class MindsBaseIE(InfoExtractor):
api_url = 'https://www.minds.com/api/' + path
token = self._get_cookies(api_url).get('XSRF-TOKEN')
return self._download_json(
- api_url, video_id, 'Downloading %s JSON metadata' % resource, headers={
+ api_url, video_id, f'Downloading {resource} JSON metadata', headers={
'Referer': 'https://www.minds.com/',
'X-XSRF-TOKEN': token.value if token else '',
}, query=query)
@@ -98,7 +97,7 @@ class MindsIE(MindsBaseIE):
uploader_id = owner.get('username')
tags = entity.get('tags')
- if tags and isinstance(tags, compat_str):
+ if tags and isinstance(tags, str):
tags = [tags]
thumbnail = None
@@ -135,8 +134,8 @@ class MindsFeedBaseIE(MindsBaseIE):
i = 1
while True:
data = self._call_api(
- 'v2/feeds/container/%s/videos' % feed_id,
- feed_id, 'page %s' % i, query)
+ f'v2/feeds/container/{feed_id}/videos',
+ feed_id, f'page {i}', query)
entities = data.get('entities') or []
for entity in entities:
guid = entity.get('guid')
@@ -153,7 +152,7 @@ class MindsFeedBaseIE(MindsBaseIE):
def _real_extract(self, url):
feed_id = self._match_id(url)
feed = self._call_api(
- 'v1/%s/%s' % (self._FEED_PATH, feed_id),
+ f'v1/{self._FEED_PATH}/{feed_id}',
feed_id, self._FEED_TYPE)[self._FEED_TYPE]
return self.playlist_result(
diff --git a/yt_dlp/extractor/minoto.py b/yt_dlp/extractor/minoto.py
index 032bf3b71..69832560d 100644
--- a/yt_dlp/extractor/minoto.py
+++ b/yt_dlp/extractor/minoto.py
@@ -12,7 +12,7 @@ class MinotoIE(InfoExtractor):
mobj = self._match_valid_url(url)
player_id = mobj.group('player_id') or '1'
video_id = mobj.group('id')
- video_data = self._download_json('http://play.minoto-video.com/%s/%s.js' % (player_id, video_id), video_id)
+ video_data = self._download_json(f'http://play.minoto-video.com/{player_id}/{video_id}.js', video_id)
video_metadata = video_data['video-metadata']
formats = []
for fmt in video_data['video-files']:
diff --git a/yt_dlp/extractor/mirrativ.py b/yt_dlp/extractor/mirrativ.py
index 0a8ee0c3a..4e24371a2 100644
--- a/yt_dlp/extractor/mirrativ.py
+++ b/yt_dlp/extractor/mirrativ.py
@@ -11,7 +11,7 @@ class MirrativBaseIE(InfoExtractor):
def assert_error(self, response):
error_message = traverse_obj(response, ('status', 'error'))
if error_message:
- raise ExtractorError('Mirrativ says: %s' % error_message, expected=True)
+ raise ExtractorError(f'Mirrativ says: {error_message}', expected=True)
class MirrativIE(MirrativBaseIE):
@@ -42,7 +42,7 @@ class MirrativIE(MirrativBaseIE):
def _real_extract(self, url):
video_id = self._match_id(url)
- webpage = self._download_webpage('https://www.mirrativ.com/live/%s' % video_id, video_id)
+ webpage = self._download_webpage(f'https://www.mirrativ.com/live/{video_id}', video_id)
live_response = self._download_json(f'https://www.mirrativ.com/api/live/live?live_id={video_id}', video_id)
self.assert_error(live_response)
@@ -102,7 +102,7 @@ class MirrativUserIE(MirrativBaseIE):
# or the service will ban your IP address for a while
continue
live_id = live.get('live_id')
- url = 'https://www.mirrativ.com/live/%s' % live_id
+ url = f'https://www.mirrativ.com/live/{live_id}'
yield self.url_result(url, video_id=live_id, video_title=live.get('title'))
page = api_response.get('next_page')
diff --git a/yt_dlp/extractor/mit.py b/yt_dlp/extractor/mit.py
index 979584ed6..e75c540a2 100644
--- a/yt_dlp/extractor/mit.py
+++ b/yt_dlp/extractor/mit.py
@@ -28,7 +28,7 @@ class TechTVMITIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
raw_page = self._download_webpage(
- 'http://techtv.mit.edu/videos/%s' % video_id, video_id)
+ f'http://techtv.mit.edu/videos/{video_id}', video_id)
clean_page = re.compile(r'<!--.*?-->', re.S).sub('', raw_page)
base_url = self._proto_relative_url(self._search_regex(
@@ -79,7 +79,7 @@ class OCWMITIE(InfoExtractor):
'upload_date': '20121109',
'uploader_id': 'MIT',
'uploader': 'MIT OpenCourseWare',
- }
+ },
},
{
'url': 'http://ocw.mit.edu/courses/mathematics/18-01sc-single-variable-calculus-fall-2010/1.-differentiation/part-a-definition-and-basic-rules/session-1-introduction-to-derivatives/',
@@ -91,8 +91,8 @@ class OCWMITIE(InfoExtractor):
'uploader_id': 'MIT',
'uploader': 'MIT OpenCourseWare',
'description': 'This section contains lecture video excerpts, lecture notes, an interactive mathlet with supporting documents, and problem solving videos.',
- }
- }
+ },
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/mixch.py b/yt_dlp/extractor/mixch.py
index 58c4a2301..9b7c7b89b 100644
--- a/yt_dlp/extractor/mixch.py
+++ b/yt_dlp/extractor/mixch.py
@@ -83,7 +83,7 @@ class MixchArchiveIE(InfoExtractor):
'id': '421',
'ext': 'mp4',
'title': '96NEKO SHOW TIME',
- }
+ },
}, {
'url': 'https://mixch.tv/archive/1213',
'skip': 'paid video, no DRM. expires at Dec 31, 2023',
@@ -93,7 +93,7 @@ class MixchArchiveIE(InfoExtractor):
'title': '【特別トーク番組アーカイブス】Merm4id×燐舞曲 2nd LIVE「VERSUS」',
'release_date': '20231201',
'thumbnail': str,
- }
+ },
}, {
'url': 'https://mixch.tv/archive/1214',
'only_matching': True,
diff --git a/yt_dlp/extractor/mixcloud.py b/yt_dlp/extractor/mixcloud.py
index 8a95d1a5d..19b7fd4e7 100644
--- a/yt_dlp/extractor/mixcloud.py
+++ b/yt_dlp/extractor/mixcloud.py
@@ -1,12 +1,9 @@
+import base64
import itertools
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_b64decode,
- compat_ord,
- compat_str,
- compat_urllib_parse_unquote,
-)
+from ..compat import compat_ord
from ..utils import (
ExtractorError,
int_or_none,
@@ -25,7 +22,7 @@ class MixcloudBaseIE(InfoExtractor):
%s(lookup: {username: "%s"%s}) {
%s
}
-}''' % (lookup_key, username, ', slug: "%s"' % slug if slug else '', object_fields)
+}''' % (lookup_key, username, f', slug: "{slug}"' if slug else '', object_fields), # noqa: UP031
})['data'][lookup_key]
@@ -91,8 +88,8 @@ class MixcloudIE(MixcloudBaseIE):
def _real_extract(self, url):
username, slug = self._match_valid_url(url).groups()
- username, slug = compat_urllib_parse_unquote(username), compat_urllib_parse_unquote(slug)
- track_id = '%s_%s' % (username, slug)
+ username, slug = urllib.parse.unquote(username), urllib.parse.unquote(slug)
+ track_id = f'{username}_{slug}'
cloudcast = self._call_api('cloudcast', '''audioLength
comments(first: 100) {
@@ -162,7 +159,7 @@ class MixcloudIE(MixcloudBaseIE):
if not format_url:
continue
decrypted = self._decrypt_xor_cipher(
- self._DECRYPTION_KEY, compat_b64decode(format_url))
+ self._DECRYPTION_KEY, base64.b64decode(format_url))
if url_key == 'hlsUrl':
formats.extend(self._extract_m3u8_formats(
decrypted, track_id, 'mp4', entry_protocol='m3u8_native',
@@ -200,7 +197,7 @@ class MixcloudIE(MixcloudBaseIE):
tags = []
for t in cloudcast.get('tags'):
- tag = try_get(t, lambda x: x['tag']['name'], compat_str)
+ tag = try_get(t, lambda x: x['tag']['name'], str)
if not tag:
tags.append(tag)
@@ -213,7 +210,7 @@ class MixcloudIE(MixcloudBaseIE):
'title': title,
'formats': formats,
'description': cloudcast.get('description'),
- 'thumbnail': try_get(cloudcast, lambda x: x['picture']['url'], compat_str),
+ 'thumbnail': try_get(cloudcast, lambda x: x['picture']['url'], str),
'uploader': owner.get('displayName'),
'timestamp': parse_iso8601(cloudcast.get('publishDate')),
'uploader_id': owner.get('username'),
@@ -238,12 +235,12 @@ class MixcloudPlaylistBaseIE(MixcloudBaseIE):
def _real_extract(self, url):
username, slug = self._match_valid_url(url).groups()
- username = compat_urllib_parse_unquote(username)
+ username = urllib.parse.unquote(username)
if not slug:
slug = 'uploads'
else:
- slug = compat_urllib_parse_unquote(slug)
- playlist_id = '%s_%s' % (username, slug)
+ slug = urllib.parse.unquote(slug)
+ playlist_id = f'{username}_{slug}'
is_playlist_type = self._ROOT_TYPE == 'playlist'
playlist_type = 'items' if is_playlist_type else slug
@@ -265,7 +262,7 @@ class MixcloudPlaylistBaseIE(MixcloudBaseIE):
endCursor
hasNextPage
}
- }''' % (self._TITLE_KEY, self._DESCRIPTION_KEY, playlist_type, list_filter, self._NODE_TEMPLATE),
+ }''' % (self._TITLE_KEY, self._DESCRIPTION_KEY, playlist_type, list_filter, self._NODE_TEMPLATE), # noqa: UP031
playlist_id, username, slug if is_playlist_type else None)
items = playlist.get(playlist_type) or {}
@@ -274,15 +271,15 @@ class MixcloudPlaylistBaseIE(MixcloudBaseIE):
cloudcast_url = cloudcast.get('url')
if not cloudcast_url:
continue
- item_slug = try_get(cloudcast, lambda x: x['slug'], compat_str)
- owner_username = try_get(cloudcast, lambda x: x['owner']['username'], compat_str)
+ item_slug = try_get(cloudcast, lambda x: x['slug'], str)
+ owner_username = try_get(cloudcast, lambda x: x['owner']['username'], str)
video_id = f'{owner_username}_{item_slug}' if item_slug and owner_username else None
entries.append(self.url_result(
cloudcast_url, MixcloudIE.ie_key(), video_id))
page_info = items['pageInfo']
has_next_page = page_info['hasNextPage']
- list_filter = ', after: "%s"' % page_info['endCursor']
+ list_filter = ', after: "{}"'.format(page_info['endCursor'])
return self.playlist_result(
entries, playlist_id,
@@ -351,7 +348,7 @@ class MixcloudUserIE(MixcloudPlaylistBaseIE):
owner { username }'''
def _get_playlist_title(self, title, slug):
- return '%s (%s)' % (title, slug)
+ return f'{title} ({slug})'
class MixcloudPlaylistIE(MixcloudPlaylistBaseIE):
diff --git a/yt_dlp/extractor/mlb.py b/yt_dlp/extractor/mlb.py
index d715b9789..8a693dc0b 100644
--- a/yt_dlp/extractor/mlb.py
+++ b/yt_dlp/extractor/mlb.py
@@ -203,7 +203,7 @@ class MLBIE(MLBBaseIE):
def _download_video_data(self, display_id):
return self._download_json(
- 'http://content.mlb.com/mlb/item/id/v1/%s/details/web-v1.json' % display_id,
+ f'http://content.mlb.com/mlb/item/id/v1/{display_id}/details/web-v1.json',
display_id)
@@ -227,7 +227,7 @@ class MLBVideoIE(MLBBaseIE):
@classmethod
def suitable(cls, url):
- return False if MLBIE.suitable(url) else super(MLBVideoIE, cls).suitable(url)
+ return False if MLBIE.suitable(url) else super().suitable(url)
@staticmethod
def _get_feed(video):
@@ -268,7 +268,7 @@ class MLBVideoIE(MLBBaseIE):
timestamp
title
}
-}''' % display_id,
+}''' % display_id, # noqa: UP031
})['data']['mediaPlayback'][0]
@@ -300,14 +300,14 @@ class MLBTVIE(InfoExtractor):
'https://ids.mlb.com/oauth2/aus1m088yK07noBfh356/v1/token', None,
headers={
'User-Agent': 'okhttp/3.12.1',
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
}, data=data.encode())['access_token']
entitlement = self._download_webpage(
- f'https://media-entitlement.mlb.com/api/v3/jwt?os=Android&appname=AtBat&did={str(uuid.uuid4())}', None,
+ f'https://media-entitlement.mlb.com/api/v3/jwt?os=Android&appname=AtBat&did={uuid.uuid4()}', None,
headers={
'User-Agent': 'okhttp/3.12.1',
- 'Authorization': f'Bearer {access_token}'
+ 'Authorization': f'Bearer {access_token}',
})
data = f'grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token={entitlement}&subject_token_type=urn:ietf:params:oauth:token-type:jwt&platform=android-tv'
@@ -316,7 +316,7 @@ class MLBTVIE(InfoExtractor):
headers={
'Accept': 'application/json',
'Authorization': 'Bearer bWxidHYmYW5kcm9pZCYxLjAuMA.6LZMbH2r--rbXcgEabaDdIslpo4RyZrlVfWZhsAgXIk',
- 'Content-Type': 'application/x-www-form-urlencoded'
+ 'Content-Type': 'application/x-www-form-urlencoded',
}, data=data.encode())['access_token']
def _real_extract(self, url):
@@ -331,7 +331,7 @@ class MLBTVIE(InfoExtractor):
airing['playbackUrls'][0]['href'].format(scenario='browser~csai'), video_id,
headers={
'Authorization': self._access_token,
- 'Accept': 'application/vnd.media-service+json; version=2'
+ 'Accept': 'application/vnd.media-service+json; version=2',
})['stream']['complete']
f, s = self._extract_m3u8_formats_and_subtitles(
m3u8_url, video_id, 'mp4', m3u8_id=join_nonempty(airing.get('feedType'), airing.get('feedLanguage')))
diff --git a/yt_dlp/extractor/mlssoccer.py b/yt_dlp/extractor/mlssoccer.py
index 9383f1358..a0b141ae8 100644
--- a/yt_dlp/extractor/mlssoccer.py
+++ b/yt_dlp/extractor/mlssoccer.py
@@ -3,7 +3,7 @@ from .common import InfoExtractor
class MLSSoccerIE(InfoExtractor):
_VALID_DOMAINS = r'(?:(?:cfmontreal|intermiamicf|lagalaxy|lafc|houstondynamofc|dcunited|atlutd|mlssoccer|fcdallas|columbuscrew|coloradorapids|fccincinnati|chicagofirefc|austinfc|nashvillesc|whitecapsfc|sportingkc|soundersfc|sjearthquakes|rsl|timbers|philadelphiaunion|orlandocitysc|newyorkredbulls|nycfc)\.com|(?:torontofc)\.ca|(?:revolutionsoccer)\.net)'
- _VALID_URL = r'https?://(?:www\.)?%s/video/#?(?P<id>[^/&$#?]+)' % _VALID_DOMAINS
+ _VALID_URL = rf'https?://(?:www\.)?{_VALID_DOMAINS}/video/#?(?P<id>[^/&$#?]+)'
_TESTS = [{
'url': 'https://www.mlssoccer.com/video/the-octagon-can-alphonso-davies-lead-canada-to-first-world-cup-since-1986#the-octagon-can-alphonso-davies-lead-canada-to-first-world-cup-since-1986',
@@ -19,96 +19,97 @@ class MLSSoccerIE(InfoExtractor):
'tags': ['club/canada'],
'is_live': False,
'upload_date': '20211007',
- 'filesize_approx': 255193528.83200002
+ 'filesize_approx': 255193528.83200002,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://www.whitecapsfc.com/video/highlights-san-jose-earthquakes-vs-vancouver-whitecaps-fc-october-23-2021#highlights-san-jose-earthquakes-vs-vancouver-whitecaps-fc-october-23-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.torontofc.ca/video/highlights-toronto-fc-vs-cf-montreal-october-23-2021-x6733#highlights-toronto-fc-vs-cf-montreal-october-23-2021-x6733',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.sportingkc.com/video/post-match-press-conference-john-pulskamp-oct-27-2021#post-match-press-conference-john-pulskamp-oct-27-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.soundersfc.com/video/highlights-seattle-sounders-fc-vs-sporting-kansas-city-october-23-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.sjearthquakes.com/video/#highlights-austin-fc-vs-san-jose-earthquakes-june-19-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.rsl.com/video/2021-u-of-u-health-mic-d-up-vs-colorado-10-16-21#2021-u-of-u-health-mic-d-up-vs-colorado-10-16-21',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.timbers.com/video/highlights-d-chara-asprilla-with-goals-in-portland-timbers-2-0-win-over-san-jose#highlights-d-chara-asprilla-with-goals-in-portland-timbers-2-0-win-over-san-jose',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.philadelphiaunion.com/video/highlights-torvphi',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.orlandocitysc.com/video/highlight-columbus-crew-vs-orlando-city-sc',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.newyorkredbulls.com/video/all-access-matchday-double-derby-week#all-access-matchday-double-derby-week',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.nycfc.com/video/highlights-nycfc-1-0-chicago-fire-fc#highlights-nycfc-1-0-chicago-fire-fc',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.revolutionsoccer.net/video/two-minute-highlights-revs-1-rapids-0-october-27-2021#two-minute-highlights-revs-1-rapids-0-october-27-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.nashvillesc.com/video/goal-c-j-sapong-nashville-sc-92nd-minute',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.cfmontreal.com/video/faits-saillants-tor-v-mtl#faits-saillants-orl-v-mtl-x5645',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.intermiamicf.com/video/all-access-victory-vs-nashville-sc-by-ukg#all-access-victory-vs-nashville-sc-by-ukg',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.lagalaxy.com/video/#moment-of-the-month-presented-by-san-manuel-casino-rayan-raveloson-scores-his-se',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.lafc.com/video/breaking-down-lafc-s-final-6-matches-of-the-2021-mls-regular-season#breaking-down-lafc-s-final-6-matches-of-the-2021-mls-regular-season',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.houstondynamofc.com/video/postgame-press-conference-michael-nelson-presented-by-coushatta-casino-res-x9660#postgame-press-conference-michael-nelson-presented-by-coushatta-casino-res-x9660',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.dcunited.com/video/tony-alfaro-my-family-pushed-me-to-believe-everything-was-possible',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.fcdallas.com/video/highlights-fc-dallas-vs-minnesota-united-fc-october-02-2021#highlights-fc-dallas-vs-minnesota-united-fc-october-02-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.columbuscrew.com/video/match-rewind-columbus-crew-vs-new-york-red-bulls-october-23-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.coloradorapids.com/video/postgame-reaction-robin-fraser-october-27#postgame-reaction-robin-fraser-october-27',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.fccincinnati.com/video/#keeping-cincy-chill-presented-by-coors-lite',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.chicagofirefc.com/video/all-access-fire-score-dramatic-road-win-in-cincy#all-access-fire-score-dramatic-road-win-in-cincy',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.austinfc.com/video/highlights-colorado-rapids-vs-austin-fc-september-29-2021#highlights-colorado-rapids-vs-austin-fc-september-29-2021',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.atlutd.com/video/goal-josef-martinez-scores-in-the-73rd-minute#goal-josef-martinez-scores-in-the-73rd-minute',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
- data_json = self._parse_json(self._html_search_regex(r'data-options\=\"([^\"]+)\"', webpage, 'json'), id)['videoList'][0]
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
+ data_json = self._parse_json(
+ self._html_search_regex(r'data-options\=\"([^\"]+)\"', webpage, 'json'), video_id)['videoList'][0]
return {
- 'id': id,
+ 'id': video_id,
'_type': 'url',
- 'url': 'https://players.brightcove.net/%s/default_default/index.html?videoId=%s' % (data_json['accountId'], data_json['videoId']),
+ 'url': 'https://players.brightcove.net/{}/default_default/index.html?videoId={}'.format(data_json['accountId'], data_json['videoId']),
'ie_key': 'BrightcoveNew',
}
diff --git a/yt_dlp/extractor/mocha.py b/yt_dlp/extractor/mocha.py
index 2fbc0e911..c3f4055ba 100644
--- a/yt_dlp/extractor/mocha.py
+++ b/yt_dlp/extractor/mocha.py
@@ -20,8 +20,8 @@ class MochaVideoIE(InfoExtractor):
'timestamp': 1652254203,
'upload_date': '20220511',
'comment_count': int,
- 'categories': ['Kids']
- }
+ 'categories': ['Kids'],
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/mojvideo.py b/yt_dlp/extractor/mojvideo.py
index d47ad0742..6bc362a53 100644
--- a/yt_dlp/extractor/mojvideo.py
+++ b/yt_dlp/extractor/mojvideo.py
@@ -17,7 +17,7 @@ class MojvideoIE(InfoExtractor):
'title': 'V avtu pred mano rdečelaska - Alfi Nipič',
'thumbnail': r're:^http://.*\.jpg$',
'duration': 242,
- }
+ },
}
def _real_extract(self, url):
@@ -27,12 +27,12 @@ class MojvideoIE(InfoExtractor):
# XML is malformed
playerapi = self._download_webpage(
- 'http://www.mojvideo.com/playerapi.php?v=%s&t=1' % video_id, display_id)
+ f'http://www.mojvideo.com/playerapi.php?v={video_id}&t=1', display_id)
if '<error>true</error>' in playerapi:
error_desc = self._html_search_regex(
r'<errordesc>([^<]*)</errordesc>', playerapi, 'error description', fatal=False)
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error_desc), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error_desc}', expected=True)
title = self._html_extract_title(playerapi)
video_url = self._html_search_regex(
diff --git a/yt_dlp/extractor/monstercat.py b/yt_dlp/extractor/monstercat.py
index 411d41cb0..930c13e27 100644
--- a/yt_dlp/extractor/monstercat.py
+++ b/yt_dlp/extractor/monstercat.py
@@ -27,7 +27,7 @@ class MonstercatIE(InfoExtractor):
'release_date': '20230711',
'album': 'The Secret Language of Trees',
'album_artist': 'BT',
- }
+ },
}]
def _extract_tracks(self, table, album_meta):
@@ -41,7 +41,7 @@ class MonstercatIE(InfoExtractor):
track_number = int_or_none(try_call(lambda: get_element_by_class('py-xsmall', td)))
if not track_id or not release_id:
self.report_warning(f'Skipping track {track_number}, ID(s) not found')
- self.write_debug(f'release_id={repr(release_id)} track_id={repr(track_id)}')
+ self.write_debug(f'release_id={release_id!r} track_id={track_id!r}')
continue
yield {
**album_meta,
@@ -51,7 +51,7 @@ class MonstercatIE(InfoExtractor):
'artist': clean_html(try_call(lambda: get_element_by_class('d-block fs-xxsmall', td))),
'url': f'https://www.monstercat.com/api/release/{release_id}/track-stream/{track_id}',
'id': track_id,
- 'ext': 'mp3'
+ 'ext': 'mp3',
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/motherless.py b/yt_dlp/extractor/motherless.py
index b6c18fe5b..86551950b 100644
--- a/yt_dlp/extractor/motherless.py
+++ b/yt_dlp/extractor/motherless.py
@@ -115,10 +115,10 @@ class MotherlessIE(InfoExtractor):
if any(p in webpage for p in (
'<title>404 - MOTHERLESS.COM<',
">The page you're looking for cannot be found.<")):
- raise ExtractorError('Video %s does not exist' % video_id, expected=True)
+ raise ExtractorError(f'Video {video_id} does not exist', expected=True)
if '>The content you are trying to view is for friends only.' in webpage:
- raise ExtractorError('Video %s is for friends only' % video_id, expected=True)
+ raise ExtractorError(f'Video {video_id} is for friends only', expected=True)
title = self._html_search_regex(
(r'(?s)<div[^>]+\bclass=["\']media-meta-title[^>]+>(.+?)</div>',
@@ -127,7 +127,7 @@ class MotherlessIE(InfoExtractor):
(r'setup\(\{\s*["\']file["\']\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1',
r'fileurl\s*=\s*(["\'])(?P<url>(?:(?!\1).)+)\1'),
webpage, 'video URL', default=None, group='url')
- or 'http://cdn4.videos.motherlessmedia.com/videos/%s.mp4?fs=opencloud' % video_id)
+ or f'http://cdn4.videos.motherlessmedia.com/videos/{video_id}.mp4?fs=opencloud')
age_limit = self._rta_search(webpage)
view_count = str_to_int(self._html_search_regex(
(r'>([\d,.]+)\s+Views<', r'<strong>Views</strong>\s+([^<]+)<'),
diff --git a/yt_dlp/extractor/motorsport.py b/yt_dlp/extractor/motorsport.py
index 167d85fa9..017836710 100644
--- a/yt_dlp/extractor/motorsport.py
+++ b/yt_dlp/extractor/motorsport.py
@@ -1,7 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_urlparse,
-)
class MotorsportIE(InfoExtractor):
@@ -19,7 +18,7 @@ class MotorsportIE(InfoExtractor):
'uploader': 'mcomstaff',
'uploader_id': 'UC334JIYKkVnyFoNCclfZtHQ',
'upload_date': '20140903',
- 'thumbnail': r're:^https?://.+\.jpg$'
+ 'thumbnail': r're:^https?://.+\.jpg$',
},
'add_ie': ['Youtube'],
'params': {
@@ -40,7 +39,7 @@ class MotorsportIE(InfoExtractor):
return self.url_result(iframe_path)
iframe = self._download_webpage(
- compat_urlparse.urljoin(url, iframe_path), display_id,
+ urllib.parse.urljoin(url, iframe_path), display_id,
'Downloading iframe')
youtube_id = self._search_regex(
r'www.youtube.com/embed/(.{11})', iframe, 'youtube id')
@@ -48,5 +47,5 @@ class MotorsportIE(InfoExtractor):
return {
'_type': 'url_transparent',
'display_id': display_id,
- 'url': 'https://youtube.com/watch?v=%s' % youtube_id,
+ 'url': f'https://youtube.com/watch?v={youtube_id}',
}
diff --git a/yt_dlp/extractor/moview.py b/yt_dlp/extractor/moview.py
index 678b2eb06..560154e1a 100644
--- a/yt_dlp/extractor/moview.py
+++ b/yt_dlp/extractor/moview.py
@@ -16,7 +16,7 @@ class MoviewPlayIE(JixieBaseIE):
'title': 'Candy Monster',
'description': 'Mengapa Candy Monster ingin mengambil permen Chloe?',
'thumbnail': 'https://video.jixie.media/1034/146182/146182_1280x720.jpg',
- }
+ },
}, {
# non-drm hls
'url': 'https://www.moview.id/play/75/Paris-Van-Java-Episode-16',
@@ -29,8 +29,8 @@ class MoviewPlayIE(JixieBaseIE):
'thumbnail': 'https://video.jixie.media/1003/28210/28210_1280x720.jpg',
'description': 'md5:2a5e18d98eef9b39d7895029cac96c63',
'title': 'Paris Van Java Episode 16',
- }
- }
+ },
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/moviezine.py b/yt_dlp/extractor/moviezine.py
index cffcdcfb5..331a56234 100644
--- a/yt_dlp/extractor/moviezine.py
+++ b/yt_dlp/extractor/moviezine.py
@@ -20,7 +20,7 @@ class MoviezineIE(InfoExtractor):
video_id = mobj.group('id')
webpage = self._download_webpage(url, video_id)
- jsplayer = self._download_webpage('http://www.moviezine.se/api/player.js?video=%s' % video_id, video_id, 'Downloading js api player')
+ jsplayer = self._download_webpage(f'http://www.moviezine.se/api/player.js?video={video_id}', video_id, 'Downloading js api player')
formats = [{
'format_id': 'sd',
diff --git a/yt_dlp/extractor/movingimage.py b/yt_dlp/extractor/movingimage.py
index 6e0ea2652..7b1c7979c 100644
--- a/yt_dlp/extractor/movingimage.py
+++ b/yt_dlp/extractor/movingimage.py
@@ -31,7 +31,7 @@ class MovingImageIE(InfoExtractor):
def search_field(field_name, fatal=False):
return self._search_regex(
- r'<span\s+class="field_title">%s:</span>\s*<span\s+class="field_content">([^<]+)</span>' % field_name,
+ rf'<span\s+class="field_title">{field_name}:</span>\s*<span\s+class="field_content">([^<]+)</span>',
webpage, 'title', fatal=fatal)
title = unescapeHTML(search_field('Title', fatal=True)).strip('()[]')
diff --git a/yt_dlp/extractor/msn.py b/yt_dlp/extractor/msn.py
index 79728e106..dd864952c 100644
--- a/yt_dlp/extractor/msn.py
+++ b/yt_dlp/extractor/msn.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
determine_ext,
@@ -139,7 +138,7 @@ class MSNIE(InfoExtractor):
format_code = file_.get('formatCode')
if not format_url or not format_code:
continue
- if compat_str(format_code) == '3100':
+ if str(format_code) == '3100':
subtitles.setdefault(file_.get('culture', 'en'), []).append({
'ext': determine_ext(format_url, 'ttml'),
'url': format_url,
@@ -163,6 +162,6 @@ class MSNIE(InfoExtractor):
error = unescapeHTML(self._search_regex(
r'data-error=(["\'])(?P<error>.+?)\1',
webpage, 'error', group='error'))
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error}', expected=True)
return self.playlist_result(entries, page_id)
diff --git a/yt_dlp/extractor/mtv.py b/yt_dlp/extractor/mtv.py
index 404e431bc..34e015dfc 100644
--- a/yt_dlp/extractor/mtv.py
+++ b/yt_dlp/extractor/mtv.py
@@ -2,7 +2,6 @@ import re
import xml.etree.ElementTree
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking import HEADRequest, Request
from ..utils import (
ExtractorError,
@@ -23,7 +22,7 @@ from ..utils import (
def _media_xml_tag(tag):
- return '{http://search.yahoo.com/mrss/}%s' % tag
+ return f'{{http://search.yahoo.com/mrss/}}{tag}'
class MTVServicesInfoExtractor(InfoExtractor):
@@ -43,7 +42,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
return self._FEED_URL
def _get_thumbnail_url(self, uri, itemdoc):
- search_path = '%s/%s' % (_media_xml_tag('group'), _media_xml_tag('thumbnail'))
+ search_path = '{}/{}'.format(_media_xml_tag('group'), _media_xml_tag('thumbnail'))
thumb_node = itemdoc.find(search_path)
if thumb_node is None:
return None
@@ -61,7 +60,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
response = self._request_webpage(req, mtvn_id, 'Resolving url')
url = response.url
# Transform the url to get the best quality:
- url = re.sub(r'.+pxE=mp4', 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=0+_pxK=18639+_pxE=mp4', url, 1)
+ url = re.sub(r'.+pxE=mp4', 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=0+_pxK=18639+_pxE=mp4', url, count=1)
return [{'url': url, 'ext': 'mp4'}]
def _extract_video_formats(self, mdoc, mtvn_id, video_id):
@@ -87,7 +86,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
rtmp_video_url = rendition.find('./src').text
if 'error_not_available.swf' in rtmp_video_url:
raise ExtractorError(
- '%s said: video is not available' % self.IE_NAME,
+ f'{self.IE_NAME} said: video is not available',
expected=True)
if rtmp_video_url.endswith('siteunavail.png'):
continue
@@ -118,8 +117,8 @@ class MTVServicesInfoExtractor(InfoExtractor):
if ext == 'cea-608':
ext = 'scc'
subtitles.setdefault(lang, []).append({
- 'url': compat_str(sub_src),
- 'ext': ext
+ 'url': str(sub_src),
+ 'ext': ext,
})
return subtitles
@@ -127,7 +126,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
uri = itemdoc.find('guid').text
video_id = self._id_from_uri(uri)
self.report_extraction(video_id)
- content_el = itemdoc.find('%s/%s' % (_media_xml_tag('group'), _media_xml_tag('content')))
+ content_el = itemdoc.find('{}/{}'.format(_media_xml_tag('group'), _media_xml_tag('content')))
mediagen_url = self._remove_template_parameter(content_el.attrib['url'])
mediagen_url = mediagen_url.replace('device={device}', '')
if 'acceptMethods' not in mediagen_url:
@@ -143,9 +142,9 @@ class MTVServicesInfoExtractor(InfoExtractor):
item = mediagen_doc.find('./video/item')
if item is not None and item.get('type') == 'text':
- message = '%s returned error: ' % self.IE_NAME
+ message = f'{self.IE_NAME} returned error: '
if item.get('code') is not None:
- message += '%s - ' % item.get('code')
+ message += '{} - '.format(item.get('code'))
message += item.text
raise ExtractorError(message, expected=True)
@@ -184,7 +183,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
episode = episode.text if episode is not None else None
if season and episode:
# episode number includes season, so remove it
- episode = re.sub(r'^%s' % season, '', episode)
+ episode = re.sub(rf'^{season}', '', episode)
# This a short id that's used in the webpage urls
mtvn_id = None
@@ -255,7 +254,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
feed_url = try_get(
triforce_feed, lambda x: x['manifest']['zones'][data_zone]['feed'],
- compat_str)
+ str)
if not feed_url:
return
@@ -263,7 +262,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
if not feed:
return
- return try_get(feed, lambda x: x['result']['data']['id'], compat_str)
+ return try_get(feed, lambda x: x['result']['data']['id'], str)
@staticmethod
def _extract_child_with_type(parent, t):
@@ -320,8 +319,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
title = url_basename(url)
webpage = self._download_webpage(url, title)
mgid = self._extract_mgid(webpage)
- videos_info = self._get_videos_info(mgid, url=url)
- return videos_info
+ return self._get_videos_info(mgid, url=url)
class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
@@ -346,7 +344,7 @@ class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
def _get_feed_url(self, uri, url=None):
video_id = self._id_from_uri(uri)
config = self._download_json(
- 'http://media.mtvnservices.com/pmt/e1/access/index.html?uri=%s&configtype=edge' % uri, video_id)
+ f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge', video_id)
return self._remove_template_parameter(config['feedWithQueryParams'])
def _real_extract(self, url):
@@ -443,8 +441,8 @@ class MTVVideoIE(MTVServicesInfoExtractor):
r'(?s)isVevoVideo = true;.*?vevoVideoId = "(.*?)";', webpage)
if m_vevo:
vevo_id = m_vevo.group(1)
- self.to_screen('Vevo video detected: %s' % vevo_id)
- return self.url_result('vevo:%s' % vevo_id, ie='Vevo')
+ self.to_screen(f'Vevo video detected: {vevo_id}')
+ return self.url_result(f'vevo:{vevo_id}', ie='Vevo')
uri = self._html_search_regex(r'/uri/(.*?)\?', webpage, 'uri')
return self._get_videos_info(uri)
@@ -577,9 +575,9 @@ class MTVItaliaProgrammaIE(MTVItaliaIE): # XXX: Do not subclass from concrete I
def _get_entries(self, title, url):
while True:
pg = self._search_regex(r'/(\d+)$', url, 'entries', '1')
- entries = self._download_json(url, title, 'page %s' % pg)
+ entries = self._download_json(url, title, f'page {pg}')
url = try_get(
- entries, lambda x: x['result']['nextPageURL'], compat_str)
+ entries, lambda x: x['result']['nextPageURL'], str)
entries = try_get(
entries, (
lambda x: x['result']['data']['items'],
@@ -598,15 +596,15 @@ class MTVItaliaProgrammaIE(MTVItaliaIE): # XXX: Do not subclass from concrete I
info = self._download_json(info_url, video_id).get('manifest')
redirect = try_get(
- info, lambda x: x['newLocation']['url'], compat_str)
+ info, lambda x: x['newLocation']['url'], str)
if redirect:
return self.url_result(redirect)
title = info.get('title')
video_id = try_get(
- info, lambda x: x['reporting']['itemId'], compat_str)
+ info, lambda x: x['reporting']['itemId'], str)
parent_id = try_get(
- info, lambda x: x['reporting']['parentId'], compat_str)
+ info, lambda x: x['reporting']['parentId'], str)
playlist_url = current_url = None
for z in (info.get('zones') or {}).values():
@@ -630,15 +628,15 @@ class MTVItaliaProgrammaIE(MTVItaliaIE): # XXX: Do not subclass from concrete I
info, (
lambda x: x['title'],
lambda x: x['headline']),
- compat_str)
- description = try_get(info, lambda x: x['content'], compat_str)
+ str)
+ description = try_get(info, lambda x: x['content'], str)
if current_url:
season = try_get(
self._download_json(playlist_url, video_id, 'Seasons info'),
lambda x: x['result']['data'], dict)
current = try_get(
- season, lambda x: x['currentSeason'], compat_str)
+ season, lambda x: x['currentSeason'], str)
seasons = try_get(
season, lambda x: x['seasons'], list) or []
diff --git a/yt_dlp/extractor/muenchentv.py b/yt_dlp/extractor/muenchentv.py
index 934cd4fbc..5d2dd392b 100644
--- a/yt_dlp/extractor/muenchentv.py
+++ b/yt_dlp/extractor/muenchentv.py
@@ -20,11 +20,11 @@ class MuenchenTVIE(InfoExtractor):
'ext': 'mp4',
'title': 're:^münchen.tv-Livestream [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
'is_live': True,
- 'thumbnail': r're:^https?://.*\.jpg$'
+ 'thumbnail': r're:^https?://.*\.jpg$',
},
'params': {
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
@@ -47,12 +47,12 @@ class MuenchenTVIE(InfoExtractor):
ext = determine_ext(s['file'], None)
label_str = s.get('label')
if label_str is None:
- label_str = '_%d' % format_num
+ label_str = f'_{format_num}'
if ext is None:
format_id = label_str
else:
- format_id = '%s-%s' % (ext, label_str)
+ format_id = f'{ext}-{label_str}'
formats.append({
'url': s['file'],
diff --git a/yt_dlp/extractor/murrtube.py b/yt_dlp/extractor/murrtube.py
index 74365c0c0..3b39a1b9a 100644
--- a/yt_dlp/extractor/murrtube.py
+++ b/yt_dlp/extractor/murrtube.py
@@ -36,7 +36,7 @@ class MurrtubeIE(InfoExtractor):
'view_count': int,
'like_count': int,
'tags': ['hump', 'breed', 'Fursuit', 'murrsuit', 'bareback'],
- }
+ },
}
def _download_gql(self, video_id, op, note=None, fatal=True):
@@ -129,14 +129,14 @@ query Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!)
__typename
}
}'''},
- 'Downloading page {0}'.format(page + 1))
+ f'Downloading page {page + 1}')
if data is None:
raise ExtractorError(f'Failed to retrieve video list for page {page + 1}')
media = data['media']
for entry in media:
- yield self.url_result('murrtube:{0}'.format(entry['id']), MurrtubeIE.ie_key())
+ yield self.url_result('murrtube:{}'.format(entry['id']), MurrtubeIE.ie_key())
def _real_extract(self, url):
username = self._match_id(url)
diff --git a/yt_dlp/extractor/musescore.py b/yt_dlp/extractor/musescore.py
index 289ae5733..0ef2fa0c8 100644
--- a/yt_dlp/extractor/musescore.py
+++ b/yt_dlp/extractor/musescore.py
@@ -13,7 +13,7 @@ class MuseScoreIE(InfoExtractor):
'thumbnail': r're:https?://(?:www\.)?musescore\.com/.*\.png[^$]+',
'uploader': 'PapyPiano',
'creator': 'Wolfgang Amadeus Mozart',
- }
+ },
}, {
'url': 'https://musescore.com/user/36164500/scores/6837638',
'info_dict': {
@@ -24,7 +24,7 @@ class MuseScoreIE(InfoExtractor):
'thumbnail': r're:https?://(?:www\.)?musescore\.com/.*\.png[^$]+',
'uploader': 'roxbelviolin',
'creator': 'Guns N´Roses Arr. Roxbel Violin',
- }
+ },
}, {
'url': 'https://musescore.com/classicman/fur-elise',
'info_dict': {
@@ -35,7 +35,7 @@ class MuseScoreIE(InfoExtractor):
'thumbnail': r're:https?://(?:www\.)?musescore\.com/.*\.png[^$]+',
'uploader': 'ClassicMan',
'creator': 'Ludwig van Beethoven (1770–1827)',
- }
+ },
}, {
'url': 'https://musescore.com/minh_cuteee/scores/6555384',
'only_matching': True,
@@ -44,8 +44,8 @@ class MuseScoreIE(InfoExtractor):
def _real_extract(self, url):
webpage = self._download_webpage(url, None)
url = self._og_search_url(webpage) or url
- id = self._match_id(url)
- mp3_url = self._download_json(f'https://musescore.com/api/jmuse?id={id}&index=0&type=mp3&v2=1', id,
+ video_id = self._match_id(url)
+ mp3_url = self._download_json(f'https://musescore.com/api/jmuse?id={video_id}&index=0&type=mp3&v2=1', video_id,
headers={'authorization': '63794e5461e4cfa046edfbdddfccc1ac16daffd2'})['info']['url']
formats = [{
'url': mp3_url,
@@ -54,7 +54,7 @@ class MuseScoreIE(InfoExtractor):
}]
return {
- 'id': id,
+ 'id': video_id,
'formats': formats,
'title': self._og_search_title(webpage),
'description': self._og_search_description(webpage),
diff --git a/yt_dlp/extractor/musicdex.py b/yt_dlp/extractor/musicdex.py
index a86351458..5ca390ef9 100644
--- a/yt_dlp/extractor/musicdex.py
+++ b/yt_dlp/extractor/musicdex.py
@@ -8,9 +8,9 @@ from ..utils import (
class MusicdexBaseIE(InfoExtractor):
- def _return_info(self, track_json, album_json, id):
+ def _return_info(self, track_json, album_json, video_id):
return {
- 'id': str(id),
+ 'id': str(video_id),
'title': track_json.get('name'),
'track': track_json.get('name'),
'description': track_json.get('description'),
@@ -50,15 +50,16 @@ class MusicdexSongIE(MusicdexBaseIE):
'album_artists': ['fripSide'],
'thumbnail': 'https://www.musicdex.org/storage/album/9iDIam1DHTVqUG4UclFIEq1WAFGXfPW4y0TtZa91.png',
'album': 'To Aru Kagaku no Railgun T OP2 Single - dual existence',
- 'release_year': 2020
+ 'release_year': 2020,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://www.musicdex.org/secure/tracks/{id}?defaultRelations=true', id)['track']
- return self._return_info(data_json, data_json.get('album') or {}, id)
+ video_id = self._match_id(url)
+ data_json = self._download_json(
+ f'https://www.musicdex.org/secure/tracks/{video_id}?defaultRelations=true', video_id)['track']
+ return self._return_info(data_json, data_json.get('album') or {}, video_id)
class MusicdexAlbumIE(MusicdexBaseIE):
@@ -79,13 +80,15 @@ class MusicdexAlbumIE(MusicdexBaseIE):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://www.musicdex.org/secure/albums/{id}?defaultRelations=true', id)['album']
- entries = [self._return_info(track, data_json, track['id']) for track in data_json.get('tracks') or [] if track.get('id')]
+ playlist_id = self._match_id(url)
+ data_json = self._download_json(
+ f'https://www.musicdex.org/secure/albums/{playlist_id}?defaultRelations=true', playlist_id)['album']
+ entries = [self._return_info(track, data_json, track['id'])
+ for track in data_json.get('tracks') or [] if track.get('id')]
return {
'_type': 'playlist',
- 'id': id,
+ 'id': playlist_id,
'title': data_json.get('name'),
'description': data_json.get('description'),
'genres': [genre.get('name') for genre in data_json.get('genres') or []],
@@ -98,12 +101,11 @@ class MusicdexAlbumIE(MusicdexBaseIE):
class MusicdexPageIE(MusicdexBaseIE): # XXX: Conventionally, base classes should end with BaseIE/InfoExtractor
- def _entries(self, id):
- next_page_url = self._API_URL % id
+ def _entries(self, playlist_id):
+ next_page_url = self._API_URL % playlist_id
while next_page_url:
- data_json = self._download_json(next_page_url, id)['pagination']
- for data in data_json.get('data') or []:
- yield data
+ data_json = self._download_json(next_page_url, playlist_id)['pagination']
+ yield from data_json.get('data') or []
next_page_url = data_json.get('next_page_url')
@@ -123,15 +125,15 @@ class MusicdexArtistIE(MusicdexPageIE):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://www.musicdex.org/secure/artists/{id}', id)['artist']
+ playlist_id = self._match_id(url)
+ data_json = self._download_json(f'https://www.musicdex.org/secure/artists/{playlist_id}', playlist_id)['artist']
entries = []
- for album in self._entries(id):
+ for album in self._entries(playlist_id):
entries.extend(self._return_info(track, album, track['id']) for track in album.get('tracks') or [] if track.get('id'))
return {
'_type': 'playlist',
- 'id': id,
+ 'id': playlist_id,
'title': data_json.get('name'),
'view_count': data_json.get('plays'),
'thumbnail': format_field(data_json, 'image_small', 'https://www.musicdex.org/%s'),
@@ -156,14 +158,14 @@ class MusicdexPlaylistIE(MusicdexPageIE):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://www.musicdex.org/secure/playlists/{id}', id)['playlist']
+ playlist_id = self._match_id(url)
+ data_json = self._download_json(f'https://www.musicdex.org/secure/playlists/{playlist_id}', playlist_id)['playlist']
entries = [self._return_info(track, track.get('album') or {}, track['id'])
- for track in self._entries(id) or [] if track.get('id')]
+ for track in self._entries(playlist_id) or [] if track.get('id')]
return {
'_type': 'playlist',
- 'id': id,
+ 'id': playlist_id,
'title': data_json.get('name'),
'description': data_json.get('description'),
'view_count': data_json.get('plays'),
diff --git a/yt_dlp/extractor/mx3.py b/yt_dlp/extractor/mx3.py
index cb9f50e0c..5c42f4d15 100644
--- a/yt_dlp/extractor/mx3.py
+++ b/yt_dlp/extractor/mx3.py
@@ -94,7 +94,7 @@ class Mx3IE(Mx3BaseIE):
'title': "S'envoler",
'release_year': 2021,
'tags': [],
- }
+ },
}, {
'url': 'https://mx3.ch/t/1LIY',
'md5': '48293cb908342547827f963a5a2e9118',
@@ -110,7 +110,7 @@ class Mx3IE(Mx3BaseIE):
'release_year': 2023,
'tags': ['the broots', 'cassata records', 'larytta'],
'description': '"Begging for Help" Larytta Remix Official Video\nRealized By Kali Donkilie in 2023',
- }
+ },
}, {
'url': 'https://mx3.ch/t/1C6E',
'md5': '1afcd578493ddb8e5008e94bb6d97e25',
@@ -125,7 +125,7 @@ class Mx3IE(Mx3BaseIE):
'title': 'Wide Awake',
'release_year': 2021,
'tags': ['alien bubblegum', 'bubblegum', 'alien', 'pop punk', 'poppunk'],
- }
+ },
}]
@@ -146,7 +146,7 @@ class Mx3NeoIE(Mx3BaseIE):
'thumbnail': 'https://neo.mx3.ch/pictures/neo/file/0000/0241/square_xlarge/kammerorchester-basel-group-photo-2_c_-lukasz-rajchert.jpg?1560341252',
'release_year': 2023,
'tags': [],
- }
+ },
}]
@@ -167,5 +167,5 @@ class Mx3VolksmusikIE(Mx3BaseIE):
'thumbnail': 'https://volksmusik.mx3.ch/pictures/vxm/file/0000/3815/square_xlarge/grischart1.jpg?1450530120',
'release_year': 2012,
'tags': [],
- }
+ },
}]
diff --git a/yt_dlp/extractor/mxplayer.py b/yt_dlp/extractor/mxplayer.py
index 1fdb08edf..8d3e35a7c 100644
--- a/yt_dlp/extractor/mxplayer.py
+++ b/yt_dlp/extractor/mxplayer.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
traverse_obj,
@@ -23,7 +22,7 @@ class MxplayerIE(InfoExtractor):
'duration': 2451,
'season': 'Season 1',
'series': 'My Girlfriend Is An Alien (Hindi Dubbed)',
- 'episode': 'Episode 1'
+ 'episode': 'Episode 1',
},
'params': {
'format': 'bv',
@@ -56,13 +55,13 @@ class MxplayerIE(InfoExtractor):
'duration': 2332,
'season': 'Season 1',
'series': 'Shaitaan',
- 'episode': 'Episode 1'
+ 'episode': 'Episode 1',
},
'params': {
'format': 'best',
'skip_download': True,
},
- 'skip': 'No longer available.'
+ 'skip': 'No longer available.',
}, {
'url': 'https://www.mxplayer.in/show/watch-aashram/chapter-1/duh-swapna-online-d445579792b0135598ba1bc9088a84cb',
'info_dict': {
@@ -76,7 +75,7 @@ class MxplayerIE(InfoExtractor):
'duration': 2568,
'season': 'Season 1',
'series': 'Aashram',
- 'episode': 'Episode 3'
+ 'episode': 'Episode 3',
},
'params': {
'format': 'bv',
@@ -95,7 +94,7 @@ class MxplayerIE(InfoExtractor):
'duration': 1305,
'season': 'Season 1',
'series': 'Dangerous',
- 'episode': 'Episode 1'
+ 'episode': 'Episode 1',
},
'params': {
'format': 'bv',
@@ -114,7 +113,7 @@ class MxplayerIE(InfoExtractor):
'format': 'best',
'skip_download': True,
},
- 'skip': 'No longer available. Cannot be played on browser'
+ 'skip': 'No longer available. Cannot be played on browser',
}, {
'url': 'https://www.mxplayer.in/movie/watch-kitne-door-kitne-paas-movie-online-a9e9c76c566205955f70d8b2cb88a6a2',
'info_dict': {
@@ -206,11 +205,11 @@ class MxplayerShowIE(InfoExtractor):
'info_dict': {
'id': 'a8f44e3cc0814b5601d17772cedf5417',
'title': 'Watch Chakravartin Ashoka Samrat Series Online',
- }
+ },
}]
- _API_SHOW_URL = "https://api.mxplay.com/v1/web/detail/tab/tvshowseasons?type=tv_show&id={}&device-density=2&platform=com.mxplay.desktop&content-languages=hi,en"
- _API_EPISODES_URL = "https://api.mxplay.com/v1/web/detail/tab/tvshowepisodes?type=season&id={}&device-density=1&platform=com.mxplay.desktop&content-languages=hi,en&{}"
+ _API_SHOW_URL = 'https://api.mxplay.com/v1/web/detail/tab/tvshowseasons?type=tv_show&id={}&device-density=2&platform=com.mxplay.desktop&content-languages=hi,en'
+ _API_EPISODES_URL = 'https://api.mxplay.com/v1/web/detail/tab/tvshowepisodes?type=season&id={}&device-density=1&platform=com.mxplay.desktop&content-languages=hi,en&{}'
def _entries(self, show_id):
show_json = self._download_json(
@@ -218,7 +217,7 @@ class MxplayerShowIE(InfoExtractor):
video_id=show_id, headers={'Referer': 'https://mxplayer.in'})
page_num = 0
for season in show_json.get('items') or []:
- season_id = try_get(season, lambda x: x['id'], compat_str)
+ season_id = try_get(season, lambda x: x['id'], str)
next_url = ''
while next_url is not None:
page_num += 1
@@ -226,11 +225,11 @@ class MxplayerShowIE(InfoExtractor):
self._API_EPISODES_URL.format(season_id, next_url),
video_id=season_id,
headers={'Referer': 'https://mxplayer.in'},
- note='Downloading JSON metadata page %d' % page_num)
+ note=f'Downloading JSON metadata page {page_num}')
for episode in season_json.get('items') or []:
video_url = episode['webUrl']
yield self.url_result(
- 'https://mxplayer.in%s' % video_url,
+ f'https://mxplayer.in{video_url}',
ie=MxplayerIE.ie_key(), video_id=video_url.split('-')[-1])
next_url = season_json.get('next')
diff --git a/yt_dlp/extractor/myspace.py b/yt_dlp/extractor/myspace.py
index 345109837..fa2ef14e1 100644
--- a/yt_dlp/extractor/myspace.py
+++ b/yt_dlp/extractor/myspace.py
@@ -95,17 +95,17 @@ class MySpaceIE(InfoExtractor):
if is_song:
# songs don't store any useful info in the 'context' variable
song_data = self._search_regex(
- r'''<button.*data-song-id=(["\'])%s\1.*''' % video_id,
+ rf'''<button.*data-song-id=(["\']){video_id}\1.*''',
webpage, 'song_data', default=None, group=0)
if song_data is None:
# some songs in an album are not playable
self.report_warning(
- '%s: No downloadable song on this page' % video_id)
+ f'{video_id}: No downloadable song on this page')
return
def search_data(name):
return self._search_regex(
- r'''data-%s=([\'"])(?P<data>.*?)\1''' % name,
+ rf'''data-{name}=([\'"])(?P<data>.*?)\1''',
song_data, name, default='', group='data')
formats = formats_from_stream_urls(
search_data('stream-url'), search_data('hls-stream-url'),
@@ -114,10 +114,10 @@ class MySpaceIE(InfoExtractor):
vevo_id = search_data('vevo-id')
youtube_id = search_data('youtube-id')
if vevo_id:
- self.to_screen('Vevo video detected: %s' % vevo_id)
- return self.url_result('vevo:%s' % vevo_id, ie='Vevo')
+ self.to_screen(f'Vevo video detected: {vevo_id}')
+ return self.url_result(f'vevo:{vevo_id}', ie='Vevo')
elif youtube_id:
- self.to_screen('Youtube video detected: %s' % youtube_id)
+ self.to_screen(f'Youtube video detected: {youtube_id}')
return self.url_result(youtube_id, ie='Youtube')
else:
raise ExtractorError(
@@ -181,7 +181,7 @@ class MySpaceAlbumIE(InfoExtractor):
tracks_paths = re.findall(r'"music:song" content="(.*?)"', webpage)
if not tracks_paths:
raise ExtractorError(
- '%s: No songs found, try using proxy' % display_id,
+ f'{display_id}: No songs found, try using proxy',
expected=True)
entries = [
self.url_result(t_path, ie=MySpaceIE.ie_key())
diff --git a/yt_dlp/extractor/myspass.py b/yt_dlp/extractor/myspass.py
index 28ac982d6..3e8d506c4 100644
--- a/yt_dlp/extractor/myspass.py
+++ b/yt_dlp/extractor/myspass.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
parse_duration,
@@ -75,7 +74,7 @@ class MySpassIE(InfoExtractor):
for group in self._search_regex(r'/myspass2009/\d+/(\d+)/(\d+)/(\d+)/', video_url, 'myspass', group=(1, 2, 3), default=[]):
group_int = int(group)
if group_int > video_id_int:
- video_url = video_url.replace(group, compat_str(group_int // video_id_int))
+ video_url = video_url.replace(group, str(group_int // video_id_int))
return {
'id': video_id,
diff --git a/yt_dlp/extractor/mzaalo.py b/yt_dlp/extractor/mzaalo.py
index 1996368cc..52e5ea89e 100644
--- a/yt_dlp/extractor/mzaalo.py
+++ b/yt_dlp/extractor/mzaalo.py
@@ -24,7 +24,7 @@ class MzaaloIE(InfoExtractor):
'categories': ['Drama'],
'age_limit': 13,
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
# Shows
'url': 'https://www.mzaalo.com/play/original/93d42b2b-f373-4c2d-bca4-997412cb069d/Modi-Season-2-CM-TO-PM/Episode-1:Decision,-Not-Promises',
@@ -40,7 +40,7 @@ class MzaaloIE(InfoExtractor):
'categories': ['Drama'],
'age_limit': 13,
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
# Streams/Clips
'url': 'https://www.mzaalo.com/play/clip/83cdbcb5-400a-42f1-a1d2-459053cfbda5/Manto-Ki-Kahaaniya',
@@ -54,7 +54,7 @@ class MzaaloIE(InfoExtractor):
'duration': 1937.0,
'language': 'hin',
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://mzaalo.com/watch/MOVIE/389c892d-0b65-4019-bf73-d4edcb1c014f/Chalo-Dilli',
'only_matching': True,
diff --git a/yt_dlp/extractor/n1.py b/yt_dlp/extractor/n1.py
index 8a8a5fec7..bbb327e75 100644
--- a/yt_dlp/extractor/n1.py
+++ b/yt_dlp/extractor/n1.py
@@ -16,7 +16,7 @@ class N1InfoAssetIE(InfoExtractor):
'id': 'ljsottomazilirija3060921-n1info-si-worldwide',
'ext': 'mp4',
'title': 'ljsottomazilirija3060921-n1info-si-worldwide',
- }
+ },
}]
def _real_extract(self, url):
@@ -46,7 +46,7 @@ class N1InfoIIE(InfoExtractor):
'description': 'md5:467f330af1effedd2e290f10dc31bb8e',
'uploader': 'Sport Klub',
'uploader_id': 'sportklub',
- }
+ },
}, {
'url': 'https://rs.n1info.com/vesti/djilas-los-plan-za-metro-nece-resiti-nijedan-saobracajni-problem/',
'info_dict': {
@@ -102,7 +102,7 @@ class N1InfoIIE(InfoExtractor):
'title': 'Ćuta: Biti u Kosovskoj Mitrovici znači da te dočekaju eksplozivnim napravama',
'upload_date': '20230620',
'timestamp': 1687290536,
- 'thumbnail': 'https://cdn.brid.tv/live/partners/26827/snapshot/1332368_th_6492013a8356f_1687290170.jpg'
+ 'thumbnail': 'https://cdn.brid.tv/live/partners/26827/snapshot/1332368_th_6492013a8356f_1687290170.jpg',
},
}, {
'url': 'https://hr.n1info.com/vijesti/pravobraniteljica-o-ubojstvu-u-zagrebu-radi-se-o-doista-nezapamcenoj-situaciji/',
diff --git a/yt_dlp/extractor/nate.py b/yt_dlp/extractor/nate.py
index 5e74caa7f..bbc641f0e 100644
--- a/yt_dlp/extractor/nate.py
+++ b/yt_dlp/extractor/nate.py
@@ -29,7 +29,7 @@ class NateIE(InfoExtractor):
'uploader_id': '3606',
'tags': 'count:59',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://tv.nate.com/clip/4300566',
'info_dict': {
@@ -47,7 +47,7 @@ class NateIE(InfoExtractor):
'uploader_id': '27987',
'tags': 'count:20',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
_QUALITY = {
@@ -60,8 +60,8 @@ class NateIE(InfoExtractor):
}
def _real_extract(self, url):
- id = self._match_id(url)
- video_data = self._download_json(f'https://tv.nate.com/api/v1/clip/{id}', id)
+ video_id = self._match_id(url)
+ video_data = self._download_json(f'https://tv.nate.com/api/v1/clip/{video_id}', video_id)
formats = [{
'format_id': f_url[-2:],
'url': f_url,
@@ -69,7 +69,7 @@ class NateIE(InfoExtractor):
'quality': int_or_none(f_url[-2:]),
} for f_url in video_data.get('smcUriList') or []]
return {
- 'id': id,
+ 'id': video_id,
'title': video_data.get('clipTitle'),
'description': video_data.get('synopsis'),
'thumbnail': video_data.get('contentImg'),
@@ -102,19 +102,19 @@ class NateProgramIE(InfoExtractor):
},
}]
- def _entries(self, id):
+ def _entries(self, playlist_id):
for page_num in itertools.count(1):
- program_data = self._download_json(f'https://tv.nate.com/api/v1/program/{id}/clip/ranking?size=20&page={page_num}',
- id, note=f'Downloading page {page_num}')
+ program_data = self._download_json(
+ f'https://tv.nate.com/api/v1/program/{playlist_id}/clip/ranking?size=20&page={page_num}',
+ playlist_id, note=f'Downloading page {page_num}')
for clip in program_data.get('content') or []:
clip_id = clip.get('clipSeq')
if clip_id:
yield self.url_result(
- 'https://tv.nate.com/clip/%s' % clip_id,
- ie=NateIE.ie_key(), video_id=clip_id)
+ f'https://tv.nate.com/clip/{clip_id}', NateIE, playlist_id)
if program_data.get('last'):
break
def _real_extract(self, url):
- id = self._match_id(url)
- return self.playlist_result(self._entries(id), playlist_id=id)
+ playlist_id = self._match_id(url)
+ return self.playlist_result(self._entries(playlist_id), playlist_id=playlist_id)
diff --git a/yt_dlp/extractor/nationalgeographic.py b/yt_dlp/extractor/nationalgeographic.py
index 6f046bc29..43f84a952 100644
--- a/yt_dlp/extractor/nationalgeographic.py
+++ b/yt_dlp/extractor/nationalgeographic.py
@@ -55,7 +55,7 @@ class NationalGeographicVideoIE(InfoExtractor):
'_type': 'url_transparent',
'ie_key': 'ThePlatform',
'url': smuggle_url(
- 'http://link.theplatform.com/s/ngs/media/guid/2423130747/%s?mbr=true' % guid,
+ f'http://link.theplatform.com/s/ngs/media/guid/2423130747/{guid}?mbr=true',
{'force_smil_url': True}),
'id': guid,
}
diff --git a/yt_dlp/extractor/naver.py b/yt_dlp/extractor/naver.py
index 26400e383..a9f7f4607 100644
--- a/yt_dlp/extractor/naver.py
+++ b/yt_dlp/extractor/naver.py
@@ -36,7 +36,7 @@ class NaverBaseIE(InfoExtractor):
type_ = 'automatic_captions' if caption.get('type') == 'auto' else 'subtitles'
lang = caption.get('locale') or join_nonempty('language', 'country', from_dict=caption) or 'und'
if caption.get('type') == 'fan':
- lang += '_fan%d' % next(i for i in itertools.count(1) if f'{lang}_fan{i}' not in ret[type_])
+ lang += '_fan{}'.format(next(i for i in itertools.count(1) if f'{lang}_fan{i}' not in ret[type_]))
ret[type_].setdefault(lang, []).extend({
'url': sub_url,
'name': join_nonempty('label', 'fanName', from_dict=caption, delim=' - '),
@@ -63,7 +63,7 @@ class NaverBaseIE(InfoExtractor):
encoding_option = stream.get('encodingOption', {})
bitrate = stream.get('bitrate', {})
formats.append({
- 'format_id': '%s_%s' % (stream.get('type') or stream_type, dict_get(encoding_option, ('name', 'id'))),
+ 'format_id': '{}_{}'.format(stream.get('type') or stream_type, dict_get(encoding_option, ('name', 'id'))),
'url': stream_url,
'ext': 'mp4',
'width': int_or_none(encoding_option.get('width')),
@@ -261,7 +261,7 @@ class NaverLiveIE(NaverBaseIE):
'thumbnail': ('thumbnailImageUrl', {url_or_none}),
'start_time': (('startTime', 'startDateTime', 'startYmdt'), {parse_iso8601}),
}), get_all=False),
- 'is_live': True
+ 'is_live': True,
}
@@ -286,7 +286,7 @@ class NaverNowIE(NaverBaseIE):
},
'params': {
'noplaylist': True,
- }
+ },
}, {
'url': 'https://now.naver.com/s/now.4759?shareHightlight=26601461#highlight=',
'md5': '9f6118e398aa0f22b2152f554ea7851b',
@@ -311,7 +311,7 @@ class NaverNowIE(NaverBaseIE):
'id': '4759',
'title': '아이키의 떰즈업',
},
- 'playlist_mincount': 101
+ 'playlist_mincount': 101,
}, {
'url': 'https://now.naver.com/s/now.4759?shareReplayId=26331132#replay',
'info_dict': {
@@ -348,7 +348,7 @@ class NaverNowIE(NaverBaseIE):
show_vod_info = self._download_json(
f'{self._API_URL}/vod-shows/now.{show_id}', show_id,
query={'page': page, 'page_size': page_size},
- note=f'Downloading JSON vod list for show {show_id} - page {page}'
+ note=f'Downloading JSON vod list for show {show_id} - page {page}',
)['response']['result']
for v in show_vod_info.get('vod_list') or []:
yield self._extract_replay(show_id, v['id'])
diff --git a/yt_dlp/extractor/nba.py b/yt_dlp/extractor/nba.py
index ec4d6368e..91ae1d14c 100644
--- a/yt_dlp/extractor/nba.py
+++ b/yt_dlp/extractor/nba.py
@@ -1,11 +1,8 @@
import functools
import re
+import urllib.parse
from .turner import TurnerBaseIE
-from ..compat import (
- compat_str,
- compat_urllib_parse_unquote,
-)
from ..utils import (
OnDemandPagedList,
int_or_none,
@@ -22,7 +19,7 @@ from ..utils import (
class NBACVPBaseIE(TurnerBaseIE):
def _extract_nba_cvp_info(self, path, video_id, fatal=False):
return self._extract_cvp_info(
- 'http://secure.nba.com/%s' % path, video_id, {
+ f'http://secure.nba.com/{path}', video_id, {
'default': {
'media_src': 'http://nba.cdn.turner.com/nba/big',
},
@@ -185,7 +182,7 @@ class NBAWatchCollectionIE(NBAWatchBaseIE):
page += 1
videos = self._download_json(
'https://content-api-prod.nba.com/public/1/endeavor/video-list/collection/' + collection_id,
- collection_id, 'Downloading page %d JSON metadata' % page, query={
+ collection_id, f'Downloading page {page} JSON metadata', query={
'count': self._PAGE_SIZE,
'page': page,
})['results']['videos']
@@ -260,14 +257,14 @@ class NBABaseIE(NBACVPBaseIE):
def _call_api(self, team, content_id, query, resource):
return self._download_json(
- 'https://api.nba.net/2/%s/video,imported_video,wsc/' % team,
- content_id, 'Download %s JSON metadata' % resource,
+ f'https://api.nba.net/2/{team}/video,imported_video,wsc/',
+ content_id, f'Download {resource} JSON metadata',
query=query, headers={
'accessToken': 'internal|bb88df6b4c2244e78822812cecf1ee1b',
})['response']['result']
def _extract_video(self, video, team, extract_all=True):
- video_id = compat_str(video['nid'])
+ video_id = str(video['nid'])
team = video['brand']
info = {
@@ -330,7 +327,7 @@ class NBABaseIE(NBACVPBaseIE):
def _real_extract(self, url):
team, display_id = self._match_valid_url(url).groups()
if '/play#/' in url:
- display_id = compat_urllib_parse_unquote(display_id)
+ display_id = urllib.parse.unquote(display_id)
else:
webpage = self._download_webpage(url, display_id)
display_id = self._search_regex(
@@ -362,7 +359,7 @@ class NBAEmbedIE(NBABaseIE):
class NBAIE(NBABaseIE):
IE_NAME = 'nba'
- _VALID_URL = NBABaseIE._VALID_URL_BASE + '(?!%s)video/(?P<id>(?:[^/]+/)*[^/?#&]+)' % NBABaseIE._CHANNEL_PATH_REGEX
+ _VALID_URL = NBABaseIE._VALID_URL_BASE + f'(?!{NBABaseIE._CHANNEL_PATH_REGEX})video/(?P<id>(?:[^/]+/)*[^/?#&]+)'
_TESTS = [{
'url': 'https://www.nba.com/bulls/video/teams/bulls/2020/12/04/3478774/1607105587854-20201204schedulereleasefinaldrupal-3478774',
'info_dict': {
@@ -389,7 +386,7 @@ class NBAIE(NBABaseIE):
class NBAChannelIE(NBABaseIE):
IE_NAME = 'nba:channel'
- _VALID_URL = NBABaseIE._VALID_URL_BASE + '(?:%s)/(?P<id>[^/?#&]+)' % NBABaseIE._CHANNEL_PATH_REGEX
+ _VALID_URL = NBABaseIE._VALID_URL_BASE + f'(?:{NBABaseIE._CHANNEL_PATH_REGEX})/(?P<id>[^/?#&]+)'
_TESTS = [{
'url': 'https://www.nba.com/blazers/video/channel/summer_league',
'info_dict': {
@@ -408,7 +405,7 @@ class NBAChannelIE(NBABaseIE):
'channels': channel,
'count': self._PAGE_SIZE,
'offset': page * self._PAGE_SIZE,
- }, 'page %d' % (page + 1))
+ }, f'page {page + 1}')
for video in results:
yield self._extract_video(video, team, False)
diff --git a/yt_dlp/extractor/nbc.py b/yt_dlp/extractor/nbc.py
index e88f98abf..1da2cad3d 100644
--- a/yt_dlp/extractor/nbc.py
+++ b/yt_dlp/extractor/nbc.py
@@ -1,12 +1,12 @@
import base64
import json
import re
+import urllib.parse
import xml.etree.ElementTree
from .adobepass import AdobePassIE
from .common import InfoExtractor
from .theplatform import ThePlatformIE, default_ns
-from ..compat import compat_urllib_parse_unquote
from ..networking import HEADRequest
from ..utils import (
ExtractorError,
@@ -148,12 +148,12 @@ class NBCIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
# Percent escaped url
'url': 'https://www.nbc.com/up-all-night/video/day-after-valentine%27s-day/n2189',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
permalink, video_id = self._match_valid_url(url).groups()
- permalink = 'http' + compat_urllib_parse_unquote(permalink)
+ permalink = 'http' + urllib.parse.unquote(permalink)
video_data = self._download_json(
'https://friendship.nbc.co/v2/graphql', video_id, query={
'query': '''query bonanzaPage(
@@ -201,7 +201,7 @@ class NBCIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
'switch': 'HLSServiceSecure',
}
video_id = video_data['mpxGuid']
- tp_path = 'NnzsPC/media/guid/%s/%s' % (video_data.get('mpxAccountId') or '2410887629', video_id)
+ tp_path = 'NnzsPC/media/guid/{}/{}'.format(video_data.get('mpxAccountId') or '2410887629', video_id)
tpm = self._download_theplatform_metadata(tp_path, video_id)
title = tpm.get('title') or video_data.get('secondaryTitle')
if video_data.get('locked'):
@@ -211,7 +211,7 @@ class NBCIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
query['auth'] = self._extract_mvpd_auth(
url, video_id, 'nbcentertainment', resource)
theplatform_url = smuggle_url(update_url_query(
- 'http://link.theplatform.com/s/NnzsPC/media/guid/%s/%s' % (video_data.get('mpxAccountId') or '2410887629', video_id),
+ 'http://link.theplatform.com/s/NnzsPC/media/guid/{}/{}'.format(video_data.get('mpxAccountId') or '2410887629', video_id),
query), {'force_smil_url': True})
# Empty string or 0 can be valid values for these. So the check must be `is None`
@@ -253,7 +253,7 @@ class NBCIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
class NBCSportsVPlayerIE(InfoExtractor):
_VALID_URL_BASE = r'https?://(?:vplayer\.nbcsports\.com|(?:www\.)?nbcsports\.com/vplayer)/'
_VALID_URL = _VALID_URL_BASE + r'(?:[^/]+/)+(?P<id>[0-9a-zA-Z_]+)'
- _EMBED_REGEX = [r'(?:iframe[^>]+|var video|div[^>]+data-(?:mpx-)?)[sS]rc\s?=\s?"(?P<url>%s[^\"]+)' % _VALID_URL_BASE]
+ _EMBED_REGEX = [rf'(?:iframe[^>]+|var video|div[^>]+data-(?:mpx-)?)[sS]rc\s?=\s?"(?P<url>{_VALID_URL_BASE}[^\"]+)']
_TESTS = [{
'url': 'https://vplayer.nbcsports.com/p/BxmELC/nbcsports_embed/select/9CsDKds0kvHI',
@@ -267,8 +267,8 @@ class NBCSportsVPlayerIE(InfoExtractor):
'uploader': 'NBCU-SPORTS',
'duration': 72.818,
'chapters': [],
- 'thumbnail': r're:^https?://.*\.jpg$'
- }
+ 'thumbnail': r're:^https?://.*\.jpg$',
+ },
}, {
'url': 'https://vplayer.nbcsports.com/p/BxmELC/nbcsports_embed/select/media/PEgOtlNcC_y2',
'only_matching': True,
@@ -301,7 +301,7 @@ class NBCSportsIE(InfoExtractor):
'chapters': [],
'thumbnail': 'https://hdliveextra-a.akamaihd.net/HD/image_sports/NBCU_Sports_Group_-_nbcsports/253/303/izzodps.jpg',
'duration': 528.395,
- }
+ },
}, {
# data-mpx-src
'url': 'https://www.nbcsports.com/philadelphia/philadelphia-phillies/bruce-bochy-hector-neris-hes-idiot',
@@ -339,7 +339,7 @@ class NBCSportsStreamIE(AdobePassIE):
def _real_extract(self, url):
video_id = self._match_id(url)
live_source = self._download_json(
- 'http://stream.nbcsports.com/data/live_sources_%s.json' % video_id,
+ f'http://stream.nbcsports.com/data/live_sources_{video_id}.json',
video_id)
video_source = live_source['videoSources'][0]
title = video_source['title']
@@ -499,7 +499,7 @@ class NBCNewsIE(ThePlatformIE): # XXX: Do not subclass from concrete IE
continue
tbr = int_or_none(va.get('bitrate'), 1000)
if tbr:
- format_id += '-%d' % tbr
+ format_id += f'-{tbr}'
formats.append({
'format_id': format_id,
'url': public_url,
@@ -568,7 +568,7 @@ class NBCOlympicsIE(InfoExtractor):
except RegexNotFoundError:
theplatform_url = self._search_regex(
r"([\"'])embedUrl\1: *([\"'])(?P<embedUrl>.+)\2",
- webpage, 'embedding URL', group="embedUrl")
+ webpage, 'embedding URL', group='embedUrl')
return {
'_type': 'url_transparent',
@@ -623,7 +623,7 @@ class NBCOlympicsStreamIE(AdobePassIE):
source_url = self._download_json(
f'https://api-leap.nbcsports.com/feeds/assets/{pid}?application=NBCOlympics&platform=desktop&format=nbc-player&env=staging',
- pid, 'Downloading leap config'
+ pid, 'Downloading leap config',
)['videoSources'][0]['cdnSources']['primary'][0]['sourceUrl']
if event_config.get('cdnToken'):
diff --git a/yt_dlp/extractor/ndr.py b/yt_dlp/extractor/ndr.py
index 243221d46..5181c7f20 100644
--- a/yt_dlp/extractor/ndr.py
+++ b/yt_dlp/extractor/ndr.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_urlparse
from ..utils import (
ExtractorError,
determine_ext,
@@ -125,13 +125,13 @@ class NDRIE(NDRBaseIE):
# some more work needed if we only found sophoraID
if re.match(r'^[a-z]+\d+$', embed_url):
# get the initial part of the url path,. eg /panorama/archiv/2022/
- parsed_url = compat_urllib_parse_urlparse(url)
- path = self._search_regex(r'(.+/)%s' % display_id, parsed_url.path or '', 'embed URL', default='')
+ parsed_url = urllib.parse.urlparse(url)
+ path = self._search_regex(rf'(.+/){display_id}', parsed_url.path or '', 'embed URL', default='')
# find tell-tale image with the actual ID
- ndr_id = self._search_regex(r'%s([a-z]+\d+)(?!\.)\b' % (path, ), webpage, 'embed URL', default=None)
+ ndr_id = self._search_regex(rf'{path}([a-z]+\d+)(?!\.)\b', webpage, 'embed URL', default=None)
# or try to use special knowledge!
NDR_INFO_URL_TPL = 'https://www.ndr.de/info/%s-player.html'
- embed_url = 'ndr:%s' % (ndr_id, ) if ndr_id else NDR_INFO_URL_TPL % (embed_url, )
+ embed_url = f'ndr:{ndr_id}' if ndr_id else NDR_INFO_URL_TPL % (embed_url, )
if not embed_url:
raise ExtractorError('Unable to extract embedUrl')
@@ -141,7 +141,7 @@ class NDRIE(NDRBaseIE):
timestamp = parse_iso8601(
self._search_regex(
(r'<span[^>]+itemprop="(?:datePublished|uploadDate)"[^>]+content="(?P<cont>[^"]+)"',
- r'\bvar\s*pdt\s*=\s*(?P<q>["\'])(?P<cont>(?:(?!(?P=q)).)+)(?P=q)', ),
+ r'\bvar\s*pdt\s*=\s*(?P<q>["\'])(?P<cont>(?:(?!(?P=q)).)+)(?P=q)'),
webpage, 'upload date', group='cont', default=None))
info = self._search_json_ld(webpage, display_id, default={})
return merge_dicts({
@@ -200,7 +200,7 @@ class NJoyIE(NDRBaseIE):
# find tell-tale URL with the actual ID, or ...
video_id = self._search_regex(
(r'''\bsrc\s*=\s*["']?(?:/\w+)+/([a-z]+\d+)(?!\.)\b''',
- r'<iframe[^>]+id="pp_([\da-z]+)"', ),
+ r'<iframe[^>]+id="pp_([\da-z]+)"'),
webpage, 'NDR id', default=None)
description = (
@@ -211,7 +211,7 @@ class NJoyIE(NDRBaseIE):
return {
'_type': 'url_transparent',
'ie_key': 'NDREmbedBase',
- 'url': 'ndr:%s' % video_id,
+ 'url': f'ndr:{video_id}',
'display_id': display_id,
'description': description,
'title': display_id.replace('-', ' ').strip(),
@@ -234,7 +234,7 @@ class NDREmbedBaseIE(InfoExtractor): # XXX: Conventionally, Concrete class name
video_id = mobj.group('id') or mobj.group('id_s')
ppjson = self._download_json(
- 'http://www.ndr.de/%s-ppjson.json' % video_id, video_id)
+ f'http://www.ndr.de/{video_id}-ppjson.json', video_id)
playlist = ppjson['playlist']
diff --git a/yt_dlp/extractor/ndtv.py b/yt_dlp/extractor/ndtv.py
index d099db37b..c328bd4d2 100644
--- a/yt_dlp/extractor/ndtv.py
+++ b/yt_dlp/extractor/ndtv.py
@@ -15,12 +15,12 @@ class NDTVIE(InfoExtractor):
'info_dict': {
'id': '468818',
'ext': 'mp4',
- 'title': "प्राइम टाइम: सिस्टम बीमार, स्कूल बदहाल",
+ 'title': 'प्राइम टाइम: सिस्टम बीमार, स्कूल बदहाल',
'description': 'md5:f410512f1b49672e5695dea16ef2731d',
'upload_date': '20170928',
'duration': 2218,
'thumbnail': r're:https?://.*\.jpg',
- }
+ },
},
{
# __filename is url
@@ -29,45 +29,45 @@ class NDTVIE(InfoExtractor):
'info_dict': {
'id': '470304',
'ext': 'mp4',
- 'title': "Cracker-Free Diwali Wishes From Karan Johar, Kriti Sanon & Other Stars",
+ 'title': 'Cracker-Free Diwali Wishes From Karan Johar, Kriti Sanon & Other Stars',
'description': 'md5:f115bba1adf2f6433fa7c1ade5feb465',
'upload_date': '20171019',
'duration': 137,
'thumbnail': r're:https?://.*\.jpg',
- }
+ },
},
{
'url': 'https://www.ndtv.com/video/news/news/delhi-s-air-quality-status-report-after-diwali-is-very-poor-470372',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://auto.ndtv.com/videos/the-cnb-daily-october-13-2017-469935',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://sports.ndtv.com/cricket/videos/2nd-t20i-rock-thrown-at-australia-cricket-team-bus-after-win-over-india-469764',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'http://gadgets.ndtv.com/videos/uncharted-the-lost-legacy-review-465568',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'http://profit.ndtv.com/videos/news/video-indian-economy-on-very-solid-track-international-monetary-fund-chief-470040',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'http://food.ndtv.com/video-basil-seeds-coconut-porridge-419083',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://doctor.ndtv.com/videos/top-health-stories-of-the-week-467396',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://swirlster.ndtv.com/video/how-to-make-friends-at-work-469324',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/nekohacker.py b/yt_dlp/extractor/nekohacker.py
index 24b66570e..537158e87 100644
--- a/yt_dlp/extractor/nekohacker.py
+++ b/yt_dlp/extractor/nekohacker.py
@@ -38,8 +38,8 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'Spaceship',
'track_number': 1,
- 'duration': 195.0
- }
+ 'duration': 195.0,
+ },
},
{
'url': 'https://nekohacker.com/wp-content/uploads/2022/11/02-City-Runner.mp3',
@@ -56,8 +56,8 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'City Runner',
'track_number': 2,
- 'duration': 148.0
- }
+ 'duration': 148.0,
+ },
},
{
'url': 'https://nekohacker.com/wp-content/uploads/2022/11/03-Nature-Talk.mp3',
@@ -74,8 +74,8 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'Nature Talk',
'track_number': 3,
- 'duration': 174.0
- }
+ 'duration': 174.0,
+ },
},
{
'url': 'https://nekohacker.com/wp-content/uploads/2022/11/04-Crystal-World.mp3',
@@ -92,10 +92,10 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'Crystal World',
'track_number': 4,
- 'duration': 199.0
- }
- }
- ]
+ 'duration': 199.0,
+ },
+ },
+ ],
}, {
'url': 'https://nekohacker.com/susume/',
'info_dict': {
@@ -118,7 +118,7 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'md5:1a5fcbc96ca3c3265b1c6f9f79f30fd0',
'track_number': 1,
- }
+ },
},
{
'url': 'https://nekohacker.com/wp-content/uploads/2021/01/むじな-de-なじむ-feat.-六科なじむ-CV_-日高里菜-.mp3',
@@ -135,7 +135,7 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'むじな de なじむ feat. 六科なじむ (CV: 日高里菜 )',
'track_number': 2,
- }
+ },
},
{
'url': 'https://nekohacker.com/wp-content/uploads/2021/01/進め!むじなカンパニー-instrumental.mp3',
@@ -152,7 +152,7 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': '進め!むじなカンパニー (instrumental)',
'track_number': 3,
- }
+ },
},
{
'url': 'https://nekohacker.com/wp-content/uploads/2021/01/むじな-de-なじむ-instrumental.mp3',
@@ -169,9 +169,9 @@ class NekoHackerIE(InfoExtractor):
'artist': 'Neko Hacker',
'track': 'むじな de なじむ (instrumental)',
'track_number': 4,
- }
- }
- ]
+ },
+ },
+ ],
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/neteasemusic.py b/yt_dlp/extractor/neteasemusic.py
index b54c12e1e..dd50efe51 100644
--- a/yt_dlp/extractor/neteasemusic.py
+++ b/yt_dlp/extractor/neteasemusic.py
@@ -56,7 +56,7 @@ class NetEaseMusicBaseIE(InfoExtractor):
'requestId': f'{int(time.time() * 1000)}_{random.randint(0, 1000):04}',
**traverse_obj(self._get_cookies(self._API_BASE), {
'MUSIC_U': ('MUSIC_U', {lambda i: i.value}),
- })
+ }),
}
return self._download_json(
urljoin('https://interface3.music.163.com/', f'/eapi{path}'), video_id,
@@ -140,7 +140,7 @@ class NetEaseMusicIE(NetEaseMusicBaseIE):
'upload_date': '20180405',
'description': 'md5:3650af9ee22c87e8637cb2dde22a765c',
'subtitles': {'lyrics': [{'ext': 'lrc'}]},
- "duration": 256,
+ 'duration': 256,
'thumbnail': r're:^http.*\.jpg',
'album': '偶像练习生 表演曲目合集',
'average_rating': int,
@@ -418,7 +418,7 @@ class NetEaseMusicListIE(NetEaseMusicBaseIE):
info = self._download_eapi_json(
'/v3/playlist/detail', list_id,
{'id': list_id, 't': '-1', 'n': '500', 's': '0'},
- note="Downloading playlist info")
+ note='Downloading playlist info')
metainfo = traverse_obj(info, ('playlist', {
'title': ('name', {str}),
@@ -543,7 +543,7 @@ class NetEaseMusicProgramIE(NetEaseMusicBaseIE):
'duration': 1104,
},
'params': {
- 'noplaylist': True
+ 'noplaylist': True,
},
}]
@@ -585,7 +585,7 @@ class NetEaseMusicDjRadioIE(NetEaseMusicBaseIE):
'info_dict': {
'id': '42',
'title': '声音蔓延',
- 'description': 'md5:c7381ebd7989f9f367668a5aee7d5f08'
+ 'description': 'md5:c7381ebd7989f9f367668a5aee7d5f08',
},
'playlist_mincount': 40,
}
diff --git a/yt_dlp/extractor/netverse.py b/yt_dlp/extractor/netverse.py
index ef53e15da..2ddec5c0a 100644
--- a/yt_dlp/extractor/netverse.py
+++ b/yt_dlp/extractor/netverse.py
@@ -63,7 +63,7 @@ class NetverseIE(NetverseBaseIE):
'timestamp': 1626919804,
'like_count': int,
'uploader': 'Net Prime',
- }
+ },
}, {
# series
'url': 'https://www.netverse.id/watch/jadoo-seorang-model',
@@ -87,7 +87,7 @@ class NetverseIE(NetverseBaseIE):
'uploader': 'Net Prime',
'age_limit': 0,
},
- 'skip': 'video get Geo-blocked for some country'
+ 'skip': 'video get Geo-blocked for some country',
}, {
# non www host
'url': 'https://netverse.id/watch/tetangga-baru',
@@ -135,7 +135,7 @@ class NetverseIE(NetverseBaseIE):
'timestamp': 1645764984,
'upload_date': '20220225',
},
- 'skip': 'This video get Geo-blocked for some country'
+ 'skip': 'This video get Geo-blocked for some country',
}, {
# video with comments
'url': 'https://netverse.id/video/episode-1-season-2016-ok-food',
@@ -161,8 +161,8 @@ class NetverseIE(NetverseBaseIE):
'comment_count': int,
},
'params': {
- 'getcomments': True
- }
+ 'getcomments': True,
+ },
}, {
# video with multiple page comment
'url': 'https://netverse.id/video/match-island-eps-1-fix',
@@ -188,8 +188,8 @@ class NetverseIE(NetverseBaseIE):
'comment_count': int,
},
'params': {
- 'getcomments': True
- }
+ 'getcomments': True,
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/netzkino.py b/yt_dlp/extractor/netzkino.py
index e9422eebf..c07b1715a 100644
--- a/yt_dlp/extractor/netzkino.py
+++ b/yt_dlp/extractor/netzkino.py
@@ -26,7 +26,7 @@ class NetzkinoIE(InfoExtractor):
},
'params': {
'skip_download': 'Download only works from Germany',
- }
+ },
}, {
'url': 'https://www.netzkino.de/#!/filme/dr-jekyll-mrs-hyde-2',
'md5': 'c7728b2dadd04ff6727814847a51ef03',
@@ -42,14 +42,14 @@ class NetzkinoIE(InfoExtractor):
},
'params': {
'skip_download': 'Download only works from Germany',
- }
+ },
}]
def _real_extract(self, url):
mobj = self._match_valid_url(url)
video_id = mobj.group('id')
- api_url = 'https://api.netzkino.de.simplecache.net/capi-2.0a/movies/%s.json?d=www' % video_id
+ api_url = f'https://api.netzkino.de.simplecache.net/capi-2.0a/movies/{video_id}.json?d=www'
info = self._download_json(api_url, video_id)
custom_fields = info['custom_fields']
diff --git a/yt_dlp/extractor/newgrounds.py b/yt_dlp/extractor/newgrounds.py
index 67e52efd6..9f5a464e6 100644
--- a/yt_dlp/extractor/newgrounds.py
+++ b/yt_dlp/extractor/newgrounds.py
@@ -86,7 +86,7 @@ class NewgroundsIE(InfoExtractor):
'description': 'md5:9246c181614e23754571995104da92e0',
'age_limit': 13,
'thumbnail': r're:^https://picon\.ngfiles\.com/297000/flash_297383_card\.png',
- }
+ },
}, {
'url': 'https://www.newgrounds.com/portal/view/297383/format/flash',
'md5': '5d05585a9a0caca059f5abfbd3865524',
@@ -101,7 +101,7 @@ class NewgroundsIE(InfoExtractor):
'view_count': int,
'age_limit': 13,
'thumbnail': r're:^https://picon\.ngfiles\.com/297000/flash_297383_card\.png',
- }
+ },
}, {
'url': 'https://www.newgrounds.com/portal/view/823109',
'info_dict': {
@@ -115,7 +115,7 @@ class NewgroundsIE(InfoExtractor):
'view_count': int,
'age_limit': 18,
'thumbnail': r're:^https://picon\.ngfiles\.com/823000/flash_823109_card\.png',
- }
+ },
}]
_AGE_LIMIT = {
'e': 0,
@@ -132,7 +132,7 @@ class NewgroundsIE(InfoExtractor):
result = self._download_json(login_url, None, 'Logging in', headers={
'Accept': 'application/json',
'Referer': self._LOGIN_URL,
- 'X-Requested-With': 'XMLHttpRequest'
+ 'X-Requested-With': 'XMLHttpRequest',
}, data=urlencode_postdata({
**self._hidden_inputs(login_webpage),
'username': username,
@@ -164,7 +164,7 @@ class NewgroundsIE(InfoExtractor):
json_video = self._download_json(f'https://www.newgrounds.com/portal/video/{media_id}', media_id, headers={
'Accept': 'application/json',
'Referer': url,
- 'X-Requested-With': 'XMLHttpRequest'
+ 'X-Requested-With': 'XMLHttpRequest',
})
formats = []
diff --git a/yt_dlp/extractor/newsy.py b/yt_dlp/extractor/newsy.py
index a5a7b168c..941cb9331 100644
--- a/yt_dlp/extractor/newsy.py
+++ b/yt_dlp/extractor/newsy.py
@@ -19,9 +19,9 @@ class NewsyIE(InfoExtractor):
'timestamp': 1621339200,
'duration': 339630,
'thumbnail': 'https://cdn.newsy.com/images/videos/x/1620927824_xyrrP4.jpg',
- 'upload_date': '20210518'
+ 'upload_date': '20210518',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/nextmedia.py b/yt_dlp/extractor/nextmedia.py
index 871d3e669..81da3ffde 100644
--- a/yt_dlp/extractor/nextmedia.py
+++ b/yt_dlp/extractor/nextmedia.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
clean_html,
get_element_by_class,
@@ -24,7 +25,7 @@ class NextMediaIE(InfoExtractor):
'description': 'md5:28222b9912b6665a21011b034c70fcc7',
'timestamp': 1415456273,
'upload_date': '20141108',
- }
+ },
}]
_URL_PATTERN = r'\{ url: \'(.+)\' \}'
@@ -39,7 +40,7 @@ class NextMediaIE(InfoExtractor):
r'window\.location\.href\s*=\s*([\'"])(?P<url>(?!\1).+)\1',
page, 'redirection URL', default=None, group='url')
if redirection_url:
- return self.url_result(compat_urlparse.urljoin(url, redirection_url))
+ return self.url_result(urllib.parse.urljoin(url, redirection_url))
title = self._fetch_title(page)
video_url = self._search_regex(self._URL_PATTERN, page, 'video url')
@@ -67,8 +68,8 @@ class NextMediaIE(InfoExtractor):
return self._og_search_thumbnail(page)
def _fetch_timestamp(self, page):
- dateCreated = self._search_regex('"dateCreated":"([^"]+)"', page, 'created time')
- return parse_iso8601(dateCreated)
+ date_created = self._search_regex('"dateCreated":"([^"]+)"', page, 'created time')
+ return parse_iso8601(date_created)
def _fetch_upload_date(self, url):
return self._search_regex(self._VALID_URL, url, 'upload date', group='date')
@@ -91,7 +92,7 @@ class NextMediaActionNewsIE(NextMediaIE): # XXX: Do not subclass from concrete
'description': 'md5:cd802fad1f40fd9ea178c1e2af02d659',
'timestamp': 1421791200,
'upload_date': '20150120',
- }
+ },
}]
def _real_extract(self, url):
@@ -115,7 +116,7 @@ class AppleDailyIE(NextMediaIE): # XXX: Do not subclass from concrete IE
'thumbnail': r're:^https?://.*\.jpg$',
'description': 'md5:2acd430e59956dc47cd7f67cb3c003f4',
'upload_date': '20150128',
- }
+ },
}, {
'url': 'http://www.appledaily.com.tw/realtimenews/article/strange/20150128/550549/%E4%B8%8D%E6%BB%BF%E8%A2%AB%E8%B8%A9%E8%85%B3%E3%80%80%E5%B1%B1%E6%9D%B1%E5%85%A9%E5%A4%A7%E5%AA%BD%E4%B8%80%E8%B7%AF%E6%89%93%E4%B8%8B%E8%BB%8A',
'md5': '86b4e9132d158279c7883822d94ccc49',
@@ -126,7 +127,7 @@ class AppleDailyIE(NextMediaIE): # XXX: Do not subclass from concrete IE
'thumbnail': r're:^https?://.*\.jpg$',
'description': 'md5:175b4260c1d7c085993474217e4ab1b4',
'upload_date': '20150128',
- }
+ },
}, {
'url': 'http://www.appledaily.com.tw/animation/realtimenews/new/20150128/5003671',
'md5': '03df296d95dedc2d5886debbb80cb43f',
diff --git a/yt_dlp/extractor/nexx.py b/yt_dlp/extractor/nexx.py
index b4874c8f3..cd32892fa 100644
--- a/yt_dlp/extractor/nexx.py
+++ b/yt_dlp/extractor/nexx.py
@@ -4,7 +4,6 @@ import re
import time
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -41,7 +40,7 @@ class NexxIE(InfoExtractor):
'timestamp': 1384264416,
'upload_date': '20131112',
},
- 'skip': 'Spiegel nexx CDNs are now disabled'
+ 'skip': 'Spiegel nexx CDNs are now disabled',
}, {
# episode with captions
'url': 'https://api.nexx.cloud/v3.1/741/videos/byid/1701834',
@@ -92,7 +91,7 @@ class NexxIE(InfoExtractor):
'timestamp': 1527874460,
'upload_date': '20180601',
},
- 'skip': 'Spiegel nexx CDNs are now disabled'
+ 'skip': 'Spiegel nexx CDNs are now disabled',
}, {
'url': 'https://api.nexxcdn.com/v3/748/videos/byid/128907',
'only_matching': True,
@@ -128,8 +127,7 @@ class NexxIE(InfoExtractor):
r'(?is)onPLAYReady.+?_play\.(?:init|(?:control\.)?addPlayer)\s*\(.+?\s*,\s*["\']?(\d+)',
webpage):
entries.append(
- 'https://api.nexx.cloud/v3/%s/videos/byid/%s'
- % (domain_id, video_id))
+ f'https://api.nexx.cloud/v3/{domain_id}/videos/byid/{video_id}')
# TODO: support more embed formats
@@ -137,20 +135,20 @@ class NexxIE(InfoExtractor):
def _handle_error(self, response):
if traverse_obj(response, ('metadata', 'notice'), expected_type=str):
- self.report_warning('%s said: %s' % (self.IE_NAME, response['metadata']['notice']))
+ self.report_warning('{} said: {}'.format(self.IE_NAME, response['metadata']['notice']))
status = int_or_none(try_get(
response, lambda x: x['metadata']['status']) or 200)
if 200 <= status < 300:
return
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, response['metadata']['errorhint']),
+ '{} said: {}'.format(self.IE_NAME, response['metadata']['errorhint']),
expected=True)
def _call_api(self, domain_id, path, video_id, data=None, headers={}):
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
result = self._download_json(
- 'https://api.nexx.cloud/v3/%s/%s' % (domain_id, path), video_id,
- 'Downloading %s JSON' % path, data=urlencode_postdata(data),
+ f'https://api.nexx.cloud/v3/{domain_id}/{path}', video_id,
+ f'Downloading {path} JSON', data=urlencode_postdata(data),
headers=headers)
self._handle_error(result)
return result['result']
@@ -160,20 +158,20 @@ class NexxIE(InfoExtractor):
cdn = stream_data['cdnType']
assert cdn == 'free'
- hash = video['general']['hash']
+ video_hash = video['general']['hash']
- ps = compat_str(stream_data['originalDomain'])
+ ps = str(stream_data['originalDomain'])
if stream_data['applyFolderHierarchy'] == 1:
s = ('%04d' % int(video_id))[::-1]
- ps += '/%s/%s' % (s[0:2], s[2:4])
- ps += '/%s/%s_' % (video_id, hash)
+ ps += f'/{s[0:2]}/{s[2:4]}'
+ ps += f'/{video_id}/{video_hash}_'
t = 'http://%s' + ps
fd = stream_data['azureFileDistribution'].split(',')
cdn_provider = stream_data['cdnProvider']
def p0(p):
- return '_%s' % p if stream_data['applyAzureStructure'] == 1 else ''
+ return f'_{p}' if stream_data['applyAzureStructure'] == 1 else ''
formats = []
if cdn_provider == 'ak':
@@ -191,10 +189,10 @@ class NexxIE(InfoExtractor):
for i in fd:
p = i.split(':')
tbr = int(p[0])
- filename = '%s%s%s.mp4' % (h, p[1], p0(tbr))
+ filename = f'{h}{p[1]}{p0(tbr)}.mp4'
f = {
'url': http_base + '/' + filename,
- 'format_id': '%s-http-%d' % (cdn, tbr),
+ 'format_id': f'{cdn}-http-{tbr}',
'tbr': tbr,
}
width_height = p[1].split('x')
@@ -204,7 +202,7 @@ class NexxIE(InfoExtractor):
'height': int_or_none(width_height[1]),
})
formats.append(f)
- a = filename + ':%s' % (tbr * 1000)
+ a = filename + f':{tbr * 1000}'
t += a + ','
t = t[:-1] + '&audiostream=' + a.split(':')[0]
else:
@@ -213,10 +211,10 @@ class NexxIE(InfoExtractor):
if cdn_provider == 'ce':
formats.extend(self._extract_mpd_formats(
t % (stream_data['cdnPathDASH'], 'mpd'), video_id,
- mpd_id='%s-dash' % cdn, fatal=False))
+ mpd_id=f'{cdn}-dash', fatal=False))
formats.extend(self._extract_m3u8_formats(
t % (stream_data['cdnPathHLS'], 'm3u8'), video_id, 'mp4',
- entry_protocol='m3u8_native', m3u8_id='%s-hls' % cdn, fatal=False))
+ entry_protocol='m3u8_native', m3u8_id=f'{cdn}-hls', fatal=False))
return formats
@@ -231,9 +229,9 @@ class NexxIE(InfoExtractor):
def get_cdn_shield_base(shield_type=''):
for secure in ('', 's'):
- cdn_shield = stream_data.get('cdnShield%sHTTP%s' % (shield_type, secure.upper()))
+ cdn_shield = stream_data.get(f'cdnShield{shield_type}HTTP{secure.upper()}')
if cdn_shield:
- return 'http%s://%s' % (secure, cdn_shield)
+ return f'http{secure}://{cdn_shield}'
return f'http://sdn-global-{"prog" if shield_type.lower() == "prog" else "streaming"}-cache.3qsdn.com/' + (f's/{protection_key}/' if protection_key else '')
stream_base = get_cdn_shield_base()
@@ -256,7 +254,7 @@ class NexxIE(InfoExtractor):
tbr = int_or_none(ss[1], scale=1000)
formats.append({
'url': f'{progressive_base}{q_acc}/uploads/{q_acc}-{ss[2]}.webm',
- 'format_id': f'{cdn}-{ss[0]}{"-%s" % tbr if tbr else ""}',
+ 'format_id': f'{cdn}-{ss[0]}{f"-{tbr}" if tbr else ""}',
'tbr': tbr,
})
@@ -270,7 +268,7 @@ class NexxIE(InfoExtractor):
width, height = ss[1].split('x') if len(ss[1].split('x')) == 2 else (None, None)
f = {
'url': f'{progressive_base}{q_acc}/files/{q_prefix}/{q_locator}/{ss[2]}.mp4',
- 'format_id': f'{cdn}-http-{"-%s" % tbr if tbr else ""}',
+ 'format_id': f'{cdn}-http-{f"-{tbr}" if tbr else ""}',
'tbr': tbr,
'width': int_or_none(width),
'height': int_or_none(height),
@@ -288,38 +286,37 @@ class NexxIE(InfoExtractor):
def get_cdn_shield_base(shield_type='', static=False):
for secure in ('', 's'):
- cdn_shield = stream_data.get('cdnShield%sHTTP%s' % (shield_type, secure.upper()))
+ cdn_shield = stream_data.get(f'cdnShield{shield_type}HTTP{secure.upper()}')
if cdn_shield:
- return 'http%s://%s' % (secure, cdn_shield)
+ return f'http{secure}://{cdn_shield}'
+ if 'fb' in stream_data['azureAccount']:
+ prefix = 'df' if static else 'f'
else:
- if 'fb' in stream_data['azureAccount']:
- prefix = 'df' if static else 'f'
- else:
- prefix = 'd' if static else 'p'
- account = int(stream_data['azureAccount'].replace('nexxplayplus', '').replace('nexxplayfb', ''))
- return 'http://nx-%s%02d.akamaized.net/' % (prefix, account)
+ prefix = 'd' if static else 'p'
+ account = int(stream_data['azureAccount'].replace('nexxplayplus', '').replace('nexxplayfb', ''))
+ return 'http://nx-%s%02d.akamaized.net/' % (prefix, account)
language = video['general'].get('language_raw') or ''
azure_stream_base = get_cdn_shield_base()
is_ml = ',' in language
- azure_manifest_url = '%s%s/%s_src%s.ism/Manifest' % (
+ azure_manifest_url = '{}{}/{}_src{}.ism/Manifest'.format(
azure_stream_base, azure_locator, video_id, ('_manifest' if is_ml else '')) + '%s'
protection_token = try_get(
- video, lambda x: x['protectiondata']['token'], compat_str)
+ video, lambda x: x['protectiondata']['token'], str)
if protection_token:
- azure_manifest_url += '?hdnts=%s' % protection_token
+ azure_manifest_url += f'?hdnts={protection_token}'
formats = self._extract_m3u8_formats(
azure_manifest_url % '(format=m3u8-aapl)',
video_id, 'mp4', 'm3u8_native',
- m3u8_id='%s-hls' % cdn, fatal=False)
+ m3u8_id=f'{cdn}-hls', fatal=False)
formats.extend(self._extract_mpd_formats(
azure_manifest_url % '(format=mpd-time-csf)',
- video_id, mpd_id='%s-dash' % cdn, fatal=False))
+ video_id, mpd_id=f'{cdn}-dash', fatal=False))
formats.extend(self._extract_ism_formats(
- azure_manifest_url % '', video_id, ism_id='%s-mss' % cdn, fatal=False))
+ azure_manifest_url % '', video_id, ism_id=f'{cdn}-mss', fatal=False))
azure_progressive_base = get_cdn_shield_base('Prog', True)
azure_file_distribution = stream_data.get('azureFileDistribution')
@@ -332,9 +329,8 @@ class NexxIE(InfoExtractor):
tbr = int_or_none(ss[0])
if tbr:
f = {
- 'url': '%s%s/%s_src_%s_%d.mp4' % (
- azure_progressive_base, azure_locator, video_id, ss[1], tbr),
- 'format_id': '%s-http-%d' % (cdn, tbr),
+ 'url': f'{azure_progressive_base}{azure_locator}/{video_id}_src_{ss[1]}_{tbr}.mp4',
+ 'format_id': f'{cdn}-http-{tbr}',
'tbr': tbr,
}
width_height = ss[1].split('x')
@@ -365,7 +361,7 @@ class NexxIE(InfoExtractor):
return None
response = self._download_json(
- 'https://arc.nexx.cloud/api/video/%s.json' % video_id,
+ f'https://arc.nexx.cloud/api/video/{video_id}.json',
video_id, fatal=False)
if response and isinstance(response, dict):
result = response.get('result')
@@ -375,9 +371,7 @@ class NexxIE(InfoExtractor):
# not all videos work via arc, e.g. nexx:741:1269984
if not video:
# Reverse engineered from JS code (see getDeviceID function)
- device_id = '%d:%d:%d%d' % (
- random.randint(1, 4), int(time.time()),
- random.randint(1e4, 99999), random.randint(1, 9))
+ device_id = f'{random.randint(1, 4)}:{int(time.time())}:{random.randint(1e4, 99999)}{random.randint(1, 9)}'
result = self._call_api(domain_id, 'session/init', video_id, data={
'nxp_devh': device_id,
@@ -416,10 +410,10 @@ class NexxIE(InfoExtractor):
# Reversed from JS code for _play.api.call function (search for
# X-Request-Token)
request_token = hashlib.md5(
- ''.join((op, domain_id, secret)).encode('utf-8')).hexdigest()
+ ''.join((op, domain_id, secret)).encode()).hexdigest()
result = self._call_api(
- domain_id, 'videos/%s/%s' % (op, video_id), video_id, data={
+ domain_id, f'videos/{op}/{video_id}', video_id, data={
'additionalfields': 'language,channel,format,licenseby,slug,fileversion,episode,season',
'addInteractionOptions': '1',
'addStatusDetails': '1',
@@ -460,13 +454,13 @@ class NexxIE(InfoExtractor):
'data': '\n\n'.join(
f'{i + 1}\n{srt_subtitles_timecode(line["fromms"] / 1000)} --> {srt_subtitles_timecode(line["toms"] / 1000)}\n{line["caption"]}'
for i, line in enumerate(sub['data'])),
- 'name': sub.get('language_long') or sub.get('title')
+ 'name': sub.get('language_long') or sub.get('title'),
})
elif sub.get('url'):
subtitles.setdefault(sub.get('language', 'en'), []).append({
'url': sub['url'],
'ext': sub.get('format'),
- 'name': sub.get('language_long') or sub.get('title')
+ 'name': sub.get('language_long') or sub.get('title'),
})
return {
@@ -477,7 +471,7 @@ class NexxIE(InfoExtractor):
'release_year': int_or_none(general.get('year')),
'creator': general.get('studio') or general.get('studio_adref') or None,
'thumbnail': try_get(
- video, lambda x: x['imagedata']['thumb'], compat_str),
+ video, lambda x: x['imagedata']['thumb'], str),
'duration': parse_duration(general.get('runtime')),
'timestamp': int_or_none(general.get('uploaded')),
'episode_number': traverse_obj(
diff --git a/yt_dlp/extractor/nfhsnetwork.py b/yt_dlp/extractor/nfhsnetwork.py
index be732a32f..ec746ecb1 100644
--- a/yt_dlp/extractor/nfhsnetwork.py
+++ b/yt_dlp/extractor/nfhsnetwork.py
@@ -17,12 +17,12 @@ class NFHSNetworkIE(InfoExtractor):
'uploader_url': 'https://www.nfhsnetwork.com/schools/rockford-high-school-rockford-mi',
'location': 'Rockford, Michigan',
'timestamp': 1616859000,
- 'upload_date': '20210327'
+ 'upload_date': '20210327',
},
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
# Non-sport activity with description
'url': 'https://www.nfhsnetwork.com/events/limon-high-school-limon-co/evt4a30e3726c',
@@ -36,12 +36,12 @@ class NFHSNetworkIE(InfoExtractor):
'uploader_url': 'https://www.nfhsnetwork.com/schools/limon-high-school-limon-co',
'location': 'Limon, Colorado',
'timestamp': 1607893200,
- 'upload_date': '20201213'
+ 'upload_date': '20201213',
},
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
# Postseason game
'url': 'https://www.nfhsnetwork.com/events/nfhs-network-special-events/dd8de71d45',
@@ -54,12 +54,12 @@ class NFHSNetworkIE(InfoExtractor):
'uploader_url': 'https://www.nfhsnetwork.com/affiliates/socal-sports-productions',
'location': 'San Diego, California',
'timestamp': 1451187000,
- 'upload_date': '20151226'
+ 'upload_date': '20151226',
},
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
# Video with no broadcasts object
'url': 'https://www.nfhsnetwork.com/events/wiaa-wi/9aa2f92f82',
@@ -73,13 +73,13 @@ class NFHSNetworkIE(InfoExtractor):
'uploader_url': 'https://www.nfhsnetwork.com/associations/wiaa-wi',
'location': 'Stevens Point, Wisconsin',
'timestamp': 1421856000,
- 'upload_date': '20150121'
+ 'upload_date': '20150121',
},
'params': {
# m3u8 download
'skip_download': True,
- }
- }
+ },
+ },
]
def _real_extract(self, url):
@@ -91,17 +91,17 @@ class NFHSNetworkIE(InfoExtractor):
publisher = data.get('publishers')[0] # always exists
broadcast = (publisher.get('broadcasts') or publisher.get('vods'))[0] # some (older) videos don't have a broadcasts object
uploader = publisher.get('formatted_name') or publisher.get('name')
- uploaderID = publisher.get('publisher_key')
- pubType = publisher.get('type')
- uploaderPrefix = (
- "schools" if pubType == "school"
- else "associations" if "association" in pubType
- else "affiliates" if (pubType == "publisher" or pubType == "affiliate")
- else "schools")
- uploaderPage = 'https://www.nfhsnetwork.com/%s/%s' % (uploaderPrefix, publisher.get('slug'))
- location = '%s, %s' % (data.get('city'), data.get('state_name'))
+ uploader_id = publisher.get('publisher_key')
+ pub_type = publisher.get('type')
+ uploader_prefix = (
+ 'schools' if pub_type == 'school'
+ else 'associations' if 'association' in pub_type
+ else 'affiliates' if (pub_type == 'publisher' or pub_type == 'affiliate')
+ else 'schools')
+ uploader_page = 'https://www.nfhsnetwork.com/{}/{}'.format(uploader_prefix, publisher.get('slug'))
+ location = '{}, {}'.format(data.get('city'), data.get('state_name'))
description = broadcast.get('description')
- isLive = broadcast.get('on_air') or broadcast.get('status') == 'on_air' or False
+ is_live = broadcast.get('on_air') or broadcast.get('status') == 'on_air' or False
timestamp = unified_timestamp(data.get('local_start_time'))
upload_date = unified_strdate(data.get('local_start_time'))
@@ -111,13 +111,13 @@ class NFHSNetworkIE(InfoExtractor):
or self._html_search_regex(r'<h1 class="sr-hidden">(.*?)</h1>', webpage, 'title'))
title = title.split('|')[0].strip()
- video_type = 'broadcasts' if isLive else 'vods'
- key = broadcast.get('key') if isLive else try_get(publisher, lambda x: x['vods'][0]['key'])
+ video_type = 'broadcasts' if is_live else 'vods'
+ key = broadcast.get('key') if is_live else try_get(publisher, lambda x: x['vods'][0]['key'])
m3u8_url = self._download_json(
- 'https://cfunity.nfhsnetwork.com/v2/%s/%s/url' % (video_type, key),
+ f'https://cfunity.nfhsnetwork.com/v2/{video_type}/{key}/url',
video_id).get('video_url')
- formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', live=isLive)
+ formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', live=is_live)
return {
'id': video_id,
@@ -126,10 +126,10 @@ class NFHSNetworkIE(InfoExtractor):
'description': description,
'timestamp': timestamp,
'uploader': uploader,
- 'uploader_id': uploaderID,
- 'uploader_url': uploaderPage,
+ 'uploader_id': uploader_id,
+ 'uploader_url': uploader_page,
'location': location,
'upload_date': upload_date,
- 'is_live': isLive,
+ 'is_live': is_live,
'_format_sort_fields': ('res', 'tbr'),
}
diff --git a/yt_dlp/extractor/nfl.py b/yt_dlp/extractor/nfl.py
index 3f83cd20e..c537c1c47 100644
--- a/yt_dlp/extractor/nfl.py
+++ b/yt_dlp/extractor/nfl.py
@@ -195,7 +195,7 @@ class NFLIE(NFLBaseIE):
'tags': 'count:6',
'duration': 157,
'categories': 'count:3',
- }
+ },
}, {
'url': 'https://www.chiefs.com/listen/patrick-mahomes-travis-kelce-react-to-win-over-dolphins-the-breakdown',
'md5': '6886b32c24b463038c760ceb55a34566',
@@ -332,7 +332,7 @@ class NFLPlusReplayIE(NFLBaseIE):
def entries():
for replay in traverse_obj(
- replays, ('items', lambda _, v: v['mcpPlaybackId'] and v['subType'] in requested_types)
+ replays, ('items', lambda _, v: v['mcpPlaybackId'] and v['subType'] in requested_types),
):
video_id = replay['mcpPlaybackId']
yield self.url_result(f'{self._ANVATO_PREFIX}{video_id}', AnvatoIE, video_id)
diff --git a/yt_dlp/extractor/nhk.py b/yt_dlp/extractor/nhk.py
index 8bb017a73..0ff25a690 100644
--- a/yt_dlp/extractor/nhk.py
+++ b/yt_dlp/extractor/nhk.py
@@ -104,7 +104,7 @@ class NhkBaseIE(InfoExtractor):
if not img_path:
continue
thumbnails.append({
- 'id': '%dp' % h,
+ 'id': f'{h}p',
'height': h,
'width': w,
'url': 'https://www3.nhk.or.jp' + img_path,
@@ -211,7 +211,7 @@ class NhkVodIE(NhkBaseIE):
'series': 'Living in Japan',
'description': 'md5:0a0e2077d8f07a03071e990a6f51bfab',
'thumbnail': r're:https://.+/.+\.jpg',
- 'episode': 'Tips for Travelers to Japan / Ramen Vending Machines'
+ 'episode': 'Tips for Travelers to Japan / Ramen Vending Machines',
},
}, {
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2015173/',
@@ -458,7 +458,7 @@ class NhkForSchoolBangumiIE(InfoExtractor):
'upload_date': '20140402',
'ext': 'mp4',
- 'chapters': 'count:12'
+ 'chapters': 'count:12',
},
'params': {
# m3u8 download
@@ -521,7 +521,8 @@ class NhkForSchoolSubjectIE(InfoExtractor):
'eigo', 'tokkatsu',
'tokushi', 'sonota',
)
- _VALID_URL = r'https?://www\.nhk\.or\.jp/school/(?P<id>%s)/?(?:[\?#].*)?$' % '|'.join(re.escape(s) for s in KNOWN_SUBJECTS)
+ _VALID_URL = r'https?://www\.nhk\.or\.jp/school/(?P<id>{})/?(?:[\?#].*)?$'.format(
+ '|'.join(re.escape(s) for s in KNOWN_SUBJECTS))
_TESTS = [{
'url': 'https://www.nhk.or.jp/school/sougou/',
@@ -551,9 +552,8 @@ class NhkForSchoolSubjectIE(InfoExtractor):
class NhkForSchoolProgramListIE(InfoExtractor):
- _VALID_URL = r'https?://www\.nhk\.or\.jp/school/(?P<id>(?:%s)/[a-zA-Z0-9_-]+)' % (
- '|'.join(re.escape(s) for s in NhkForSchoolSubjectIE.KNOWN_SUBJECTS)
- )
+ _VALID_URL = r'https?://www\.nhk\.or\.jp/school/(?P<id>(?:{})/[a-zA-Z0-9_-]+)'.format(
+ '|'.join(re.escape(s) for s in NhkForSchoolSubjectIE.KNOWN_SUBJECTS))
_TESTS = [{
'url': 'https://www.nhk.or.jp/school/sougou/q/',
'info_dict': {
@@ -747,7 +747,7 @@ class NhkRadioNewsPageIE(InfoExtractor):
'channel': 'NHKラジオ第1',
'uploader': 'NHKラジオ第1',
'title': 'NHKラジオニュース',
- }
+ },
}]
def _real_extract(self, url):
@@ -789,7 +789,7 @@ class NhkRadiruLiveIE(InfoExtractor):
'ext': 'm4a',
'thumbnail': 'https://www.nhk.or.jp/common/img/media/fm-200x200.png',
'live_status': 'is_live',
- }
+ },
}]
_NOA_STATION_IDS = {'r1': 'n1', 'r2': 'n2', 'fm': 'n3'}
@@ -803,8 +803,8 @@ class NhkRadiruLiveIE(InfoExtractor):
data = config.find(f'.//data//area[.="{area}"]/..')
if not data:
- raise ExtractorError('Invalid area. Valid areas are: %s' % ', '.join(
- [i.text for i in config.findall('.//data//area')]), expected=True)
+ raise ExtractorError('Invalid area. Valid areas are: {}'.format(', '.join(
+ [i.text for i in config.findall('.//data//area')])), expected=True)
noa_info = self._download_json(
f'https:{config.find(".//url_program_noa").text}'.format(area=data.find('areakey').text),
@@ -812,7 +812,7 @@ class NhkRadiruLiveIE(InfoExtractor):
present_info = traverse_obj(noa_info, ('nowonair_list', self._NOA_STATION_IDS.get(station), 'present'))
return {
- 'title': ' '.join(traverse_obj(present_info, (('service', 'area',), 'name', {str}))),
+ 'title': ' '.join(traverse_obj(present_info, (('service', 'area'), 'name', {str}))),
'id': join_nonempty(station, area),
'thumbnails': traverse_obj(present_info, ('service', 'images', ..., {
'url': 'url',
diff --git a/yt_dlp/extractor/nhl.py b/yt_dlp/extractor/nhl.py
index 64cddb408..83dd480cf 100644
--- a/yt_dlp/extractor/nhl.py
+++ b/yt_dlp/extractor/nhl.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
determine_ext,
int_or_none,
@@ -12,8 +11,8 @@ class NHLBaseIE(InfoExtractor):
def _real_extract(self, url):
site, tmp_id = self._match_valid_url(url).groups()
video_data = self._download_json(
- 'https://%s/%s/%sid/v1/%s/details/web-v1.json'
- % (self._CONTENT_DOMAIN, site[:3], 'item/' if site == 'mlb' else '', tmp_id), tmp_id)
+ 'https://{}/{}/{}id/v1/{}/details/web-v1.json'.format(
+ self._CONTENT_DOMAIN, site[:3], 'item/' if site == 'mlb' else '', tmp_id), tmp_id)
if video_data.get('type') != 'video':
video_data = video_data['media']
video = video_data.get('video')
@@ -24,7 +23,7 @@ class NHLBaseIE(InfoExtractor):
if videos:
video_data = videos[0]
- video_id = compat_str(video_data['id'])
+ video_id = str(video_data['id'])
title = video_data['title']
formats = []
@@ -42,7 +41,7 @@ class NHLBaseIE(InfoExtractor):
else:
height = int_or_none(playback.get('height'))
formats.append({
- 'format_id': playback.get('name', 'http' + ('-%dp' % height if height else '')),
+ 'format_id': playback.get('name', 'http' + (f'-{height}p' if height else '')),
'url': playback_url,
'width': int_or_none(playback.get('width')),
'height': height,
diff --git a/yt_dlp/extractor/nick.py b/yt_dlp/extractor/nick.py
index 165d8ce9d..653b10b9d 100644
--- a/yt_dlp/extractor/nick.py
+++ b/yt_dlp/extractor/nick.py
@@ -22,7 +22,7 @@ class NickIE(MTVServicesInfoExtractor):
'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S1',
'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
- }
+ },
},
{
'md5': '839a04f49900a1fcbf517020d94e0737',
@@ -32,7 +32,7 @@ class NickIE(MTVServicesInfoExtractor):
'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S2',
'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
- }
+ },
},
{
'md5': 'f1145699f199770e2919ee8646955d46',
@@ -42,7 +42,7 @@ class NickIE(MTVServicesInfoExtractor):
'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S3',
'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
- }
+ },
},
{
'md5': 'd463116875aee2585ee58de3b12caebd',
@@ -52,7 +52,7 @@ class NickIE(MTVServicesInfoExtractor):
'title': 'SpongeBob SquarePants: "A Place for Pets/Lockdown for Love" S4',
'description': 'A Place for Pets/Lockdown for Love: When customers bring pets into the Krusty Krab, Mr. Krabs realizes pets are more profitable than owners. Plankton ruins another date with Karen, so she puts the Chum Bucket on lockdown until he proves his affection.',
- }
+ },
},
],
}, {
@@ -63,7 +63,7 @@ class NickIE(MTVServicesInfoExtractor):
'description': 'md5:9d65a66df38e02254852794b2809d1cf',
'title': 'Blue\'s Imagination Station',
},
- 'skip': 'Not accessible?'
+ 'skip': 'Not accessible?',
}]
def _get_feed_query(self, uri):
@@ -74,10 +74,10 @@ class NickIE(MTVServicesInfoExtractor):
def _real_extract(self, url):
domain, video_type, display_id = self._match_valid_url(url).groups()
- if video_type.startswith("episodes"):
+ if video_type.startswith('episodes'):
return super()._real_extract(url)
video_data = self._download_json(
- 'http://%s/data/video.endLevel.json' % domain,
+ f'http://{domain}/data/video.endLevel.json',
display_id, query={
'urlKey': display_id,
})
@@ -184,7 +184,7 @@ class NickDeIE(MTVServicesInfoExtractor):
def _get_feed_url(self, uri, url=None):
video_id = self._id_from_uri(uri)
config = self._download_json(
- 'http://media.mtvnservices.com/pmt/e1/access/index.html?uri=%s&configtype=edge&ref=%s' % (uri, url), video_id)
+ f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', video_id)
return self._remove_template_parameter(config['feedWithQueryParams'])
@@ -221,4 +221,4 @@ class NickRuIE(MTVServicesInfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
mgid = self._extract_mgid(webpage, url)
- return self.url_result('http://media.mtvnservices.com/embed/%s' % mgid)
+ return self.url_result(f'http://media.mtvnservices.com/embed/{mgid}')
diff --git a/yt_dlp/extractor/niconico.py b/yt_dlp/extractor/niconico.py
index b04ce9615..9d7b010c5 100644
--- a/yt_dlp/extractor/niconico.py
+++ b/yt_dlp/extractor/niconico.py
@@ -213,7 +213,7 @@ class NiconicoIE(InfoExtractor):
urljoin('https://account.nicovideo.jp', post_url), None,
note='Performing MFA', errnote='Unable to complete MFA',
data=urlencode_postdata({
- 'otp': self._get_tfa_info('6 digits code')
+ 'otp': self._get_tfa_info('6 digits code'),
}), headers={
'Content-Type': 'application/x-www-form-urlencoded',
})
@@ -264,7 +264,7 @@ class NiconicoIE(InfoExtractor):
'http_output_download_parameters': {
'use_ssl': yesno(session_api_data['urls'][0]['isSsl']),
'use_well_known_port': yesno(session_api_data['urls'][0]['isWellKnownPort']),
- }
+ },
}
elif dmc_protocol == 'hls':
protocol = 'm3u8'
@@ -277,14 +277,14 @@ class NiconicoIE(InfoExtractor):
'transfer_preset': '',
'use_ssl': yesno(session_api_data['urls'][0]['isSsl']),
'use_well_known_port': yesno(session_api_data['urls'][0]['isWellKnownPort']),
- }
+ },
}
if 'hls_encryption' in parsed_token and encryption:
protocol_parameters['hls_parameters']['encryption'] = {
parsed_token['hls_encryption']: {
'encrypted_key': encryption['encryptedKey'],
'key_uri': encryption['keyUri'],
- }
+ },
}
else:
protocol = 'm3u8_native'
@@ -295,7 +295,7 @@ class NiconicoIE(InfoExtractor):
session_api_endpoint['url'], video_id,
query={'_format': 'json'},
headers={'Content-Type': 'application/json'},
- note='Downloading JSON metadata for %s' % info_dict['format_id'],
+ note='Downloading JSON metadata for {}'.format(info_dict['format_id']),
data=json.dumps({
'session': {
'client_info': {
@@ -305,7 +305,7 @@ class NiconicoIE(InfoExtractor):
'auth_type': try_get(session_api_data, lambda x: x['authTypes'][session_api_data['protocols'][0]]),
'content_key_timeout': session_api_data.get('contentKeyTimeout'),
'service_id': 'nicovideo',
- 'service_user_id': session_api_data.get('serviceUserId')
+ 'service_user_id': session_api_data.get('serviceUserId'),
},
'content_id': session_api_data.get('contentId'),
'content_src_id_sets': [{
@@ -313,34 +313,34 @@ class NiconicoIE(InfoExtractor):
'src_id_to_mux': {
'audio_src_ids': [audio_src_id],
'video_src_ids': [video_src_id],
- }
- }]
+ },
+ }],
}],
'content_type': 'movie',
'content_uri': '',
'keep_method': {
'heartbeat': {
- 'lifetime': session_api_data.get('heartbeatLifetime')
- }
+ 'lifetime': session_api_data.get('heartbeatLifetime'),
+ },
},
'priority': session_api_data['priority'],
'protocol': {
'name': 'http',
'parameters': {
'http_parameters': {
- 'parameters': protocol_parameters
- }
- }
+ 'parameters': protocol_parameters,
+ },
+ },
},
'recipe_id': session_api_data.get('recipeId'),
'session_operation_auth': {
'session_operation_auth_by_signature': {
'signature': session_api_data.get('signature'),
'token': session_api_data.get('token'),
- }
+ },
},
- 'timing_constraint': 'unlimited'
- }
+ 'timing_constraint': 'unlimited',
+ },
}).encode())
info_dict['url'] = session_response['data']['session']['content_uri']
@@ -352,7 +352,7 @@ class NiconicoIE(InfoExtractor):
'data': json.dumps(session_response['data']),
# interval, convert milliseconds to seconds, then halve to make a buffer.
'interval': float_or_none(session_api_data.get('heartbeatLifetime'), scale=3000),
- 'ping': ping
+ 'ping': ping,
}
return info_dict, heartbeat_info_dict
@@ -368,7 +368,7 @@ class NiconicoIE(InfoExtractor):
vid_qual_label = traverse_obj(video_quality, ('metadata', 'label'))
return {
- 'url': 'niconico_dmc:%s/%s/%s' % (video_id, video_quality['id'], audio_quality['id']),
+ 'url': 'niconico_dmc:{}/{}/{}'.format(video_id, video_quality['id'], audio_quality['id']),
'format_id': format_id,
'format_note': join_nonempty('DMC', vid_qual_label, dmc_protocol.upper(), delim=' '),
'ext': 'mp4', # Session API are used in HTML5, which always serves mp4
@@ -389,7 +389,7 @@ class NiconicoIE(InfoExtractor):
'http_headers': {
'Origin': 'https://www.nicovideo.jp',
'Referer': 'https://www.nicovideo.jp/watch/' + video_id,
- }
+ },
}
def _yield_dmc_formats(self, api_data, video_id):
@@ -416,7 +416,7 @@ class NiconicoIE(InfoExtractor):
dms_m3u8_url = self._download_json(
f'https://nvapi.nicovideo.jp/v1/watch/{video_id}/access-rights/hls', video_id,
data=json.dumps({
- 'outputs': list(itertools.product((v['id'] for v in videos), (a['id'] for a in audios)))
+ 'outputs': list(itertools.product((v['id'] for v in videos), (a['id'] for a in audios))),
}).encode(), query={'actionTrackId': track_id}, headers={
'x-access-right-key': access_key,
'x-frontend-id': 6,
@@ -464,7 +464,7 @@ class NiconicoIE(InfoExtractor):
except ExtractorError as e:
try:
api_data = self._download_json(
- 'https://www.nicovideo.jp/api/watch/v3/%s?_frontendId=6&_frontendVersion=0&actionTrackId=AAAAAAAAAA_%d' % (video_id, round(time.time() * 1000)), video_id,
+ f'https://www.nicovideo.jp/api/watch/v3/{video_id}?_frontendId=6&_frontendVersion=0&actionTrackId=AAAAAAAAAA_{round(time.time() * 1000)}', video_id,
note='Downloading API JSON', errnote='Unable to fetch data')['data']
except ExtractorError:
if not isinstance(e.cause, HTTPError):
@@ -586,7 +586,7 @@ class NiconicoPlaylistBaseIE(InfoExtractor):
_API_HEADERS = {
'X-Frontend-ID': '6',
'X-Frontend-Version': '0',
- 'X-Niconico-Language': 'en-us'
+ 'X-Niconico-Language': 'en-us',
}
def _call_api(self, list_id, resource, query):
@@ -601,7 +601,7 @@ class NiconicoPlaylistBaseIE(InfoExtractor):
def _fetch_page(self, list_id, page):
page += 1
- resp = self._call_api(list_id, 'page %d' % page, {
+ resp = self._call_api(list_id, f'page {page}', {
'page': page,
'pageSize': self._PAGE_SIZE,
})
@@ -789,14 +789,14 @@ class NicovideoSearchURLIE(NicovideoSearchBaseIE):
'url': 'http://www.nicovideo.jp/search/sm9',
'info_dict': {
'id': 'sm9',
- 'title': 'sm9'
+ 'title': 'sm9',
},
'playlist_mincount': 40,
}, {
'url': 'https://www.nicovideo.jp/search/sm9?sort=h&order=d&end=2020-12-31&start=2020-01-01',
'info_dict': {
'id': 'sm9',
- 'title': 'sm9'
+ 'title': 'sm9',
},
'playlist_count': 31,
}]
@@ -814,7 +814,7 @@ class NicovideoSearchDateIE(NicovideoSearchBaseIE, SearchInfoExtractor):
'url': 'nicosearchdateall:a',
'info_dict': {
'id': 'a',
- 'title': 'a'
+ 'title': 'a',
},
'playlist_mincount': 1610,
}]
@@ -861,7 +861,7 @@ class NicovideoTagURLIE(NicovideoSearchBaseIE):
'url': 'https://www.nicovideo.jp/tag/ドキュメンタリー淫夢',
'info_dict': {
'id': 'ドキュメンタリー淫夢',
- 'title': 'ドキュメンタリー淫夢'
+ 'title': 'ドキュメンタリー淫夢',
},
'playlist_mincount': 400,
}]
@@ -880,12 +880,12 @@ class NiconicoUserIE(InfoExtractor):
},
'playlist_mincount': 101,
}
- _API_URL = "https://nvapi.nicovideo.jp/v1/users/%s/videos?sortKey=registeredAt&sortOrder=desc&pageSize=%s&page=%s"
+ _API_URL = 'https://nvapi.nicovideo.jp/v1/users/%s/videos?sortKey=registeredAt&sortOrder=desc&pageSize=%s&page=%s'
_PAGE_SIZE = 100
_API_HEADERS = {
'X-Frontend-ID': '6',
- 'X-Frontend-Version': '0'
+ 'X-Frontend-Version': '0',
}
def _entries(self, list_id):
@@ -895,12 +895,12 @@ class NiconicoUserIE(InfoExtractor):
json_parsed = self._download_json(
self._API_URL % (list_id, self._PAGE_SIZE, page_num + 1), list_id,
headers=self._API_HEADERS,
- note='Downloading JSON metadata%s' % (' page %d' % page_num if page_num else ''))
+ note='Downloading JSON metadata%s' % (f' page {page_num}' if page_num else ''))
if not page_num:
total_count = int_or_none(json_parsed['data'].get('totalCount'))
- for entry in json_parsed["data"]["items"]:
+ for entry in json_parsed['data']['items']:
count += 1
- yield self.url_result('https://www.nicovideo.jp/watch/%s' % entry['id'])
+ yield self.url_result('https://www.nicovideo.jp/watch/{}'.format(entry['id']))
page_num += 1
def _real_extract(self, url):
@@ -917,7 +917,7 @@ class NiconicoLiveIE(InfoExtractor):
'url': 'https://live.nicovideo.jp/watch/lv339533123',
'info_dict': {
'id': 'lv339533123',
- 'title': '激辛ペヤング食べます‪( ;ᯅ; )‬(歌枠オーディション参加中)',
+ 'title': '激辛ペヤング食べます\u202a( ;ᯅ; )\u202c(歌枠オーディション参加中)',
'view_count': 1526,
'comment_count': 1772,
'description': '初めましてもかって言います❕\nのんびり自由に適当に暮らしてます',
@@ -973,14 +973,14 @@ class NiconicoLiveIE(InfoExtractor):
'quality': 'abr',
'protocol': 'hls+fmp4',
'latency': latency,
- 'chasePlay': False
+ 'chasePlay': False,
},
'room': {
'protocol': 'webSocket',
- 'commentable': True
+ 'commentable': True,
},
'reconnect': False,
- }
+ },
}))
while True:
@@ -1004,7 +1004,7 @@ class NiconicoLiveIE(InfoExtractor):
elif self.get_param('verbose', False):
if len(recv) > 100:
recv = recv[:100] + '...'
- self.write_debug('Server said: %s' % recv)
+ self.write_debug(f'Server said: {recv}')
title = traverse_obj(embedded_data, ('program', 'title')) or self._html_search_meta(
('og:title', 'twitter:title'), webpage, 'live title', fatal=False)
diff --git a/yt_dlp/extractor/niconicochannelplus.py b/yt_dlp/extractor/niconicochannelplus.py
index 89af3f7b5..f39d0000d 100644
--- a/yt_dlp/extractor/niconicochannelplus.py
+++ b/yt_dlp/extractor/niconicochannelplus.py
@@ -18,9 +18,9 @@ from ..utils import (
class NiconicoChannelPlusBaseIE(InfoExtractor):
_WEBPAGE_BASE_URL = 'https://nicochannel.jp'
- def _call_api(self, path, item_id, *args, **kwargs):
+ def _call_api(self, path, item_id, **kwargs):
return self._download_json(
- f'https://nfc-api.nicochannel.jp/fc/{path}', video_id=item_id, *args, **kwargs)
+ f'https://nfc-api.nicochannel.jp/fc/{path}', video_id=item_id, **kwargs)
def _find_fanclub_site_id(self, channel_name):
fanclub_list_json = self._call_api(
diff --git a/yt_dlp/extractor/ninaprotocol.py b/yt_dlp/extractor/ninaprotocol.py
index ea57c5f38..c8063fbd1 100644
--- a/yt_dlp/extractor/ninaprotocol.py
+++ b/yt_dlp/extractor/ninaprotocol.py
@@ -41,7 +41,7 @@ class NinaProtocolIE(InfoExtractor):
'channel_id': '4ceG4zsb7VVxBTGPtZMqDZWGHo3VUg2xRvzC2b17ymWP',
'upload_date': '20231201',
'album_artist': 'Post Present Medium ',
- }
+ },
}, {
'info_dict': {
'id': '3SvsMM3y4oTPZ5DXFJnLkCAqkxz34hjzFxqms1vu9XBJ_2',
@@ -60,7 +60,7 @@ class NinaProtocolIE(InfoExtractor):
'timestamp': 1701417610,
'album': 'The Spatulas - March Chant',
'uploader_id': '2bGjgdKUddJoj2shYGqfNcUfoSoABP21RJoiwGMZDq3A',
- }
+ },
}, {
'info_dict': {
'id': '3SvsMM3y4oTPZ5DXFJnLkCAqkxz34hjzFxqms1vu9XBJ_3',
@@ -79,7 +79,7 @@ class NinaProtocolIE(InfoExtractor):
'tags': ['punk', 'postpresentmedium', 'cambridge'],
'uploader': 'ppmrecs',
'channel': 'ppm',
- }
+ },
}, {
'info_dict': {
'id': '3SvsMM3y4oTPZ5DXFJnLkCAqkxz34hjzFxqms1vu9XBJ_4',
@@ -98,7 +98,7 @@ class NinaProtocolIE(InfoExtractor):
'channel': 'ppm',
'uploader_id': '2bGjgdKUddJoj2shYGqfNcUfoSoABP21RJoiwGMZDq3A',
'uploader': 'ppmrecs',
- }
+ },
}, {
'info_dict': {
'id': '3SvsMM3y4oTPZ5DXFJnLkCAqkxz34hjzFxqms1vu9XBJ_5',
@@ -117,7 +117,7 @@ class NinaProtocolIE(InfoExtractor):
'uploader': 'ppmrecs',
'channel_id': '4ceG4zsb7VVxBTGPtZMqDZWGHo3VUg2xRvzC2b17ymWP',
'upload_date': '20231201',
- }
+ },
}, {
'info_dict': {
'id': '3SvsMM3y4oTPZ5DXFJnLkCAqkxz34hjzFxqms1vu9XBJ_6',
diff --git a/yt_dlp/extractor/ninecninemedia.py b/yt_dlp/extractor/ninecninemedia.py
index 579370f1b..93e28624b 100644
--- a/yt_dlp/extractor/ninecninemedia.py
+++ b/yt_dlp/extractor/ninecninemedia.py
@@ -23,7 +23,7 @@ class NineCNineMediaIE(InfoExtractor):
title = content['Name']
content_package = content['ContentPackages'][0]
package_id = content_package['Id']
- content_package_url = api_base_url + 'contentpackages/%s/' % package_id
+ content_package_url = api_base_url + f'contentpackages/{package_id}/'
content_package = self._download_json(
content_package_url, content_id, query={
'$include': '[HasClosedCaptions]',
@@ -91,7 +91,7 @@ class NineCNineMediaIE(InfoExtractor):
}, {
'url': manifest_base_url + 'srt',
'ext': 'srt',
- }]
+ }],
}
return info
@@ -118,13 +118,13 @@ class CPTwentyFourIE(InfoExtractor):
'thumbnail': 'http://images2.9c9media.com/image_asset/2014_11_5_2eb609a0-475b-0132-fbd6-34b52f6f1279_jpg_2000x1125.jpg',
'upload_date': '20211122',
},
- 'params': {'skip_download': True, 'format': 'bv'}
+ 'params': {'skip_download': True, 'format': 'bv'},
}]
def _real_extract(self, url):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id)
- id, destination = self._search_regex(
+ video_id, destination = self._search_regex(
r'getAuthStates\("(?P<id>[^"]+)",\s?"(?P<destination>[^"]+)"\);',
webpage, 'video id and destination', group=('id', 'destination'))
- return self.url_result(f'9c9media:{destination}:{id}', ie=NineCNineMediaIE.ie_key(), video_id=id)
+ return self.url_result(f'9c9media:{destination}:{video_id}', NineCNineMediaIE, video_id)
diff --git a/yt_dlp/extractor/ninegag.py b/yt_dlp/extractor/ninegag.py
index 865ad99ac..2979f3a50 100644
--- a/yt_dlp/extractor/ninegag.py
+++ b/yt_dlp/extractor/ninegag.py
@@ -29,7 +29,7 @@ class NineGagIE(InfoExtractor):
'like_count': int,
'dislike_count': int,
'comment_count': int,
- }
+ },
}, {
# HTML escaped title
'url': 'https://9gag.com/gag/av5nvyb',
@@ -53,14 +53,14 @@ class NineGagIE(InfoExtractor):
'uploader': 'Peter Klaus',
'uploader_id': 'peterklaus12',
'uploader_url': 'https://9gag.com/u/peterklaus12',
- }
+ },
}]
def _real_extract(self, url):
post_id = self._match_id(url)
post = self._download_json(
'https://9gag.com/v1/post', post_id, query={
- 'id': post_id
+ 'id': post_id,
})['data']['post']
if post.get('type') != 'Animated':
diff --git a/yt_dlp/extractor/ninenews.py b/yt_dlp/extractor/ninenews.py
index 0b4f47b48..08d7fe47d 100644
--- a/yt_dlp/extractor/ninenews.py
+++ b/yt_dlp/extractor/ninenews.py
@@ -21,7 +21,7 @@ class NineNewsIE(InfoExtractor):
'upload_date': '20231222',
'uploader_id': '664969388001',
'tags': ['networkclip', 'aunews_aunationalninenews', 'christmas presents', 'toys', 'fair trading', 'au_news'],
- }
+ },
}, {
'url': 'https://www.9news.com.au/world/tape-reveals-donald-trump-pressured-michigan-officials-not-to-certify-2020-vote-a-new-report-says/0b8b880e-7d3c-41b9-b2bd-55bc7e492259',
'md5': 'a885c44d20898c3e70e9a53e8188cea1',
@@ -36,7 +36,7 @@ class NineNewsIE(InfoExtractor):
'upload_date': '20231220',
'uploader_id': '664969388001',
'tags': ['networkclip', 'aunews_aunationalninenews', 'ineligible', 'presidential candidate', 'donald trump', 'au_news'],
- }
+ },
}, {
'url': 'https://www.9news.com.au/national/outrage-as-parents-banned-from-giving-gifts-to-kindergarten-teachers/e19b49d4-a1a4-4533-9089-6e10e2d9386a',
'info_dict': {
diff --git a/yt_dlp/extractor/ninenow.py b/yt_dlp/extractor/ninenow.py
index b7170b0e7..f17531e62 100644
--- a/yt_dlp/extractor/ninenow.py
+++ b/yt_dlp/extractor/ninenow.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
float_or_none,
@@ -55,7 +54,7 @@ class NineNowIE(InfoExtractor):
'expected_warnings': ['Ignoring subtitle tracks'],
'params': {
'skip_download': True,
- }
+ },
}]
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/4460760524001/default_default/index.html?videoId=%s'
@@ -72,15 +71,15 @@ class NineNowIE(InfoExtractor):
for kind in ('episode', 'clip'):
current_key = page_data.get(kind, {}).get(
- 'current%sKey' % kind.capitalize())
+ f'current{kind.capitalize()}Key')
if not current_key:
continue
- cache = page_data.get(kind, {}).get('%sCache' % kind, {})
+ cache = page_data.get(kind, {}).get(f'{kind}Cache', {})
if not cache:
continue
common_data = {
- 'episode': (cache.get(current_key) or list(cache.values())[0])[kind],
- 'season': (cache.get(current_key) or list(cache.values())[0]).get('season', None)
+ 'episode': (cache.get(current_key) or next(iter(cache.values())))[kind],
+ 'season': (cache.get(current_key) or next(iter(cache.values()))).get('season', None),
}
break
else:
@@ -89,14 +88,14 @@ class NineNowIE(InfoExtractor):
if not self.get_param('allow_unplayable_formats') and try_get(common_data, lambda x: x['episode']['video']['drm'], bool):
self.report_drm(display_id)
brightcove_id = try_get(
- common_data, lambda x: x['episode']['video']['brightcoveId'], compat_str) or 'ref:%s' % common_data['episode']['video']['referenceId']
+ common_data, lambda x: x['episode']['video']['brightcoveId'], str) or 'ref:{}'.format(common_data['episode']['video']['referenceId'])
video_id = str_or_none(try_get(common_data, lambda x: x['episode']['video']['id'])) or brightcove_id
- title = try_get(common_data, lambda x: x['episode']['name'], compat_str)
+ title = try_get(common_data, lambda x: x['episode']['name'], str)
season_number = try_get(common_data, lambda x: x['season']['seasonNumber'], int)
episode_number = try_get(common_data, lambda x: x['episode']['episodeNumber'], int)
- timestamp = unified_timestamp(try_get(common_data, lambda x: x['episode']['airDate'], compat_str))
- release_date = unified_strdate(try_get(common_data, lambda x: x['episode']['availability'], compat_str))
+ timestamp = unified_timestamp(try_get(common_data, lambda x: x['episode']['airDate'], str))
+ release_date = unified_strdate(try_get(common_data, lambda x: x['episode']['availability'], str))
thumbnails_data = try_get(common_data, lambda x: x['episode']['image']['sizes'], dict) or {}
thumbnails = [{
'id': thumbnail_id,
@@ -111,7 +110,7 @@ class NineNowIE(InfoExtractor):
{'geo_countries': self._GEO_COUNTRIES}),
'id': video_id,
'title': title,
- 'description': try_get(common_data, lambda x: x['episode']['description'], compat_str),
+ 'description': try_get(common_data, lambda x: x['episode']['description'], str),
'duration': float_or_none(try_get(common_data, lambda x: x['episode']['video']['duration'], float), 1000),
'thumbnails': thumbnails,
'ie_key': 'BrightcoveNew',
diff --git a/yt_dlp/extractor/nintendo.py b/yt_dlp/extractor/nintendo.py
index 853a169bb..d8eb85306 100644
--- a/yt_dlp/extractor/nintendo.py
+++ b/yt_dlp/extractor/nintendo.py
@@ -91,7 +91,7 @@ class NintendoIE(InfoExtractor):
'extensions': json.dumps({
'persistedQuery': {
'version': 1,
- 'sha256Hash': '969b16fe9f08b686fa37bc44d1fd913b6188e65794bb5e341c54fa683a8004cb'
+ 'sha256Hash': '969b16fe9f08b686fa37bc44d1fd913b6188e65794bb5e341c54fa683a8004cb',
},
}, separators=(',', ':')),
})
diff --git a/yt_dlp/extractor/nitter.py b/yt_dlp/extractor/nitter.py
index 249e7cd33..7609b4017 100644
--- a/yt_dlp/extractor/nitter.py
+++ b/yt_dlp/extractor/nitter.py
@@ -1,8 +1,8 @@
import random
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
determine_ext,
parse_count,
@@ -265,7 +265,7 @@ class NitterIE(InfoExtractor):
'like_count': int,
'repost_count': int,
'comment_count': int,
- }
+ },
}, { # no OpenGraph title
'url': f'https://{current_instance}/LocalBateman/status/1678455464038735895#m',
'info_dict': {
@@ -286,12 +286,12 @@ class NitterIE(InfoExtractor):
},
'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest'],
'params': {'skip_download': 'm3u8'},
- }
+ },
]
def _real_extract(self, url):
video_id, uploader_id = self._match_valid_url(url).group('id', 'uploader_id')
- parsed_url = compat_urlparse.urlparse(url)
+ parsed_url = urllib.parse.urlparse(url)
base_url = f'{parsed_url.scheme}://{parsed_url.netloc}'
self._set_cookie(parsed_url.netloc, 'hlsPlayback', 'on')
@@ -301,7 +301,7 @@ class NitterIE(InfoExtractor):
if main_tweet_start > 0:
webpage = full_webpage[main_tweet_start:]
- video_url = '%s%s' % (base_url, self._html_search_regex(
+ video_url = '{}{}'.format(base_url, self._html_search_regex(
r'(?:<video[^>]+data-url|<source[^>]+src)="([^"]+)"', webpage, 'video url'))
ext = determine_ext(video_url)
@@ -310,7 +310,7 @@ class NitterIE(InfoExtractor):
else:
formats = [{
'url': video_url,
- 'ext': ext
+ 'ext': ext,
}]
title = description = self._og_search_description(full_webpage, default=None) or self._html_search_regex(
@@ -334,12 +334,12 @@ class NitterIE(InfoExtractor):
thumbnail = (
self._html_search_meta('og:image', full_webpage, 'thumbnail url')
- or remove_end('%s%s' % (base_url, self._html_search_regex(
+ or remove_end('{}{}'.format(base_url, self._html_search_regex(
r'<video[^>]+poster="([^"]+)"', webpage, 'thumbnail url', fatal=False)), '%3Asmall'))
thumbnails = [
- {'id': id, 'url': f'{thumbnail}%3A{id}'}
- for id in ('thumb', 'small', 'large', 'medium', 'orig')
+ {'id': id_, 'url': f'{thumbnail}%3A{id_}'}
+ for id_ in ('thumb', 'small', 'large', 'medium', 'orig')
]
date = self._html_search_regex(
diff --git a/yt_dlp/extractor/nobelprize.py b/yt_dlp/extractor/nobelprize.py
index 513529bea..536ca27f7 100644
--- a/yt_dlp/extractor/nobelprize.py
+++ b/yt_dlp/extractor/nobelprize.py
@@ -20,7 +20,7 @@ class NobelPrizeIE(InfoExtractor):
'ext': 'mp4',
'title': 'Announcement of the 2016 Nobel Prize in Physics',
'description': 'md5:05beba57f4f5a4bbd4cf2ef28fcff739',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/noice.py b/yt_dlp/extractor/noice.py
index e6e343303..f413055b3 100644
--- a/yt_dlp/extractor/noice.py
+++ b/yt_dlp/extractor/noice.py
@@ -35,7 +35,7 @@ class NoicePodcastIE(InfoExtractor):
'comment_count': int,
'dislike_count': int,
'channel_follower_count': int,
- }
+ },
}, {
'url': 'https://open.noice.id/content/222134e4-99f2-456f-b8a2-b8be404bf063',
'info_dict': {
@@ -60,7 +60,7 @@ class NoicePodcastIE(InfoExtractor):
'comment_count': int,
'channel': 'Dear Jerome',
'channel_follower_count': int,
- }
+ },
}]
def _get_formats_and_subtitles(self, media_url, video_id):
@@ -112,5 +112,5 @@ class NoicePodcastIE(InfoExtractor):
'dislike_count': 'dislikes',
'comment_count': 'comments',
'channel_follower_count': 'followers',
- }))
+ })),
}
diff --git a/yt_dlp/extractor/nonktube.py b/yt_dlp/extractor/nonktube.py
index f191be33b..192e6bb52 100644
--- a/yt_dlp/extractor/nonktube.py
+++ b/yt_dlp/extractor/nonktube.py
@@ -14,7 +14,7 @@ class NonkTubeIE(NuevoBaseIE):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.nonktube.com/embed/118636',
'only_matching': True,
diff --git a/yt_dlp/extractor/noodlemagazine.py b/yt_dlp/extractor/noodlemagazine.py
index 1c1a763dc..6414f46ef 100644
--- a/yt_dlp/extractor/noodlemagazine.py
+++ b/yt_dlp/extractor/noodlemagazine.py
@@ -25,8 +25,8 @@ class NoodleMagazineIE(InfoExtractor):
'description': 'Aria alexander manojob',
'tags': ['aria', 'alexander', 'manojob'],
'upload_date': '20190218',
- 'age_limit': 18
- }
+ 'age_limit': 18,
+ },
}
def _real_extract(self, url):
@@ -76,5 +76,5 @@ class NoodleMagazineIE(InfoExtractor):
'view_count': view_count,
'like_count': like_count,
'upload_date': upload_date,
- 'age_limit': 18
+ 'age_limit': 18,
}
diff --git a/yt_dlp/extractor/noovo.py b/yt_dlp/extractor/noovo.py
index acbb74c6e..772d4ed9e 100644
--- a/yt_dlp/extractor/noovo.py
+++ b/yt_dlp/extractor/noovo.py
@@ -1,6 +1,5 @@
from .brightcove import BrightcoveNewIE
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
js_to_json,
@@ -65,7 +64,7 @@ class NoovoIE(InfoExtractor):
title = try_get(
data, lambda x: x['video']['nom'],
- compat_str) or self._html_search_meta(
+ str) or self._html_search_meta(
'dcterms.Title', webpage, 'title', fatal=True)
description = self._html_search_meta(
@@ -77,11 +76,11 @@ class NoovoIE(InfoExtractor):
webpage, 'series', default=None)
season_el = try_get(data, lambda x: x['emission']['saison'], dict) or {}
- season = try_get(season_el, lambda x: x['nom'], compat_str)
+ season = try_get(season_el, lambda x: x['nom'], str)
season_number = int_or_none(try_get(season_el, lambda x: x['numero']))
episode_el = try_get(season_el, lambda x: x['episode'], dict) or {}
- episode = try_get(episode_el, lambda x: x['nom'], compat_str)
+ episode = try_get(episode_el, lambda x: x['nom'], str)
episode_number = int_or_none(try_get(episode_el, lambda x: x['numero']))
return {
diff --git a/yt_dlp/extractor/nosnl.py b/yt_dlp/extractor/nosnl.py
index cea54c98e..13f908c2c 100644
--- a/yt_dlp/extractor/nosnl.py
+++ b/yt_dlp/extractor/nosnl.py
@@ -15,7 +15,7 @@ class NOSNLArticleIE(InfoExtractor):
'title': '\'We hebben een huis vol met scheuren\'',
'duration': 95.0,
'thumbnail': 'https://cdn.nos.nl/image/2022/08/12/887149/3840x2160a.jpg',
- }
+ },
}, {
# more than 1 video
'url': 'https://nos.nl/artikel/2440409-vannacht-sliepen-weer-enkele-honderden-asielzoekers-in-ter-apel-buiten',
@@ -64,7 +64,7 @@ class NOSNLArticleIE(InfoExtractor):
'categories': ['Buitenland'],
},
'playlist_mincount': 1,
- }
+ },
]
def _entries(self, nextjs_json, display_id):
@@ -82,7 +82,7 @@ class NOSNLArticleIE(InfoExtractor):
'thumbnails': [{
'url': traverse_obj(image, ('url', ...), get_all=False),
'width': image.get('width'),
- 'height': image.get('height')
+ 'height': image.get('height'),
} for image in traverse_obj(item, ('imagesByRatio', ...))[0]],
}
diff --git a/yt_dlp/extractor/nova.py b/yt_dlp/extractor/nova.py
index 72884aaaa..e7b69e370 100644
--- a/yt_dlp/extractor/nova.py
+++ b/yt_dlp/extractor/nova.py
@@ -58,7 +58,7 @@ class NovaEmbedIE(InfoExtractor):
duration = None
formats = []
- def process_format_list(format_list, format_id=""):
+ def process_format_list(format_list, format_id=''):
nonlocal formats, has_drm
if not isinstance(format_list, list):
format_list = [format_list]
@@ -144,7 +144,7 @@ class NovaIE(InfoExtractor):
'description': 'md5:f0a42dd239c26f61c28f19e62d20ef53',
'thumbnail': r're:^https?://.*\.(?:jpg)',
'duration': 151,
- }
+ },
}, {
'url': 'http://fanda.nova.cz/clanek/fun-and-games/krvavy-epos-zaklinac-3-divoky-hon-vychazi-vyhrajte-ho-pro-sebe.html',
'info_dict': {
@@ -216,11 +216,11 @@ class NovaIE(InfoExtractor):
if embed_id:
return {
'_type': 'url_transparent',
- 'url': 'https://media.cms.nova.cz/embed/%s' % embed_id,
+ 'url': f'https://media.cms.nova.cz/embed/{embed_id}',
'ie_key': NovaEmbedIE.ie_key(),
'id': embed_id,
'description': description,
- 'upload_date': upload_date
+ 'upload_date': upload_date,
}
video_id = self._search_regex(
diff --git a/yt_dlp/extractor/novaplay.py b/yt_dlp/extractor/novaplay.py
index adab33f59..77d7ce186 100644
--- a/yt_dlp/extractor/novaplay.py
+++ b/yt_dlp/extractor/novaplay.py
@@ -34,7 +34,7 @@ class NovaPlayIE(InfoExtractor):
'thumbnail': 'https://nbg-img.fite.tv/img/606609_460x260.jpg',
'description': '29 сек',
},
- }
+ },
]
_access_token = None
@@ -50,7 +50,7 @@ class NovaPlayIE(InfoExtractor):
video_id, headers={
'x-flipps-user-agent': 'Flipps/75/9.7',
'x-flipps-version': '2022-05-17',
- 'Authorization': f'Bearer {self._access_token}'
+ 'Authorization': f'Bearer {self._access_token}',
})[0]['links']['play']['href']
formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', m3u8_id='hls')
diff --git a/yt_dlp/extractor/nowness.py b/yt_dlp/extractor/nowness.py
index a3c29f62c..c001a82e9 100644
--- a/yt_dlp/extractor/nowness.py
+++ b/yt_dlp/extractor/nowness.py
@@ -3,7 +3,6 @@ from .brightcove import (
BrightcoveNewIE,
)
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking import Request
from ..utils import ExtractorError
@@ -17,7 +16,7 @@ class NownessBaseIE(InfoExtractor):
source = media['source']
if source == 'brightcove':
player_code = self._download_webpage(
- 'http://www.nowness.com/iframe?id=%s' % video_id, video_id,
+ f'http://www.nowness.com/iframe?id={video_id}', video_id,
note='Downloading player JavaScript',
errnote='Unable to download player JavaScript')
bc_url = BrightcoveLegacyIE._extract_brightcove_url(player_code)
@@ -28,7 +27,7 @@ class NownessBaseIE(InfoExtractor):
return self.url_result(bc_url, BrightcoveNewIE.ie_key())
raise ExtractorError('Could not find player definition')
elif source == 'vimeo':
- return self.url_result('http://vimeo.com/%s' % video_id, 'Vimeo')
+ return self.url_result(f'http://vimeo.com/{video_id}', 'Vimeo')
elif source == 'youtube':
return self.url_result(video_id, 'Youtube')
elif source == 'cinematique':
@@ -139,4 +138,4 @@ class NownessSeriesIE(NownessBaseIE):
series_title = translations[0].get('title') or translations[0]['seoTitle']
series_description = translations[0].get('seoDescription')
return self.playlist_result(
- entries, compat_str(series['id']), series_title, series_description)
+ entries, str(series['id']), series_title, series_description)
diff --git a/yt_dlp/extractor/noz.py b/yt_dlp/extractor/noz.py
index 19cb972c0..8476a857f 100644
--- a/yt_dlp/extractor/noz.py
+++ b/yt_dlp/extractor/noz.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
find_xpath_attr,
int_or_none,
@@ -35,9 +36,9 @@ class NozIE(InfoExtractor):
config_url_encoded = self._search_regex(
r'so\.addVariable\("config_url","[^,]*,(.*?)"',
- edge_content, 'config URL'
+ edge_content, 'config URL',
)
- config_url = compat_urllib_parse_unquote(config_url_encoded)
+ config_url = urllib.parse.unquote(config_url_encoded)
doc = self._download_xml(config_url, 'video configuration')
title = xpath_text(doc, './/title')
@@ -53,7 +54,7 @@ class NozIE(InfoExtractor):
formats.append({
'url': http_url,
'format_name': xpath_text(qnode, './name'),
- 'format_id': '%s-%s' % ('http', xpath_text(qnode, './id')),
+ 'format_id': '{}-{}'.format('http', xpath_text(qnode, './id')),
'height': int_or_none(xpath_text(qnode, './height')),
'width': int_or_none(xpath_text(qnode, './width')),
'tbr': int_or_none(xpath_text(qnode, './bitrate'), scale=1000),
diff --git a/yt_dlp/extractor/npo.py b/yt_dlp/extractor/npo.py
index 4d5ff50de..178fd98bf 100644
--- a/yt_dlp/extractor/npo.py
+++ b/yt_dlp/extractor/npo.py
@@ -200,7 +200,7 @@ class NPOIE(InfoExtractor):
def suitable(cls, url):
return (False if any(ie.suitable(url)
for ie in (NPOLiveIE, NPORadioIE, NPORadioFragmentIE))
- else super(NPOIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -228,7 +228,7 @@ class NPOIE(InfoExtractor):
'hasAdConsent': 0,
}), headers={
'x-xsrf-token': try_call(lambda: urllib.parse.unquote(
- self._get_cookies('https://www.npostart.nl')['XSRF-TOKEN'].value))
+ self._get_cookies('https://www.npostart.nl')['XSRF-TOKEN'].value)),
})
player_token = player['token']
@@ -238,8 +238,8 @@ class NPOIE(InfoExtractor):
formats = []
for profile in ('hls', 'dash-widevine', 'dash-playready', 'smooth'):
streams = self._download_json(
- 'https://start-player.npo.nl/video/%s/streams' % video_id,
- video_id, 'Downloading %s profile JSON' % profile, fatal=False,
+ f'https://start-player.npo.nl/video/{video_id}/streams',
+ video_id, f'Downloading {profile} profile JSON', fatal=False,
query={
'profile': profile,
'quality': 'npoplus',
@@ -339,7 +339,7 @@ class NPOLiveIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'http://www.npo.nl/live',
'only_matching': True,
@@ -358,7 +358,7 @@ class NPOLiveIE(InfoExtractor):
return {
'_type': 'url_transparent',
- 'url': 'npo:%s' % live_id,
+ 'url': f'npo:{live_id}',
'ie_key': NPOIE.ie_key(),
'id': live_id,
'display_id': display_id,
@@ -379,16 +379,16 @@ class NPORadioIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}
@classmethod
def suitable(cls, url):
- return False if NPORadioFragmentIE.suitable(url) else super(NPORadioIE, cls).suitable(url)
+ return False if NPORadioFragmentIE.suitable(url) else super().suitable(url)
@staticmethod
def _html_get_attribute_regex(attribute):
- return r'{0}\s*=\s*\'([^\']+)\''.format(attribute)
+ return rf'{attribute}\s*=\s*\'([^\']+)\''
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -434,7 +434,7 @@ class NPORadioFragmentIE(InfoExtractor):
webpage = self._download_webpage(url, audio_id)
title = self._html_search_regex(
- r'href="/radio/[^/]+/fragment/%s" title="([^"]+)"' % audio_id,
+ rf'href="/radio/[^/]+/fragment/{audio_id}" title="([^"]+)"',
webpage, 'title')
audio_url = self._search_regex(
@@ -456,8 +456,8 @@ class NPODataMidEmbedIE(InfoExtractor): # XXX: Conventionally, base classes sho
return {
'_type': 'url_transparent',
'ie_key': 'NPO',
- 'url': 'npo:%s' % video_id,
- 'display_id': display_id
+ 'url': f'npo:{video_id}',
+ 'display_id': display_id,
}
@@ -472,12 +472,12 @@ class SchoolTVIE(NPODataMidEmbedIE):
'display_id': 'ademhaling-de-hele-dag-haal-je-adem-maar-wat-gebeurt-er-dan-eigenlijk-in-je-lichaam',
'title': 'Ademhaling: De hele dag haal je adem. Maar wat gebeurt er dan eigenlijk in je lichaam?',
'ext': 'mp4',
- 'description': 'md5:abfa0ff690adb73fd0297fd033aaa631'
+ 'description': 'md5:abfa0ff690adb73fd0297fd033aaa631',
},
'params': {
# Skip because of m3u8 download
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}
@@ -496,8 +496,8 @@ class HetKlokhuisIE(NPODataMidEmbedIE):
'upload_date': '20170223',
},
'params': {
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}
@@ -508,7 +508,7 @@ class NPOPlaylistBaseIE(NPOIE): # XXX: Do not subclass from concrete IE
webpage = self._download_webpage(url, playlist_id)
entries = [
- self.url_result('npo:%s' % video_id if not video_id.startswith('http') else video_id)
+ self.url_result(f'npo:{video_id}' if not video_id.startswith('http') else video_id)
for video_id in orderedSet(re.findall(self._PLAYLIST_ENTRY_RE, webpage))
]
@@ -574,9 +574,9 @@ class VPROIE(NPOPlaylistBaseIE):
},
'params': {
# Skip because of m3u8 download
- 'skip_download': True
+ 'skip_download': True,
},
- }
+ },
]
diff --git a/yt_dlp/extractor/npr.py b/yt_dlp/extractor/npr.py
index 4b6855c93..06103ff38 100644
--- a/yt_dlp/extractor/npr.py
+++ b/yt_dlp/extractor/npr.py
@@ -8,14 +8,14 @@ class NprIE(InfoExtractor):
'url': 'https://www.npr.org/sections/allsongs/2015/10/21/449974205/new-music-from-beach-house-chairlift-cmj-discoveries-and-more',
'info_dict': {
'id': '449974205',
- 'title': 'New Music From Beach House, Chairlift, CMJ Discoveries And More'
+ 'title': 'New Music From Beach House, Chairlift, CMJ Discoveries And More',
},
'playlist_count': 7,
}, {
'url': 'https://www.npr.org/sections/deceptivecadence/2015/10/09/446928052/music-from-the-shadows-ancient-armenian-hymns-and-piano-jazz',
'info_dict': {
'id': '446928052',
- 'title': "Songs We Love: Tigran Hamasyan, 'Your Mercy is Boundless'"
+ 'title': "Songs We Love: Tigran Hamasyan, 'Your Mercy is Boundless'",
},
'playlist': [{
'md5': '12fa60cb2d3ed932f53609d4aeceabf1',
diff --git a/yt_dlp/extractor/nrk.py b/yt_dlp/extractor/nrk.py
index 384865acc..658ae5f91 100644
--- a/yt_dlp/extractor/nrk.py
+++ b/yt_dlp/extractor/nrk.py
@@ -3,7 +3,6 @@ import random
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -53,12 +52,12 @@ class NRKBaseIE(InfoExtractor):
msg=MESSAGES.get('ProgramIsGeoBlocked'),
countries=self._GEO_COUNTRIES)
message = data.get('endUserMessage') or MESSAGES.get(message_type, message_type)
- raise ExtractorError('%s said: %s' % (self.IE_NAME, message), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {message}', expected=True)
def _call_api(self, path, video_id, item=None, note=None, fatal=True, query=None):
return self._download_json(
urljoin('https://psapi.nrk.no/', path),
- video_id, note or 'Downloading %s JSON' % item,
+ video_id, note or f'Downloading {item} JSON',
fatal=fatal, query=query)
@@ -85,7 +84,7 @@ class NRKIE(NRKBaseIE):
'title': 'Dompap og andre fugler i Piip-Show',
'description': 'md5:d9261ba34c43b61c812cb6b0269a5c8f',
'duration': 262,
- }
+ },
}, {
# audio
'url': 'http://www.nrk.no/video/PS*154915',
@@ -96,7 +95,7 @@ class NRKIE(NRKBaseIE):
'title': 'Slik høres internett ut når du er blind',
'description': 'md5:a621f5cc1bd75c8d5104cb048c6b8568',
'duration': 20,
- }
+ },
}, {
'url': 'nrk:ecc1b952-96dc-4a98-81b9-5296dc7a98d9',
'only_matching': True,
@@ -156,7 +155,7 @@ class NRKIE(NRKBaseIE):
# known values for preferredCdn: akamai, iponly, minicdn and telenor
manifest = call_playback_api('manifest', {'preferredCdn': 'akamai'})
- video_id = try_get(manifest, lambda x: x['id'], compat_str) or video_id
+ video_id = try_get(manifest, lambda x: x['id'], str) or video_id
if manifest.get('playability') == 'nonPlayable':
self._raise_error(manifest['nonPlayable'])
@@ -216,13 +215,13 @@ class NRKIE(NRKBaseIE):
sub_key = str_or_none(sub.get('language')) or 'nb'
sub_type = str_or_none(sub.get('type'))
if sub_type:
- sub_key += '-%s' % sub_type
+ sub_key += f'-{sub_type}'
subtitles.setdefault(sub_key, []).append({
'url': sub_url,
})
legal_age = try_get(
- data, lambda x: x['legalAge']['body']['rating']['code'], compat_str)
+ data, lambda x: x['legalAge']['body']['rating']['code'], str)
# https://en.wikipedia.org/wiki/Norwegian_Media_Authority
age_limit = None
if legal_age:
@@ -243,13 +242,13 @@ class NRKIE(NRKBaseIE):
'age_limit': age_limit,
'formats': formats,
'subtitles': subtitles,
- 'timestamp': parse_iso8601(try_get(manifest, lambda x: x['availability']['onDemand']['from'], str))
+ 'timestamp': parse_iso8601(try_get(manifest, lambda x: x['availability']['onDemand']['from'], str)),
}
if is_series:
series = season_id = season_number = episode = episode_number = None
programs = self._call_api(
- 'programs/%s' % video_id, video_id, 'programs', fatal=False)
+ f'programs/{video_id}', video_id, 'programs', fatal=False)
if programs and isinstance(programs, dict):
series = str_or_none(programs.get('seriesTitle'))
season_id = str_or_none(programs.get('seasonId'))
@@ -259,7 +258,7 @@ class NRKIE(NRKBaseIE):
if not series:
series = title
if alt_title:
- title += ' - %s' % alt_title
+ title += f' - {alt_title}'
if not season_number:
season_number = int_or_none(self._search_regex(
r'Sesong\s+(\d+)', description or '', 'season number',
@@ -289,7 +288,7 @@ class NRKIE(NRKBaseIE):
class NRKTVIE(InfoExtractor):
IE_DESC = 'NRK TV and NRK Radio'
_EPISODE_RE = r'(?P<id>[a-zA-Z]{4}\d{8})'
- _VALID_URL = r'https?://(?:tv|radio)\.nrk(?:super)?\.no/(?:[^/]+/)*%s' % _EPISODE_RE
+ _VALID_URL = rf'https?://(?:tv|radio)\.nrk(?:super)?\.no/(?:[^/]+/)*{_EPISODE_RE}'
_TESTS = [{
'url': 'https://tv.nrk.no/program/MDDP12000117',
'md5': 'c4a5960f1b00b40d47db65c1064e0ab1',
@@ -306,7 +305,7 @@ class NRKTVIE(InfoExtractor):
}],
'nb-ttv': [{
'ext': 'vtt',
- }]
+ }],
},
},
}, {
@@ -411,7 +410,7 @@ class NRKTVIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
return self.url_result(
- 'nrk:%s' % video_id, ie=NRKIE.ie_key(), video_id=video_id)
+ f'nrk:{video_id}', ie=NRKIE.ie_key(), video_id=video_id)
class NRKTVEpisodeIE(InfoExtractor):
@@ -461,14 +460,14 @@ class NRKTVEpisodeIE(InfoExtractor):
info = self._search_json_ld(webpage, display_id, default={})
nrk_id = info.get('@id') or self._html_search_meta(
'nrk:program-id', webpage, default=None) or self._search_regex(
- r'data-program-id=["\'](%s)' % NRKTVIE._EPISODE_RE, webpage,
+ rf'data-program-id=["\']({NRKTVIE._EPISODE_RE})', webpage,
'nrk id')
assert re.match(NRKTVIE._EPISODE_RE, nrk_id)
info.update({
'_type': 'url',
'id': nrk_id,
- 'url': 'nrk:%s' % nrk_id,
+ 'url': f'nrk:{nrk_id}',
'ie_key': NRKIE.ie_key(),
'season_number': int(season_number),
'episode_number': int(episode_number),
@@ -483,13 +482,13 @@ class NRKTVSerieBaseIE(NRKBaseIE):
entries = []
for episode in entry_list:
nrk_id = episode.get('prfId') or episode.get('episodeId')
- if not nrk_id or not isinstance(nrk_id, compat_str):
+ if not nrk_id or not isinstance(nrk_id, str):
continue
entries.append(self.url_result(
- 'nrk:%s' % nrk_id, ie=NRKIE.ie_key(), video_id=nrk_id))
+ f'nrk:{nrk_id}', ie=NRKIE.ie_key(), video_id=nrk_id))
return entries
- _ASSETS_KEYS = ('episodes', 'instalments',)
+ _ASSETS_KEYS = ('episodes', 'instalments')
def _extract_assets_key(self, embedded):
for asset_key in self._ASSETS_KEYS:
@@ -514,19 +513,18 @@ class NRKTVSerieBaseIE(NRKBaseIE):
(lambda x: x[assets_key]['_embedded'][assets_key],
lambda x: x[assets_key]),
list)
- for e in self._extract_entries(entries):
- yield e
+ yield from self._extract_entries(entries)
# Find next URL
next_url_path = try_get(
data,
(lambda x: x['_links']['next']['href'],
lambda x: x['_embedded'][assets_key]['_links']['next']['href']),
- compat_str)
+ str)
if not next_url_path:
break
data = self._call_api(
next_url_path, display_id,
- note='Downloading %s JSON page %d' % (assets_key, page_num),
+ note=f'Downloading {assets_key} JSON page {page_num}',
fatal=False)
if not data:
break
@@ -593,7 +591,7 @@ class NRKTVSeasonIE(NRKTVSerieBaseIE):
@classmethod
def suitable(cls, url):
return (False if NRKTVIE.suitable(url) or NRKTVEpisodeIE.suitable(url) or NRKRadioPodkastIE.suitable(url)
- else super(NRKTVSeasonIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -601,14 +599,13 @@ class NRKTVSeasonIE(NRKTVSerieBaseIE):
serie_kind = mobj.group('serie_kind')
serie = mobj.group('serie')
season_id = mobj.group('id') or mobj.group('id_2')
- display_id = '%s/%s' % (serie, season_id)
+ display_id = f'{serie}/{season_id}'
data = self._call_api(
- '%s/catalog/%s/%s/seasons/%s'
- % (domain, self._catalog_name(serie_kind), serie, season_id),
+ f'{domain}/catalog/{self._catalog_name(serie_kind)}/{serie}/seasons/{season_id}',
display_id, 'season', query={'pageSize': 50})
- title = try_get(data, lambda x: x['titles']['title'], compat_str) or display_id
+ title = try_get(data, lambda x: x['titles']['title'], str) or display_id
return self.playlist_result(
self._entries(data, display_id),
display_id, title)
@@ -691,7 +688,7 @@ class NRKTVSeriesIE(NRKTVSerieBaseIE):
return (
False if any(ie.suitable(url)
for ie in (NRKTVIE, NRKTVEpisodeIE, NRKRadioPodkastIE, NRKTVSeasonIE))
- else super(NRKTVSeriesIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
site, serie_kind, series_id = self._match_valid_url(url).groups()
@@ -700,8 +697,7 @@ class NRKTVSeriesIE(NRKTVSerieBaseIE):
size_prefix = 'p' if is_radio else 'embeddedInstalmentsP'
series = self._call_api(
- '%s/catalog/%s/%s'
- % (domain, self._catalog_name(serie_kind), series_id),
+ f'{domain}/catalog/{self._catalog_name(serie_kind)}/{series_id}',
series_id, 'serie', query={size_prefix + 'ageSize': 50})
titles = try_get(series, [
lambda x: x['titles'],
@@ -719,8 +715,8 @@ class NRKTVSeriesIE(NRKTVSerieBaseIE):
season_url = urljoin(url, season.get('href'))
if not season_url:
season_name = season.get('name')
- if season_name and isinstance(season_name, compat_str):
- season_url = 'https://%s.nrk.no/serie/%s/sesong/%s' % (domain, series_id, season_name)
+ if season_name and isinstance(season_name, str):
+ season_url = f'https://{domain}.nrk.no/serie/{series_id}/sesong/{season_name}'
if season_url:
entries.append(self.url_result(
season_url, ie=NRKTVSeasonIE.ie_key(),
@@ -777,7 +773,7 @@ class NRKRadioPodkastIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
return self.url_result(
- 'nrk:%s' % video_id, ie=NRKIE.ie_key(), video_id=video_id)
+ f'nrk:{video_id}', ie=NRKIE.ie_key(), video_id=video_id)
class NRKPlaylistBaseIE(InfoExtractor):
@@ -790,7 +786,7 @@ class NRKPlaylistBaseIE(InfoExtractor):
webpage = self._download_webpage(url, playlist_id)
entries = [
- self.url_result('nrk:%s' % video_id, NRKIE.ie_key())
+ self.url_result(f'nrk:{video_id}', NRKIE.ie_key())
for video_id in re.findall(self._ITEM_RE, webpage)
]
@@ -831,7 +827,7 @@ class NRKPlaylistIE(NRKPlaylistBaseIE):
class NRKTVEpisodesIE(NRKPlaylistBaseIE):
_VALID_URL = r'https?://tv\.nrk\.no/program/[Ee]pisodes/[^/]+/(?P<id>\d+)'
- _ITEM_RE = r'data-episode=["\']%s' % NRKTVIE._EPISODE_RE
+ _ITEM_RE = rf'data-episode=["\']{NRKTVIE._EPISODE_RE}'
_TESTS = [{
'url': 'https://tv.nrk.no/program/episodes/nytt-paa-nytt/69031',
'info_dict': {
@@ -869,7 +865,7 @@ class NRKSkoleIE(InfoExtractor):
video_id = self._match_id(url)
nrk_id = self._download_json(
- 'https://nrkno-skole-prod.kube.nrk.no/skole/api/media/%s' % video_id,
+ f'https://nrkno-skole-prod.kube.nrk.no/skole/api/media/{video_id}',
video_id)['psId']
- return self.url_result('nrk:%s' % nrk_id)
+ return self.url_result(f'nrk:{nrk_id}')
diff --git a/yt_dlp/extractor/ntvru.py b/yt_dlp/extractor/ntvru.py
index fe3965729..1ab1be0f6 100644
--- a/yt_dlp/extractor/ntvru.py
+++ b/yt_dlp/extractor/ntvru.py
@@ -102,7 +102,7 @@ class NTVRuIE(InfoExtractor):
self._VIDEO_ID_REGEXES, webpage, 'video id')
player = self._download_xml(
- 'http://www.ntv.ru/vi%s/' % video_id,
+ f'http://www.ntv.ru/vi{video_id}/',
video_id, 'Downloading video XML')
title = strip_or_none(unescapeHTML(xpath_text(player, './data/title', 'title', fatal=True)))
@@ -111,7 +111,7 @@ class NTVRuIE(InfoExtractor):
formats = []
for format_id in ['', 'hi', 'webm']:
- file_ = xpath_text(video, './%sfile' % format_id)
+ file_ = xpath_text(video, f'./{format_id}file')
if not file_:
continue
if file_.startswith('//'):
@@ -120,7 +120,7 @@ class NTVRuIE(InfoExtractor):
file_ = 'http://media.ntv.ru/vod/' + file_
formats.append({
'url': file_,
- 'filesize': int_or_none(xpath_text(video, './%ssize' % format_id)),
+ 'filesize': int_or_none(xpath_text(video, f'./{format_id}size')),
})
hls_manifest = xpath_text(video, './playback/hls')
if hls_manifest:
diff --git a/yt_dlp/extractor/nubilesporn.py b/yt_dlp/extractor/nubilesporn.py
index 1d630f547..c2079d8b0 100644
--- a/yt_dlp/extractor/nubilesporn.py
+++ b/yt_dlp/extractor/nubilesporn.py
@@ -51,8 +51,8 @@ class NubilesPornIE(InfoExtractor):
'season': 'Season 3',
'season_number': 3,
'episode': 'Episode 1',
- 'episode_number': 1
- }
+ 'episode_number': 1,
+ },
}]
def _perform_login(self, username, password):
@@ -95,5 +95,5 @@ class NubilesPornIE(InfoExtractor):
'series': channel_name,
'series_id': channel_id,
'season_number': int_or_none(url_match.group('season')),
- 'episode_number': int_or_none(url_match.group('episode'))
+ 'episode_number': int_or_none(url_match.group('episode')),
}
diff --git a/yt_dlp/extractor/nuevo.py b/yt_dlp/extractor/nuevo.py
index 5670445aa..945fd0c9c 100644
--- a/yt_dlp/extractor/nuevo.py
+++ b/yt_dlp/extractor/nuevo.py
@@ -28,5 +28,5 @@ class NuevoBaseIE(InfoExtractor):
'title': title,
'thumbnail': thumbnail,
'duration': duration,
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/nuvid.py b/yt_dlp/extractor/nuvid.py
index 0ef0ec70b..99a426b25 100644
--- a/yt_dlp/extractor/nuvid.py
+++ b/yt_dlp/extractor/nuvid.py
@@ -22,7 +22,7 @@ class NuvidIE(InfoExtractor):
'duration': 321.0,
'age_limit': 18,
'thumbnail': r're:https?://.+\.jpg',
- }
+ },
}, {
'url': 'https://m.nuvid.com/video/6523263',
'md5': 'ebd22ce8e47e1d9a4d0756a15c67da52',
@@ -34,7 +34,7 @@ class NuvidIE(InfoExtractor):
'age_limit': 18,
'thumbnail': r're:https?://.+\.jpg',
'thumbnails': list,
- }
+ },
}, {
'url': 'http://m.nuvid.com/video/6415801/',
'md5': '638d5ececb138d5753593f751ae3f697',
@@ -45,7 +45,7 @@ class NuvidIE(InfoExtractor):
'duration': 1882,
'age_limit': 18,
'thumbnail': r're:https?://.+\.jpg',
- }
+ },
}]
def _real_extract(self, url):
@@ -64,7 +64,7 @@ class NuvidIE(InfoExtractor):
})
webpage = self._download_webpage(
- 'http://m.nuvid.com/video/%s' % (video_id, ),
+ f'http://m.nuvid.com/video/{video_id}',
video_id, 'Downloading video page', fatal=False) or ''
title = strip_or_none(video_data.get('title') or self._html_search_regex(
diff --git a/yt_dlp/extractor/nytimes.py b/yt_dlp/extractor/nytimes.py
index 3019202a2..5ec3cdd67 100644
--- a/yt_dlp/extractor/nytimes.py
+++ b/yt_dlp/extractor/nytimes.py
@@ -251,7 +251,7 @@ class NYTimesArticleIE(NYTimesBaseIE):
'thumbnails': self._extract_thumbnails(traverse_obj(
block, ('promotionalMedia', 'crops', ..., 'renditions', ...))),
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/nzherald.py b/yt_dlp/extractor/nzherald.py
index 0a12aea71..7c09e9ee8 100644
--- a/yt_dlp/extractor/nzherald.py
+++ b/yt_dlp/extractor/nzherald.py
@@ -2,7 +2,6 @@ import json
from .brightcove import BrightcoveNewIE
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import ExtractorError, traverse_obj
@@ -24,7 +23,7 @@ class NZHeraldIE(InfoExtractor):
'tags': [],
'thumbnail': r're:https?://.*\.jpg$',
'description': 'md5:2f17713fcbfcfbe38bb9e7dfccbb0f2e',
- }
+ },
}, {
# Webpage has brightcove embed player url
'url': 'https://www.nzherald.co.nz/travel/pencarrow-coastal-trail/HDVTPJEPP46HJ2UEMK4EGD2DFI/',
@@ -39,7 +38,7 @@ class NZHeraldIE(InfoExtractor):
'thumbnail': r're:https?://.*\.jpg$',
'tags': ['travel', 'video'],
'duration': 43.627,
- }
+ },
}, {
# two video embeds of the same video
'url': 'https://www.nzherald.co.nz/nz/truck-driver-captured-cutting-off-motorist-on-state-highway-1-in-canterbury/FIHNJB7PLLPHWQPK4S7ZBDUC4I/',
@@ -50,7 +49,7 @@ class NZHeraldIE(InfoExtractor):
'timestamp': 1619730509,
'upload_date': '20210429',
'uploader_id': '1308227299001',
- 'description': 'md5:4cae7dfb7613ac4c73b9e73a75c6b5d7'
+ 'description': 'md5:4cae7dfb7613ac4c73b9e73a75c6b5d7',
},
'skip': 'video removed',
}, {
@@ -67,17 +66,17 @@ class NZHeraldIE(InfoExtractor):
'tags': ['video', 'nz herald focus', 'politics', 'politics videos'],
'thumbnail': r're:https?://.*\.jpg$',
'duration': 99.584,
- }
+ },
}, {
'url': 'https://www.nzherald.co.nz/kahu/kaupapa-companies-my-taiao-supporting-maori-in-study-and-business/PQBO2J25WCG77VGRX7W7BVYEAI/',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://nzherald.co.nz/the-country/video/focus-nzs-first-mass-covid-19-vaccination-event/N5I7IL3BRFLZSD33TLDLYJDGK4/',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.nzherald.co.nz/the-vision-is-clear/news/tvic-damian-roper-planting-trees-an-addiction/AN2AAEPNRK5VLISDWQAJZB6ATQ',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/1308227299001/S1BXZn8t_default/index.html?videoId=%s'
@@ -86,7 +85,7 @@ class NZHeraldIE(InfoExtractor):
"""The initial webpage may include the brightcove player embed url"""
bc_url = BrightcoveNewIE._extract_url(self, webpage)
return bc_url or self._search_regex(
- r'(?:embedUrl)\"\s*:\s*\"(?P<embed_url>%s)' % BrightcoveNewIE._VALID_URL,
+ rf'(?:embedUrl)\"\s*:\s*\"(?P<embed_url>{BrightcoveNewIE._VALID_URL})',
webpage, 'embed url', default=None, group='embed_url')
def _real_extract(self, url):
@@ -108,7 +107,7 @@ class NZHeraldIE(InfoExtractor):
bc_video_id = traverse_obj(
video_metadata or fusion_metadata, # fusion metadata is the video metadata for video-only pages
'brightcoveId', ('content_elements', ..., 'referent', 'id'),
- get_all=False, expected_type=compat_str)
+ get_all=False, expected_type=str)
if not bc_video_id:
if isinstance(video_metadata, dict) and len(video_metadata) == 0:
diff --git a/yt_dlp/extractor/nzonscreen.py b/yt_dlp/extractor/nzonscreen.py
index bf2dbca59..5fc516daf 100644
--- a/yt_dlp/extractor/nzonscreen.py
+++ b/yt_dlp/extractor/nzonscreen.py
@@ -89,5 +89,5 @@ class NZOnScreenIE(InfoExtractor):
'http_headers': {
'Referer': 'https://www.nzonscreen.com/',
'Origin': 'https://www.nzonscreen.com/',
- }
+ },
}
diff --git a/yt_dlp/extractor/odkmedia.py b/yt_dlp/extractor/odkmedia.py
index 8321b0741..766cb941b 100644
--- a/yt_dlp/extractor/odkmedia.py
+++ b/yt_dlp/extractor/odkmedia.py
@@ -24,7 +24,7 @@ class OnDemandChinaEpisodeIE(InfoExtractor):
'thumbnail': 'https://d2y2efdi5wgkcl.cloudfront.net/fit-in/256x256/media-io/2020/9/11/image.d9816e81.jpg',
'description': '疫情严峻,党政军民学、东西南北中协同应考',
'tags': ['Social Humanities', 'Documentary', 'Medical', 'Social'],
- }
+ },
}]
_QUERY = '''
@@ -101,5 +101,5 @@ class OnDemandChinaEpisodeIE(InfoExtractor):
or self._html_search_meta(['og:description', 'twitter:description', 'description'], webpage)),
'formats': formats,
'subtitles': subtitles,
- 'tags': try_call(lambda: self._html_search_meta('keywords', webpage).split(', '))
+ 'tags': try_call(lambda: self._html_search_meta('keywords', webpage).split(', ')),
}
diff --git a/yt_dlp/extractor/odnoklassniki.py b/yt_dlp/extractor/odnoklassniki.py
index 1be45d8ad..d27d1c3f0 100644
--- a/yt_dlp/extractor/odnoklassniki.py
+++ b/yt_dlp/extractor/odnoklassniki.py
@@ -1,12 +1,7 @@
import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_etree_fromstring,
- compat_parse_qs,
- compat_urllib_parse_unquote,
- compat_urllib_parse_urlparse,
-)
+from ..compat import compat_etree_fromstring
from ..networking import HEADRequest
from ..utils import (
ExtractorError,
@@ -257,8 +252,8 @@ class OdnoklassnikiIE(InfoExtractor):
raise e
def _extract_desktop(self, url):
- start_time = int_or_none(compat_parse_qs(
- compat_urllib_parse_urlparse(url).query).get('fromTime', [None])[0])
+ start_time = int_or_none(urllib.parse.parse_qs(
+ urllib.parse.urlparse(url).query).get('fromTime', [None])[0])
url, smuggled = unsmuggle_url(url, {})
video_id, is_embed = self._match_valid_url(url).group('id', 'embed')
@@ -281,7 +276,7 @@ class OdnoklassnikiIE(InfoExtractor):
player = self._parse_json(
unescapeHTML(self._search_regex(
- r'data-options=(?P<quote>["\'])(?P<player>{.+?%s.+?})(?P=quote)' % video_id,
+ rf'data-options=(?P<quote>["\'])(?P<player>{{.+?{video_id}.+?}})(?P=quote)',
webpage, 'player', group='player')),
video_id)
@@ -300,7 +295,7 @@ class OdnoklassnikiIE(InfoExtractor):
if st_location:
data['st.location'] = st_location
metadata = self._download_json(
- compat_urllib_parse_unquote(flashvars['metadataUrl']),
+ urllib.parse.unquote(flashvars['metadataUrl']),
video_id, 'Downloading metadata JSON',
data=urlencode_postdata(data))
@@ -434,7 +429,7 @@ class OdnoklassnikiIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://m.ok.ru/video/%s' % video_id, video_id,
+ f'http://m.ok.ru/video/{video_id}', video_id,
note='Downloading mobile webpage')
error = self._search_regex(
@@ -460,5 +455,5 @@ class OdnoklassnikiIE(InfoExtractor):
'format_id': 'mobile',
'url': redirect_url,
'ext': 'mp4',
- }]
+ }],
}
diff --git a/yt_dlp/extractor/oftv.py b/yt_dlp/extractor/oftv.py
index 4cac51846..415694cee 100644
--- a/yt_dlp/extractor/oftv.py
+++ b/yt_dlp/extractor/oftv.py
@@ -20,8 +20,8 @@ class OfTVIE(InfoExtractor):
'timestamp': 1652391300,
'upload_date': '20220512',
'view_count': 0,
- 'creator': 'This is Fire'
- }
+ 'creator': 'This is Fire',
+ },
}]
def _real_extract(self, url):
@@ -39,8 +39,8 @@ class OfTVPlaylistIE(InfoExtractor):
'url': 'https://of.tv/creators/this-is-fire/',
'playlist_count': 8,
'info_dict': {
- 'id': 'this-is-fire'
- }
+ 'id': 'this-is-fire',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/oktoberfesttv.py b/yt_dlp/extractor/oktoberfesttv.py
index e0ac8563a..b4bcdc741 100644
--- a/yt_dlp/extractor/oktoberfesttv.py
+++ b/yt_dlp/extractor/oktoberfesttv.py
@@ -15,7 +15,7 @@ class OktoberfestTVIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/olympics.py b/yt_dlp/extractor/olympics.py
index 5507d2fda..becf052f6 100644
--- a/yt_dlp/extractor/olympics.py
+++ b/yt_dlp/extractor/olympics.py
@@ -24,9 +24,9 @@ class OlympicsReplayIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
+ video_id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ webpage = self._download_webpage(url, video_id)
title = self._html_search_meta(('title', 'og:title', 'twitter:title'), webpage)
uuid = self._html_search_meta('episode_uid', webpage)
m3u8_url = self._html_search_meta('video_url', webpage)
@@ -46,7 +46,7 @@ class OlympicsReplayIE(InfoExtractor):
thumbnails.append({
'url': thumbnail,
'width': width,
- 'height': int_or_none(try_get(width, lambda x: x * height_a / width_a))
+ 'height': int_or_none(try_get(width, lambda x: x * height_a / width_a)),
})
m3u8_url = self._download_json(
f'https://olympics.com/tokenGenerator?url={m3u8_url}', uuid, note='Downloading m3u8 url')
@@ -58,5 +58,5 @@ class OlympicsReplayIE(InfoExtractor):
'thumbnails': thumbnails,
'formats': formats,
'subtitles': subtitles,
- **json_ld
+ **json_ld,
}
diff --git a/yt_dlp/extractor/on24.py b/yt_dlp/extractor/on24.py
index 9a4abc98d..05218e9de 100644
--- a/yt_dlp/extractor/on24.py
+++ b/yt_dlp/extractor/on24.py
@@ -27,7 +27,7 @@ class On24IE(InfoExtractor):
'upload_date': '20200219',
'timestamp': 1582149600.0,
'view_count': int,
- }
+ },
}, {
'url': 'https://event.on24.com/wcc/r/2639291/82829018E813065A122363877975752E?mode=login&email=johnsmith@gmail.com',
'only_matching': True,
@@ -47,7 +47,7 @@ class On24IE(InfoExtractor):
'eventId': event_id,
'displayProfile': 'player',
'key': event_key,
- 'contentType': 'A'
+ 'contentType': 'A',
})
event_id = str(try_get(event_data, lambda x: x['presentationLogInfo']['eventid'])) or event_id
language = event_data.get('localelanguagecode')
@@ -74,7 +74,7 @@ class On24IE(InfoExtractor):
'language': language,
'ext': 'wav',
'vcodec': 'none',
- 'acodec': 'wav'
+ 'acodec': 'wav',
})
return {
diff --git a/yt_dlp/extractor/onefootball.py b/yt_dlp/extractor/onefootball.py
index e1b726830..ee432e8ed 100644
--- a/yt_dlp/extractor/onefootball.py
+++ b/yt_dlp/extractor/onefootball.py
@@ -36,7 +36,7 @@ class OneFootballIE(InfoExtractor):
'tags': ['Football', 'Soccer', 'OneFootball'],
'_old_archive_ids': ['onefootball 34041020'],
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/onenewsnz.py b/yt_dlp/extractor/onenewsnz.py
index 351b397de..c849da081 100644
--- a/yt_dlp/extractor/onenewsnz.py
+++ b/yt_dlp/extractor/onenewsnz.py
@@ -26,8 +26,8 @@ class OneNewsNZIE(InfoExtractor):
'duration': 38.272,
'thumbnail': r're:^https?://.*\.jpg$',
'description': 'Greenpeace accused the Government of "greenwashing" instead of taking climate action.',
- }
- }]
+ },
+ }],
}, {
# YouTube video
'url': 'https://www.1news.co.nz/2022/09/30/now-is-the-time-to-care-about-womens-rugby/',
@@ -59,8 +59,8 @@ class OneNewsNZIE(InfoExtractor):
'availability': 'public',
'playable_in_embed': True,
'live_status': 'not_live',
- }
- }]
+ },
+ }],
}, {
# 2 Brightcove videos
'url': 'https://www.1news.co.nz/2022/09/29/raw-videos-capture-hurricane-ians-fury-as-it-slams-florida/',
@@ -89,7 +89,7 @@ class OneNewsNZIE(InfoExtractor):
brightcove_config = traverse_obj(item, ('embed', 'config'))
brightcove_url = self.BRIGHTCOVE_URL_TEMPLATE % (
traverse_obj(brightcove_config, 'brightcoveAccount') or '963482464001',
- traverse_obj(brightcove_config, 'brightcoveVideoId')
+ traverse_obj(brightcove_config, 'brightcoveVideoId'),
)
entries.append(self.url_result(brightcove_url, BrightcoveNewIE))
elif item_type == 'youtube':
diff --git a/yt_dlp/extractor/oneplace.py b/yt_dlp/extractor/oneplace.py
index 86337ad0a..12e6ef64c 100644
--- a/yt_dlp/extractor/oneplace.py
+++ b/yt_dlp/extractor/oneplace.py
@@ -10,7 +10,7 @@ class OnePlacePodcastIE(InfoExtractor):
'ext': 'mp3',
'title': 'Living in the Last Days Part 2 | A Daily Walk with John Randall',
'description': 'md5:fbb8f1cf21447ac54ecaa2887fc20c6e',
- }
+ },
}, {
'url': 'https://www.oneplace.com/ministries/ankerberg-show/listen/ep-3-relying-on-the-constant-companionship-of-the-holy-spirit-part-2-922513.html',
'info_dict': {
@@ -18,7 +18,7 @@ class OnePlacePodcastIE(InfoExtractor):
'ext': 'mp3',
'description': 'md5:8b810b4349aa40a5d033b4536fe428e1',
'title': 'md5:ce10f7d8d5ddcf485ed8905ef109659d',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/onet.py b/yt_dlp/extractor/onet.py
index da10f3779..05e4d692a 100644
--- a/yt_dlp/extractor/onet.py
+++ b/yt_dlp/extractor/onet.py
@@ -39,7 +39,7 @@ class OnetBaseIE(InfoExtractor):
error = response.get('error')
if error:
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, error['message']), expected=True)
+ '{} said: {}'.format(self.IE_NAME, error['message']), expected=True)
video = response['result'].get('0')
@@ -182,7 +182,7 @@ class OnetChannelIE(OnetBaseIE):
return self._extract_from_id(video_id, webpage)
matches = re.findall(
- r'<a[^>]+href=[\'"](%s[a-z]+/[0-9a-z-]+/[0-9a-z]+)' % self._URL_BASE_RE,
+ rf'<a[^>]+href=[\'"]({self._URL_BASE_RE}[a-z]+/[0-9a-z-]+/[0-9a-z]+)',
webpage)
entries = [
self.url_result(video_link, OnetIE.ie_key())
@@ -256,4 +256,4 @@ class OnetPlIE(InfoExtractor):
mvp_id = self._search_mvp_id(webpage)
return self.url_result(
- 'onetmvp:%s' % mvp_id, OnetMVPIE.ie_key(), video_id=mvp_id)
+ f'onetmvp:{mvp_id}', OnetMVPIE.ie_key(), video_id=mvp_id)
diff --git a/yt_dlp/extractor/onionstudios.py b/yt_dlp/extractor/onionstudios.py
index 5fa49e142..7e30b2d33 100644
--- a/yt_dlp/extractor/onionstudios.py
+++ b/yt_dlp/extractor/onionstudios.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import js_to_json
@@ -34,7 +33,7 @@ class OnionStudiosIE(InfoExtractor):
webpage = self._download_webpage(
'http://onionstudios.com/embed/dc94dc2899fe644c0e7241fa04c1b732.js',
video_id)
- mcp_id = compat_str(self._parse_json(self._search_regex(
+ mcp_id = str(self._parse_json(self._search_regex(
r'window\.mcpMapping\s*=\s*({.+?});', webpage,
'MCP Mapping'), video_id, js_to_json)[video_id]['mcp_id'])
return self.url_result(
diff --git a/yt_dlp/extractor/opencast.py b/yt_dlp/extractor/opencast.py
index 12bf55704..a4b0a1989 100644
--- a/yt_dlp/extractor/opencast.py
+++ b/yt_dlp/extractor/opencast.py
@@ -126,7 +126,7 @@ class OpencastIE(OpencastBaseIE):
'series': 'Kryptographie - WiSe 15/16',
'creator': 'Alexander May',
},
- }
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/openload.py b/yt_dlp/extractor/openload.py
index 56b8330ff..2d56252b1 100644
--- a/yt_dlp/extractor/openload.py
+++ b/yt_dlp/extractor/openload.py
@@ -4,8 +4,8 @@ import json
import os
import subprocess
import tempfile
+import urllib.parse
-from ..compat import compat_urlparse
from ..utils import (
ExtractorError,
Popen,
@@ -121,7 +121,7 @@ class PhantomJSwrapper:
if is_outdated_version(version, required_version):
self.extractor._downloader.report_warning(
'Your copy of PhantomJS is outdated, update it to version '
- '%s or newer if you encounter any errors.' % required_version)
+ f'{required_version} or newer if you encounter any errors.')
for name in self._TMP_FILE_NAMES:
tmp = tempfile.NamedTemporaryFile(delete=False)
@@ -146,9 +146,9 @@ class PhantomJSwrapper:
if 'path' not in cookie:
cookie['path'] = '/'
if 'domain' not in cookie:
- cookie['domain'] = compat_urlparse.urlparse(url).netloc
+ cookie['domain'] = urllib.parse.urlparse(url).netloc
with open(self._TMP_FILES['cookies'].name, 'wb') as f:
- f.write(json.dumps(cookies).encode('utf-8'))
+ f.write(json.dumps(cookies).encode())
def _load_cookies(self):
with open(self._TMP_FILES['cookies'].name, 'rb') as f:
@@ -201,7 +201,7 @@ class PhantomJSwrapper:
if not html:
html = self.extractor._download_webpage(url, video_id, note=note, headers=headers)
with open(self._TMP_FILES['html'].name, 'wb') as f:
- f.write(html.encode('utf-8'))
+ f.write(html.encode())
self._save_cookies(url)
diff --git a/yt_dlp/extractor/openrec.py b/yt_dlp/extractor/openrec.py
index c9a96aeb4..b4f1c7d85 100644
--- a/yt_dlp/extractor/openrec.py
+++ b/yt_dlp/extractor/openrec.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
get_first,
@@ -120,9 +119,9 @@ class OpenRecCaptureIE(OpenRecBaseIE):
'title': capture_data.get('title'),
'thumbnail': capture_data.get('thumbnailUrl'),
'formats': formats,
- 'timestamp': unified_timestamp(traverse_obj(movie_store, 'createdAt', expected_type=compat_str)),
- 'uploader': traverse_obj(movie_store, ('channel', 'name'), expected_type=compat_str),
- 'uploader_id': traverse_obj(movie_store, ('channel', 'id'), expected_type=compat_str),
+ 'timestamp': unified_timestamp(traverse_obj(movie_store, 'createdAt', expected_type=str)),
+ 'uploader': traverse_obj(movie_store, ('channel', 'name'), expected_type=str),
+ 'uploader_id': traverse_obj(movie_store, ('channel', 'id'), expected_type=str),
'upload_date': unified_strdate(capture_data.get('createdAt')),
'http_headers': self._M3U8_HEADERS,
}
diff --git a/yt_dlp/extractor/ora.py b/yt_dlp/extractor/ora.py
index 0e7a8484e..c6ba4b0d3 100644
--- a/yt_dlp/extractor/ora.py
+++ b/yt_dlp/extractor/ora.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
get_element_by_attribute,
qualities,
@@ -19,7 +19,7 @@ class OraTVIE(InfoExtractor):
'ext': 'mp4',
'title': 'Vine & YouTube Stars Zach King & King Bach On Their Viral Videos!',
'description': 'md5:ebbc5b1424dd5dba7be7538148287ac1',
- }
+ },
}, {
'url': 'http://www.unsafespeech.com/video/2016/5/10/student-self-censorship-and-the-thought-police-on-university-campuses-0_6622bnkppw4d',
'only_matching': True,
@@ -38,14 +38,14 @@ class OraTVIE(InfoExtractor):
m3u8_url, display_id, 'mp4', 'm3u8_native',
m3u8_id='hls', fatal=False)
# similar to GameSpotIE
- m3u8_path = compat_urlparse.urlparse(m3u8_url).path
+ m3u8_path = urllib.parse.urlparse(m3u8_url).path
QUALITIES_RE = r'((,[a-z]+\d+)+,?)'
available_qualities = self._search_regex(
QUALITIES_RE, m3u8_path, 'qualities').strip(',').split(',')
http_path = m3u8_path[1:].split('/', 1)[1]
http_template = re.sub(QUALITIES_RE, r'%s', http_path)
http_template = http_template.replace('.csmil/master.m3u8', '')
- http_template = compat_urlparse.urljoin(
+ http_template = urllib.parse.urljoin(
'http://videocdn-pmd.ora.tv/', http_template)
preference = qualities(
['mobile400', 'basic400', 'basic600', 'sd900', 'sd1200', 'sd1500', 'hd720', 'hd1080'])
diff --git a/yt_dlp/extractor/orf.py b/yt_dlp/extractor/orf.py
index 039f33bd6..f1403d920 100644
--- a/yt_dlp/extractor/orf.py
+++ b/yt_dlp/extractor/orf.py
@@ -65,8 +65,8 @@ class ORFRadioIE(InfoExtractor):
'duration': 18000,
'timestamp': 1659322789,
'description': 'md5:a3f6083399ef92b8cbe2d421b180835a',
- }
- }]
+ },
+ }],
}, {
'url': 'https://ooe.orf.at/player/20220801/OGMO',
'info_dict': {
@@ -84,8 +84,8 @@ class ORFRadioIE(InfoExtractor):
'duration': 18000,
'timestamp': 1659322789,
'description': 'md5:a3f6083399ef92b8cbe2d421b180835a',
- }
- }]
+ },
+ }],
}, {
'url': 'http://fm4.orf.at/player/20170107/4CC',
'only_matching': True,
@@ -127,7 +127,7 @@ class ORFRadioIE(InfoExtractor):
'timestamp': 1483858796,
'upload_date': '20170108',
},
- 'skip': 'Shows from ORF radios are only available for 7 days.'
+ 'skip': 'Shows from ORF radios are only available for 7 days.',
}]
def _entries(self, data, station):
@@ -175,7 +175,7 @@ class ORFPodcastIE(InfoExtractor):
'duration': 3396.0,
'series': 'Frühstück bei mir',
},
- 'skip': 'ORF podcasts are only available for a limited time'
+ 'skip': 'ORF podcasts are only available for a limited time',
}]
def _real_extract(self, url):
@@ -221,13 +221,13 @@ class ORFIPTVIE(InfoExtractor):
story_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://iptv.orf.at/stories/%s' % story_id, story_id)
+ f'http://iptv.orf.at/stories/{story_id}', story_id)
video_id = self._search_regex(
r'data-video(?:id)?="(\d+)"', webpage, 'video id')
data = self._download_json(
- 'http://bits.orf.at/filehandler/static-api/json/current/data.json?file=%s' % video_id,
+ f'http://bits.orf.at/filehandler/static-api/json/current/data.json?file={video_id}',
video_id)[0]
duration = float_or_none(data['duration'], 1000)
@@ -326,7 +326,7 @@ class ORFFM4StoryIE(InfoExtractor):
all_ids = orderedSet(re.findall(r'data-video(?:id)?="(\d+)"', webpage))
for idx, video_id in enumerate(all_ids):
data = self._download_json(
- 'http://bits.orf.at/filehandler/static-api/json/current/data.json?file=%s' % video_id,
+ f'http://bits.orf.at/filehandler/static-api/json/current/data.json?file={video_id}',
video_id)[0]
duration = float_or_none(data['duration'], 1000)
diff --git a/yt_dlp/extractor/outsidetv.py b/yt_dlp/extractor/outsidetv.py
index b1fcbd6a7..b9191c9cc 100644
--- a/yt_dlp/extractor/outsidetv.py
+++ b/yt_dlp/extractor/outsidetv.py
@@ -13,7 +13,7 @@ class OutsideTVIE(InfoExtractor):
'description': 'md5:41a12e94f3db3ca253b04bb1e8d8f4cd',
'upload_date': '20181225',
'timestamp': 1545742800,
- }
+ },
}, {
'url': 'http://www.outsidetv.com/home/play/ZjQYboH6/1/10/Hdg0jukV/4',
'only_matching': True,
diff --git a/yt_dlp/extractor/packtpub.py b/yt_dlp/extractor/packtpub.py
index 3e969c846..38ffd3451 100644
--- a/yt_dlp/extractor/packtpub.py
+++ b/yt_dlp/extractor/packtpub.py
@@ -63,7 +63,7 @@ class PacktPubIE(PacktPubBaseIE):
headers['Authorization'] = 'Bearer ' + self._TOKEN
try:
video_url = self._download_json(
- 'https://services.packtpub.com/products-v1/products/%s/%s/%s' % (course_id, chapter_id, video_id), video_id,
+ f'https://services.packtpub.com/products-v1/products/{course_id}/{chapter_id}/{video_id}', video_id,
'Downloading JSON video', headers=headers)['data']
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 400:
@@ -109,17 +109,16 @@ class PacktPubCourseIE(PacktPubBaseIE):
@classmethod
def suitable(cls, url):
- return False if PacktPubIE.suitable(url) else super(
- PacktPubCourseIE, cls).suitable(url)
+ return False if PacktPubIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
mobj = self._match_valid_url(url)
url, course_id = mobj.group('url', 'id')
course = self._download_json(
- self._STATIC_PRODUCTS_BASE + '%s/toc' % course_id, course_id)
+ self._STATIC_PRODUCTS_BASE + f'{course_id}/toc', course_id)
metadata = self._download_json(
- self._STATIC_PRODUCTS_BASE + '%s/summary' % course_id,
+ self._STATIC_PRODUCTS_BASE + f'{course_id}/summary',
course_id, fatal=False) or {}
entries = []
diff --git a/yt_dlp/extractor/palcomp3.py b/yt_dlp/extractor/palcomp3.py
index 4b0801c1a..138a7853a 100644
--- a/yt_dlp/extractor/palcomp3.py
+++ b/yt_dlp/extractor/palcomp3.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
str_or_none,
@@ -30,7 +29,7 @@ class PalcoMP3BaseIE(InfoExtractor):
})['data']
def _parse_music(self, music):
- music_id = compat_str(music['musicID'])
+ music_id = str(music['musicID'])
title = music['title']
formats = []
@@ -77,12 +76,12 @@ class PalcoMP3IE(PalcoMP3BaseIE):
'title': 'Nossas Composições - CUIDA BEM DELA',
'duration': 210,
'view_count': int,
- }
+ },
}]
@classmethod
def suitable(cls, url):
- return False if PalcoMP3VideoIE.suitable(url) else super(PalcoMP3IE, cls).suitable(url)
+ return False if PalcoMP3VideoIE.suitable(url) else super().suitable(url)
class PalcoMP3ArtistIE(PalcoMP3BaseIE):
@@ -106,7 +105,7 @@ class PalcoMP3ArtistIE(PalcoMP3BaseIE):
@classmethod
def suitable(cls, url):
- return False if PalcoMP3IE._match_valid_url(url) else super(PalcoMP3ArtistIE, cls).suitable(url)
+ return False if PalcoMP3IE._match_valid_url(url) else super().suitable(url)
def _real_extract(self, url):
artist_slug = self._match_id(url)
@@ -134,7 +133,7 @@ class PalcoMP3VideoIE(PalcoMP3BaseIE):
'upload_date': '20161107',
'uploader_id': 'maiaramaraisaoficial',
'uploader': 'Maiara e Maraisa',
- }
+ },
}]
_MUSIC_FIELDS = 'youtubeID'
diff --git a/yt_dlp/extractor/panopto.py b/yt_dlp/extractor/panopto.py
index 6b2596236..91f105519 100644
--- a/yt_dlp/extractor/panopto.py
+++ b/yt_dlp/extractor/panopto.py
@@ -3,9 +3,9 @@ import datetime as dt
import functools
import json
import random
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_urlparse, compat_urlparse
from ..utils import (
ExtractorError,
OnDemandPagedList,
@@ -44,7 +44,7 @@ class PanoptoBaseIE(InfoExtractor):
18: 'hu-HU',
19: 'nb-NO',
20: 'sv-SE',
- 21: 'it-IT'
+ 21: 'it-IT',
}
def _call_api(self, base_url, path, video_id, data=None, fatal=True, **kwargs):
@@ -66,7 +66,7 @@ class PanoptoBaseIE(InfoExtractor):
@staticmethod
def _parse_fragment(url):
- return {k: json.loads(v[0]) for k, v in compat_urlparse.parse_qs(compat_urllib_parse_urlparse(url).fragment).items()}
+ return {k: json.loads(v[0]) for k, v in urllib.parse.parse_qs(urllib.parse.urlparse(url).fragment).items()}
class PanoptoIE(PanoptoBaseIE):
@@ -88,7 +88,7 @@ class PanoptoIE(PanoptoBaseIE):
'average_rating': int,
'uploader_id': '2db6b718-47a0-4b0b-9e17-ab0b00f42b1e',
'channel_id': 'e4c6a2fc-1214-4ca0-8fb7-aef2e29ff63a',
- 'channel': 'Showcase Videos'
+ 'channel': 'Showcase Videos',
},
},
{
@@ -131,7 +131,7 @@ class PanoptoIE(PanoptoBaseIE):
'uploader': 'Kathryn Kelly',
'channel_id': 'fb93bc3c-6750-4b80-a05b-a921013735d3',
'channel': 'Getting Started',
- }
+ },
},
{
# Does not allow normal Viewer.aspx. AUDIO livestream has no url, so should be skipped and only give one stream.
@@ -174,7 +174,7 @@ class PanoptoIE(PanoptoBaseIE):
'chapters': 'count:28',
'thumbnail': r're:https://demo\.hosted\.panopto\.com/.+',
},
- 'params': {'format': 'mhtml', 'skip_download': True}
+ 'params': {'format': 'mhtml', 'skip_download': True},
},
{
'url': 'https://na-training-1.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=8285224a-9a2b-4957-84f2-acb0000c4ea9',
@@ -196,7 +196,7 @@ class PanoptoIE(PanoptoBaseIE):
'subtitles': {'en-US': [{'ext': 'srt', 'data': 'md5:a3f4d25963fdeace838f327097c13265'}],
'es-ES': [{'ext': 'srt', 'data': 'md5:57e9dad365fd0fbaf0468eac4949f189'}]},
},
- 'params': {'writesubtitles': True, 'skip_download': True}
+ 'params': {'writesubtitles': True, 'skip_download': True},
}, {
# On Panopto there are two subs: "Default" and en-US. en-US is blank and should be skipped.
'url': 'https://na-training-1.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=940cbd41-f616-4a45-b13e-aaf1000c915b',
@@ -218,15 +218,15 @@ class PanoptoIE(PanoptoBaseIE):
'upload_date': '20191129',
},
- 'params': {'writesubtitles': True, 'skip_download': True}
+ 'params': {'writesubtitles': True, 'skip_download': True},
},
{
'url': 'https://ucc.cloud.panopto.eu/Panopto/Pages/Viewer.aspx?id=0e8484a4-4ceb-4d98-a63f-ac0200b455cb',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://brown.hosted.panopto.com/Panopto/Pages/Embed.aspx?id=0b3ff73b-36a0-46c5-8455-aadf010a3638',
- 'only_matching': True
+ 'only_matching': True,
},
]
@@ -254,7 +254,7 @@ class PanoptoIE(PanoptoBaseIE):
'StopReason': None,
'StreamID': stream_id,
'TimeStamp': timestamp_str,
- 'UpdatesRejected': 0
+ 'UpdatesRejected': 0,
},
]}
@@ -274,7 +274,7 @@ class PanoptoIE(PanoptoBaseIE):
chapters.append({
'start_time': start,
'end_time': start + duration,
- 'title': caption
+ 'title': caption,
})
return chapters
@@ -283,11 +283,11 @@ class PanoptoIE(PanoptoBaseIE):
image_frags = {}
for timestamp in timestamps or []:
duration = timestamp.get('Duration')
- obj_id, obj_sn = timestamp.get('ObjectIdentifier'), timestamp.get('ObjectSequenceNumber'),
+ obj_id, obj_sn = timestamp.get('ObjectIdentifier'), timestamp.get('ObjectSequenceNumber')
if timestamp.get('EventTargetType') == 'PowerPoint' and obj_id is not None and obj_sn is not None:
image_frags.setdefault('slides', []).append({
'url': base_url + f'/Pages/Viewer/Image.aspx?id={obj_id}&number={obj_sn}',
- 'duration': duration
+ 'duration': duration,
})
obj_pid, session_id, abs_time = timestamp.get('ObjectPublicIdentifier'), timestamp.get('SessionID'), timestamp.get('AbsoluteTime')
@@ -304,7 +304,7 @@ class PanoptoIE(PanoptoBaseIE):
'acodec': 'none',
'vcodec': 'none',
'url': 'about:invalid',
- 'fragments': fragments
+ 'fragments': fragments,
}
@staticmethod
@@ -329,8 +329,8 @@ class PanoptoIE(PanoptoBaseIE):
'deliveryId': video_id,
'getCaptions': True,
'language': str(lang),
- 'responseType': 'json'
- }
+ 'responseType': 'json',
+ },
)
if not isinstance(response, list):
continue
@@ -359,12 +359,12 @@ class PanoptoIE(PanoptoBaseIE):
subtitles = self._merge_subtitles(subtitles, stream_subtitles)
else:
stream_formats.append({
- 'url': stream_url
+ 'url': stream_url,
})
for fmt in stream_formats:
fmt.update({
'format_note': stream.get('Tag'),
- **fmt_kwargs
+ **fmt_kwargs,
})
formats.extend(stream_formats)
@@ -384,7 +384,7 @@ class PanoptoIE(PanoptoBaseIE):
'isKollectiveAgentInstalled': 'false',
'isEmbed': 'false',
'responseType': 'json',
- }
+ },
)
delivery = delivery_info['Delivery']
@@ -421,7 +421,7 @@ class PanoptoIE(PanoptoBaseIE):
'channel_id': delivery.get('SessionGroupPublicID'),
'channel': traverse_obj(delivery, 'SessionGroupLongName', 'SessionGroupShortName', get_all=False),
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
@@ -435,7 +435,7 @@ class PanoptoPlaylistIE(PanoptoBaseIE):
'id': 'f3b39fcf-882f-4849-93d6-a9f401236d36',
'description': '',
},
- 'playlist_mincount': 36
+ 'playlist_mincount': 36,
},
{
'url': 'https://utsa.hosted.panopto.com/Panopto/Pages/Viewer.aspx?pid=e2900555-3ad4-4bdb-854d-ad2401686190',
@@ -444,7 +444,7 @@ class PanoptoPlaylistIE(PanoptoBaseIE):
'id': 'e2900555-3ad4-4bdb-854d-ad2401686190',
'description': 'md5:f958bca50a1cbda15fdc1e20d32b3ecb',
},
- 'playlist_mincount': 4
+ 'playlist_mincount': 4,
},
]
@@ -466,7 +466,7 @@ class PanoptoPlaylistIE(PanoptoBaseIE):
'description': item.get('Description'),
'duration': item.get('Duration'),
'channel': traverse_obj(item, ('Parent', 'Name')),
- 'channel_id': traverse_obj(item, ('Parent', 'Id'))
+ 'channel_id': traverse_obj(item, ('Parent', 'Id')),
}
def _real_extract(self, url):
@@ -475,7 +475,7 @@ class PanoptoPlaylistIE(PanoptoBaseIE):
video_id = get_first(parse_qs(url), 'id')
if video_id:
if self.get_param('noplaylist'):
- self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
+ self.to_screen(f'Downloading just video {video_id} because of --no-playlist')
return self.url_result(base_url + f'/Pages/Viewer.aspx?id={video_id}', ie_key=PanoptoIE.ie_key(), video_id=video_id)
else:
self.to_screen(f'Downloading playlist {playlist_id}; add --no-playlist to just download video {video_id}')
@@ -495,28 +495,28 @@ class PanoptoListIE(PanoptoBaseIE):
'url': 'https://demo.hosted.panopto.com/Panopto/Pages/Sessions/List.aspx#folderID=%22e4c6a2fc-1214-4ca0-8fb7-aef2e29ff63a%22',
'info_dict': {
'id': 'e4c6a2fc-1214-4ca0-8fb7-aef2e29ff63a',
- 'title': 'Showcase Videos'
+ 'title': 'Showcase Videos',
},
- 'playlist_mincount': 140
+ 'playlist_mincount': 140,
},
{
'url': 'https://demo.hosted.panopto.com/Panopto/Pages/Sessions/List.aspx#view=2&maxResults=250',
'info_dict': {
'id': 'panopto_list',
- 'title': 'panopto_list'
+ 'title': 'panopto_list',
},
- 'playlist_mincount': 300
+ 'playlist_mincount': 300,
},
{
# Folder that contains 8 folders and a playlist
'url': 'https://howtovideos.hosted.panopto.com/Panopto/Pages/Sessions/List.aspx?noredirect=true#folderID=%224b9de7ae-0080-4158-8496-a9ba01692c2e%22',
'info_dict': {
'id': '4b9de7ae-0080-4158-8496-a9ba01692c2e',
- 'title': 'Video Tutorials'
+ 'title': 'Video Tutorials',
},
- 'playlist_mincount': 9
- }
+ 'playlist_mincount': 9,
+ },
]
@@ -559,7 +559,7 @@ class PanoptoListIE(PanoptoBaseIE):
base_url, '/Services/Data.svc/GetFolderInfo', folder_id,
data={'folderID': folder_id}, fatal=False)
return {
- 'title': get_first(response, 'Name')
+ 'title': get_first(response, 'Name'),
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/paramountplus.py b/yt_dlp/extractor/paramountplus.py
index 3f19803c0..317f53b2b 100644
--- a/yt_dlp/extractor/paramountplus.py
+++ b/yt_dlp/extractor/paramountplus.py
@@ -170,19 +170,19 @@ class ParamountPlusSeriesIE(InfoExtractor):
'playlist_mincount': 50,
'info_dict': {
'id': 'drake-josh',
- }
+ },
}, {
'url': 'https://www.paramountplus.com/shows/hawaii_five_0/',
'playlist_mincount': 240,
'info_dict': {
'id': 'hawaii_five_0',
- }
+ },
}, {
'url': 'https://www.paramountplus.com/shows/spongebob-squarepants/',
'playlist_mincount': 248,
'info_dict': {
'id': 'spongebob-squarepants',
- }
+ },
}]
def _entries(self, show_name):
@@ -193,7 +193,7 @@ class ParamountPlusSeriesIE(InfoExtractor):
return
for episode in show_json['result']['data']:
yield self.url_result(
- 'https://www.paramountplus.com%s' % episode['url'],
+ 'https://www.paramountplus.com{}'.format(episode['url']),
ie=ParamountPlusIE.ie_key(), video_id=episode['content_id'])
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/parler.py b/yt_dlp/extractor/parler.py
index 563012f35..9be288a7d 100644
--- a/yt_dlp/extractor/parler.py
+++ b/yt_dlp/extractor/parler.py
@@ -87,5 +87,5 @@ class ParlerIE(InfoExtractor):
'view_count': ('views', {int_or_none}),
'comment_count': ('total_comments', {int_or_none}),
'repost_count': ('echos', {int_or_none}),
- })
+ }),
}
diff --git a/yt_dlp/extractor/parlview.py b/yt_dlp/extractor/parlview.py
index 777b00889..b93b5edac 100644
--- a/yt_dlp/extractor/parlview.py
+++ b/yt_dlp/extractor/parlview.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
try_get,
@@ -24,7 +23,7 @@ class ParlviewIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://parlview.aph.gov.au/mediaPlayer.php?videoID=539936',
'only_matching': True,
@@ -36,13 +35,13 @@ class ParlviewIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
media = self._download_json(self._API_URL % video_id, video_id).get('media')
- timestamp = try_get(media, lambda x: x['timeMap']['source']['timecode_offsets'][0], compat_str) or '/'
+ timestamp = try_get(media, lambda x: x['timeMap']['source']['timecode_offsets'][0], str) or '/'
stream = try_get(media, lambda x: x['renditions'][0], dict)
if not stream:
self.raise_no_formats('No streams were detected')
elif stream.get('streamType') != 'VOD':
- self.raise_no_formats('Unknown type of stream was detected: "%s"' % str(stream.get('streamType')))
+ self.raise_no_formats('Unknown type of stream was detected: "{}"'.format(str(stream.get('streamType'))))
formats = self._extract_m3u8_formats(stream['url'], video_id, 'mp4', 'm3u8_native')
media_info = self._download_webpage(
diff --git a/yt_dlp/extractor/patreon.py b/yt_dlp/extractor/patreon.py
index efbface4b..26ca84ab3 100644
--- a/yt_dlp/extractor/patreon.py
+++ b/yt_dlp/extractor/patreon.py
@@ -33,7 +33,7 @@ class PatreonBaseIE(InfoExtractor):
try:
return self._download_json(
f'https://www.patreon.com/api/{ep}',
- item_id, note='Downloading API JSON' if not note else note,
+ item_id, note=note if note else 'Downloading API JSON',
query=query, fatal=fatal, headers=headers)
except ExtractorError as e:
if not isinstance(e.cause, HTTPError) or mimetype2ext(e.cause.response.headers.get('Content-Type')) != 'json':
@@ -113,7 +113,7 @@ class PatreonIE(PatreonBaseIE):
'params': {
'noplaylist': True,
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.patreon.com/posts/episode-166-of-743933',
'only_matching': True,
@@ -133,7 +133,7 @@ class PatreonIE(PatreonBaseIE):
'description': 'md5:557a409bd79d3898689419094934ba79',
'uploader_id': '14936315',
},
- 'skip': 'Patron-only content'
+ 'skip': 'Patron-only content',
}, {
# m3u8 video (https://github.com/yt-dlp/yt-dlp/issues/2277)
'url': 'https://www.patreon.com/posts/video-sketchbook-32452882',
@@ -154,7 +154,7 @@ class PatreonIE(PatreonBaseIE):
'channel_id': '1641751',
'channel_url': 'https://www.patreon.com/loish',
'channel_follower_count': int,
- }
+ },
}, {
# bad videos under media (if media is included). Real one is under post_file
'url': 'https://www.patreon.com/posts/premium-access-70282931',
@@ -378,7 +378,7 @@ class PatreonIE(PatreonBaseIE):
params.update({'page[cursor]': cursor} if cursor else {})
response = self._call_api(
- f'posts/{post_id}/comments', post_id, query=params, note='Downloading comments page %d' % page)
+ f'posts/{post_id}/comments', post_id, query=params, note=f'Downloading comments page {page}')
cursor = None
for comment in traverse_obj(response, (('data', ('included', lambda _, v: v['type'] == 'comment')), ...)):
@@ -446,18 +446,18 @@ class PatreonCampaignIE(PatreonBaseIE):
'uploader_id': '37306634',
'thumbnail': r're:^https?://.*$',
},
- 'playlist_mincount': 71
+ 'playlist_mincount': 71,
}, {
'url': 'https://www.patreon.com/dissonancepod/posts',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.patreon.com/m/5932659',
- 'only_matching': True
+ 'only_matching': True,
}]
@classmethod
def suitable(cls, url):
- return False if PatreonIE.suitable(url) else super(PatreonCampaignIE, cls).suitable(url)
+ return False if PatreonIE.suitable(url) else super().suitable(url)
def _entries(self, campaign_id):
cursor = None
@@ -472,7 +472,7 @@ class PatreonCampaignIE(PatreonBaseIE):
for page in itertools.count(1):
params.update({'page[cursor]': cursor} if cursor else {})
- posts_json = self._call_api('posts', campaign_id, query=params, note='Downloading posts page %d' % page)
+ posts_json = self._call_api('posts', campaign_id, query=params, note=f'Downloading posts page {page}')
cursor = traverse_obj(posts_json, ('meta', 'pagination', 'cursors', 'next'))
for post_url in traverse_obj(posts_json, ('data', ..., 'attributes', 'patreon_url')):
@@ -493,7 +493,7 @@ class PatreonCampaignIE(PatreonBaseIE):
'json-api-use-default-includes': 'false',
'fields[user]': 'full_name,url',
'fields[campaign]': 'name,summary,url,patron_count,creation_count,is_nsfw,avatar_photo_url',
- 'include': 'creator'
+ 'include': 'creator',
}
campaign_response = self._call_api(
diff --git a/yt_dlp/extractor/pbs.py b/yt_dlp/extractor/pbs.py
index f6f5a5c3e..686796491 100644
--- a/yt_dlp/extractor/pbs.py
+++ b/yt_dlp/extractor/pbs.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
US_RATINGS,
ExtractorError,
@@ -182,18 +181,18 @@ class PBSIE(InfoExtractor):
)
IE_NAME = 'pbs'
- IE_DESC = 'Public Broadcasting Service (PBS) and member stations: %s' % ', '.join(list(zip(*_STATIONS))[1])
+ IE_DESC = 'Public Broadcasting Service (PBS) and member stations: {}'.format(', '.join(list(zip(*_STATIONS))[1]))
_VALID_URL = r'''(?x)https?://
(?:
# Direct video URL
- (?:%s)/(?:(?:vir|port)alplayer|video)/(?P<id>[0-9]+)(?:[?/]|$) |
+ (?:{})/(?:(?:vir|port)alplayer|video)/(?P<id>[0-9]+)(?:[?/]|$) |
# Article with embedded player (or direct video)
- (?:www\.)?pbs\.org/(?:[^/]+/){1,5}(?P<presumptive_id>[^/]+?)(?:\.html)?/?(?:$|[?\#]) |
+ (?:www\.)?pbs\.org/(?:[^/]+/){{1,5}}(?P<presumptive_id>[^/]+?)(?:\.html)?/?(?:$|[?\#]) |
# Player
(?:video|player)\.pbs\.org/(?:widget/)?partnerplayer/(?P<player_id>[^/]+)
)
- ''' % '|'.join(list(zip(*_STATIONS))[0])
+ '''.format('|'.join(next(zip(*_STATIONS))))
_GEO_COUNTRIES = ['US']
@@ -415,7 +414,7 @@ class PBSIE(InfoExtractor):
{
'url': 'https://player.pbs.org/portalplayer/3004638221/?uid=',
'only_matching': True,
- }
+ },
]
_ERRORS = {
101: 'We\'re sorry, but this video is not yet available.',
@@ -518,7 +517,7 @@ class PBSIE(InfoExtractor):
if not video_id:
video_info = self._extract_video_data(
player_page, 'video data', display_id)
- video_id = compat_str(
+ video_id = str(
video_info.get('id') or video_info['contentID'])
else:
video_id = mobj.group('id')
@@ -539,7 +538,7 @@ class PBSIE(InfoExtractor):
if isinstance(video_id, list):
entries = [self.url_result(
- 'http://video.pbs.org/video/%s' % vid_id, 'PBS', vid_id)
+ f'http://video.pbs.org/video/{vid_id}', 'PBS', vid_id)
for vid_id in video_id]
return self.playlist_result(entries, display_id)
@@ -568,11 +567,11 @@ class PBSIE(InfoExtractor):
# Player pages may also serve different qualities
for page in ('widget/partnerplayer', 'portalplayer'):
player = self._download_webpage(
- 'http://player.pbs.org/%s/%s' % (page, video_id),
- display_id, 'Downloading %s page' % page, fatal=False)
+ f'http://player.pbs.org/{page}/{video_id}',
+ display_id, f'Downloading {page} page', fatal=False)
if player:
video_info = self._extract_video_data(
- player, '%s video data' % page, display_id, fatal=False)
+ player, f'{page} video data', display_id, fatal=False)
if video_info:
extract_redirect_urls(video_info)
if not info:
@@ -603,7 +602,7 @@ class PBSIE(InfoExtractor):
redirect_id = redirect.get('eeid')
redirect_info = self._download_json(
- '%s?format=json' % redirect['url'], display_id,
+ '{}?format=json'.format(redirect['url']), display_id,
'Downloading %s video url info' % (redirect_id or num),
headers=self.geo_verification_headers())
@@ -614,7 +613,7 @@ class PBSIE(InfoExtractor):
self.raise_geo_restricted(
msg=message, countries=self._GEO_COUNTRIES)
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, message), expected=True)
+ f'{self.IE_NAME} said: {message}', expected=True)
format_url = redirect_info.get('url')
if not format_url:
@@ -649,7 +648,7 @@ class PBSIE(InfoExtractor):
f_url = re.sub(r'\d+k|baseline', bitrate + 'k', http_url)
# This may produce invalid links sometimes (e.g.
# http://www.pbs.org/wgbh/frontline/film/suicide-plan)
- if not self._is_valid_url(f_url, display_id, 'http-%sk video' % bitrate):
+ if not self._is_valid_url(f_url, display_id, f'http-{bitrate}k video'):
continue
f = m3u8_format.copy()
f.update({
@@ -671,7 +670,7 @@ class PBSIE(InfoExtractor):
captions = info.get('cc') or {}
for caption_url in captions.values():
subtitles.setdefault('en', []).append({
- 'url': caption_url
+ 'url': caption_url,
})
subtitles = self._merge_subtitles(subtitles, hls_subs)
@@ -715,7 +714,7 @@ class PBSKidsIE(InfoExtractor):
'description': 'md5:d006b2211633685d8ebc8d03b6d5611e',
'categories': ['Episode'],
'upload_date': '20190718',
- }
+ },
},
{
'url': 'https://pbskids.org/video/plum-landing/2365205059',
@@ -730,8 +729,8 @@ class PBSKidsIE(InfoExtractor):
'description': 'md5:657e5fc4356a84ead1c061eb280ff05d',
'categories': ['Episode'],
'upload_date': '20140302',
- }
- }
+ },
+ },
]
def _real_extract(self, url):
@@ -753,5 +752,5 @@ class PBSKidsIE(InfoExtractor):
'series': ('video_obj', 'program_title', {str}),
'title': ('video_obj', 'title', {str}),
'upload_date': ('video_obj', 'air_date', {unified_strdate}),
- })
+ }),
}
diff --git a/yt_dlp/extractor/pearvideo.py b/yt_dlp/extractor/pearvideo.py
index 086eaaf00..4bea04bf2 100644
--- a/yt_dlp/extractor/pearvideo.py
+++ b/yt_dlp/extractor/pearvideo.py
@@ -19,7 +19,7 @@ class PearVideoIE(InfoExtractor):
'description': 'md5:01d576b747de71be0ee85eb7cac25f9d',
'timestamp': 1494275280,
'upload_date': '20170508',
- }
+ },
}
def _real_extract(self, url):
@@ -43,7 +43,7 @@ class PearVideoIE(InfoExtractor):
query={'contId': video_id}, headers={'Referer': url})
formats = [{
'format_id': k,
- 'url': v.replace(info['systemTime'], f'cont-{video_id}') if k == 'srcUrl' else v
+ 'url': v.replace(info['systemTime'], f'cont-{video_id}') if k == 'srcUrl' else v,
} for k, v in traverse_obj(info, ('videoInfo', 'videos'), default={}).items() if v]
title = self._search_regex(
diff --git a/yt_dlp/extractor/peertube.py b/yt_dlp/extractor/peertube.py
index fb4d02562..2b69c7e6c 100644
--- a/yt_dlp/extractor/peertube.py
+++ b/yt_dlp/extractor/peertube.py
@@ -2,7 +2,6 @@ import functools
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
OnDemandPagedList,
format_field,
@@ -1316,13 +1315,13 @@ class PeerTubeIE(InfoExtractor):
)'''
_UUID_RE = r'[\da-zA-Z]{22}|[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}'
_API_BASE = 'https://%s/api/v1/videos/%s/%s'
- _VALID_URL = r'''(?x)
+ _VALID_URL = rf'''(?x)
(?:
peertube:(?P<host>[^:]+):|
- https?://(?P<host_2>%s)/(?:videos/(?:watch|embed)|api/v\d/videos|w)/
+ https?://(?P<host_2>{_INSTANCES_RE})/(?:videos/(?:watch|embed)|api/v\d/videos|w)/
)
- (?P<id>%s)
- ''' % (_INSTANCES_RE, _UUID_RE)
+ (?P<id>{_UUID_RE})
+ '''
_EMBED_REGEX = [r'''(?x)<iframe[^>]+\bsrc=["\'](?P<url>(?:https?:)?//{_INSTANCES_RE}/videos/embed/{cls._UUID_RE})''']
_TESTS = [{
'url': 'https://framatube.org/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d',
@@ -1349,7 +1348,7 @@ class PeerTubeIE(InfoExtractor):
'dislike_count': int,
'tags': ['framasoft', 'peertube'],
'categories': ['Science & Technology'],
- }
+ },
}, {
'url': 'https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e',
'info_dict': {
@@ -1360,7 +1359,7 @@ class PeerTubeIE(InfoExtractor):
'timestamp': 1589276219,
'upload_date': '20200512',
'uploader': 'chocobozzz',
- }
+ },
}, {
'url': 'https://peertube2.cpy.re/w/3fbif9S3WmtTP8gGsC5HBd',
'info_dict': {
@@ -1394,7 +1393,7 @@ class PeerTubeIE(InfoExtractor):
'timestamp': 1587401293,
'upload_date': '20200420',
'uploader': 'Drew DeVault',
- }
+ },
}, {
'url': 'https://peertube.debian.social/videos/watch/0b04f13d-1e18-4f1d-814e-4979aa7c9c44',
'only_matching': True,
@@ -1416,14 +1415,13 @@ class PeerTubeIE(InfoExtractor):
@staticmethod
def _extract_peertube_url(webpage, source_url):
mobj = re.match(
- r'https?://(?P<host>[^/]+)/(?:videos/(?:watch|embed)|w)/(?P<id>%s)'
- % PeerTubeIE._UUID_RE, source_url)
+ rf'https?://(?P<host>[^/]+)/(?:videos/(?:watch|embed)|w)/(?P<id>{PeerTubeIE._UUID_RE})', source_url)
if mobj and any(p in webpage for p in (
'meta property="og:platform" content="PeerTube"',
'<title>PeerTube<',
'There will be other non JS-based clients to access PeerTube',
'>We are sorry but it seems that PeerTube is not compatible with your web browser.<')):
- return 'peertube:%s:%s' % mobj.group('host', 'id')
+ return 'peertube:{}:{}'.format(*mobj.group('host', 'id'))
@classmethod
def _extract_embed_urls(cls, url, webpage):
@@ -1451,8 +1449,8 @@ class PeerTubeIE(InfoExtractor):
return
subtitles = {}
for e in data:
- language_id = try_get(e, lambda x: x['language']['id'], compat_str)
- caption_url = urljoin('https://%s' % host, e.get('captionPath'))
+ language_id = try_get(e, lambda x: x['language']['id'], str)
+ caption_url = urljoin(f'https://{host}', e.get('captionPath'))
if not caption_url:
continue
subtitles.setdefault(language_id or 'en', []).append({
@@ -1491,7 +1489,7 @@ class PeerTubeIE(InfoExtractor):
continue
file_size = int_or_none(file_.get('size'))
format_id = try_get(
- file_, lambda x: x['resolution']['label'], compat_str)
+ file_, lambda x: x['resolution']['label'], str)
f = parse_resolution(format_id)
f.update({
'url': file_url,
@@ -1526,7 +1524,7 @@ class PeerTubeIE(InfoExtractor):
def channel_data(field, type_):
return data('channel', field, type_)
- category = data('category', 'label', compat_str)
+ category = data('category', 'label', str)
categories = [category] if category else None
nsfw = video.get('nsfw')
@@ -1535,7 +1533,7 @@ class PeerTubeIE(InfoExtractor):
else:
age_limit = None
- webpage_url = 'https://%s/videos/watch/%s' % (host, video_id)
+ webpage_url = f'https://{host}/videos/watch/{video_id}'
return {
'id': video_id,
@@ -1543,14 +1541,14 @@ class PeerTubeIE(InfoExtractor):
'description': description,
'thumbnail': urljoin(webpage_url, video.get('thumbnailPath')),
'timestamp': unified_timestamp(video.get('publishedAt')),
- 'uploader': account_data('displayName', compat_str),
+ 'uploader': account_data('displayName', str),
'uploader_id': str_or_none(account_data('id', int)),
- 'uploader_url': url_or_none(account_data('url', compat_str)),
- 'channel': channel_data('displayName', compat_str),
+ 'uploader_url': url_or_none(account_data('url', str)),
+ 'channel': channel_data('displayName', str),
'channel_id': str_or_none(channel_data('id', int)),
- 'channel_url': url_or_none(channel_data('url', compat_str)),
- 'language': data('language', 'id', compat_str),
- 'license': data('licence', 'label', compat_str),
+ 'channel_url': url_or_none(channel_data('url', str)),
+ 'language': data('language', 'id', str),
+ 'license': data('licence', 'label', str),
'duration': int_or_none(video.get('duration')),
'view_count': int_or_none(video.get('views')),
'like_count': int_or_none(video.get('likes')),
@@ -1573,9 +1571,9 @@ class PeerTubePlaylistIE(InfoExtractor):
'w/p': 'video-playlists',
}
_VALID_URL = r'''(?x)
- https?://(?P<host>%s)/(?P<type>(?:%s))/
+ https?://(?P<host>{})/(?P<type>(?:{}))/
(?P<id>[^/]+)
- ''' % (PeerTubeIE._INSTANCES_RE, '|'.join(_TYPES.keys()))
+ '''.format(PeerTubeIE._INSTANCES_RE, '|'.join(_TYPES.keys()))
_TESTS = [{
'url': 'https://peertube.debian.social/w/p/hFdJoTuyhNJVa1cDWd1d12',
'info_dict': {
@@ -1617,21 +1615,21 @@ class PeerTubePlaylistIE(InfoExtractor):
return self._download_json(
self._API_BASE % (host, base, name, path), name, **kwargs)
- def fetch_page(self, host, id, type, page):
+ def fetch_page(self, host, playlist_id, playlist_type, page):
page += 1
video_data = self.call_api(
- host, id,
+ host, playlist_id,
f'/videos?sort=-createdAt&start={self._PAGE_SIZE * (page - 1)}&count={self._PAGE_SIZE}&nsfw=both',
- type, note=f'Downloading page {page}').get('data', [])
+ playlist_type, note=f'Downloading page {page}').get('data', [])
for video in video_data:
- shortUUID = video.get('shortUUID') or try_get(video, lambda x: x['video']['shortUUID'])
+ short_uuid = video.get('shortUUID') or try_get(video, lambda x: x['video']['shortUUID'])
video_title = video.get('name') or try_get(video, lambda x: x['video']['name'])
yield self.url_result(
- f'https://{host}/w/{shortUUID}', PeerTubeIE.ie_key(),
- video_id=shortUUID, video_title=video_title)
+ f'https://{host}/w/{short_uuid}', PeerTubeIE.ie_key(),
+ video_id=short_uuid, video_title=video_title)
- def _extract_playlist(self, host, type, id):
- info = self.call_api(host, id, '', type, note='Downloading playlist information', fatal=False)
+ def _extract_playlist(self, host, playlist_type, playlist_id):
+ info = self.call_api(host, playlist_id, '', playlist_type, note='Downloading playlist information', fatal=False)
playlist_title = info.get('displayName')
playlist_description = info.get('description')
@@ -1641,13 +1639,12 @@ class PeerTubePlaylistIE(InfoExtractor):
thumbnail = format_field(info, 'thumbnailPath', f'https://{host}%s')
entries = OnDemandPagedList(functools.partial(
- self.fetch_page, host, id, type), self._PAGE_SIZE)
+ self.fetch_page, host, playlist_id, playlist_type), self._PAGE_SIZE)
return self.playlist_result(
- entries, id, playlist_title, playlist_description,
+ entries, playlist_id, playlist_title, playlist_description,
timestamp=playlist_timestamp, channel=channel, channel_id=channel_id, thumbnail=thumbnail)
def _real_extract(self, url):
- type, host, id = self._match_valid_url(url).group('type', 'host', 'id')
- type = self._TYPES[type]
- return self._extract_playlist(host, type, id)
+ playlist_type, host, playlist_id = self._match_valid_url(url).group('type', 'host', 'id')
+ return self._extract_playlist(host, self._TYPES[playlist_type], playlist_id)
diff --git a/yt_dlp/extractor/peertv.py b/yt_dlp/extractor/peertv.py
index a709e21b4..726d5e14c 100644
--- a/yt_dlp/extractor/peertv.py
+++ b/yt_dlp/extractor/peertv.py
@@ -48,5 +48,5 @@ class PeerTVIE(InfoExtractor):
'title': self._html_search_regex(r'<h1>(.+?)</h1>', webpage, 'title').replace('\xa0', ' '),
'formats': formats,
'description': self._html_search_meta(('og:description', 'description'), webpage),
- 'thumbnail': self._html_search_meta(('og:image', 'image'), webpage)
+ 'thumbnail': self._html_search_meta(('og:image', 'image'), webpage),
}
diff --git a/yt_dlp/extractor/peloton.py b/yt_dlp/extractor/peloton.py
index 786429988..c0443e9c9 100644
--- a/yt_dlp/extractor/peloton.py
+++ b/yt_dlp/extractor/peloton.py
@@ -36,12 +36,12 @@ class PelotonIE(InfoExtractor):
'chapters': 'count:1',
'subtitles': {'en': [{
'url': r're:^https?://.+',
- 'ext': 'vtt'
+ 'ext': 'vtt',
}]},
}, 'params': {
'skip_download': 'm3u8',
},
- '_skip': 'Account needed'
+ '_skip': 'Account needed',
}, {
'url': 'https://members.onepeloton.com/classes/player/26603d53d6bb4de1b340514864a6a6a8',
'info_dict': {
@@ -57,11 +57,11 @@ class PelotonIE(InfoExtractor):
'duration': 1802,
'categories': ['Running'],
'is_live': False,
- 'chapters': 'count:3'
+ 'chapters': 'count:3',
}, 'params': {
'skip_download': 'm3u8',
},
- '_skip': 'Account needed'
+ '_skip': 'Account needed',
}]
_MANIFEST_URL_TEMPLATE = '%s?hdnea=%s'
@@ -79,7 +79,7 @@ class PelotonIE(InfoExtractor):
data=json.dumps({
'username_or_email': username,
'password': password,
- 'with_pubsub': False
+ 'with_pubsub': False,
}).encode(),
headers={'Content-Type': 'application/json', 'User-Agent': 'web'})
except ExtractorError as e:
@@ -115,7 +115,7 @@ class PelotonIE(InfoExtractor):
else:
raise
- metadata = self._download_json('https://api.onepeloton.com/api/ride/%s/details?stream_source=multichannel' % video_id, video_id)
+ metadata = self._download_json(f'https://api.onepeloton.com/api/ride/{video_id}/details?stream_source=multichannel', video_id)
ride_data = metadata.get('ride')
if not ride_data:
raise ExtractorError('Missing stream metadata')
@@ -133,7 +133,7 @@ class PelotonIE(InfoExtractor):
subtitles = {}
else:
if ride_data.get('vod_stream_url'):
- url = 'https://members.onepeloton.com/.netlify/functions/m3u8-proxy?displayLanguage=en&acceptedSubtitles=%s&url=%s?hdnea=%s' % (
+ url = 'https://members.onepeloton.com/.netlify/functions/m3u8-proxy?displayLanguage=en&acceptedSubtitles={}&url={}?hdnea={}'.format(
','.join([re.sub('^([a-z]+)-([A-Z]+)$', r'\1', caption) for caption in ride_data['captions']]),
ride_data['vod_stream_url'],
urllib.parse.quote(urllib.parse.quote(token)))
@@ -147,14 +147,14 @@ class PelotonIE(InfoExtractor):
if metadata.get('instructor_cues'):
subtitles['cues'] = [{
'data': json.dumps(metadata.get('instructor_cues')),
- 'ext': 'json'
+ 'ext': 'json',
}]
category = ride_data.get('fitness_discipline_display_name')
chapters = [{
'start_time': segment.get('start_time_offset'),
'end_time': segment.get('start_time_offset') + segment.get('length'),
- 'title': segment.get('name')
+ 'title': segment.get('name'),
} for segment in traverse_obj(metadata, ('segments', 'segment_list'))]
return {
@@ -171,7 +171,7 @@ class PelotonIE(InfoExtractor):
'categories': [category] if category else None,
'tags': traverse_obj(ride_data, ('equipment_tags', ..., 'name')),
'is_live': is_live,
- 'chapters': chapters
+ 'chapters': chapters,
}
@@ -194,12 +194,12 @@ class PelotonLiveIE(InfoExtractor):
'duration': 2014,
'categories': ['Cycling'],
'is_live': False,
- 'chapters': 'count:3'
+ 'chapters': 'count:3',
},
'params': {
'skip_download': 'm3u8',
},
- '_skip': 'Account needed'
+ '_skip': 'Account needed',
}
def _real_extract(self, url):
@@ -208,7 +208,7 @@ class PelotonLiveIE(InfoExtractor):
if peloton.get('ride_id'):
if not peloton.get('is_live') or peloton.get('is_encore') or peloton.get('status') != 'PRE_START':
- return self.url_result('https://members.onepeloton.com/classes/player/%s' % peloton['ride_id'])
+ return self.url_result('https://members.onepeloton.com/classes/player/{}'.format(peloton['ride_id']))
else:
raise ExtractorError('Ride has not started', expected=True)
else:
diff --git a/yt_dlp/extractor/performgroup.py b/yt_dlp/extractor/performgroup.py
index f4d7f22d0..c0d557591 100644
--- a/yt_dlp/extractor/performgroup.py
+++ b/yt_dlp/extractor/performgroup.py
@@ -15,12 +15,12 @@ class PerformGroupIE(InfoExtractor):
'description': 'md5:7cd3b459c82725b021e046ab10bf1c5b',
'timestamp': 1511533477,
'upload_date': '20171124',
- }
+ },
}]
def _call_api(self, service, auth_token, content_id, referer_url):
return self._download_json(
- 'http://ep3.performfeeds.com/ep%s/%s/%s/' % (service, auth_token, content_id),
+ f'http://ep3.performfeeds.com/ep{service}/{auth_token}/{content_id}/',
content_id, headers={
'Referer': referer_url,
'Origin': 'http://player.performgroup.com',
@@ -52,7 +52,7 @@ class PerformGroupIE(InfoExtractor):
tbr = int_or_none(c.get('bitrate'), 1000)
format_id = 'http'
if tbr:
- format_id += '-%d' % tbr
+ format_id += f'-{tbr}'
formats.append({
'format_id': format_id,
'url': c_url,
diff --git a/yt_dlp/extractor/periscope.py b/yt_dlp/extractor/periscope.py
index d2351df1a..e3b33c4d9 100644
--- a/yt_dlp/extractor/periscope.py
+++ b/yt_dlp/extractor/periscope.py
@@ -9,18 +9,18 @@ from ..utils.traversal import traverse_obj
class PeriscopeBaseIE(InfoExtractor):
_M3U8_HEADERS = {
- 'Referer': 'https://www.periscope.tv/'
+ 'Referer': 'https://www.periscope.tv/',
}
def _call_api(self, method, query, item_id):
return self._download_json(
- 'https://api.periscope.tv/api/v2/%s' % method,
+ f'https://api.periscope.tv/api/v2/{method}',
item_id, query=query)
def _parse_broadcast_data(self, broadcast, video_id):
title = broadcast.get('status') or 'Periscope Broadcast'
uploader = broadcast.get('user_display_name') or broadcast.get('username')
- title = '%s - %s' % (uploader, title) if uploader else title
+ title = f'{uploader} - {title}' if uploader else title
thumbnails = [{
'url': broadcast[image],
} for image in ('image_url', 'image_url_medium', 'image_url_small') if broadcast.get(image)]
@@ -40,7 +40,7 @@ class PeriscopeBaseIE(InfoExtractor):
'live_status': {
'running': 'is_live',
'not_started': 'is_upcoming',
- }.get(traverse_obj(broadcast, ('state', {str.lower}))) or 'was_live'
+ }.get(traverse_obj(broadcast, ('state', {str.lower}))) or 'was_live',
}
@staticmethod
@@ -165,7 +165,7 @@ class PeriscopeUserIE(PeriscopeBaseIE):
webpage, 'data store', default='{}', group='data')),
user_name)
- user = list(data_store['UserCache']['users'].values())[0]['user']
+ user = next(iter(data_store['UserCache']['users'].values()))['user']
user_id = user['id']
session_id = data_store['SessionToken']['public']['broadcastHistory']['token']['session_id']
@@ -182,7 +182,7 @@ class PeriscopeUserIE(PeriscopeBaseIE):
entries = [
self.url_result(
- 'https://www.periscope.tv/%s/%s' % (user_name, broadcast_id))
+ f'https://www.periscope.tv/{user_name}/{broadcast_id}')
for broadcast_id in broadcast_ids]
return self.playlist_result(entries, user_id, title, description)
diff --git a/yt_dlp/extractor/philharmoniedeparis.py b/yt_dlp/extractor/philharmoniedeparis.py
index e8494a084..310ea0f74 100644
--- a/yt_dlp/extractor/philharmoniedeparis.py
+++ b/yt_dlp/extractor/philharmoniedeparis.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import try_get
@@ -48,7 +47,7 @@ class PhilharmonieDeParisIE(InfoExtractor):
video_id = self._match_id(url)
config = self._download_json(
- 'https://otoplayer.philharmoniedeparis.fr/fr/config/%s.json' % video_id, video_id, query={
+ f'https://otoplayer.philharmoniedeparis.fr/fr/config/{video_id}.json', video_id, query={
'id': video_id,
'lang': 'fr-FR',
})
@@ -66,7 +65,7 @@ class PhilharmonieDeParisIE(InfoExtractor):
formats = []
for format_id in ('mobile', 'desktop'):
format_url = try_get(
- files, lambda x: x[format_id]['file'], compat_str)
+ files, lambda x: x[format_id]['file'], str)
if not format_url or format_url in format_urls:
continue
format_urls.add(format_url)
@@ -91,7 +90,7 @@ class PhilharmonieDeParisIE(InfoExtractor):
entry = extract_entry(chapter)
if entry is None:
continue
- entry['id'] = '%s-%d' % (video_id, num)
+ entry['id'] = f'{video_id}-{num}'
entries.append(entry)
return self.playlist_result(entries, video_id, config.get('title'))
diff --git a/yt_dlp/extractor/phoenix.py b/yt_dlp/extractor/phoenix.py
index 5fa133afe..63c256019 100644
--- a/yt_dlp/extractor/phoenix.py
+++ b/yt_dlp/extractor/phoenix.py
@@ -2,7 +2,6 @@ import re
from .youtube import YoutubeIE
from .zdf import ZDFBaseIE
-from ..compat import compat_str
from ..utils import (
int_or_none,
merge_dicts,
@@ -64,7 +63,7 @@ class PhoenixIE(ZDFBaseIE):
article_id = self._match_id(url)
article = self._download_json(
- 'https://www.phoenix.de/response/id/%s' % article_id, article_id,
+ f'https://www.phoenix.de/response/id/{article_id}', article_id,
'Downloading article JSON')
video = article['absaetze'][0]
@@ -76,7 +75,7 @@ class PhoenixIE(ZDFBaseIE):
video_id, ie=YoutubeIE.ie_key(), video_id=video_id,
video_title=title)
- video_id = compat_str(video.get('basename') or video.get('content'))
+ video_id = str(video.get('basename') or video.get('content'))
details = self._download_json(
'https://www.phoenix.de/php/mediaplayer/data/beitrags_details.php',
@@ -91,7 +90,7 @@ class PhoenixIE(ZDFBaseIE):
content_id = details['tracking']['nielsen']['content']['assetid']
info = self._extract_ptmd(
- 'https://tmd.phoenix.de/tmd/2/ngplayer_2_3/vod/ptmd/phoenix/%s' % content_id,
+ f'https://tmd.phoenix.de/tmd/2/ngplayer_2_3/vod/ptmd/phoenix/{content_id}',
content_id, None, url)
duration = int_or_none(try_get(
@@ -99,7 +98,7 @@ class PhoenixIE(ZDFBaseIE):
timestamp = unified_timestamp(details.get('editorialDate'))
series = try_get(
details, lambda x: x['tracking']['nielsen']['content']['program'],
- compat_str)
+ str)
episode = title if details.get('contentType') == 'episode' else None
thumbnails = []
diff --git a/yt_dlp/extractor/photobucket.py b/yt_dlp/extractor/photobucket.py
index 71e9a4805..a7e5bc007 100644
--- a/yt_dlp/extractor/photobucket.py
+++ b/yt_dlp/extractor/photobucket.py
@@ -1,7 +1,7 @@
import json
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote
class PhotobucketIE(InfoExtractor):
@@ -16,7 +16,7 @@ class PhotobucketIE(InfoExtractor):
'upload_date': '20130504',
'uploader': 'rachaneronas',
'title': 'Tired of Link Building? Try BacklinkMyDomain.com!',
- }
+ },
}
def _real_extract(self, url):
@@ -31,7 +31,7 @@ class PhotobucketIE(InfoExtractor):
info_json = self._search_regex(r'Pb\.Data\.Shared\.put\(Pb\.Data\.Shared\.MEDIA, (.*?)\);',
webpage, 'info json')
info = json.loads(info_json)
- url = compat_urllib_parse_unquote(self._html_search_regex(r'file=(.+\.mp4)', info['linkcodes']['html'], 'url'))
+ url = urllib.parse.unquote(self._html_search_regex(r'file=(.+\.mp4)', info['linkcodes']['html'], 'url'))
return {
'id': video_id,
'url': url,
diff --git a/yt_dlp/extractor/piapro.py b/yt_dlp/extractor/piapro.py
index 87d912d56..72e3748a2 100644
--- a/yt_dlp/extractor/piapro.py
+++ b/yt_dlp/extractor/piapro.py
@@ -1,5 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
ExtractorError,
clean_html,
@@ -30,7 +31,7 @@ class PiaproIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.(?:png|jpg)$',
'upload_date': '20090901',
'view_count': int,
- }
+ },
}, {
'note': 'There are break lines in description, mandating (?s) flag',
'url': 'https://piapro.jp/t/9cSd',
@@ -47,13 +48,13 @@ class PiaproIE(InfoExtractor):
'view_count': int,
'thumbnail': r're:^https?://.*\.(?:png|jpg)$',
'uploader_id': 'cyankino',
- }
+ },
}, {
'url': 'https://piapro.jp/content/hcw0z3a169wtemz6',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://piapro.jp/t/-SO-',
- 'only_matching': True
+ 'only_matching': True,
}]
_login_status = False
@@ -64,7 +65,7 @@ class PiaproIE(InfoExtractor):
'_username': username,
'_password': password,
'_remember_me': 'on',
- 'login': 'ログイン'
+ 'login': 'ログイン',
}
self._request_webpage('https://piapro.jp/login/', None)
urlh = self._request_webpage(
@@ -74,7 +75,7 @@ class PiaproIE(InfoExtractor):
if urlh is False:
login_ok = False
else:
- parts = compat_urlparse.urlparse(urlh.url)
+ parts = urllib.parse.urlparse(urlh.url)
if parts.path != '/':
login_ok = False
if not login_ok:
diff --git a/yt_dlp/extractor/picarto.py b/yt_dlp/extractor/picarto.py
index d415ba28e..726fe4142 100644
--- a/yt_dlp/extractor/picarto.py
+++ b/yt_dlp/extractor/picarto.py
@@ -17,14 +17,14 @@ class PicartoIE(InfoExtractor):
'ext': 'mp4',
'title': 're:^Setz [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
'timestamp': int,
- 'is_live': True
+ 'is_live': True,
},
'skip': 'Stream is offline',
}
@classmethod
def suitable(cls, url):
- return False if PicartoVodIE.suitable(url) else super(PicartoIE, cls).suitable(url)
+ return False if PicartoVodIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
channel_id = self._match_id(url)
@@ -42,7 +42,7 @@ class PicartoIE(InfoExtractor):
getLoadBalancerUrl(channel_name: "%s") {
url
}
-}''' % (channel_id, channel_id),
+}''' % (channel_id, channel_id), # noqa: UP031
})['data']
metadata = data['channel']
@@ -80,7 +80,7 @@ class PicartoIE(InfoExtractor):
'is_live': True,
'channel': channel_id,
'channel_id': metadata.get('id'),
- 'channel_url': 'https://picarto.tv/%s' % channel_id,
+ 'channel_url': f'https://picarto.tv/{channel_id}',
'age_limit': age_limit,
'formats': formats,
}
@@ -95,7 +95,7 @@ class PicartoVodIE(InfoExtractor):
'id': 'ArtofZod_2017.12.12.00.13.23.flv',
'ext': 'mp4',
'title': 'ArtofZod_2017.12.12.00.13.23.flv',
- 'thumbnail': r're:^https?://.*\.jpg'
+ 'thumbnail': r're:^https?://.*\.jpg',
},
'skip': 'The VOD does not exist',
}, {
@@ -108,7 +108,7 @@ class PicartoVodIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg',
'channel': 'ArtofZod',
'age_limit': 18,
- }
+ },
}, {
'url': 'https://picarto.tv/videopopout/Plague',
'only_matching': True,
@@ -130,7 +130,7 @@ class PicartoVodIE(InfoExtractor):
name
}}
}}
-}}'''
+}}''',
})['data']['video']
file_name = data['file_name']
diff --git a/yt_dlp/extractor/piksel.py b/yt_dlp/extractor/piksel.py
index 02ae2fe1a..ceb65627a 100644
--- a/yt_dlp/extractor/piksel.py
+++ b/yt_dlp/extractor/piksel.py
@@ -45,7 +45,7 @@ class PikselIE(InfoExtractor):
'upload_date': '20161210',
'description': '',
'thumbnail': 'https://thumbs.piksel.tech/thumbs/aid/t1488331553/3238987.jpg?w=640&h=480',
- }
+ },
},
{
# Original source: http://www.uscourts.gov/cameras-courts/state-washington-vs-donald-j-trump-et-al
@@ -59,13 +59,13 @@ class PikselIE(InfoExtractor):
'timestamp': 1486171129,
'upload_date': '20170204',
'thumbnail': 'https://thumbs.piksel.tech/thumbs/aid/t1495569155/3279887.jpg?w=640&h=360',
- }
+ },
},
{
# https://www3.nhk.or.jp/nhkworld/en/ondemand/video/2019240/
'url': 'http://player.piksel.com/v/refid/nhkworld/prefid/nw_vod_v_en_2019_240_20190823233000_02_1566873477',
'only_matching': True,
- }
+ },
]
def _call_api(self, app_token, resource, display_id, query, host='https://player.piksel.tech', fatal=True):
@@ -84,7 +84,7 @@ class PikselIE(InfoExtractor):
webpage = self._download_webpage(url, display_id)
app_token = self._search_regex([
r'clientAPI\s*:\s*"([^"]+)"',
- r'data-de-api-key\s*=\s*"([^"]+)"'
+ r'data-de-api-key\s*=\s*"([^"]+)"',
], webpage, 'app token')
query = {'refid': ref_id, 'prefid': display_id} if ref_id else {'v': display_id}
program = self._call_api(
diff --git a/yt_dlp/extractor/pinkbike.py b/yt_dlp/extractor/pinkbike.py
index e4e1caaa2..0cd9632c2 100644
--- a/yt_dlp/extractor/pinkbike.py
+++ b/yt_dlp/extractor/pinkbike.py
@@ -27,7 +27,7 @@ class PinkbikeIE(InfoExtractor):
'location': 'Victoria, British Columbia, Canada',
'view_count': int,
'comment_count': int,
- }
+ },
}, {
'url': 'http://es.pinkbike.org/i/kvid/kvid-y5.swf?id=406629',
'only_matching': True,
@@ -37,7 +37,7 @@ class PinkbikeIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://www.pinkbike.com/video/%s' % video_id, video_id)
+ f'http://www.pinkbike.com/video/{video_id}', video_id)
formats = []
for _, format_id, src in re.findall(
@@ -72,7 +72,7 @@ class PinkbikeIE(InfoExtractor):
def extract_count(webpage, label):
return str_to_int(self._search_regex(
- r'<span[^>]+class="stat-num"[^>]*>([\d,.]+)</span>\s*<span[^>]+class="stat-label"[^>]*>%s' % label,
+ rf'<span[^>]+class="stat-num"[^>]*>([\d,.]+)</span>\s*<span[^>]+class="stat-label"[^>]*>{label}',
webpage, label, fatal=False))
view_count = extract_count(webpage, 'Views')
@@ -89,5 +89,5 @@ class PinkbikeIE(InfoExtractor):
'location': location,
'view_count': view_count,
'comment_count': comment_count,
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/pinterest.py b/yt_dlp/extractor/pinterest.py
index 8361fbbc5..07f249498 100644
--- a/yt_dlp/extractor/pinterest.py
+++ b/yt_dlp/extractor/pinterest.py
@@ -22,9 +22,9 @@ class PinterestBaseIE(InfoExtractor):
def _call_api(self, resource, video_id, options):
return self._download_json(
- 'https://www.pinterest.com/resource/%sResource/get/' % resource,
- video_id, 'Download %s JSON metadata' % resource, query={
- 'data': json.dumps({'options': options})
+ f'https://www.pinterest.com/resource/{resource}Resource/get/',
+ video_id, f'Download {resource} JSON metadata', query={
+ 'data': json.dumps({'options': options}),
})['resource_response']
def _extract_video(self, data, extract_formats=True):
@@ -32,7 +32,7 @@ class PinterestBaseIE(InfoExtractor):
thumbnails = []
images = data.get('images')
if isinstance(images, dict):
- for thumbnail_id, thumbnail in images.items():
+ for thumbnail in images.values():
if not isinstance(thumbnail, dict):
continue
thumbnail_url = url_or_none(thumbnail.get('url'))
@@ -109,7 +109,7 @@ class PinterestBaseIE(InfoExtractor):
class PinterestIE(PinterestBaseIE):
- _VALID_URL = r'%s/pin/(?P<id>\d+)' % PinterestBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{PinterestBaseIE._VALID_URL_BASE}/pin/(?P<id>\d+)'
_TESTS = [{
# formats found in data['videos']
'url': 'https://www.pinterest.com/pin/664281013778109217/',
@@ -187,7 +187,7 @@ class PinterestIE(PinterestBaseIE):
class PinterestCollectionIE(PinterestBaseIE):
- _VALID_URL = r'%s/(?P<username>[^/]+)/(?P<id>[^/?#&]+)' % PinterestBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{PinterestBaseIE._VALID_URL_BASE}/(?P<username>[^/]+)/(?P<id>[^/?#&]+)'
_TESTS = [{
'url': 'https://www.pinterest.ca/mashal0407/cool-diys/',
'info_dict': {
@@ -207,15 +207,14 @@ class PinterestCollectionIE(PinterestBaseIE):
@classmethod
def suitable(cls, url):
- return False if PinterestIE.suitable(url) else super(
- PinterestCollectionIE, cls).suitable(url)
+ return False if PinterestIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
username, slug = self._match_valid_url(url).groups()
board = self._call_api(
'Board', slug, {
'slug': slug,
- 'username': username
+ 'username': username,
})['data']
board_id = board['id']
options = {
diff --git a/yt_dlp/extractor/pixivsketch.py b/yt_dlp/extractor/pixivsketch.py
index 850c6f23d..344cdb3d0 100644
--- a/yt_dlp/extractor/pixivsketch.py
+++ b/yt_dlp/extractor/pixivsketch.py
@@ -81,7 +81,7 @@ class PixivSketchIE(PixivSketchBaseIE):
'channel_id': str(traverse_obj(data, ('user', 'pixiv_user_id'), ('owner', 'user', 'pixiv_user_id'))),
'age_limit': 18 if data.get('is_r18') else 15 if data.get('is_r15') else 0,
'timestamp': unified_timestamp(data.get('created_at')),
- 'is_live': True
+ 'is_live': True,
}
@@ -101,7 +101,7 @@ class PixivSketchUserIE(PixivSketchBaseIE):
@classmethod
def suitable(cls, url):
- return super(PixivSketchUserIE, cls).suitable(url) and not PixivSketchIE.suitable(url)
+ return super().suitable(url) and not PixivSketchIE.suitable(url)
def _real_extract(self, url):
user_id = self._match_id(url)
diff --git a/yt_dlp/extractor/pladform.py b/yt_dlp/extractor/pladform.py
index c72a3876c..f4355d0cf 100644
--- a/yt_dlp/extractor/pladform.py
+++ b/yt_dlp/extractor/pladform.py
@@ -39,7 +39,7 @@ class PladformIE(InfoExtractor):
'uploader': 'Comedy Club',
'duration': 367,
},
- 'expected_warnings': ['HTTP Error 404: Not Found']
+ 'expected_warnings': ['HTTP Error 404: Not Found'],
}, {
'url': 'https://out.pladform.ru/player?pl=64471&videoid=3777899&vk_puid15=0&vk_puid34=0',
'md5': '53362fac3a27352da20fa2803cc5cd6f',
@@ -73,14 +73,14 @@ class PladformIE(InfoExtractor):
def fail(text):
raise ExtractorError(
- '%s returned error: %s' % (self.IE_NAME, text),
+ f'{self.IE_NAME} returned error: {text}',
expected=True)
if not video:
- targetUrl = self._request_webpage(url, video_id, note='Resolving final URL').url
- if targetUrl == url:
+ target_url = self._request_webpage(url, video_id, note='Resolving final URL').url
+ if target_url == url:
raise ExtractorError('Can\'t parse page')
- return self.url_result(targetUrl)
+ return self.url_result(target_url)
if video.tag == 'error':
fail(video.text)
@@ -111,7 +111,7 @@ class PladformIE(InfoExtractor):
fail(error)
webpage = self._download_webpage(
- 'http://video.pladform.ru/catalog/video/videoid/%s' % video_id,
+ f'http://video.pladform.ru/catalog/video/videoid/{video_id}',
video_id)
title = self._og_search_title(webpage, fatal=False) or xpath_text(
diff --git a/yt_dlp/extractor/planetmarathi.py b/yt_dlp/extractor/planetmarathi.py
index a4b612a6e..94861836a 100644
--- a/yt_dlp/extractor/planetmarathi.py
+++ b/yt_dlp/extractor/planetmarathi.py
@@ -24,7 +24,7 @@ class PlanetMarathiIE(InfoExtractor):
'duration': 5539,
'upload_date': '20210829',
},
- }] # Trailer skipped
+ }], # Trailer skipped
}, {
'url': 'https://www.planetmarathi.com/titles/baap-beep-baap-season-1',
'playlist_mincount': 10,
@@ -43,18 +43,19 @@ class PlanetMarathiIE(InfoExtractor):
'duration': 29,
'upload_date': '20210829',
},
- }] # Trailers, Episodes, other Character profiles skipped
+ }], # Trailers, Episodes, other Character profiles skipped
}]
def _real_extract(self, url):
- id = self._match_id(url)
+ playlist_id = self._match_id(url)
entries = []
- json_data = self._download_json(f'https://www.planetmarathi.com/api/v1/titles/{id}/assets', id)['assets']
+ json_data = self._download_json(
+ f'https://www.planetmarathi.com/api/v1/titles/{playlist_id}/assets', playlist_id)['assets']
for asset in json_data:
asset_title = asset['mediaAssetName']['en']
if asset_title == 'Movie':
- asset_title = id.replace('-', ' ')
- asset_id = f'{asset["sk"]}_{id}'.replace('#', '-')
+ asset_title = playlist_id.replace('-', ' ')
+ asset_id = f'{asset["sk"]}_{playlist_id}'.replace('#', '-')
formats, subtitles = self._extract_m3u8_formats_and_subtitles(asset['mediaAssetURL'], asset_id)
entries.append({
'id': asset_id,
@@ -68,4 +69,4 @@ class PlanetMarathiIE(InfoExtractor):
'formats': formats,
'subtitles': subtitles,
})
- return self.playlist_result(entries, playlist_id=id)
+ return self.playlist_result(entries, playlist_id=playlist_id)
diff --git a/yt_dlp/extractor/platzi.py b/yt_dlp/extractor/platzi.py
index d978c080b..e29f4c209 100644
--- a/yt_dlp/extractor/platzi.py
+++ b/yt_dlp/extractor/platzi.py
@@ -1,8 +1,6 @@
+import base64
+
from .common import InfoExtractor
-from ..compat import (
- compat_b64decode,
- compat_str,
-)
from ..utils import (
ExtractorError,
clean_html,
@@ -48,10 +46,10 @@ class PlatziBaseIE(InfoExtractor):
None)
for kind in ('error', 'password', 'nonFields'):
- error = str_or_none(login.get('%sError' % kind))
+ error = str_or_none(login.get(f'{kind}Error'))
if error:
raise ExtractorError(
- 'Unable to login: %s' % error, expected=True)
+ f'Unable to login: {error}', expected=True)
raise ExtractorError('Unable to log in')
@@ -120,16 +118,16 @@ class PlatziIE(PlatziBaseIE):
formats.extend(self._extract_m3u8_formats(
format_url, lecture_id, 'mp4',
entry_protocol='m3u8_native', m3u8_id=format_id,
- note='Downloading %s m3u8 information' % server_id,
+ note=f'Downloading {server_id} m3u8 information',
fatal=False))
elif format_id == 'dash':
formats.extend(self._extract_mpd_formats(
format_url, lecture_id, mpd_id=format_id,
- note='Downloading %s MPD manifest' % server_id,
+ note=f'Downloading {server_id} MPD manifest',
fatal=False))
content = str_or_none(desc.get('content'))
- description = (clean_html(compat_b64decode(content).decode('utf-8'))
+ description = (clean_html(base64.b64decode(content).decode('utf-8'))
if content else None)
duration = int_or_none(material.get('duration'), invscale=60)
@@ -168,7 +166,7 @@ class PlatziCourseIE(PlatziBaseIE):
@classmethod
def suitable(cls, url):
- return False if PlatziIE.suitable(url) else super(PlatziCourseIE, cls).suitable(url)
+ return False if PlatziIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
course_name = self._match_id(url)
@@ -207,7 +205,7 @@ class PlatziCourseIE(PlatziBaseIE):
'chapter_id': chapter_id,
})
- course_id = compat_str(try_get(props, lambda x: x['course']['id']))
- course_title = try_get(props, lambda x: x['course']['name'], compat_str)
+ course_id = str(try_get(props, lambda x: x['course']['id']))
+ course_title = try_get(props, lambda x: x['course']['name'], str)
return self.playlist_result(entries, course_id, course_title)
diff --git a/yt_dlp/extractor/playsuisse.py b/yt_dlp/extractor/playsuisse.py
index 7c5cad1be..905f8fc2f 100644
--- a/yt_dlp/extractor/playsuisse.py
+++ b/yt_dlp/extractor/playsuisse.py
@@ -36,7 +36,7 @@ class PlaySuisseIE(InfoExtractor):
'episode': 'Knochen',
'episode_number': 1,
'thumbnail': 're:https://playsuisse-img.akamaized.net/',
- }
+ },
}, {
# film
'url': 'https://www.playsuisse.ch/watch/808675',
@@ -48,7 +48,7 @@ class PlaySuisseIE(InfoExtractor):
'description': 'md5:9f61265c7e6dcc3e046137a792b275fd',
'duration': 5280,
'thumbnail': 're:https://playsuisse-img.akamaized.net/',
- }
+ },
}, {
# series (treated as a playlist)
'url': 'https://www.playsuisse.ch/detail/1115687',
@@ -70,7 +70,7 @@ class PlaySuisseIE(InfoExtractor):
'series': 'They all came out to Montreux',
'thumbnail': 're:https://playsuisse-img.akamaized.net/',
'title': 'Folge 1',
- 'ext': 'mp4'
+ 'ext': 'mp4',
},
}, {
'info_dict': {
@@ -84,7 +84,7 @@ class PlaySuisseIE(InfoExtractor):
'series': 'They all came out to Montreux',
'thumbnail': 're:https://playsuisse-img.akamaized.net/',
'title': 'Folge 2',
- 'ext': 'mp4'
+ 'ext': 'mp4',
},
}, {
'info_dict': {
@@ -98,10 +98,10 @@ class PlaySuisseIE(InfoExtractor):
'series': 'They all came out to Montreux',
'thumbnail': 're:https://playsuisse-img.akamaized.net/',
'title': 'Folge 3',
- 'ext': 'mp4'
- }
+ 'ext': 'mp4',
+ },
}],
- }
+ },
]
_GRAPHQL_QUERY = '''
@@ -160,7 +160,7 @@ class PlaySuisseIE(InfoExtractor):
query=query, headers={'X-CSRF-TOKEN': csrf_token}, data=urlencode_postdata({
'request_type': 'RESPONSE',
'signInName': username,
- 'password': password
+ 'password': password,
}), expected_status=400), ('status', {int_or_none}))
if status == 400:
raise ExtractorError('Invalid username or password', expected=True)
@@ -186,8 +186,8 @@ class PlaySuisseIE(InfoExtractor):
media_id, data=json.dumps({
'operationName': 'AssetWatch',
'query': self._GRAPHQL_QUERY,
- 'variables': {'assetId': media_id}
- }).encode('utf-8'),
+ 'variables': {'assetId': media_id},
+ }).encode(),
headers={'Content-Type': 'application/json', 'locale': 'de'})
return response['data']['assetV2']
diff --git a/yt_dlp/extractor/playtvak.py b/yt_dlp/extractor/playtvak.py
index a01b42290..12e2fa255 100644
--- a/yt_dlp/extractor/playtvak.py
+++ b/yt_dlp/extractor/playtvak.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_urllib_parse_urlencode,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
int_or_none,
@@ -27,7 +25,7 @@ class PlaytvakIE(InfoExtractor):
'timestamp': 1438732860,
'upload_date': '20150805',
'is_live': False,
- }
+ },
}, { # live video test
'url': 'http://slowtv.playtvak.cz/planespotting-0pr-/planespotting.aspx?c=A150624_164934_planespotting_cat',
'info_dict': {
@@ -64,7 +62,7 @@ class PlaytvakIE(InfoExtractor):
'timestamp': 1438969140,
'upload_date': '20150807',
'is_live': False,
- }
+ },
}, { # lidovky.cz
'url': 'http://www.lidovky.cz/dalsi-demonstrace-v-praze-o-migraci-duq-/video.aspx?c=A150808_214044_ln-video_ELE',
'md5': 'c7209ac4ba9d234d4ad5bab7485bcee8',
@@ -77,7 +75,7 @@ class PlaytvakIE(InfoExtractor):
'timestamp': 1439052180,
'upload_date': '20150808',
'is_live': False,
- }
+ },
}, { # metro.cz
'url': 'http://www.metro.cz/video-pod-billboardem-se-na-vltavske-roztocil-kolotoc-deti-vozil-jen-par-hodin-1hx-/metro-extra.aspx?c=A141111_173251_metro-extra_row',
'md5': '84fc1deedcac37b7d4a6ccae7c716668',
@@ -90,7 +88,7 @@ class PlaytvakIE(InfoExtractor):
'timestamp': 1415725500,
'upload_date': '20141111',
'is_live': False,
- }
+ },
}, {
'url': 'http://www.playtvak.cz/embed.aspx?idvideo=V150729_141549_play-porad_kuko',
'only_matching': True,
@@ -104,16 +102,16 @@ class PlaytvakIE(InfoExtractor):
info_url = self._html_search_regex(
r'Misc\.video(?:FLV)?\(\s*{\s*data\s*:\s*"([^"]+)"', webpage, 'info url')
- parsed_url = compat_urlparse.urlparse(info_url)
+ parsed_url = urllib.parse.urlparse(info_url)
- qs = compat_urlparse.parse_qs(parsed_url.query)
+ qs = urllib.parse.parse_qs(parsed_url.query)
qs.update({
'reklama': ['0'],
'type': ['js'],
})
- info_url = compat_urlparse.urlunparse(
- parsed_url._replace(query=compat_urllib_parse_urlencode(qs, True)))
+ info_url = urllib.parse.urlunparse(
+ parsed_url._replace(query=urllib.parse.urlencode(qs, True)))
json_info = self._download_json(
info_url, video_id,
@@ -136,7 +134,7 @@ class PlaytvakIE(InfoExtractor):
continue
format_ = fmt['format']
- format_id = '%s_%s' % (format_, fmt['quality'])
+ format_id = '{}_{}'.format(format_, fmt['quality'])
preference = None
if format_ in ('mp4', 'webm'):
diff --git a/yt_dlp/extractor/playwire.py b/yt_dlp/extractor/playwire.py
index 1057bff3a..8539a4b56 100644
--- a/yt_dlp/extractor/playwire.py
+++ b/yt_dlp/extractor/playwire.py
@@ -48,7 +48,7 @@ class PlaywireIE(InfoExtractor):
publisher_id, video_id = mobj.group('publisher_id'), mobj.group('id')
player = self._download_json(
- 'http://config.playwire.com/%s/videos/v2/%s/zeus.json' % (publisher_id, video_id),
+ f'http://config.playwire.com/{publisher_id}/videos/v2/{video_id}/zeus.json',
video_id)
title = player['settings']['title']
diff --git a/yt_dlp/extractor/pluralsight.py b/yt_dlp/extractor/pluralsight.py
index 60c9efffe..d3f03f7ee 100644
--- a/yt_dlp/extractor/pluralsight.py
+++ b/yt_dlp/extractor/pluralsight.py
@@ -3,12 +3,9 @@ import json
import os
import random
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
dict_get,
@@ -27,7 +24,7 @@ from ..utils import (
class PluralsightBaseIE(InfoExtractor):
_API_BASE = 'https://app.pluralsight.com'
- _GRAPHQL_EP = '%s/player/api/graphql' % _API_BASE
+ _GRAPHQL_EP = f'{_API_BASE}/player/api/graphql'
_GRAPHQL_HEADERS = {
'Content-Type': 'application/json;charset=UTF-8',
}
@@ -95,8 +92,8 @@ query BootstrapPlayer {
response = self._download_json(
self._GRAPHQL_EP, display_id, data=json.dumps({
'query': self._GRAPHQL_COURSE_TMPL % course_id,
- 'variables': {}
- }).encode('utf-8'), headers=self._GRAPHQL_HEADERS)
+ 'variables': {},
+ }).encode(), headers=self._GRAPHQL_HEADERS)
course = try_get(
response, lambda x: x['data']['rpc']['bootstrapPlayer']['course'],
@@ -105,7 +102,7 @@ query BootstrapPlayer {
return course
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, response['error']['message']),
+ '{} said: {}'.format(self.IE_NAME, response['error']['message']),
expected=True)
@@ -176,7 +173,7 @@ query viewClip {
'post url', default=self._LOGIN_URL, group='url')
if not post_url.startswith('http'):
- post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
+ post_url = urllib.parse.urljoin(self._LOGIN_URL, post_url)
response = self._download_webpage(
post_url, None, 'Logging in',
@@ -187,7 +184,7 @@ query viewClip {
r'<span[^>]+class="field-validation-error"[^>]*>([^<]+)</span>',
response, 'error message', default=None)
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
if all(not re.search(p, response) for p in (
r'__INITIAL_STATE__', r'["\']currentUser["\']',
@@ -196,13 +193,12 @@ query viewClip {
BLOCKED = 'Your account has been blocked due to suspicious activity'
if BLOCKED in response:
raise ExtractorError(
- 'Unable to login: %s' % BLOCKED, expected=True)
+ f'Unable to login: {BLOCKED}', expected=True)
MUST_AGREE = 'To continue using Pluralsight, you must agree to'
if any(p in response for p in (MUST_AGREE, '>Disagree<', '>Agree<')):
raise ExtractorError(
- 'Unable to login: %s some documents. Go to pluralsight.com, '
- 'log in and agree with what Pluralsight requires.'
- % MUST_AGREE, expected=True)
+ f'Unable to login: {MUST_AGREE} some documents. Go to pluralsight.com, '
+ 'log in and agree with what Pluralsight requires.', expected=True)
raise ExtractorError('Unable to log in')
@@ -210,8 +206,7 @@ query viewClip {
captions = None
if clip_id:
captions = self._download_json(
- '%s/transcript/api/v1/caption/json/%s/%s'
- % (self._API_BASE, clip_id, lang), video_id,
+ f'{self._API_BASE}/transcript/api/v1/caption/json/{clip_id}/{lang}', video_id,
'Downloading captions JSON', 'Unable to download captions JSON',
fatal=False)
if not captions:
@@ -222,9 +217,9 @@ query viewClip {
'm': name,
}
captions = self._download_json(
- '%s/player/retrieve-captions' % self._API_BASE, video_id,
+ f'{self._API_BASE}/player/retrieve-captions', video_id,
'Downloading captions JSON', 'Unable to download captions JSON',
- fatal=False, data=json.dumps(captions_post).encode('utf-8'),
+ fatal=False, data=json.dumps(captions_post).encode(),
headers={'Content-Type': 'application/json;charset=utf-8'})
if captions:
return {
@@ -234,7 +229,7 @@ query viewClip {
}, {
'ext': 'srt',
'data': self._convert_subtitles(duration, captions),
- }]
+ }],
}
@staticmethod
@@ -255,10 +250,8 @@ query viewClip {
continue
srt += os.linesep.join(
(
- '%d' % num,
- '%s --> %s' % (
- srt_subtitles_timecode(start),
- srt_subtitles_timecode(end)),
+ f'{num}',
+ f'{srt_subtitles_timecode(start)} --> {srt_subtitles_timecode(end)}',
text,
os.linesep,
))
@@ -272,10 +265,10 @@ query viewClip {
clip_idx = qs.get('clip', [None])[0]
course_name = qs.get('course', [None])[0]
- if any(not f for f in (author, name, clip_idx, course_name,)):
+ if any(not f for f in (author, name, clip_idx, course_name)):
raise ExtractorError('Invalid URL', expected=True)
- display_id = '%s-%s' % (name, clip_idx)
+ display_id = f'{name}-{clip_idx}'
course = self._download_course(course_name, url, display_id)
@@ -291,7 +284,7 @@ query viewClip {
clip_index = clip_.get('index')
if clip_index is None:
continue
- if compat_str(clip_index) == clip_idx:
+ if str(clip_index) == clip_idx:
clip = clip_
break
@@ -308,14 +301,14 @@ query viewClip {
'high-widescreen': {'width': 1280, 'height': 720},
}
- QUALITIES_PREFERENCE = ('low', 'medium', 'high', 'high-widescreen',)
+ QUALITIES_PREFERENCE = ('low', 'medium', 'high', 'high-widescreen')
quality_key = qualities(QUALITIES_PREFERENCE)
AllowedQuality = collections.namedtuple('AllowedQuality', ['ext', 'qualities'])
ALLOWED_QUALITIES = (
- AllowedQuality('webm', ['high', ]),
- AllowedQuality('mp4', ['low', 'medium', 'high', ]),
+ AllowedQuality('webm', ['high']),
+ AllowedQuality('mp4', ['low', 'medium', 'high']),
)
# Some courses also offer widescreen resolution for high quality (see
@@ -359,23 +352,23 @@ query viewClip {
'mediaType': ext,
'quality': '%dx%d' % (f['width'], f['height']),
}
- format_id = '%s-%s' % (ext, quality)
+ format_id = f'{ext}-{quality}'
try:
viewclip = self._download_json(
self._GRAPHQL_EP, display_id,
- 'Downloading %s viewclip graphql' % format_id,
+ f'Downloading {format_id} viewclip graphql',
data=json.dumps({
'query': self.GRAPHQL_VIEWCLIP_TMPL % clip_post,
- 'variables': {}
- }).encode('utf-8'),
+ 'variables': {},
+ }).encode(),
headers=self._GRAPHQL_HEADERS)['data']['viewClip']
except ExtractorError:
# Still works but most likely will go soon
viewclip = self._download_json(
- '%s/video/clips/viewclip' % self._API_BASE, display_id,
- 'Downloading %s viewclip JSON' % format_id, fatal=False,
- data=json.dumps(clip_post).encode('utf-8'),
+ f'{self._API_BASE}/video/clips/viewclip', display_id,
+ f'Downloading {format_id} viewclip JSON', fatal=False,
+ data=json.dumps(clip_post).encode(),
headers={'Content-Type': 'application/json;charset=utf-8'})
# Pluralsight tracks multiple sequential calls to ViewClip API and start
@@ -404,7 +397,7 @@ query viewClip {
clip_f.update({
'url': clip_url,
'ext': ext,
- 'format_id': '%s-%s' % (format_id, cdn) if cdn else format_id,
+ 'format_id': f'{format_id}-{cdn}' if cdn else format_id,
'quality': quality_key(quality),
'source_preference': int_or_none(clip_url_data.get('rank')),
})
@@ -472,7 +465,7 @@ class PluralsightCourseIE(PluralsightBaseIE):
if clip_index is None:
continue
clip_url = update_url_query(
- '%s/player' % self._API_BASE, query={
+ f'{self._API_BASE}/player', query={
'mode': 'live',
'course': course_name,
'author': author,
diff --git a/yt_dlp/extractor/plutotv.py b/yt_dlp/extractor/plutotv.py
index 5898d927c..234ee987b 100644
--- a/yt_dlp/extractor/plutotv.py
+++ b/yt_dlp/extractor/plutotv.py
@@ -1,11 +1,8 @@
import re
+import urllib.parse
import uuid
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
float_or_none,
@@ -31,14 +28,14 @@ class PlutoTVIE(InfoExtractor):
_INFO_QUERY_PARAMS = {
'appName': 'web',
'appVersion': 'na',
- 'clientID': compat_str(uuid.uuid1()),
+ 'clientID': str(uuid.uuid1()),
'clientModelNumber': 'na',
'serverSideAds': 'false',
'deviceMake': 'unknown',
'deviceModel': 'web',
'deviceType': 'web',
'deviceVersion': 'unknown',
- 'sid': compat_str(uuid.uuid1()),
+ 'sid': str(uuid.uuid1()),
}
_TESTS = [
{
@@ -54,21 +51,21 @@ class PlutoTVIE(InfoExtractor):
'season_number': 2,
'episode_number': 3,
'duration': 3600,
- }
+ },
}, {
'url': 'https://pluto.tv/on-demand/series/i-love-money/season/1/',
'playlist_count': 11,
'info_dict': {
'id': '5de6c582e9379ae4912dedbd',
'title': 'I Love Money - Season 1',
- }
+ },
}, {
'url': 'https://pluto.tv/on-demand/series/i-love-money/',
'playlist_count': 26,
'info_dict': {
'id': '5de6c582e9379ae4912dedbd',
'title': 'I Love Money',
- }
+ },
}, {
'url': 'https://pluto.tv/on-demand/movies/arrival-2015-1-1',
'md5': '3cead001d317a018bf856a896dee1762',
@@ -78,7 +75,7 @@ class PlutoTVIE(InfoExtractor):
'title': 'Arrival',
'description': 'When mysterious spacecraft touch down across the globe, an elite team - led by expert translator Louise Banks (Academy Award® nominee Amy Adams) – races against time to decipher their intent.',
'duration': 9000,
- }
+ },
}, {
'url': 'https://pluto.tv/en/on-demand/series/manhunters-fugitive-task-force/seasons/1/episode/third-times-the-charm-1-1',
'only_matching': True,
@@ -95,8 +92,8 @@ class PlutoTVIE(InfoExtractor):
'title': 'Attack of the Killer Tomatoes',
'description': 'A group of scientists band together to save the world from mutated tomatoes that KILL! (1978)',
'duration': 5700,
- }
- }
+ },
+ },
]
def _to_ad_free_formats(self, video_id, formats, subtitles):
@@ -112,14 +109,14 @@ class PlutoTVIE(InfoExtractor):
re.MULTILINE)
if first_segment_url:
m3u8_urls.add(
- compat_urlparse.urljoin(first_segment_url.group(1), '0-end/master.m3u8'))
+ urllib.parse.urljoin(first_segment_url.group(1), '0-end/master.m3u8'))
continue
first_segment_url = re.search(
r'^(https?://.*/).+\-0+[0-1]0\.ts$', res,
re.MULTILINE)
if first_segment_url:
m3u8_urls.add(
- compat_urlparse.urljoin(first_segment_url.group(1), 'master.m3u8'))
+ urllib.parse.urljoin(first_segment_url.group(1), 'master.m3u8'))
continue
for m3u8_url in m3u8_urls:
diff --git a/yt_dlp/extractor/podchaser.py b/yt_dlp/extractor/podchaser.py
index fc2d407b1..4570f0f17 100644
--- a/yt_dlp/extractor/podchaser.py
+++ b/yt_dlp/extractor/podchaser.py
@@ -29,8 +29,8 @@ class PodchaserIE(InfoExtractor):
'duration': 3708,
'timestamp': 1636531259,
'upload_date': '20211110',
- 'average_rating': 4.0
- }
+ 'average_rating': 4.0,
+ },
}, {
'url': 'https://www.podchaser.com/podcasts/the-bone-zone-28853',
'info_dict': {
@@ -38,15 +38,15 @@ class PodchaserIE(InfoExtractor):
'title': 'The Bone Zone',
'description': 'Podcast by The Bone Zone',
},
- 'playlist_count': 275
+ 'playlist_count': 275,
}, {
'url': 'https://www.podchaser.com/podcasts/sean-carrolls-mindscape-scienc-699349/episodes',
'info_dict': {
'id': '699349',
'title': 'Sean Carroll\'s Mindscape: Science, Society, Philosophy, Culture, Arts, and Ideas',
- 'description': 'md5:2cbd8f4749891a84dc8235342e0b5ff1'
+ 'description': 'md5:2cbd8f4749891a84dc8235342e0b5ff1',
},
- 'playlist_mincount': 225
+ 'playlist_mincount': 225,
}]
@staticmethod
@@ -77,9 +77,9 @@ class PodchaserIE(InfoExtractor):
'count': self._PAGE_SIZE,
'sort_order': 'SORT_ORDER_RECENT',
'filters': {
- 'podcast_id': podcast_id
+ 'podcast_id': podcast_id,
},
- 'options': {}
+ 'options': {},
}).encode())
for episode in json_response['entities']:
diff --git a/yt_dlp/extractor/podomatic.py b/yt_dlp/extractor/podomatic.py
index 37b68694b..b5af82471 100644
--- a/yt_dlp/extractor/podomatic.py
+++ b/yt_dlp/extractor/podomatic.py
@@ -26,7 +26,7 @@ class PodomaticIE(InfoExtractor):
'uploader_id': 'scienceteachingtips',
'title': '64. When the Moon Hits Your Eye',
'duration': 446,
- }
+ },
}, {
'url': 'http://ostbahnhof.podomatic.com/entry/2013-11-15T16_31_21-08_00',
'md5': 'd2cf443931b6148e27638650e2638297',
@@ -37,7 +37,7 @@ class PodomaticIE(InfoExtractor):
'uploader_id': 'ostbahnhof',
'title': 'Einunddreizig',
'duration': 3799,
- }
+ },
}, {
'url': 'https://www.podomatic.com/podcasts/scienceteachingtips/episodes/2009-01-02T16_03_35-08_00',
'only_matching': True,
@@ -48,16 +48,15 @@ class PodomaticIE(InfoExtractor):
video_id = mobj.group('id')
channel = mobj.group('channel') or mobj.group('channel_2')
- json_url = (('%s://%s.podomatic.com/entry/embed_params/%s'
- + '?permalink=true&rtmp=0') %
- (mobj.group('proto'), channel, video_id))
+ json_url = ('{}://{}.podomatic.com/entry/embed_params/{}?permalink=true&rtmp=0'.format(
+ mobj.group('proto'), channel, video_id))
data_json = self._download_webpage(
json_url, video_id, 'Downloading video info')
data = json.loads(data_json)
video_url = data['downloadLink']
if not video_url:
- video_url = '%s/%s' % (data['streamer'].replace('rtmp', 'http'), data['mediaLocation'])
+ video_url = '{}/{}'.format(data['streamer'].replace('rtmp', 'http'), data['mediaLocation'])
uploader = data['podcast']
title = data['title']
thumbnail = data['imageLocation']
diff --git a/yt_dlp/extractor/pokemon.py b/yt_dlp/extractor/pokemon.py
index 0911893d4..1769684f7 100644
--- a/yt_dlp/extractor/pokemon.py
+++ b/yt_dlp/extractor/pokemon.py
@@ -48,7 +48,7 @@ class PokemonIE(InfoExtractor):
video_id, display_id = self._match_valid_url(url).groups()
webpage = self._download_webpage(url, video_id or display_id)
video_data = extract_attributes(self._search_regex(
- r'(<[^>]+data-video-id="%s"[^>]*>)' % (video_id if video_id else '[a-z0-9]{32}'),
+ r'(<[^>]+data-video-id="{}"[^>]*>)'.format(video_id if video_id else '[a-z0-9]{32}'),
webpage, 'video data element'))
video_id = video_data['data-video-id']
title = video_data.get('data-video-title') or self._html_search_meta(
@@ -57,7 +57,7 @@ class PokemonIE(InfoExtractor):
return {
'_type': 'url_transparent',
'id': video_id,
- 'url': 'limelight:media:%s' % video_id,
+ 'url': f'limelight:media:{video_id}',
'title': title,
'description': video_data.get('data-video-summary'),
'thumbnail': video_data.get('data-video-poster'),
@@ -80,13 +80,13 @@ class PokemonWatchIE(InfoExtractor):
'ext': 'mp4',
'title': 'Lillier and the Staff!',
'description': 'md5:338841b8c21b283d24bdc9b568849f04',
- }
+ },
}, {
'url': 'https://watch.pokemon.com/en-us/#/player?id=3fe7752ba09141f0b0f7756d1981c6b2',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://watch.pokemon.com/de-de/player.html?id=b3c402e111a4459eb47e12160ab0ba07',
- 'only_matching': True
+ 'only_matching': True,
}]
def _extract_media(self, channel_array, video_id):
@@ -102,7 +102,7 @@ class PokemonWatchIE(InfoExtractor):
info = {
'_type': 'url',
'id': video_id,
- 'url': 'limelight:media:%s' % video_id,
+ 'url': f'limelight:media:{video_id}',
'ie_key': 'LimelightMedia',
}
@@ -120,7 +120,7 @@ class PokemonWatchIE(InfoExtractor):
if video_data is None:
raise ExtractorError(
- 'Video %s does not exist' % video_id, expected=True)
+ f'Video {video_id} does not exist', expected=True)
info['_type'] = 'url_transparent'
images = video_data.get('images')
diff --git a/yt_dlp/extractor/pokergo.py b/yt_dlp/extractor/pokergo.py
index 5c7baadf2..e22348053 100644
--- a/yt_dlp/extractor/pokergo.py
+++ b/yt_dlp/extractor/pokergo.py
@@ -49,26 +49,27 @@ class PokerGoIE(PokerGoBaseIE):
'episode': 'Episode 2',
'display_id': '2a70ec4e-4a80-414b-97ec-725d9b72a7dc',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- data_json = self._download_json(f'https://api.pokergo.com/v2/properties/{self._PROPERTY_ID}/videos/{id}', id,
- headers={'authorization': f'Bearer {self._AUTH_TOKEN}'})['data']
+ video_id = self._match_id(url)
+ data_json = self._download_json(
+ f'https://api.pokergo.com/v2/properties/{self._PROPERTY_ID}/videos/{video_id}', video_id,
+ headers={'authorization': f'Bearer {self._AUTH_TOKEN}'})['data']
v_id = data_json['source']
thumbnails = [{
'url': image['url'],
'id': image.get('label'),
'width': image.get('width'),
- 'height': image.get('height')
+ 'height': image.get('height'),
} for image in data_json.get('images') or [] if image.get('url')]
- series_json = next(dct for dct in data_json.get('show_tags') or [] if dct.get('video_id') == id) or {}
+ series_json = next(dct for dct in data_json.get('show_tags') or [] if dct.get('video_id') == video_id) or {}
return {
'_type': 'url_transparent',
- 'display_id': id,
+ 'display_id': video_id,
'title': data_json.get('title'),
'description': data_json.get('description'),
'duration': data_json.get('duration'),
@@ -76,7 +77,7 @@ class PokerGoIE(PokerGoBaseIE):
'season_number': series_json.get('season'),
'episode_number': series_json.get('episode_number'),
'series': try_get(series_json, lambda x: x['tag']['name']),
- 'url': f'https://cdn.jwplayer.com/v2/media/{v_id}'
+ 'url': f'https://cdn.jwplayer.com/v2/media/{v_id}',
}
@@ -91,9 +92,10 @@ class PokerGoCollectionIE(PokerGoBaseIE):
},
}]
- def _entries(self, id):
- data_json = self._download_json(f'https://api.pokergo.com/v2/properties/{self._PROPERTY_ID}/collections/{id}?include=entities',
- id, headers={'authorization': f'Bearer {self._AUTH_TOKEN}'})['data']
+ def _entries(self, playlist_id):
+ data_json = self._download_json(
+ f'https://api.pokergo.com/v2/properties/{self._PROPERTY_ID}/collections/{playlist_id}?include=entities',
+ playlist_id, headers={'authorization': f'Bearer {self._AUTH_TOKEN}'})['data']
for video in data_json.get('collection_video') or []:
video_id = video.get('id')
if video_id:
@@ -102,5 +104,5 @@ class PokerGoCollectionIE(PokerGoBaseIE):
ie=PokerGoIE.ie_key(), video_id=video_id)
def _real_extract(self, url):
- id = self._match_id(url)
- return self.playlist_result(self._entries(id), playlist_id=id)
+ playlist_id = self._match_id(url)
+ return self.playlist_result(self._entries(playlist_id), playlist_id=playlist_id)
diff --git a/yt_dlp/extractor/polsatgo.py b/yt_dlp/extractor/polsatgo.py
index ecf2132b4..d4a0d6ab8 100644
--- a/yt_dlp/extractor/polsatgo.py
+++ b/yt_dlp/extractor/polsatgo.py
@@ -33,7 +33,7 @@ class PolsatGoIE(InfoExtractor):
continue
yield {
'url': url,
- 'height': int_or_none(try_get(source, lambda x: x['quality'][:-1]))
+ 'height': int_or_none(try_get(source, lambda x: x['quality'][:-1])),
}
def _real_extract(self, url):
@@ -47,7 +47,7 @@ class PolsatGoIE(InfoExtractor):
'id': video_id,
'title': media['displayInfo']['title'],
'formats': formats,
- 'age_limit': int_or_none(media['displayInfo']['ageGroup'])
+ 'age_limit': int_or_none(media['displayInfo']['ageGroup']),
}
def _call_api(self, endpoint, media_id, method, params):
@@ -77,7 +77,7 @@ class PolsatGoIE(InfoExtractor):
'clientId': rand_uuid,
'cpid': 1,
},
- }).encode('utf-8'),
+ }).encode(),
headers={'Content-type': 'application/json'})
if not res.get('result'):
if res['error']['code'] == 13404:
diff --git a/yt_dlp/extractor/polskieradio.py b/yt_dlp/extractor/polskieradio.py
index e0b22fffd..6fb21e156 100644
--- a/yt_dlp/extractor/polskieradio.py
+++ b/yt_dlp/extractor/polskieradio.py
@@ -5,7 +5,6 @@ import re
import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
InAdvancePagedList,
@@ -37,7 +36,7 @@ class PolskieRadioBaseExtractor(InfoExtractor):
media_urls.add(media_url)
entry = base_data.copy()
entry.update({
- 'id': compat_str(media['id']),
+ 'id': str(media['id']),
'url': media_url,
'duration': int_or_none(media.get('length')),
'vcodec': 'none' if media.get('provider') == 'audio' else None,
@@ -68,7 +67,7 @@ class PolskieRadioLegacyIE(PolskieRadioBaseExtractor):
'timestamp': 1592654400,
'upload_date': '20200620',
'duration': 1430,
- 'thumbnail': r're:^https?://static\.prsa\.pl/images/.*\.jpg$'
+ 'thumbnail': r're:^https?://static\.prsa\.pl/images/.*\.jpg$',
},
}],
}, {
@@ -328,14 +327,14 @@ class PolskieRadioCategoryIE(InfoExtractor):
'id': '4143',
'title': 'Kierunek Kraków',
},
- 'playlist_mincount': 61
+ 'playlist_mincount': 61,
}, {
'url': 'http://www.polskieradio.pl/10,czworka/214,muzyka',
'info_dict': {
'id': '214',
'title': 'Muzyka',
},
- 'playlist_mincount': 61
+ 'playlist_mincount': 61,
}, {
# billennium tabs
'url': 'https://www.polskieradio.pl/8/2385',
@@ -400,7 +399,7 @@ class PolskieRadioCategoryIE(InfoExtractor):
params = self._search_json(
r'<div[^>]+class=["\']next["\'][^>]*>\s*<a[^>]+onclick=["\']TB_LoadTab\(',
pagination, 'next page params', category_id, default=None, close_objects=1,
- contains_pattern='.+', transform_source=lambda x: '[%s' % js_to_json(unescapeHTML(x)))
+ contains_pattern='.+', transform_source=lambda x: f'[{js_to_json(unescapeHTML(x))}')
if not params:
break
tab_content = self._download_json(
@@ -409,7 +408,7 @@ class PolskieRadioCategoryIE(InfoExtractor):
data=json.dumps(dict(zip((
'boxInstanceId', 'tabId', 'categoryType', 'sectionId', 'categoryId', 'pagerMode',
'subjectIds', 'tagIndexId', 'queryString', 'name', 'openArticlesInParentTemplate',
- 'idSectionFromUrl', 'maxDocumentAge', 'showCategoryForArticle', 'pageNumber'
+ 'idSectionFromUrl', 'maxDocumentAge', 'showCategoryForArticle', 'pageNumber',
), params))).encode())['d']
content, pagination = tab_content['Content'], tab_content.get('PagerContent')
elif is_post_back:
@@ -511,7 +510,7 @@ class PolskieRadioPlayerIE(InfoExtractor):
})
return {
- 'id': compat_str(channel['id']),
+ 'id': str(channel['id']),
'formats': formats,
'title': channel.get('name') or channel.get('streamName'),
'display_id': channel_url,
@@ -603,7 +602,7 @@ class PolskieRadioPodcastIE(PolskieRadioPodcastBaseExtractor):
podcast_id, 'Downloading podcast metadata',
data=json.dumps({
'guids': [podcast_id],
- }).encode('utf-8'),
+ }).encode(),
headers={
'Content-Type': 'application/json',
})
diff --git a/yt_dlp/extractor/popcorntimes.py b/yt_dlp/extractor/popcorntimes.py
index ddc5ec8c8..cfece86ee 100644
--- a/yt_dlp/extractor/popcorntimes.py
+++ b/yt_dlp/extractor/popcorntimes.py
@@ -1,5 +1,6 @@
+import base64
+
from .common import InfoExtractor
-from ..compat import compat_b64decode
from ..utils import int_or_none
@@ -49,7 +50,7 @@ class PopcorntimesIE(InfoExtractor):
c_ord -= 26
loc_b64 += chr(c_ord)
- video_url = compat_b64decode(loc_b64).decode('utf-8')
+ video_url = base64.b64decode(loc_b64).decode('utf-8')
description = self._html_search_regex(
r'(?s)<div[^>]+class=["\']pt-movie-desc[^>]+>(.+?)</div>', webpage,
diff --git a/yt_dlp/extractor/popcorntv.py b/yt_dlp/extractor/popcorntv.py
index 77984626f..2897bb464 100644
--- a/yt_dlp/extractor/popcorntv.py
+++ b/yt_dlp/extractor/popcorntv.py
@@ -37,7 +37,7 @@ class PopcornTVIE(InfoExtractor):
m3u8_url = extract_attributes(
self._search_regex(
r'(<link[^>]+itemprop=["\'](?:content|embed)Url[^>]*>)',
- webpage, 'content'
+ webpage, 'content',
))['href']
formats = self._extract_m3u8_formats(
diff --git a/yt_dlp/extractor/pornbox.py b/yt_dlp/extractor/pornbox.py
index c381382e9..e15244dac 100644
--- a/yt_dlp/extractor/pornbox.py
+++ b/yt_dlp/extractor/pornbox.py
@@ -29,8 +29,8 @@ class PornboxIE(InfoExtractor):
'cast': ['Lily Strong', 'John Strong'],
'tags': 'count:11',
'description': 'md5:589c7f33e183aa8aa939537300efb859',
- 'thumbnail': r're:^https?://cdn-image\.gtflixtv\.com.*\.jpg.*$'
- }
+ 'thumbnail': r're:^https?://cdn-image\.gtflixtv\.com.*\.jpg.*$',
+ },
}, {
'url': 'https://pornbox.com/application/watch-page/216045',
'info_dict': {
@@ -46,15 +46,15 @@ class PornboxIE(InfoExtractor):
'cast': 'count:3',
'tags': 'count:29',
'thumbnail': r're:^https?://cdn-image\.gtflixtv\.com.*\.jpg.*$',
- 'subtitles': 'count:6'
+ 'subtitles': 'count:6',
},
'params': {
'skip_download': True,
- 'ignore_no_formats_error': True
+ 'ignore_no_formats_error': True,
},
'expected_warnings': [
'You are either not logged in or do not have access to this scene',
- 'No video formats found', 'Requested format is not available']
+ 'No video formats found', 'Requested format is not available'],
}]
def _real_extract(self, url):
@@ -64,7 +64,7 @@ class PornboxIE(InfoExtractor):
subtitles = {country_code: [{
'url': f'https://pornbox.com/contents/{video_id}/subtitles/{country_code}',
- 'ext': 'srt'
+ 'ext': 'srt',
}] for country_code in traverse_obj(public_data, ('subtitles', ..., {str}))}
is_free_scene = traverse_obj(
diff --git a/yt_dlp/extractor/pornflip.py b/yt_dlp/extractor/pornflip.py
index d711d3e67..bc684fd6f 100644
--- a/yt_dlp/extractor/pornflip.py
+++ b/yt_dlp/extractor/pornflip.py
@@ -43,7 +43,7 @@ class PornFlipIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'https://{}/sv/{}'.format(self._HOST, video_id), video_id, headers={'host': self._HOST})
+ f'https://{self._HOST}/sv/{video_id}', video_id, headers={'host': self._HOST})
description = self._html_search_regex(r'&p\[summary\]=(.*?)\s*&p', webpage, 'description', fatal=False)
duration = self._search_regex(r'"duration":\s+"([^"]+)",', webpage, 'duration', fatal=False)
view_count = self._search_regex(r'"interactionCount":\s+"([^"]+)"', webpage, 'view_count', fatal=False)
diff --git a/yt_dlp/extractor/pornhub.py b/yt_dlp/extractor/pornhub.py
index d94f28ceb..679dc6323 100644
--- a/yt_dlp/extractor/pornhub.py
+++ b/yt_dlp/extractor/pornhub.py
@@ -6,7 +6,6 @@ import re
from .common import InfoExtractor
from .openload import PhantomJSwrapper
-from ..compat import compat_str
from ..networking import Request
from ..networking.exceptions import HTTPError
from ..utils import (
@@ -81,9 +80,9 @@ class PornHubBaseIE(InfoExtractor):
if username is None:
return
- login_url = 'https://www.%s/%slogin' % (host, 'premium/' if 'premium' in host else '')
+ login_url = 'https://www.{}/{}login'.format(host, 'premium/' if 'premium' in host else '')
login_page = self._download_webpage(
- login_url, None, 'Downloading %s login page' % site)
+ login_url, None, f'Downloading {site} login page')
def is_logged(webpage):
return any(re.search(p, webpage) for p in (
@@ -102,8 +101,8 @@ class PornHubBaseIE(InfoExtractor):
})
response = self._download_json(
- 'https://www.%s/front/authenticate' % host, None,
- 'Logging in to %s' % site,
+ f'https://www.{host}/front/authenticate', None,
+ f'Logging in to {site}',
data=urlencode_postdata(login_form),
headers={
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
@@ -118,23 +117,23 @@ class PornHubBaseIE(InfoExtractor):
message = response.get('message')
if message is not None:
raise ExtractorError(
- 'Unable to login: %s' % message, expected=True)
+ f'Unable to login: {message}', expected=True)
raise ExtractorError('Unable to log in')
class PornHubIE(PornHubBaseIE):
IE_DESC = 'PornHub and Thumbzilla'
- _VALID_URL = r'''(?x)
+ _VALID_URL = rf'''(?x)
https?://
(?:
(?:[^/]+\.)?
- %s
+ {PornHubBaseIE._PORNHUB_HOST_RE}
/(?:(?:view_video\.php|video/show)\?viewkey=|embed/)|
(?:www\.)?thumbzilla\.com/video/
)
(?P<id>[\da-z]+)
- ''' % PornHubBaseIE._PORNHUB_HOST_RE
+ '''
_EMBED_REGEX = [r'<iframe[^>]+?src=["\'](?P<url>(?:https?:)?//(?:www\.)?pornhub(?:premium)?\.(?:com|net|org)/embed/[\da-z]+)']
_TESTS = [{
'url': 'http://www.pornhub.com/view_video.php?viewkey=648719015',
@@ -196,8 +195,8 @@ class PornHubIE(PornHubBaseIE):
'categories': list,
'subtitles': {
'en': [{
- "ext": 'srt'
- }]
+ 'ext': 'srt',
+ }],
},
},
'params': {
@@ -266,7 +265,7 @@ class PornHubIE(PornHubBaseIE):
}]
def _extract_count(self, pattern, webpage, name):
- return str_to_int(self._search_regex(pattern, webpage, '%s count' % name, default=None))
+ return str_to_int(self._search_regex(pattern, webpage, f'{name} count', default=None))
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -279,8 +278,8 @@ class PornHubIE(PornHubBaseIE):
def dl_webpage(platform):
self._set_cookie(host, 'platform', platform)
return self._download_webpage(
- 'https://www.%s/view_video.php?viewkey=%s' % (host, video_id),
- video_id, 'Downloading %s webpage' % platform)
+ f'https://www.{host}/view_video.php?viewkey={video_id}',
+ video_id, f'Downloading {platform} webpage')
webpage = dl_webpage('pc')
@@ -291,7 +290,7 @@ class PornHubIE(PornHubBaseIE):
if error_msg:
error_msg = re.sub(r'\s+', ' ', error_msg)
raise ExtractorError(
- 'PornHub said: %s' % error_msg,
+ f'PornHub said: {error_msg}',
expected=True, video_id=video_id)
if any(re.search(p, webpage) for p in (
@@ -332,7 +331,7 @@ class PornHubIE(PornHubBaseIE):
if not isinstance(definition, dict):
continue
video_url = definition.get('videoUrl')
- if not video_url or not isinstance(video_url, compat_str):
+ if not video_url or not isinstance(video_url, str):
continue
if video_url in video_urls_set:
continue
@@ -392,7 +391,7 @@ class PornHubIE(PornHubBaseIE):
if not video_urls:
FORMAT_PREFIXES = ('media', 'quality', 'qualityItems')
js_vars = extract_js_vars(
- webpage, r'(var\s+(?:%s)_.+)' % '|'.join(FORMAT_PREFIXES),
+ webpage, r'(var\s+(?:{})_.+)'.format('|'.join(FORMAT_PREFIXES)),
default=None)
if js_vars:
for key, format_url in js_vars.items():
@@ -403,7 +402,7 @@ class PornHubIE(PornHubBaseIE):
if not video_urls and re.search(
r'<[^>]+\bid=["\']lockedPlayer', webpage):
raise ExtractorError(
- 'Video %s is locked' % video_id, expected=True)
+ f'Video {video_id} is locked', expected=True)
if not video_urls:
js_vars = extract_js_vars(
@@ -470,8 +469,8 @@ class PornHubIE(PornHubBaseIE):
def extract_vote_count(kind, name):
return self._extract_count(
- (r'<span[^>]+\bclass="votes%s"[^>]*>([\d,\.]+)</span>' % kind,
- r'<span[^>]+\bclass=["\']votes%s["\'][^>]*\bdata-rating=["\'](\d+)' % kind),
+ (rf'<span[^>]+\bclass="votes{kind}"[^>]*>([\d,\.]+)</span>',
+ rf'<span[^>]+\bclass=["\']votes{kind}["\'][^>]*\bdata-rating=["\'](\d+)'),
webpage, name)
view_count = self._extract_count(
@@ -483,8 +482,8 @@ class PornHubIE(PornHubBaseIE):
def extract_list(meta_key):
div = self._search_regex(
- r'(?s)<div[^>]+\bclass=["\'].*?\b%sWrapper[^>]*>(.+?)</div>'
- % meta_key, webpage, meta_key, default=None)
+ rf'(?s)<div[^>]+\bclass=["\'].*?\b{meta_key}Wrapper[^>]*>(.+?)</div>',
+ webpage, meta_key, default=None)
if div:
return [clean_html(x).strip() for x in re.findall(r'(?s)<a[^>]+\bhref=[^>]+>.+?</a>', div)]
@@ -528,7 +527,7 @@ class PornHubPlaylistBaseIE(PornHubBaseIE):
return [
self.url_result(
- 'http://www.%s/%s' % (host, video_url),
+ f'http://www.{host}/{video_url}',
PornHubIE.ie_key(), video_title=title)
for video_url, title in orderedSet(re.findall(
r'href="/?(view_video\.php\?.*\bviewkey=[\da-z]+[^"]*)"[^>]*\s+title="([^"]+)"',
@@ -537,7 +536,7 @@ class PornHubPlaylistBaseIE(PornHubBaseIE):
class PornHubUserIE(PornHubPlaylistBaseIE):
- _VALID_URL = r'(?P<url>https?://(?:[^/]+\.)?%s/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/?#&]+))(?:[?#&]|/(?!videos)|$)' % PornHubBaseIE._PORNHUB_HOST_RE
+ _VALID_URL = rf'(?P<url>https?://(?:[^/]+\.)?{PornHubBaseIE._PORNHUB_HOST_RE}/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/?#&]+))(?:[?#&]|/(?!videos)|$)'
_TESTS = [{
'url': 'https://www.pornhub.com/model/zoe_ph',
'playlist_mincount': 118,
@@ -574,7 +573,7 @@ class PornHubUserIE(PornHubPlaylistBaseIE):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
user_id = mobj.group('id')
- videos_url = '%s/videos' % mobj.group('url')
+ videos_url = '{}/videos'.format(mobj.group('url'))
self._set_age_cookies(mobj.group('host'))
page = self._extract_page(url)
if page:
@@ -599,7 +598,7 @@ class PornHubPagedPlaylistBaseIE(PornHubPlaylistBaseIE):
VIDEOS = '/videos'
def download_page(base_url, num, fallback=False):
- note = 'Downloading page %d%s' % (num, ' (switch to fallback)' if fallback else '')
+ note = 'Downloading page {}{}'.format(num, ' (switch to fallback)' if fallback else '')
return self._download_webpage(
base_url, item_id, note, query={'page': num})
@@ -646,7 +645,7 @@ class PornHubPagedPlaylistBaseIE(PornHubPlaylistBaseIE):
class PornHubPagedVideoListIE(PornHubPagedPlaylistBaseIE):
- _VALID_URL = r'https?://(?:[^/]+\.)?%s/(?!playlist/)(?P<id>(?:[^/]+/)*[^/?#&]+)' % PornHubBaseIE._PORNHUB_HOST_RE
+ _VALID_URL = rf'https?://(?:[^/]+\.)?{PornHubBaseIE._PORNHUB_HOST_RE}/(?!playlist/)(?P<id>(?:[^/]+/)*[^/?#&]+)'
_TESTS = [{
'url': 'https://www.pornhub.com/model/zoe_ph/videos',
'only_matching': True,
@@ -748,11 +747,11 @@ class PornHubPagedVideoListIE(PornHubPagedPlaylistBaseIE):
def suitable(cls, url):
return (False
if PornHubIE.suitable(url) or PornHubUserIE.suitable(url) or PornHubUserVideosUploadIE.suitable(url)
- else super(PornHubPagedVideoListIE, cls).suitable(url))
+ else super().suitable(url))
class PornHubUserVideosUploadIE(PornHubPagedPlaylistBaseIE):
- _VALID_URL = r'(?P<url>https?://(?:[^/]+\.)?%s/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/]+)/videos/upload)' % PornHubBaseIE._PORNHUB_HOST_RE
+ _VALID_URL = rf'(?P<url>https?://(?:[^/]+\.)?{PornHubBaseIE._PORNHUB_HOST_RE}/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/]+)/videos/upload)'
_TESTS = [{
'url': 'https://www.pornhub.com/pornstar/jenny-blighe/videos/upload',
'info_dict': {
@@ -769,7 +768,7 @@ class PornHubUserVideosUploadIE(PornHubPagedPlaylistBaseIE):
class PornHubPlaylistIE(PornHubPlaylistBaseIE):
- _VALID_URL = r'(?P<url>https?://(?:[^/]+\.)?%s/playlist/(?P<id>[^/?#&]+))' % PornHubBaseIE._PORNHUB_HOST_RE
+ _VALID_URL = rf'(?P<url>https?://(?:[^/]+\.)?{PornHubBaseIE._PORNHUB_HOST_RE}/playlist/(?P<id>[^/?#&]+))'
_TESTS = [{
'url': 'https://www.pornhub.com/playlist/44121572',
'info_dict': {
@@ -797,8 +796,8 @@ class PornHubPlaylistIE(PornHubPlaylistBaseIE):
page_entries = self._extract_entries(webpage, host)
def download_page(page_num):
- note = 'Downloading page {}'.format(page_num)
- page_url = 'https://www.{}/playlist/viewChunked'.format(host)
+ note = f'Downloading page {page_num}'
+ page_url = f'https://www.{host}/playlist/viewChunked'
return self._download_webpage(page_url, item_id, note, query={
'id': playlist_id,
'page': page_num,
@@ -811,8 +810,7 @@ class PornHubPlaylistIE(PornHubPlaylistBaseIE):
page_entries = self._extract_entries(webpage, host)
if not page_entries:
break
- for e in page_entries:
- yield e
+ yield from page_entries
def _real_extract(self, url):
mobj = self._match_valid_url(url)
diff --git a/yt_dlp/extractor/pornotube.py b/yt_dlp/extractor/pornotube.py
index e0960f4c6..80c9b278d 100644
--- a/yt_dlp/extractor/pornotube.py
+++ b/yt_dlp/extractor/pornotube.py
@@ -20,7 +20,7 @@ class PornotubeIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'timestamp': 1417582800,
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
@@ -29,25 +29,24 @@ class PornotubeIE(InfoExtractor):
token = self._download_json(
'https://api.aebn.net/auth/v2/origins/authenticate',
video_id, note='Downloading token',
- data=json.dumps({'credentials': 'Clip Application'}).encode('utf-8'),
+ data=json.dumps({'credentials': 'Clip Application'}).encode(),
headers={
'Content-Type': 'application/json',
'Origin': 'http://www.pornotube.com',
})['tokenKey']
video_url = self._download_json(
- 'https://api.aebn.net/delivery/v1/clips/%s/MP4' % video_id,
+ f'https://api.aebn.net/delivery/v1/clips/{video_id}/MP4',
video_id, note='Downloading delivery information',
headers={'Authorization': token})['mediaUrl']
FIELDS = (
'title', 'description', 'startSecond', 'endSecond', 'publishDate',
- 'studios{name}', 'categories{name}', 'movieId', 'primaryImageNumber'
+ 'studios{name}', 'categories{name}', 'movieId', 'primaryImageNumber',
)
info = self._download_json(
- 'https://api.aebn.net/content/v2/clips/%s?fields=%s'
- % (video_id, ','.join(FIELDS)), video_id,
+ 'https://api.aebn.net/content/v2/clips/{}?fields={}'.format(video_id, ','.join(FIELDS)), video_id,
note='Downloading metadata',
headers={'Authorization': token})
diff --git a/yt_dlp/extractor/pornovoisines.py b/yt_dlp/extractor/pornovoisines.py
index b8e8701a8..587b3cdf7 100644
--- a/yt_dlp/extractor/pornovoisines.py
+++ b/yt_dlp/extractor/pornovoisines.py
@@ -29,9 +29,9 @@ class PornoVoisinesIE(InfoExtractor):
'subtitles': {
'fr': [{
'ext': 'vtt',
- }]
+ }],
},
- }
+ },
}
def _real_extract(self, url):
@@ -40,7 +40,7 @@ class PornoVoisinesIE(InfoExtractor):
display_id = mobj.group('display_id')
settings_url = self._download_json(
- 'http://www.pornovoisines.com/api/video/%s/getsettingsurl/' % video_id,
+ f'http://www.pornovoisines.com/api/video/{video_id}/getsettingsurl/',
video_id, note='Getting settings URL')['video_settings_url']
settings = self._download_json(settings_url, video_id)['data']
diff --git a/yt_dlp/extractor/pornoxo.py b/yt_dlp/extractor/pornoxo.py
index 049feb4ec..fa3154644 100644
--- a/yt_dlp/extractor/pornoxo.py
+++ b/yt_dlp/extractor/pornoxo.py
@@ -19,7 +19,7 @@ class PornoXOIE(InfoExtractor):
'categories': list, # NSFW
'thumbnail': r're:https?://.*\.jpg$',
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/pr0gramm.py b/yt_dlp/extractor/pr0gramm.py
index 3e0ccba17..f2c4e12e6 100644
--- a/yt_dlp/extractor/pr0gramm.py
+++ b/yt_dlp/extractor/pr0gramm.py
@@ -198,6 +198,6 @@ class Pr0grammIE(InfoExtractor):
'dislike_count': ('down', {int}),
'timestamp': ('created', {int}),
'upload_date': ('created', {int}, {dt.date.fromtimestamp}, {lambda x: x.strftime('%Y%m%d')}),
- 'thumbnail': ('thumb', {lambda x: urljoin('https://thumb.pr0gramm.com', x)})
+ 'thumbnail': ('thumb', {lambda x: urljoin('https://thumb.pr0gramm.com', x)}),
}),
}
diff --git a/yt_dlp/extractor/prankcast.py b/yt_dlp/extractor/prankcast.py
index 56cd40d8a..84e6f7ebc 100644
--- a/yt_dlp/extractor/prankcast.py
+++ b/yt_dlp/extractor/prankcast.py
@@ -22,8 +22,8 @@ class PrankCastIE(InfoExtractor):
'description': '',
'categories': ['prank'],
'tags': ['prank call', 'prank', 'live show'],
- 'upload_date': '20220825'
- }
+ 'upload_date': '20220825',
+ },
}, {
'url': 'https://prankcast.com/phonelosers/showreel/2048-NOT-COOL',
'info_dict': {
@@ -39,8 +39,8 @@ class PrankCastIE(InfoExtractor):
'description': '',
'categories': ['prank'],
'tags': ['prank call', 'prank', 'live show'],
- 'upload_date': '20221006'
- }
+ 'upload_date': '20221006',
+ },
}]
def _real_extract(self, url):
@@ -62,10 +62,10 @@ class PrankCastIE(InfoExtractor):
'uploader': uploader,
'channel_id': str_or_none(json_info.get('user_id')),
'duration': try_call(lambda: parse_iso8601(json_info['end_date']) - start_date),
- 'cast': list(filter(None, [uploader] + traverse_obj(guests_json, (..., 'name')))),
+ 'cast': list(filter(None, [uploader, *traverse_obj(guests_json, (..., 'name'))])),
'description': json_info.get('broadcast_description'),
'categories': [json_info.get('broadcast_category')],
- 'tags': try_call(lambda: json_info['broadcast_tags'].split(','))
+ 'tags': try_call(lambda: json_info['broadcast_tags'].split(',')),
}
@@ -85,8 +85,8 @@ class PrankCastPostIE(InfoExtractor):
'cast': ['Devonanustart'],
'description': '',
'categories': ['prank call'],
- 'upload_date': '20240104'
- }
+ 'upload_date': '20240104',
+ },
}, {
'url': 'https://prankcast.com/despicabledogs/posts/6217-jake-the-work-crow-',
'info_dict': {
@@ -101,8 +101,8 @@ class PrankCastPostIE(InfoExtractor):
'cast': ['despicabledogs'],
'description': 'https://imgur.com/a/vtxLvKU',
'categories': [],
- 'upload_date': '20240104'
- }
+ 'upload_date': '20240104',
+ },
}]
def _real_extract(self, url):
@@ -124,7 +124,7 @@ class PrankCastPostIE(InfoExtractor):
'uploader': uploader,
'channel_id': str_or_none(post.get('user_id')),
'duration': float_or_none(content.get('duration')),
- 'cast': list(filter(None, [uploader] + traverse_obj(guests_json, (..., 'name')))),
+ 'cast': list(filter(None, [uploader, *traverse_obj(guests_json, (..., 'name'))])),
'description': post.get('post_body'),
'categories': list(filter(None, [content.get('category')])),
'tags': try_call(lambda: list(filter('', post['post_tags'].split(',')))),
@@ -133,5 +133,5 @@ class PrankCastPostIE(InfoExtractor):
'url': f'https://prankcast.com/api/private/chat/select-broadcast?id={post["content_id"]}&cache=',
'ext': 'json',
}],
- } if post.get('content_id') else None
+ } if post.get('content_id') else None,
}
diff --git a/yt_dlp/extractor/premiershiprugby.py b/yt_dlp/extractor/premiershiprugby.py
index 67d41fdfd..313b4d217 100644
--- a/yt_dlp/extractor/premiershiprugby.py
+++ b/yt_dlp/extractor/premiershiprugby.py
@@ -14,7 +14,7 @@ class PremiershipRugbyIE(InfoExtractor):
'duration': 6093.0,
'tags': ['video'],
'categories': ['Full Match', 'Harlequins', 'Newcastle Falcons', 'gallaher premiership'],
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/presstv.py b/yt_dlp/extractor/presstv.py
index 26ce74a59..30eb64b90 100644
--- a/yt_dlp/extractor/presstv.py
+++ b/yt_dlp/extractor/presstv.py
@@ -15,8 +15,8 @@ class PressTVIE(InfoExtractor):
'title': 'Organic mattresses used to clean waste water',
'upload_date': '20160409',
'thumbnail': r're:^https?://.*\.jpg',
- 'description': 'md5:20002e654bbafb6908395a5c0cfcd125'
- }
+ 'description': 'md5:20002e654bbafb6908395a5c0cfcd125',
+ },
}
def _real_extract(self, url):
@@ -36,12 +36,12 @@ class PressTVIE(InfoExtractor):
(180, '_low200.mp4'),
(360, '_low400.mp4'),
(720, '_low800.mp4'),
- (1080, '.mp4')
+ (1080, '.mp4'),
]
formats = [{
'url': base_url + video_url[:-4] + extension,
- 'format_id': '%dp' % height,
+ 'format_id': f'{height}p',
'height': height,
} for height, extension in _formats]
@@ -65,5 +65,5 @@ class PressTVIE(InfoExtractor):
'formats': formats,
'thumbnail': thumbnail,
'upload_date': upload_date,
- 'description': description
+ 'description': description,
}
diff --git a/yt_dlp/extractor/projectveritas.py b/yt_dlp/extractor/projectveritas.py
index daf14054c..6f4248526 100644
--- a/yt_dlp/extractor/projectveritas.py
+++ b/yt_dlp/extractor/projectveritas.py
@@ -17,7 +17,7 @@ class ProjectVeritasIE(InfoExtractor):
'title': 'Exclusive: Inside The New York and New Jersey Hospitals Battling Coronavirus',
'upload_date': '20200327',
'thumbnail': 'md5:6076477fe50b03eb8708be9415e18e1c',
- }
+ },
}, {
'url': 'https://www.projectveritas.com/video/ilhan-omar-connected-ballot-harvester-in-cash-for-ballots-scheme-car-is-full/',
'info_dict': {
@@ -26,13 +26,13 @@ class ProjectVeritasIE(InfoExtractor):
'title': 'Ilhan Omar connected Ballot Harvester in cash-for-ballots scheme: "Car is full" of absentee ballots',
'upload_date': '20200927',
'thumbnail': 'md5:194b8edf0e2ba64f25500ff4378369a4',
- }
+ },
}]
def _real_extract(self, url):
- id, type = self._match_valid_url(url).group('id', 'type')
- api_url = f'https://www.projectveritas.com/page-data/{type}/{id}/page-data.json'
- data_json = self._download_json(api_url, id)['result']['data']
+ video_id, video_type = self._match_valid_url(url).group('id', 'type')
+ api_url = f'https://www.projectveritas.com/page-data/{video_type}/{video_id}/page-data.json'
+ data_json = self._download_json(api_url, video_id)['result']['data']
main_data = traverse_obj(data_json, 'video', 'post')
video_id = main_data['id']
thumbnail = traverse_obj(main_data, ('image', 'ogImage', 'src'))
diff --git a/yt_dlp/extractor/prosiebensat1.py b/yt_dlp/extractor/prosiebensat1.py
index 4c33baec5..6a3e0971c 100644
--- a/yt_dlp/extractor/prosiebensat1.py
+++ b/yt_dlp/extractor/prosiebensat1.py
@@ -2,7 +2,6 @@ import hashlib
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
determine_ext,
@@ -75,12 +74,12 @@ class ProSiebenSat1BaseIE(InfoExtractor):
'format_id': protocol,
})
if not formats:
- source_ids = [compat_str(source['id']) for source in video['sources']]
+ source_ids = [str(source['id']) for source in video['sources']]
- client_id = self._SALT[:2] + hashlib.sha1(''.join([clip_id, self._SALT, self._TOKEN, client_location, self._SALT, self._CLIENT_NAME]).encode('utf-8')).hexdigest()
+ client_id = self._SALT[:2] + hashlib.sha1(''.join([clip_id, self._SALT, self._TOKEN, client_location, self._SALT, self._CLIENT_NAME]).encode()).hexdigest()
sources = self._download_json(
- 'http://vas.sim-technik.de/vas/live/v2/videos/%s/sources' % clip_id,
+ f'http://vas.sim-technik.de/vas/live/v2/videos/{clip_id}/sources',
clip_id, 'Downloading sources JSON', query={
'access_token': self._TOKEN,
'client_id': client_id,
@@ -96,9 +95,9 @@ class ProSiebenSat1BaseIE(InfoExtractor):
return (bitrate // 1000) if bitrate % 1000 == 0 else bitrate
for source_id in source_ids:
- client_id = self._SALT[:2] + hashlib.sha1(''.join([self._SALT, clip_id, self._TOKEN, server_id, client_location, source_id, self._SALT, self._CLIENT_NAME]).encode('utf-8')).hexdigest()
+ client_id = self._SALT[:2] + hashlib.sha1(''.join([self._SALT, clip_id, self._TOKEN, server_id, client_location, source_id, self._SALT, self._CLIENT_NAME]).encode()).hexdigest()
urls = self._download_json(
- 'http://vas.sim-technik.de/vas/live/v2/videos/%s/sources/url' % clip_id,
+ f'http://vas.sim-technik.de/vas/live/v2/videos/{clip_id}/sources/url',
clip_id, 'Downloading urls JSON', fatal=False, query={
'access_token': self._TOKEN,
'client_id': client_id,
@@ -141,20 +140,20 @@ class ProSiebenSat1BaseIE(InfoExtractor):
app = path[:mp4colon_index]
play_path = path[mp4colon_index:]
formats.append({
- 'url': '%s/%s' % (mobj.group('url'), app),
+ 'url': '{}/{}'.format(mobj.group('url'), app),
'app': app,
'play_path': play_path,
'player_url': 'http://livepassdl.conviva.com/hf/ver/2.79.0.17083/LivePassModuleMain.swf',
'page_url': 'http://www.prosieben.de',
'tbr': tbr,
'ext': 'flv',
- 'format_id': 'rtmp%s' % ('-%d' % tbr if tbr else ''),
+ 'format_id': 'rtmp{}'.format(f'-{tbr}' if tbr else ''),
})
else:
formats.append({
'url': source_url,
'tbr': tbr,
- 'format_id': 'http%s' % ('-%d' % tbr if tbr else ''),
+ 'format_id': 'http{}'.format(f'-{tbr}' if tbr else ''),
})
return {
@@ -493,4 +492,4 @@ class ProSiebenSat1IE(ProSiebenSat1BaseIE):
return self._extract_playlist(url, webpage)
else:
raise ExtractorError(
- 'Unsupported page type %s' % page_type, expected=True)
+ f'Unsupported page type {page_type}', expected=True)
diff --git a/yt_dlp/extractor/prx.py b/yt_dlp/extractor/prx.py
index 338794ed5..742479cac 100644
--- a/yt_dlp/extractor/prx.py
+++ b/yt_dlp/extractor/prx.py
@@ -38,7 +38,7 @@ class PRXBaseIE(InfoExtractor):
'filesize': image_response.get('size'),
'width': image_response.get('width'),
'height': image_response.get('height'),
- 'url': cls._extract_file_link(image_response)
+ 'url': cls._extract_file_link(image_response),
}
@classmethod
@@ -63,7 +63,7 @@ class PRXBaseIE(InfoExtractor):
'duration': int_or_none(response.get('duration')),
'tags': response.get('tags'),
'episode_number': int_or_none(response.get('episodeIdentifier')),
- 'season_number': int_or_none(response.get('seasonIdentifier'))
+ 'season_number': int_or_none(response.get('seasonIdentifier')),
}
@classmethod
@@ -92,7 +92,7 @@ class PRXBaseIE(InfoExtractor):
**base_info,
'title': name,
'channel_id': base_info.get('id'),
- 'channel_url': 'https://beta.prx.org/accounts/%s' % base_info.get('id'),
+ 'channel_url': 'https://beta.prx.org/accounts/{}'.format(base_info.get('id')),
'channel': name,
}
@@ -111,7 +111,7 @@ class PRXBaseIE(InfoExtractor):
'series_id': series.get('series_id'),
'channel_id': account.get('channel_id'),
'channel_url': account.get('channel_url'),
- 'channel': account.get('channel')
+ 'channel': account.get('channel'),
}
def _entries(self, item_id, endpoint, entry_func, query=None):
@@ -124,7 +124,7 @@ class PRXBaseIE(InfoExtractor):
response = self._call_api(f'{item_id}: page {page}', endpoint, query={
**(query or {}),
'page': page,
- 'per': 100
+ 'per': 100,
})
items = self._get_prx_embed_response(response, 'items')
if not response or not items:
@@ -142,8 +142,8 @@ class PRXBaseIE(InfoExtractor):
return
story.update({
'_type': 'url',
- 'url': 'https://beta.prx.org/stories/%s' % story['id'],
- 'ie_key': PRXStoryIE.ie_key()
+ 'url': 'https://beta.prx.org/stories/{}'.format(story['id']),
+ 'ie_key': PRXStoryIE.ie_key(),
})
return story
@@ -153,8 +153,8 @@ class PRXBaseIE(InfoExtractor):
return
series.update({
'_type': 'url',
- 'url': 'https://beta.prx.org/series/%s' % series['id'],
- 'ie_key': PRXSeriesIE.ie_key()
+ 'url': 'https://beta.prx.org/series/{}'.format(series['id']),
+ 'ie_key': PRXSeriesIE.ie_key(),
})
return series
@@ -205,8 +205,8 @@ class PRXStoryIE(PRXBaseIE):
'episode': 'Episode 8',
'release_date': '20211223',
'season': 'Season 5',
- 'modified_date': '20220104'
- }
+ 'modified_date': '20220104',
+ },
}, {
'info_dict': {
'id': '399200_part2',
@@ -229,11 +229,11 @@ class PRXStoryIE(PRXBaseIE):
'episode': 'Episode 8',
'release_date': '20211223',
'season': 'Season 5',
- 'modified_date': '20220104'
- }
- }
+ 'modified_date': '20220104',
+ },
+ },
- ]
+ ],
}, {
# Story with only split audio
'url': 'https://beta.prx.org/stories/326414',
@@ -251,7 +251,7 @@ class PRXStoryIE(PRXBaseIE):
'channel_url': 'https://beta.prx.org/accounts/206',
'channel': 'New Hampshire Public Radio',
},
- 'playlist_count': 4
+ 'playlist_count': 4,
}, {
# Story with single combined audio
'url': 'https://beta.prx.org/stories/400404',
@@ -272,12 +272,12 @@ class PRXStoryIE(PRXBaseIE):
'tags': 'count:0',
'thumbnail': r're:https?://cms\.prx\.org/pub/\w+/0/web/story_image/767965/medium/Aurora_Over_Trees\.jpg',
'upload_date': '20220103',
- 'modified_date': '20220103'
- }
+ 'modified_date': '20220103',
+ },
}, {
'url': 'https://listen.prx.org/stories/399200',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
def _extract_audio_pieces(self, audio_response):
@@ -290,7 +290,7 @@ class PRXStoryIE(PRXBaseIE):
'asr': int_or_none(piece_response.get('frequency'), scale=1000),
'abr': int_or_none(piece_response.get('bitRate')),
'url': self._extract_file_link(piece_response),
- 'vcodec': 'none'
+ 'vcodec': 'none',
} for piece_response in sorted(
self._get_prx_embed_response(audio_response, 'items') or [],
key=lambda p: int_or_none(p.get('position')))]
@@ -304,18 +304,18 @@ class PRXStoryIE(PRXBaseIE):
if len(audio_pieces) == 1:
return {
'formats': audio_pieces,
- **info
+ **info,
}
entries = [{
**info,
- 'id': '%s_part%d' % (info['id'], (idx + 1)),
+ 'id': '{}_part{}'.format(info['id'], (idx + 1)),
'formats': [fmt],
} for idx, fmt in enumerate(audio_pieces)]
return {
'_type': 'multi_video',
'entries': entries,
- **info
+ **info,
}
def _real_extract(self, url):
@@ -340,9 +340,9 @@ class PRXSeriesIE(PRXBaseIE):
'channel_url': 'https://beta.prx.org/accounts/206',
'channel': 'New Hampshire Public Radio',
'series': 'Outside/In',
- 'series_id': '36252'
+ 'series_id': '36252',
},
- 'playlist_mincount': 39
+ 'playlist_mincount': 39,
}, {
# Blank series
'url': 'https://beta.prx.org/series/25038',
@@ -355,18 +355,18 @@ class PRXSeriesIE(PRXBaseIE):
'channel_url': 'https://beta.prx.org/accounts/206',
'channel': 'New Hampshire Public Radio',
'series': '25038',
- 'series_id': '25038'
+ 'series_id': '25038',
},
- 'playlist_count': 0
- }
+ 'playlist_count': 0,
+ },
]
def _extract_series(self, series_response):
info = self._extract_series_info(series_response)
return {
'_type': 'playlist',
- 'entries': self._entries(info['id'], 'series/%s/stories' % info['id'], self._story_playlist_entry),
- **info
+ 'entries': self._entries(info['id'], 'series/{}/stories'.format(info['id']), self._story_playlist_entry),
+ **info,
}
def _real_extract(self, url):
@@ -386,9 +386,9 @@ class PRXAccountIE(PRXBaseIE):
'channel_id': '206',
'channel_url': 'https://beta.prx.org/accounts/206',
'channel': 'New Hampshire Public Radio',
- 'thumbnails': 'count:1'
+ 'thumbnails': 'count:1',
},
- 'playlist_mincount': 380
+ 'playlist_mincount': 380,
}]
def _extract_account(self, account_response):
@@ -400,7 +400,7 @@ class PRXAccountIE(PRXBaseIE):
return {
'_type': 'playlist',
'entries': itertools.chain(series, stories),
- **info
+ **info,
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/puhutv.py b/yt_dlp/extractor/puhutv.py
index fc4c29e95..b62050ecd 100644
--- a/yt_dlp/extractor/puhutv.py
+++ b/yt_dlp/extractor/puhutv.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -48,25 +47,25 @@ class PuhuTVIE(InfoExtractor):
_SUBTITLE_LANGS = {
'English': 'en',
'Deutsch': 'de',
- 'عربى': 'ar'
+ 'عربى': 'ar',
}
def _real_extract(self, url):
display_id = self._match_id(url)
info = self._download_json(
- urljoin(url, '/api/slug/%s-izle' % display_id),
+ urljoin(url, f'/api/slug/{display_id}-izle'),
display_id)['data']
- video_id = compat_str(info['id'])
+ video_id = str(info['id'])
show = info.get('title') or {}
title = info.get('name') or show['name']
if info.get('display_name'):
- title = '%s %s' % (title, info['display_name'])
+ title = '{} {}'.format(title, info['display_name'])
try:
videos = self._download_json(
- 'https://puhutv.com/api/assets/%s/videos' % video_id,
+ f'https://puhutv.com/api/assets/{video_id}/videos',
display_id, 'Downloading video JSON',
headers=self.geo_verification_headers())
except ExtractorError as e:
@@ -94,7 +93,7 @@ class PuhuTVIE(InfoExtractor):
f = {
'url': media_url,
'ext': 'mp4',
- 'height': quality
+ 'height': quality,
}
video_format = video.get('video_format')
is_hls = (video_format == 'hls' or '/hls/' in media_url or '/chunklist.m3u8' in media_url) and playlist is False
@@ -106,12 +105,12 @@ class PuhuTVIE(InfoExtractor):
else:
continue
if quality:
- format_id += '-%sp' % quality
+ format_id += f'-{quality}p'
f['format_id'] = format_id
formats.append(f)
creator = try_get(
- show, lambda x: x['producer']['name'], compat_str)
+ show, lambda x: x['producer']['name'], str)
content = info.get('content') or {}
@@ -119,14 +118,14 @@ class PuhuTVIE(InfoExtractor):
content, lambda x: x['images']['wide'], dict) or {}
thumbnails = []
for image_id, image_url in images.items():
- if not isinstance(image_url, compat_str):
+ if not isinstance(image_url, str):
continue
if not image_url.startswith(('http', '//')):
- image_url = 'https://%s' % image_url
+ image_url = f'https://{image_url}'
t = parse_resolution(image_id)
t.update({
'id': image_id,
- 'url': image_url
+ 'url': image_url,
})
thumbnails.append(t)
@@ -135,7 +134,7 @@ class PuhuTVIE(InfoExtractor):
if not isinstance(genre, dict):
continue
genre_name = genre.get('name')
- if genre_name and isinstance(genre_name, compat_str):
+ if genre_name and isinstance(genre_name, str):
tags.append(genre_name)
subtitles = {}
@@ -144,10 +143,10 @@ class PuhuTVIE(InfoExtractor):
continue
lang = subtitle.get('language')
sub_url = url_or_none(subtitle.get('url') or subtitle.get('file'))
- if not lang or not isinstance(lang, compat_str) or not sub_url:
+ if not lang or not isinstance(lang, str) or not sub_url:
continue
subtitles[self._SUBTITLE_LANGS.get(lang, lang)] = [{
- 'url': sub_url
+ 'url': sub_url,
}]
return {
@@ -166,7 +165,7 @@ class PuhuTVIE(InfoExtractor):
'tags': tags,
'subtitles': subtitles,
'thumbnails': thumbnails,
- 'formats': formats
+ 'formats': formats,
}
@@ -195,8 +194,8 @@ class PuhuTVSerieIE(InfoExtractor):
has_more = True
while has_more is True:
season = self._download_json(
- 'https://galadriel.puhutv.com/seasons/%s' % season_id,
- season_id, 'Downloading page %s' % page, query={
+ f'https://galadriel.puhutv.com/seasons/{season_id}',
+ season_id, f'Downloading page {page}', query={
'page': page,
'per': 40,
})
@@ -208,7 +207,7 @@ class PuhuTVSerieIE(InfoExtractor):
continue
video_id = str_or_none(int_or_none(ep.get('id')))
yield self.url_result(
- 'https://puhutv.com/%s' % slug_path,
+ f'https://puhutv.com/{slug_path}',
ie=PuhuTVIE.ie_key(), video_id=video_id,
video_title=ep.get('name') or ep.get('eventLabel'))
page += 1
@@ -218,7 +217,7 @@ class PuhuTVSerieIE(InfoExtractor):
playlist_id = self._match_id(url)
info = self._download_json(
- urljoin(url, '/api/slug/%s-detay' % playlist_id),
+ urljoin(url, f'/api/slug/{playlist_id}-detay'),
playlist_id)['data']
seasons = info.get('seasons')
@@ -229,5 +228,5 @@ class PuhuTVSerieIE(InfoExtractor):
# For films, these are using same url with series
video_id = info.get('slug') or info['assets'][0]['slug']
return self.url_result(
- 'https://puhutv.com/%s-izle' % video_id,
+ f'https://puhutv.com/{video_id}-izle',
PuhuTVIE.ie_key(), video_id)
diff --git a/yt_dlp/extractor/puls4.py b/yt_dlp/extractor/puls4.py
index 38c5d1109..b43f0352b 100644
--- a/yt_dlp/extractor/puls4.py
+++ b/yt_dlp/extractor/puls4.py
@@ -1,5 +1,4 @@
from .prosiebensat1 import ProSiebenSat1BaseIE
-from ..compat import compat_str
from ..utils import parse_duration, unified_strdate
@@ -37,7 +36,7 @@ class Puls4IE(ProSiebenSat1BaseIE):
player_content = media['playerContent']
info = self._extract_video_info(url, player_content['id'])
info.update({
- 'id': compat_str(media['objectId']),
+ 'id': str(media['objectId']),
'title': player_content['title'],
'description': media.get('description'),
'thumbnail': media.get('previewLink'),
diff --git a/yt_dlp/extractor/pyvideo.py b/yt_dlp/extractor/pyvideo.py
index 7b25166b2..6ae315535 100644
--- a/yt_dlp/extractor/pyvideo.py
+++ b/yt_dlp/extractor/pyvideo.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import int_or_none
@@ -32,8 +31,8 @@ class PyvideoIE(InfoExtractor):
entries = []
data = self._download_json(
- 'https://raw.githubusercontent.com/pyvideo/data/master/%s/videos/%s.json'
- % (category, video_id), video_id, fatal=False)
+ f'https://raw.githubusercontent.com/pyvideo/data/master/{category}/videos/{video_id}.json',
+ video_id, fatal=False)
if data:
for video in data['videos']:
@@ -43,7 +42,7 @@ class PyvideoIE(InfoExtractor):
entries.append(self.url_result(video_url, 'Youtube'))
else:
entries.append({
- 'id': compat_str(data.get('id') or video_id),
+ 'id': str(data.get('id') or video_id),
'url': video_url,
'title': data['title'],
'description': data.get('description') or data.get('summary'),
diff --git a/yt_dlp/extractor/qingting.py b/yt_dlp/extractor/qingting.py
index cb00de2d5..74fecee1f 100644
--- a/yt_dlp/extractor/qingting.py
+++ b/yt_dlp/extractor/qingting.py
@@ -14,7 +14,7 @@ class QingTingIE(InfoExtractor):
'channel': '睡前消息',
'uploader': '马督工',
'ext': 'm4a',
- }
+ },
}, {
'url': 'https://m.qtfm.cn/vchannels/378005/programs/23023573/',
'md5': '2703120b6abe63b5fa90b975a58f4c0e',
@@ -25,7 +25,7 @@ class QingTingIE(InfoExtractor):
'channel': '睡前消息',
'uploader': '马督工',
'ext': 'm4a',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/qqmusic.py b/yt_dlp/extractor/qqmusic.py
index 90141e63b..a57dd5fb3 100644
--- a/yt_dlp/extractor/qqmusic.py
+++ b/yt_dlp/extractor/qqmusic.py
@@ -26,7 +26,7 @@ class QQMusicIE(InfoExtractor):
'creator': '林俊杰',
'description': 'md5:d85afb3051952ecc50a1ee8a286d1eac',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'note': 'There is no mp3-320 version of this song.',
'url': 'https://y.qq.com/n/yqq/song/004MsGEo3DdNxV.html',
@@ -39,7 +39,7 @@ class QQMusicIE(InfoExtractor):
'creator': '李季美',
'description': 'md5:46857d5ed62bc4ba84607a805dccf437',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'note': 'lyrics not in .lrc format',
'url': 'https://y.qq.com/n/yqq/song/001JyApY11tIp6.html',
@@ -60,21 +60,21 @@ class QQMusicIE(InfoExtractor):
_FORMATS = {
'mp3-320': {'prefix': 'M800', 'ext': 'mp3', 'preference': 40, 'abr': 320},
'mp3-128': {'prefix': 'M500', 'ext': 'mp3', 'preference': 30, 'abr': 128},
- 'm4a': {'prefix': 'C200', 'ext': 'm4a', 'preference': 10}
+ 'm4a': {'prefix': 'C200', 'ext': 'm4a', 'preference': 10},
}
# Reference: m_r_GetRUin() in top_player.js
# http://imgcache.gtimg.cn/music/portal_v3/y/top_player.js
@staticmethod
def m_r_get_ruin():
- curMs = int(time.time() * 1000) % 1000
- return int(round(random.random() * 2147483647) * curMs % 1E10)
+ cur_ms = int(time.time() * 1000) % 1000
+ return int(round(random.random() * 2147483647) * cur_ms % 1E10)
def _real_extract(self, url):
mid = self._match_id(url)
detail_info_page = self._download_webpage(
- 'http://s.plcloud.music.qq.com/fcgi-bin/fcg_yqq_song_detail_info.fcg?songmid=%s&play=0' % mid,
+ f'http://s.plcloud.music.qq.com/fcgi-bin/fcg_yqq_song_detail_info.fcg?songmid={mid}&play=0',
mid, note='Download song detail info',
errnote='Unable to get song detail info', encoding='gbk')
@@ -101,21 +101,20 @@ class QQMusicIE(InfoExtractor):
[r'albummid:\'([0-9a-zA-Z]+)\'', r'"albummid":"([0-9a-zA-Z]+)"'],
detail_info_page, 'album mid', default=None)
if albummid:
- thumbnail_url = 'http://i.gtimg.cn/music/photo/mid_album_500/%s/%s/%s.jpg' \
- % (albummid[-2:-1], albummid[-1], albummid)
+ thumbnail_url = f'http://i.gtimg.cn/music/photo/mid_album_500/{albummid[-2:-1]}/{albummid[-1]}/{albummid}.jpg'
guid = self.m_r_get_ruin()
vkey = self._download_json(
- 'http://base.music.qq.com/fcgi-bin/fcg_musicexpress.fcg?json=3&guid=%s' % guid,
+ f'http://base.music.qq.com/fcgi-bin/fcg_musicexpress.fcg?json=3&guid={guid}',
mid, note='Retrieve vkey', errnote='Unable to get vkey',
transform_source=strip_jsonp)['key']
formats = []
for format_id, details in self._FORMATS.items():
formats.append({
- 'url': 'http://cc.stream.qqmusic.qq.com/%s%s.%s?vkey=%s&guid=%s&fromtag=0'
- % (details['prefix'], mid, details['ext'], vkey, guid),
+ 'url': 'http://cc.stream.qqmusic.qq.com/{}{}.{}?vkey={}&guid={}&fromtag=0'.format(
+ details['prefix'], mid, details['ext'], vkey, guid),
'format': format_id,
'format_id': format_id,
'quality': details['preference'],
@@ -134,14 +133,14 @@ class QQMusicIE(InfoExtractor):
'release_date': publish_time,
'creator': singer,
'description': lrc_content,
- 'thumbnail': thumbnail_url
+ 'thumbnail': thumbnail_url,
}
if actual_lrc_lyrics:
info_dict['subtitles'] = {
'origin': [{
'ext': 'lrc',
'data': actual_lrc_lyrics,
- }]
+ }],
}
return info_dict
@@ -149,7 +148,7 @@ class QQMusicIE(InfoExtractor):
class QQPlaylistBaseIE(InfoExtractor):
@staticmethod
def qq_static_url(category, mid):
- return 'http://y.qq.com/y/static/%s/%s/%s/%s.html' % (category, mid[-2], mid[-1], mid)
+ return f'http://y.qq.com/y/static/{category}/{mid[-2]}/{mid[-1]}/{mid}.html'
def get_singer_all_songs(self, singmid, num):
return self._download_webpage(
@@ -183,7 +182,7 @@ class QQPlaylistBaseIE(InfoExtractor):
if item['musicData'].get('songmid') is not None:
songmid = item['musicData']['songmid']
entries.append(self.url_result(
- r'https://y.qq.com/n/yqq/song/%s.html' % songmid, 'QQMusic', songmid))
+ rf'https://y.qq.com/n/yqq/song/{songmid}.html', 'QQMusic', songmid))
return entries
@@ -250,12 +249,12 @@ class QQMusicAlbumIE(QQPlaylistBaseIE):
mid = self._match_id(url)
album = self._download_json(
- 'http://i.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid=%s&format=json' % mid,
+ f'http://i.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid={mid}&format=json',
mid, 'Download album page')['data']
entries = [
self.url_result(
- 'https://y.qq.com/n/yqq/song/' + song['songmid'] + '.html', 'QQMusic', song['songmid']
+ 'https://y.qq.com/n/yqq/song/' + song['songmid'] + '.html', 'QQMusic', song['songmid'],
) for song in album['list']
]
album_name = album.get('name')
diff --git a/yt_dlp/extractor/r7.py b/yt_dlp/extractor/r7.py
index 36f0b52bd..79a4c0a12 100644
--- a/yt_dlp/extractor/r7.py
+++ b/yt_dlp/extractor/r7.py
@@ -42,7 +42,7 @@ class R7IE(InfoExtractor):
video_id = self._match_id(url)
video = self._download_json(
- 'http://player-api.r7.com/video/i/%s' % video_id, video_id)
+ f'http://player-api.r7.com/video/i/{video_id}', video_id)
title = video['title']
@@ -98,7 +98,7 @@ class R7ArticleIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if R7IE.suitable(url) else super(R7ArticleIE, cls).suitable(url)
+ return False if R7IE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
@@ -109,4 +109,4 @@ class R7ArticleIE(InfoExtractor):
r'<div[^>]+(?:id=["\']player-|class=["\']embed["\'][^>]+id=["\'])([\da-f]{24})',
webpage, 'video id')
- return self.url_result('http://player.r7.com/video/i/%s' % video_id, R7IE.ie_key())
+ return self.url_result(f'http://player.r7.com/video/i/{video_id}', R7IE.ie_key())
diff --git a/yt_dlp/extractor/radiko.py b/yt_dlp/extractor/radiko.py
index f0135827b..b0b6681c9 100644
--- a/yt_dlp/extractor/radiko.py
+++ b/yt_dlp/extractor/radiko.py
@@ -100,8 +100,8 @@ class RadikoBaseIE(InfoExtractor):
def _find_program(self, video_id, station, cursor):
station_program = self._download_xml(
- 'https://radiko.jp/v3/program/station/weekly/%s.xml' % station, video_id,
- note='Downloading radio program for %s station' % station)
+ f'https://radiko.jp/v3/program/station/weekly/{station}.xml', video_id,
+ note=f'Downloading radio program for {station} station')
prog = None
for p in station_program.findall('.//prog'):
@@ -207,8 +207,8 @@ class RadikoIE(RadikoBaseIE):
'ft': radio_begin,
'end_at': radio_end,
'to': radio_end,
- 'seek': video_id
- }
+ 'seek': video_id,
+ },
),
}
diff --git a/yt_dlp/extractor/radiocanada.py b/yt_dlp/extractor/radiocanada.py
index 4a09dcdfc..950b9ec06 100644
--- a/yt_dlp/extractor/radiocanada.py
+++ b/yt_dlp/extractor/radiocanada.py
@@ -24,7 +24,7 @@ class RadioCanadaIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
},
{
# empty Title
@@ -50,7 +50,7 @@ class RadioCanadaIE(InfoExtractor):
'series': 'District 31',
},
'only_matching': True,
- }
+ },
]
_GEO_COUNTRIES = ['CA']
_access_token = None
@@ -111,7 +111,7 @@ class RadioCanadaIE(InfoExtractor):
if error == 'Le contenu sélectionné est disponible seulement en premium':
self.raise_login_required(error)
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, error), expected=True)
+ f'{self.IE_NAME} said: {error}', expected=True)
formats = self._extract_m3u8_formats(v_url, video_id, 'mp4')
subtitles = {}
@@ -162,4 +162,4 @@ class RadioCanadaAudioVideoIE(InfoExtractor):
}]
def _real_extract(self, url):
- return self.url_result('radiocanada:medianet:%s' % self._match_id(url))
+ return self.url_result(f'radiocanada:medianet:{self._match_id(url)}')
diff --git a/yt_dlp/extractor/radiocomercial.py b/yt_dlp/extractor/radiocomercial.py
index 0c219778f..7e4609a62 100644
--- a/yt_dlp/extractor/radiocomercial.py
+++ b/yt_dlp/extractor/radiocomercial.py
@@ -32,7 +32,7 @@ class RadioComercialIE(InfoExtractor):
'thumbnail': r're:https://radiocomercial.pt/upload/[^.]+.jpg',
'season': 'Season 6',
'season_number': 6,
- }
+ },
}, {
'url': 'https://radiocomercial.pt/podcasts/convenca-me-num-minuto/t3/convenca-me-num-minuto-que-os-lobisomens-existem',
'md5': '47e96c273aef96a8eb160cd6cf46d782',
@@ -44,7 +44,7 @@ class RadioComercialIE(InfoExtractor):
'thumbnail': r're:https://radiocomercial.pt/upload/[^.]+.jpg',
'season': 'Season 3',
'season_number': 3,
- }
+ },
}, {
'url': 'https://radiocomercial.pt/podcasts/inacreditavel-by-ines-castel-branco/t2/o-desastre-de-aviao',
'md5': '69be64255420fec23b7259955d771e54',
@@ -73,7 +73,7 @@ class RadioComercialIE(InfoExtractor):
'thumbnail': r're:https://radiocomercial.pt/upload/[^.]+.jpg',
'season': 'Season 2023',
'season_number': 2023,
- }
+ },
}]
def _real_extract(self, url):
@@ -99,28 +99,28 @@ class RadioComercialPlaylistIE(InfoExtractor):
'id': 'convenca-me-num-minuto_t3',
'title': 'Convença-me num Minuto - Temporada 3',
},
- 'playlist_mincount': 32
+ 'playlist_mincount': 32,
}, {
'url': 'https://radiocomercial.pt/podcasts/o-homem-que-mordeu-o-cao',
'info_dict': {
'id': 'o-homem-que-mordeu-o-cao',
'title': 'O Homem Que Mordeu o Cão',
},
- 'playlist_mincount': 19
+ 'playlist_mincount': 19,
}, {
'url': 'https://radiocomercial.pt/podcasts/as-minhas-coisas-favoritas',
'info_dict': {
'id': 'as-minhas-coisas-favoritas',
'title': 'As Minhas Coisas Favoritas',
},
- 'playlist_mincount': 131
+ 'playlist_mincount': 131,
}, {
'url': 'https://radiocomercial.pt/podcasts/tnt-todos-no-top/t2023',
'info_dict': {
'id': 'tnt-todos-no-top_t2023',
'title': 'TNT - Todos No Top - Temporada 2023',
},
- 'playlist_mincount': 39
+ 'playlist_mincount': 39,
}]
def _entries(self, url, playlist_id):
diff --git a/yt_dlp/extractor/radiode.py b/yt_dlp/extractor/radiode.py
index 726207825..1bf744951 100644
--- a/yt_dlp/extractor/radiode.py
+++ b/yt_dlp/extractor/radiode.py
@@ -17,7 +17,7 @@ class RadioDeIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
@@ -37,7 +37,7 @@ class RadioDeIE(InfoExtractor):
'ext': stream['streamContentFormat'].lower(),
'acodec': stream['streamContentFormat'],
'abr': stream['bitRate'],
- 'asr': stream['sampleRate']
+ 'asr': stream['sampleRate'],
} for stream in broadcast['streamUrls']]
return {
diff --git a/yt_dlp/extractor/radiofrance.py b/yt_dlp/extractor/radiofrance.py
index 6bd6fe9b6..ff2196354 100644
--- a/yt_dlp/extractor/radiofrance.py
+++ b/yt_dlp/extractor/radiofrance.py
@@ -126,7 +126,7 @@ class FranceCultureIE(RadioFranceBaseIE):
}, {
'url': 'https://www.radiofrance.fr/franceinfo/podcasts/le-billet-sciences/sante-bientot-un-vaccin-contre-l-asthme-allergique-3057200',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
@@ -150,7 +150,7 @@ class FranceCultureIE(RadioFranceBaseIE):
'uploader': self._html_search_regex(
r'(?s)<span class="author">(.*?)</span>', webpage, 'uploader', default=None),
'upload_date': unified_strdate(self._search_regex(
- r'"datePublished"\s*:\s*"([^"]+)', webpage, 'timestamp', fatal=False))
+ r'"datePublished"\s*:\s*"([^"]+)', webpage, 'timestamp', fatal=False)),
}
diff --git a/yt_dlp/extractor/radiojavan.py b/yt_dlp/extractor/radiojavan.py
index b3befaef9..53cbbe3f7 100644
--- a/yt_dlp/extractor/radiojavan.py
+++ b/yt_dlp/extractor/radiojavan.py
@@ -25,7 +25,7 @@ class RadioJavanIE(InfoExtractor):
'view_count': int,
'like_count': int,
'dislike_count': int,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/radiokapital.py b/yt_dlp/extractor/radiokapital.py
index 5d7d3ddeb..2d087088c 100644
--- a/yt_dlp/extractor/radiokapital.py
+++ b/yt_dlp/extractor/radiokapital.py
@@ -12,7 +12,7 @@ class RadioKapitalBaseIE(InfoExtractor):
video_id, note=note)
def _parse_episode(self, data):
- release = '%s%s%s' % (data['published'][6:11], data['published'][3:6], data['published'][:3])
+ release = '{}{}{}'.format(data['published'][6:11], data['published'][3:6], data['published'][:3])
return {
'_type': 'url_transparent',
'url': data['mixcloud_url'],
@@ -46,7 +46,7 @@ class RadioKapitalIE(RadioKapitalBaseIE):
def _real_extract(self, url):
video_id = self._match_id(url)
- episode = self._call_api('episodes/%s' % video_id, video_id)
+ episode = self._call_api(f'episodes/{video_id}', video_id)
return self._parse_episode(episode)
diff --git a/yt_dlp/extractor/radiozet.py b/yt_dlp/extractor/radiozet.py
index 632c8c281..e91b3b277 100644
--- a/yt_dlp/extractor/radiozet.py
+++ b/yt_dlp/extractor/radiozet.py
@@ -21,7 +21,7 @@ class RadioZetPodcastIE(InfoExtractor):
'duration': 83,
'series': 'Nie Ma Za Co',
'creator': 'Katarzyna Pakosińska',
- }
+ },
}
def _call_api(self, podcast_id, display_id):
diff --git a/yt_dlp/extractor/radlive.py b/yt_dlp/extractor/radlive.py
index 325e278fc..605089204 100644
--- a/yt_dlp/extractor/radlive.py
+++ b/yt_dlp/extractor/radlive.py
@@ -27,7 +27,7 @@ class RadLiveIE(InfoExtractor):
'channel': 'Proximity',
'channel_id': '9ce6dd01-70a4-4d59-afb6-d01f807cd009',
'channel_url': 'https://rad.live/content/channel/9ce6dd01-70a4-4d59-afb6-d01f807cd009',
- }
+ },
}, {
'url': 'https://rad.live/content/episode/bbcf66ec-0d02-4ca0-8dc0-4213eb2429bf',
'md5': '40b2175f347592125d93e9a344080125',
@@ -104,7 +104,7 @@ class RadLiveSeasonIE(RadLiveIE): # XXX: Do not subclass from concrete IE
@classmethod
def suitable(cls, url):
- return False if RadLiveIE.suitable(url) else super(RadLiveSeasonIE, cls).suitable(url)
+ return False if RadLiveIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
season_id = self._match_id(url)
@@ -154,7 +154,7 @@ query WebChannelListing ($lrn: ID!) {
@classmethod
def suitable(cls, url):
- return False if RadLiveIE.suitable(url) else super(RadLiveChannelIE, cls).suitable(url)
+ return False if RadLiveIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
channel_id = self._match_id(url)
@@ -164,8 +164,8 @@ query WebChannelListing ($lrn: ID!) {
headers={'Content-Type': 'application/json'},
data=json.dumps({
'query': self._QUERY,
- 'variables': {'lrn': f'lrn:12core:media:content:channel:{channel_id}'}
- }).encode('utf-8'))
+ 'variables': {'lrn': f'lrn:12core:media:content:channel:{channel_id}'},
+ }).encode())
data = traverse_obj(graphql, ('data', 'channel'))
if not data:
diff --git a/yt_dlp/extractor/rai.py b/yt_dlp/extractor/rai.py
index c2e7a6fb8..efb47affc 100644
--- a/yt_dlp/extractor/rai.py
+++ b/yt_dlp/extractor/rai.py
@@ -143,7 +143,7 @@ class RaiBaseIE(InfoExtractor):
}
def percentage(number, target, pc=20, roof=125):
- '''check if the target is in the range of number +/- percent'''
+ """check if the target is in the range of number +/- percent"""
if not number or number < 0:
return False
return abs(target - number) < min(float(number) * float(pc) / 100.0, roof)
@@ -199,7 +199,7 @@ class RaiBaseIE(InfoExtractor):
# filter out single-stream formats
fmts = [f for f in fmts
- if not f.get('vcodec') == 'none' and not f.get('acodec') == 'none']
+ if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
mobj = re.search(_MANIFEST_REG, manifest_url)
if not mobj:
@@ -213,7 +213,7 @@ class RaiBaseIE(InfoExtractor):
'url': _MP4_TMPL % (relinker_url, q),
'protocol': 'https',
'ext': 'mp4',
- **get_format_info(q)
+ **get_format_info(q),
})
return formats
@@ -299,7 +299,7 @@ class RaiPlayIE(RaiBaseIE):
'formats': 'count:7',
},
'params': {'skip_download': True},
- 'expected_warnings': ['Video not available. Likely due to geo-restriction.']
+ 'expected_warnings': ['Video not available. Likely due to geo-restriction.'],
}, {
# 1500 quality
'url': 'https://www.raiplay.it/video/2012/09/S1E11---Tutto-cio-che-luccica-0cab3323-732e-45d6-8e86-7704acab6598.html',
@@ -373,7 +373,7 @@ class RaiPlayIE(RaiBaseIE):
'episode_number': int_or_none(media.get('episode')),
'subtitles': self._extract_subtitles(url, video),
'release_year': int_or_none(traverse_obj(media, ('track_info', 'edit_year'))),
- **relinker_info
+ **relinker_info,
}
@@ -596,7 +596,7 @@ class RaiIE(RaiBaseIE):
'upload_date': '20140612',
},
'params': {'skip_download': True},
- 'expected_warnings': ['Video not available. Likely due to geo-restriction.']
+ 'expected_warnings': ['Video not available. Likely due to geo-restriction.'],
}, {
'url': 'https://www.rai.it/dl/RaiTV/programmi/media/ContentItem-efb17665-691c-45d5-a60c-5301333cbb0c.html',
'info_dict': {
@@ -606,7 +606,7 @@ class RaiIE(RaiBaseIE):
'description': 'TG1 edizione integrale ore 20:00 del giorno 03/11/2016',
'thumbnail': r're:^https?://.*\.jpg$',
'duration': 2214,
- 'upload_date': '20161103'
+ 'upload_date': '20161103',
},
'params': {'skip_download': True},
}, {
@@ -632,7 +632,7 @@ class RaiIE(RaiBaseIE):
'ext': media.get('formatoAudio'),
'vcodec': 'none',
'acodec': media.get('formatoAudio'),
- }]
+ }],
}
elif 'Video' in media['type']:
relinker_info = self._extract_relinker_info(media['mediaUri'], content_id)
@@ -652,7 +652,7 @@ class RaiIE(RaiBaseIE):
'upload_date': unified_strdate(media.get('date')),
'duration': parse_duration(media.get('length')),
'subtitles': self._extract_subtitles(url, media),
- **relinker_info
+ **relinker_info,
}
@@ -721,7 +721,7 @@ class RaiNewsIE(RaiBaseIE):
'title': player_data.get('title') or track_info.get('title') or self._og_search_title(webpage),
'upload_date': unified_strdate(track_info.get('date')),
'uploader': strip_or_none(track_info.get('editor') or None),
- **relinker_info
+ **relinker_info,
}
diff --git a/yt_dlp/extractor/raywenderlich.py b/yt_dlp/extractor/raywenderlich.py
index e0e3c3ead..3e74fd831 100644
--- a/yt_dlp/extractor/raywenderlich.py
+++ b/yt_dlp/extractor/raywenderlich.py
@@ -2,7 +2,6 @@ import re
from .common import InfoExtractor
from .vimeo import VimeoIE
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -67,12 +66,12 @@ class RayWenderlichIE(InfoExtractor):
continue
video_id = content.get('identifier')
if video_id:
- return compat_str(video_id)
+ return str(video_id)
def _real_extract(self, url):
mobj = self._match_valid_url(url)
course_id, lesson_id = mobj.group('course_id', 'id')
- display_id = '%s/%s' % (course_id, lesson_id)
+ display_id = f'{course_id}/{lesson_id}'
webpage = self._download_webpage(url, display_id)
@@ -110,8 +109,8 @@ class RayWenderlichIE(InfoExtractor):
if csrf_token:
headers['X-CSRF-Token'] = csrf_token
video = self._download_json(
- 'https://videos.raywenderlich.com/api/v1/videos/%s.json'
- % video_id, display_id, headers=headers)['video']
+ f'https://videos.raywenderlich.com/api/v1/videos/{video_id}.json',
+ display_id, headers=headers)['video']
vimeo_id = video['clips'][0]['provider_id']
info.update({
'_type': 'url_transparent',
@@ -124,7 +123,7 @@ class RayWenderlichIE(InfoExtractor):
return merge_dicts(info, self.url_result(
VimeoIE._smuggle_referrer(
- 'https://player.vimeo.com/video/%s' % vimeo_id, url),
+ f'https://player.vimeo.com/video/{vimeo_id}', url),
ie=VimeoIE.ie_key(), video_id=vimeo_id))
@@ -152,8 +151,7 @@ class RayWenderlichCourseIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if RayWenderlichIE.suitable(url) else super(
- RayWenderlichCourseIE, cls).suitable(url)
+ return False if RayWenderlichIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
course_id = self._match_id(url)
@@ -163,7 +161,7 @@ class RayWenderlichCourseIE(InfoExtractor):
entries = []
lesson_urls = set()
for lesson_url in re.findall(
- r'<a[^>]+\bhref=["\'](/%s/lessons/\d+)' % course_id, webpage):
+ rf'<a[^>]+\bhref=["\'](/{course_id}/lessons/\d+)', webpage):
if lesson_url in lesson_urls:
continue
lesson_urls.add(lesson_url)
diff --git a/yt_dlp/extractor/rbgtum.py b/yt_dlp/extractor/rbgtum.py
index 5f2d0c103..5bb465537 100644
--- a/yt_dlp/extractor/rbgtum.py
+++ b/yt_dlp/extractor/rbgtum.py
@@ -15,7 +15,7 @@ class RbgTumIE(InfoExtractor):
'ext': 'mp4',
'title': 'Lecture: October 18. 2022',
'series': 'Concepts of C++ programming (IN2377)',
- }
+ },
}, {
# Presentation only
'url': 'https://live.rbg.tum.de/w/I2DL/12349/PRES',
@@ -25,7 +25,7 @@ class RbgTumIE(InfoExtractor):
'ext': 'mp4',
'title': 'Lecture 3: Introduction to Neural Networks',
'series': 'Introduction to Deep Learning (IN2346)',
- }
+ },
}, {
# Camera only
'url': 'https://live.rbg.tum.de/w/fvv-info/16130/CAM',
@@ -35,11 +35,11 @@ class RbgTumIE(InfoExtractor):
'ext': 'mp4',
'title': 'Fachschaftsvollversammlung',
'series': 'Fachschaftsvollversammlung Informatik',
- }
+ },
}, {
'url': 'https://tum.live/w/linalginfo/27102',
'only_matching': True,
- }, ]
+ }]
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -84,7 +84,7 @@ class RbgTumCourseIE(InfoExtractor):
}, {
'url': 'https://tum.live/old/course/2023/S/linalginfo',
'only_matching': True,
- }, ]
+ }]
def _real_extract(self, url):
course_id, hostname, year, term, slug = self._match_valid_url(url).group('id', 'hostname', 'year', 'term', 'slug')
diff --git a/yt_dlp/extractor/rcs.py b/yt_dlp/extractor/rcs.py
index b865f63fb..1925afbe3 100644
--- a/yt_dlp/extractor/rcs.py
+++ b/yt_dlp/extractor/rcs.py
@@ -64,7 +64,7 @@ class RCSBaseIE(InfoExtractor):
'media2-doveviaggi-it.akamaized': 'viaggi',
'media2-vivimilano-corriere-it.akamaized': 'vivimilano',
'vivimilano-vh.akamaihd': 'vivimilano',
- 'media2-youreporter-it.akamaized': 'youreporter'
+ 'media2-youreporter-it.akamaized': 'youreporter',
}
def _get_video_src(self, video):
@@ -97,7 +97,7 @@ class RCSBaseIE(InfoExtractor):
yield {
'type': type_,
'url': url,
- 'bitrate': source.get('bitrate')
+ 'bitrate': source.get('bitrate'),
}
def _create_http_formats(self, m3u8_formats, video_id):
@@ -185,7 +185,7 @@ class RCSBaseIE(InfoExtractor):
return {
'_type': 'url_transparent',
'url': emb,
- 'ie_key': RCSEmbedsIE.ie_key()
+ 'ie_key': RCSEmbedsIE.ie_key(),
}
if not video_data:
@@ -236,13 +236,13 @@ class RCSEmbedsIE(RCSBaseIE):
'title': 'Sky Arte racconta Madonna nella serie "Artist to icon"',
'description': 'md5:65b09633df9ffee57f48b39e34c9e067',
'uploader': 'rcs.it',
- }
+ },
}, {
'url': 'https://video.gazzanet.gazzetta.it/video-embed/gazzanet-mo05-0000260789',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://video.gazzetta.it/video-embed/49612410-00ca-11eb-bcd8-30d4253e0140',
- 'only_matching': True
+ 'only_matching': True,
}]
_WEBPAGE_TESTS = [{
'url': 'https://www.iodonna.it/video-iodonna/personaggi-video/monica-bellucci-piu-del-lavoro-oggi-per-me-sono-importanti-lamicizia-e-la-famiglia/',
@@ -252,7 +252,7 @@ class RCSEmbedsIE(RCSBaseIE):
'title': 'Monica Bellucci: «Più del lavoro, oggi per me sono importanti l\'amicizia e la famiglia»',
'description': 'md5:daea6d9837351e56b1ab615c06bebac1',
'uploader': 'rcs.it',
- }
+ },
}]
@staticmethod
@@ -286,7 +286,7 @@ class RCSIE(RCSBaseIE):
'title': 'Vettel guida la Ferrari SF90 al Mugello e al suo fianco c\'è Leclerc (bendato): il video è esilarante',
'description': 'md5:3915ce5ebb3d2571deb69a5eb85ac9b5',
'uploader': 'Corriere Tv',
- }
+ },
}, {
# search for video id inside the page
'url': 'https://viaggi.corriere.it/video/norvegia-il-nuovo-ponte-spettacolare-sopra-la-cascata-di-voringsfossen/',
@@ -298,7 +298,7 @@ class RCSIE(RCSBaseIE):
'title': 'La nuova spettacolare attrazione in Norvegia: il ponte sopra Vøringsfossen',
'description': 'md5:18b35a291f6746c0c8dacd16e5f5f4f8',
'uploader': 'DOVE Viaggi',
- }
+ },
}, {
# only audio format https://github.com/yt-dlp/yt-dlp/issues/5683
'url': 'https://video.corriere.it/cronaca/audio-telefonata-il-papa-becciu-santita-lettera-che-mi-ha-inviato-condanna/b94c0d20-70c2-11ed-9572-e4b947a0ebd2',
@@ -310,7 +310,7 @@ class RCSIE(RCSBaseIE):
'description': 'md5:c0ddb61bd94a8d4e0d4bb9cda50a689b',
'uploader': 'Corriere Tv',
'formats': [{'format_id': 'https-mp3', 'ext': 'mp3'}],
- }
+ },
}, {
# old content still needs cdn migration
'url': 'https://viaggi.corriere.it/video/milano-varallo-sesia-sul-treno-a-vapore/',
@@ -322,10 +322,10 @@ class RCSIE(RCSBaseIE):
'title': 'Milano-Varallo Sesia sul treno a vapore',
'description': 'md5:6348f47aac230397fe341a74f7678d53',
'uploader': 'DOVE Viaggi',
- }
+ },
}, {
'url': 'https://video.corriere.it/video-360/metro-copenaghen-tutta-italiana/a248a7f0-e2db-11e9-9830-af2de6b1f945',
- 'only_matching': True
+ 'only_matching': True,
}]
@@ -346,7 +346,7 @@ class RCSVariousIE(RCSBaseIE):
'title': 'Cervicalgia e mal di testa, il video con i suggerimenti dell\'esperto',
'description': 'md5:ae21418f34cee0b8d02a487f55bcabb5',
'uploader': 'leitv.it',
- }
+ },
}, {
'url': 'https://www.youreporter.it/fiume-sesia-3-ottobre-2020/',
'md5': '3989b6d603482611a2abd2f32b79f739',
@@ -357,7 +357,7 @@ class RCSVariousIE(RCSBaseIE):
'title': 'Fiume Sesia 3 ottobre 2020',
'description': 'md5:0070eef1cc884d13c970a4125063de55',
'uploader': 'youreporter.it',
- }
+ },
}, {
'url': 'https://www.amica.it/video-post/saint-omer-al-cinema-il-film-leone-dargento-che-ribalta-gli-stereotipi/',
'md5': '187cce524dfd0343c95646c047375fc4',
@@ -368,5 +368,5 @@ class RCSVariousIE(RCSBaseIE):
'title': '"Saint Omer": al cinema il film Leone d\'argento che ribalta gli stereotipi',
'description': 'md5:b1c8869c2dcfd6073a2a311ba0008aa8',
'uploader': 'rcs.it',
- }
+ },
}]
diff --git a/yt_dlp/extractor/rcti.py b/yt_dlp/extractor/rcti.py
index 9c382e257..61b73a550 100644
--- a/yt_dlp/extractor/rcti.py
+++ b/yt_dlp/extractor/rcti.py
@@ -154,34 +154,34 @@ class RCTIPlusIE(RCTIPlusBaseIE):
is_upcoming = try_get(video_json, lambda x: x['current_date'] < x['start_date'])
if is_upcoming:
self.raise_no_formats(
- 'This event will start at %s.' % video_json['live_label'] if video_json.get('live_label') else 'This event has not started yet.', expected=True)
+ 'This event will start at {}.'.format(video_json['live_label']) if video_json.get('live_label') else 'This event has not started yet.', expected=True)
if 'akamaized' in video_url:
# For some videos hosted on Akamai's CDN (possibly AES-encrypted ones?), a session needs to at least be made via Conviva's API
conviva_json_data = {
**self._CONVIVA_JSON_TEMPLATE,
'url': video_url,
- 'sst': int(time.time())
+ 'sst': int(time.time()),
}
conviva_json_res = self._download_json(
'https://ff84ae928c3b33064b76dec08f12500465e59a6f.cws.conviva.com/0/wsg', display_id,
'Creating Conviva session', 'Failed to create Conviva session',
- fatal=False, data=json.dumps(conviva_json_data).encode('utf-8'))
+ fatal=False, data=json.dumps(conviva_json_data).encode())
if conviva_json_res and conviva_json_res.get('err') != 'ok':
- self.report_warning('Conviva said: %s' % str(conviva_json_res.get('err')))
+ self.report_warning('Conviva said: {}'.format(str(conviva_json_res.get('err'))))
video_meta, meta_paths = self._call_api(
- 'https://api.rctiplus.com/api/v1/%s/%s' % (video_type, video_id), display_id, 'Downloading video metadata')
+ f'https://api.rctiplus.com/api/v1/{video_type}/{video_id}', display_id, 'Downloading video metadata')
thumbnails, image_path = [], meta_paths.get('image_path', 'https://rstatic.akamaized.net/media/')
if video_meta.get('portrait_image'):
thumbnails.append({
'id': 'portrait_image',
- 'url': '%s%d%s' % (image_path, 2000, video_meta['portrait_image']) # 2000px seems to be the highest resolution that can be given
+ 'url': '{}{}{}'.format(image_path, 2000, video_meta['portrait_image']), # 2000px seems to be the highest resolution that can be given
})
if video_meta.get('landscape_image'):
thumbnails.append({
'id': 'landscape_image',
- 'url': '%s%d%s' % (image_path, 2000, video_meta['landscape_image'])
+ 'url': '{}{}{}'.format(image_path, 2000, video_meta['landscape_image']),
})
try:
formats = self._extract_m3u8_formats(video_url, display_id, 'mp4', headers={'Referer': 'https://www.rctiplus.com/'})
@@ -241,7 +241,7 @@ class RCTIPlusSeriesIE(RCTIPlusBaseIE):
'age_limit': 2,
'tags': 'count:11',
'display_id': 'inews-pagi',
- }
+ },
}]
_AGE_RATINGS = { # Based off https://id.wikipedia.org/wiki/Sistem_rating_konten_televisi with additional ratings
'S-SU': 2,
@@ -255,13 +255,13 @@ class RCTIPlusSeriesIE(RCTIPlusBaseIE):
@classmethod
def suitable(cls, url):
- return False if RCTIPlusIE.suitable(url) else super(RCTIPlusSeriesIE, cls).suitable(url)
+ return False if RCTIPlusIE.suitable(url) else super().suitable(url)
def _entries(self, url, display_id=None, note='Downloading entries JSON', metadata={}):
total_pages = 0
try:
total_pages = self._call_api(
- '%s&length=20&page=0' % url,
+ f'{url}&length=20&page=0',
display_id, note)[1]['pagination']['total_page']
except ExtractorError as e:
if 'not found' in str(e):
@@ -272,8 +272,8 @@ class RCTIPlusSeriesIE(RCTIPlusBaseIE):
for page_num in range(1, total_pages + 1):
episode_list = self._call_api(
- '%s&length=20&page=%s' % (url, page_num),
- display_id, '%s page %s' % (note, page_num))[0] or []
+ f'{url}&length=20&page={page_num}',
+ display_id, f'{note} page {page_num}')[0] or []
for video_json in episode_list:
yield {
@@ -288,7 +288,7 @@ class RCTIPlusSeriesIE(RCTIPlusBaseIE):
'duration': video_json.get('duration'),
'season_number': video_json.get('season'),
'episode_number': video_json.get('episode'),
- **metadata
+ **metadata,
}
def _series_entries(self, series_id, display_id=None, video_type=None, metadata={}):
@@ -348,7 +348,7 @@ class RCTIPlusTVIE(RCTIPlusBaseIE):
},
'params': {
'skip_download': True,
- }
+ },
}, {
# Returned video will always change
'url': 'https://www.rctiplus.com/live-event',
@@ -361,7 +361,7 @@ class RCTIPlusTVIE(RCTIPlusBaseIE):
@classmethod
def suitable(cls, url):
- return False if RCTIPlusIE.suitable(url) else super(RCTIPlusTVIE, cls).suitable(url)
+ return False if RCTIPlusIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
match = self._match_valid_url(url).groupdict()
diff --git a/yt_dlp/extractor/rds.py b/yt_dlp/extractor/rds.py
index cc76b898a..975b091aa 100644
--- a/yt_dlp/extractor/rds.py
+++ b/yt_dlp/extractor/rds.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
js_to_json,
parse_duration,
@@ -24,7 +23,7 @@ class RDSIE(InfoExtractor):
'timestamp': 1606129030,
'upload_date': '20201123',
'duration': 773.039,
- }
+ },
}, {
'url': 'http://www.rds.ca/vid%C3%A9os/un-voyage-positif-3.877934',
'only_matching': True,
@@ -36,7 +35,7 @@ class RDSIE(InfoExtractor):
webpage = self._download_webpage(url, display_id)
item = self._parse_json(self._search_regex(r'(?s)itemToPush\s*=\s*({.+?});', webpage, 'item'), display_id, js_to_json)
- video_id = compat_str(item['id'])
+ video_id = str(item['id'])
title = item.get('title') or self._og_search_title(webpage) or self._html_search_meta(
'title', webpage, 'title', fatal=True)
description = self._og_search_description(webpage) or self._html_search_meta(
@@ -57,7 +56,7 @@ class RDSIE(InfoExtractor):
'_type': 'url_transparent',
'id': video_id,
'display_id': display_id,
- 'url': '9c9media:rds_web:%s' % video_id,
+ 'url': f'9c9media:rds_web:{video_id}',
'title': title,
'description': description,
'thumbnail': thumbnail,
diff --git a/yt_dlp/extractor/redbee.py b/yt_dlp/extractor/redbee.py
index 4d71133b3..d43bb0bab 100644
--- a/yt_dlp/extractor/redbee.py
+++ b/yt_dlp/extractor/redbee.py
@@ -41,8 +41,8 @@ class RedBeeBaseIE(InfoExtractor):
return self._download_json(
f'{self._API_URL}/auth/{"gigyaLogin" if jwt else "anonymous"}',
- asset_id, data=json.dumps(request).encode('utf-8'), headers={
- 'Content-Type': 'application/json;charset=utf-8'
+ asset_id, data=json.dumps(request).encode(), headers={
+ 'Content-Type': 'application/json;charset=utf-8',
})['sessionToken']
def _get_formats_and_subtitles(self, asset_id, **kwargs):
@@ -51,26 +51,26 @@ class RedBeeBaseIE(InfoExtractor):
f'{self._API_URL}/entitlement/{asset_id}/play',
asset_id, headers={
'Authorization': f'Bearer {bearer_token}',
- 'Accept': 'application/json, text/plain, */*'
+ 'Accept': 'application/json, text/plain, */*',
})
formats, subtitles = [], {}
- for format in api_response['formats']:
- if not format.get('mediaLocator'):
+ for format_data in api_response['formats']:
+ if not format_data.get('mediaLocator'):
continue
fmts, subs = [], {}
- if format.get('format') == 'DASH':
+ if format_data.get('format') == 'DASH':
fmts, subs = self._extract_mpd_formats_and_subtitles(
- format['mediaLocator'], asset_id, fatal=False)
- elif format.get('format') == 'SMOOTHSTREAMING':
+ format_data['mediaLocator'], asset_id, fatal=False)
+ elif format_data.get('format') == 'SMOOTHSTREAMING':
fmts, subs = self._extract_ism_formats_and_subtitles(
- format['mediaLocator'], asset_id, fatal=False)
- elif format.get('format') == 'HLS':
+ format_data['mediaLocator'], asset_id, fatal=False)
+ elif format_data.get('format') == 'HLS':
fmts, subs = self._extract_m3u8_formats_and_subtitles(
- format['mediaLocator'], asset_id, fatal=False)
+ format_data['mediaLocator'], asset_id, fatal=False)
- if format.get('drm'):
+ if format_data.get('drm'):
for f in fmts:
f['has_drm'] = True
@@ -240,12 +240,12 @@ class RTBFIE(RedBeeBaseIE):
'APIKey': self._GIGYA_API_KEY,
'targetEnv': 'jssdk',
'sessionExpiration': '-2',
- }).encode('utf-8'), headers={
+ }).encode(), headers={
'Content-Type': 'application/x-www-form-urlencoded',
})
if login_response['statusCode'] != 200:
- raise ExtractorError('Login failed. Server message: %s' % login_response['errorMessage'], expected=True)
+ raise ExtractorError('Login failed. Server message: {}'.format(login_response['errorMessage']), expected=True)
self._set_cookie('.rtbf.be', self._LOGIN_COOKIE_ID, login_response['sessionInfo']['login_token'],
secure=True, expire_time=time.time() + 3600)
@@ -287,7 +287,7 @@ class RTBFIE(RedBeeBaseIE):
error = data.get('error')
if error:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error}', expected=True)
provider = data.get('provider')
if provider in self._PROVIDERS:
diff --git a/yt_dlp/extractor/redbulltv.py b/yt_dlp/extractor/redbulltv.py
index fac51b9ef..ceeef5204 100644
--- a/yt_dlp/extractor/redbulltv.py
+++ b/yt_dlp/extractor/redbulltv.py
@@ -57,7 +57,7 @@ class RedBullTVIE(InfoExtractor):
'os_family': 'http',
})
if session.get('code') == 'error':
- raise ExtractorError('%s said: %s' % (
+ raise ExtractorError('{} said: {}'.format(
self.IE_NAME, session['message']))
token = session['token']
@@ -65,20 +65,19 @@ class RedBullTVIE(InfoExtractor):
video = self._download_json(
'https://api.redbull.tv/v3/products/' + video_id,
video_id, note='Downloading video information',
- headers={'Authorization': token}
+ headers={'Authorization': token},
)
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 404:
error_message = self._parse_json(
e.cause.response.read().decode(), video_id)['error']
- raise ExtractorError('%s said: %s' % (
- self.IE_NAME, error_message), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error_message}', expected=True)
raise
title = video['title'].strip()
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
- 'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (video_id, token),
+ f'https://dms.redbull.tv/v3/{video_id}/{token}/playlist.m3u8',
video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls')
for resource in video.get('resources', []):
@@ -86,13 +85,13 @@ class RedBullTVIE(InfoExtractor):
splitted_resource = resource.split('_')
if splitted_resource[2]:
subtitles.setdefault('en', []).append({
- 'url': 'https://resources.redbull.tv/%s/%s' % (video_id, resource),
+ 'url': f'https://resources.redbull.tv/{video_id}/{resource}',
'ext': splitted_resource[2],
})
subheading = video.get('subheading')
if subheading:
- title += ' - %s' % subheading
+ title += f' - {subheading}'
return {
'id': video_id,
@@ -135,7 +134,7 @@ class RedBullEmbedIE(RedBullTVIE): # XXX: Do not subclass from concrete IE
%s
%s
}
-}''' % (rrn_id, self._VIDEO_ESSENSE_TMPL % 'LiveVideo', self._VIDEO_ESSENSE_TMPL % 'VideoResource'),
+}''' % (rrn_id, self._VIDEO_ESSENSE_TMPL % 'LiveVideo', self._VIDEO_ESSENSE_TMPL % 'VideoResource'), # noqa: UP031
})['data']['resource']['videoEssence']['attributes']['assetId']
return self.extract_info(asset_id)
@@ -155,7 +154,7 @@ class RedBullTVRrnContentIE(InfoExtractor):
def _real_extract(self, url):
region, lang, rrn_id = self._match_valid_url(url).groups()
- rrn_id += ':%s-%s' % (lang, region.upper())
+ rrn_id += f':{lang}-{region.upper()}'
return self.url_result(
'https://www.redbull.com/embed/' + rrn_id,
RedBullEmbedIE.ie_key(), rrn_id)
@@ -209,7 +208,7 @@ class RedBullIE(InfoExtractor):
regions.append('LAT')
if lang in self._INT_FALLBACK_LIST:
regions.append('INT')
- locale = '>'.join(['%s-%s' % (lang, reg) for reg in regions])
+ locale = '>'.join([f'{lang}-{reg}' for reg in regions])
rrn_id = self._download_json(
'https://www.redbull.com/v3/api/graphql/v1/v3/query/' + locale,
diff --git a/yt_dlp/extractor/redge.py b/yt_dlp/extractor/redge.py
index 875d6f8aa..7cb91eea4 100644
--- a/yt_dlp/extractor/redge.py
+++ b/yt_dlp/extractor/redge.py
@@ -51,14 +51,14 @@ class RedCDNLivxIE(InfoExtractor):
'only_matching': True,
}]
- """
+ '''
Known methods (first in url path):
- `livedash` - DASH MPD
- `livehls` - HTTP Live Streaming
- `livess` - IIS Smooth Streaming
- `nvr` - CCTV mode, directly returns a file, typically flv, avc1, aac
- `sc` - shoutcast/icecast (audio streams, like radio)
- """
+ '''
def _real_extract(self, url):
tenant, path = self._match_valid_url(url).group('tenant', 'id')
diff --git a/yt_dlp/extractor/redgifs.py b/yt_dlp/extractor/redgifs.py
index d0546bbfa..50138ab12 100644
--- a/yt_dlp/extractor/redgifs.py
+++ b/yt_dlp/extractor/redgifs.py
@@ -1,7 +1,7 @@
import functools
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_parse_qs
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -71,7 +71,7 @@ class RedGifsBaseInfoExtractor(InfoExtractor):
raise ExtractorError('Unable to get temporary token')
self._API_HEADERS['authorization'] = f'Bearer {auth["token"]}'
- def _call_api(self, ep, video_id, *args, **kwargs):
+ def _call_api(self, ep, video_id, **kwargs):
for first_attempt in True, False:
if 'authorization' not in self._API_HEADERS:
self._fetch_oauth_token(video_id)
@@ -79,7 +79,7 @@ class RedGifsBaseInfoExtractor(InfoExtractor):
headers = dict(self._API_HEADERS)
headers['x-customheader'] = f'https://www.redgifs.com/watch/{video_id}'
data = self._download_json(
- f'https://api.redgifs.com/v2/{ep}', video_id, headers=headers, *args, **kwargs)
+ f'https://api.redgifs.com/v2/{ep}', video_id, headers=headers, **kwargs)
break
except ExtractorError as e:
if first_attempt and isinstance(e.cause, HTTPError) and e.cause.status == 401:
@@ -130,7 +130,7 @@ class RedGifsIE(RedGifsBaseInfoExtractor):
'categories': list,
'age_limit': 18,
'tags': list,
- }
+ },
}, {
'url': 'https://thumbs2.redgifs.com/SqueakyHelplessWisent-mobile.mp4#t=0',
'info_dict': {
@@ -146,7 +146,7 @@ class RedGifsIE(RedGifsBaseInfoExtractor):
'categories': list,
'age_limit': 18,
'tags': list,
- }
+ },
}]
def _real_extract(self, url):
@@ -166,7 +166,7 @@ class RedGifsSearchIE(RedGifsBaseInfoExtractor):
'info_dict': {
'id': 'tags=Lesbian',
'title': 'Lesbian',
- 'description': 'RedGifs search for Lesbian, ordered by trending'
+ 'description': 'RedGifs search for Lesbian, ordered by trending',
},
'playlist_mincount': 100,
},
@@ -175,7 +175,7 @@ class RedGifsSearchIE(RedGifsBaseInfoExtractor):
'info_dict': {
'id': 'type=g&order=latest&tags=Lesbian',
'title': 'Lesbian',
- 'description': 'RedGifs search for Lesbian, ordered by latest'
+ 'description': 'RedGifs search for Lesbian, ordered by latest',
},
'playlist_mincount': 100,
},
@@ -184,15 +184,15 @@ class RedGifsSearchIE(RedGifsBaseInfoExtractor):
'info_dict': {
'id': 'type=g&order=latest&tags=Lesbian&page=2',
'title': 'Lesbian',
- 'description': 'RedGifs search for Lesbian, ordered by latest'
+ 'description': 'RedGifs search for Lesbian, ordered by latest',
},
'playlist_count': 80,
- }
+ },
]
def _real_extract(self, url):
query_str = self._match_valid_url(url).group('query')
- query = compat_parse_qs(query_str)
+ query = urllib.parse.parse_qs(query_str)
if not query.get('tags'):
raise ExtractorError('Invalid query tags', expected=True)
@@ -220,7 +220,7 @@ class RedGifsUserIE(RedGifsBaseInfoExtractor):
'info_dict': {
'id': 'lamsinka89',
'title': 'lamsinka89',
- 'description': 'RedGifs user lamsinka89, ordered by recent'
+ 'description': 'RedGifs user lamsinka89, ordered by recent',
},
'playlist_mincount': 100,
},
@@ -229,7 +229,7 @@ class RedGifsUserIE(RedGifsBaseInfoExtractor):
'info_dict': {
'id': 'lamsinka89?page=3',
'title': 'lamsinka89',
- 'description': 'RedGifs user lamsinka89, ordered by recent'
+ 'description': 'RedGifs user lamsinka89, ordered by recent',
},
'playlist_count': 30,
},
@@ -238,17 +238,17 @@ class RedGifsUserIE(RedGifsBaseInfoExtractor):
'info_dict': {
'id': 'lamsinka89?order=best&type=g',
'title': 'lamsinka89',
- 'description': 'RedGifs user lamsinka89, ordered by best'
+ 'description': 'RedGifs user lamsinka89, ordered by best',
},
'playlist_mincount': 100,
- }
+ },
]
def _real_extract(self, url):
username, query_str = self._match_valid_url(url).group('username', 'query')
playlist_id = f'{username}?{query_str}' if query_str else username
- query = compat_parse_qs(query_str)
+ query = urllib.parse.parse_qs(query_str)
order = query.get('order', ('recent',))[0]
entries = self._paged_entries(f'users/{username}/search', playlist_id, query, {
diff --git a/yt_dlp/extractor/redtube.py b/yt_dlp/extractor/redtube.py
index 14ed0edab..94a90540d 100644
--- a/yt_dlp/extractor/redtube.py
+++ b/yt_dlp/extractor/redtube.py
@@ -53,14 +53,14 @@ class RedTubeIE(InfoExtractor):
for patterns, message in ERRORS:
if any(p in webpage for p in patterns):
raise ExtractorError(
- 'Video %s %s' % (video_id, message), expected=True)
+ f'Video {video_id} {message}', expected=True)
info = self._search_json_ld(webpage, video_id, default={})
if not info.get('title'):
info['title'] = self._html_search_regex(
(r'<h(\d)[^>]+class="(?:video_title_text|videoTitle|video_title)[^"]*">(?P<title>(?:(?!\1).)+)</h\1>',
- r'(?:videoTitle|title)\s*:\s*(["\'])(?P<title>(?:(?!\1).)+)\1',),
+ r'(?:videoTitle|title)\s*:\s*(["\'])(?P<title>(?:(?!\1).)+)\1'),
webpage, 'title', group='title',
default=None) or self._og_search_title(webpage)
diff --git a/yt_dlp/extractor/rentv.py b/yt_dlp/extractor/rentv.py
index abb537cf3..aed438096 100644
--- a/yt_dlp/extractor/rentv.py
+++ b/yt_dlp/extractor/rentv.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
determine_ext,
int_or_none,
@@ -19,7 +18,7 @@ class RENTVIE(InfoExtractor):
'title': 'Документальный спецпроект: "Промывка мозгов. Технологии XXI века"',
'timestamp': 1472230800,
'upload_date': '20160826',
- }
+ },
}, {
'url': 'http://ren.tv/player/118577',
'only_matching': True,
@@ -70,7 +69,7 @@ class RENTVArticleIE(InfoExtractor):
'ext': 'mp4',
'title': 'Видео: микроавтобус, попавший в ДТП с грузовиками в Подмосковье, превратился в груду металла',
'description': 'Жертвами столкновения двух фур и микроавтобуса, по последним данным, стали семь человек.',
- }
+ },
}, {
# TODO: invalid m3u8
'url': 'http://ren.tv/novosti/2015-09-25/sluchaynyy-prohozhiy-poymal-avtougonshchika-v-murmanske-video',
@@ -99,6 +98,6 @@ class RENTVArticleIE(InfoExtractor):
media_id = config_profile.get('mediaid')
if not media_id:
continue
- media_id = compat_str(media_id)
+ media_id = str(media_id)
entries.append(self.url_result('rentv:' + media_id, 'RENTV', media_id))
return self.playlist_result(entries, display_id)
diff --git a/yt_dlp/extractor/restudy.py b/yt_dlp/extractor/restudy.py
index f49262a65..e10811ba9 100644
--- a/yt_dlp/extractor/restudy.py
+++ b/yt_dlp/extractor/restudy.py
@@ -15,7 +15,7 @@ class RestudyIE(InfoExtractor):
'params': {
# rtmp download
'skip_download': True,
- }
+ },
}, {
'url': 'https://portal.restudy.dk/video/leiden-frosteffekt/id/1637',
'only_matching': True,
@@ -30,7 +30,7 @@ class RestudyIE(InfoExtractor):
description = self._og_search_description(webpage).strip()
formats = self._extract_smil_formats(
- 'https://cdn.portal.restudy.dk/dynamic/themes/front/awsmedia/SmilDirectory/video_%s.xml' % video_id,
+ f'https://cdn.portal.restudy.dk/dynamic/themes/front/awsmedia/SmilDirectory/video_{video_id}.xml',
video_id)
return {
diff --git a/yt_dlp/extractor/reuters.py b/yt_dlp/extractor/reuters.py
index 9c9bac6af..69a858a3f 100644
--- a/yt_dlp/extractor/reuters.py
+++ b/yt_dlp/extractor/reuters.py
@@ -18,25 +18,25 @@ class ReutersIE(InfoExtractor):
'id': '368575562',
'ext': 'mp4',
'title': 'San Francisco police chief resigns',
- }
+ },
}
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://www.reuters.com/assets/iframe/yovideo?videoId=%s' % video_id, video_id)
+ f'http://www.reuters.com/assets/iframe/yovideo?videoId={video_id}', video_id)
video_data = js_to_json(self._search_regex(
r'(?s)Reuters\.yovideo\.drawPlayer\(({.*?})\);',
webpage, 'video data'))
def get_json_value(key, fatal=False):
- return self._search_regex(r'"%s"\s*:\s*"([^"]+)"' % key, video_data, key, fatal=fatal)
+ return self._search_regex(rf'"{key}"\s*:\s*"([^"]+)"', video_data, key, fatal=fatal)
title = unescapeHTML(get_json_value('title', fatal=True))
mmid, fid = re.search(r',/(\d+)\?f=(\d+)', get_json_value('flv', fatal=True)).groups()
mas_data = self._download_json(
- 'http://mas-e.cds1.yospace.com/mas/%s/%s?trans=json' % (mmid, fid),
+ f'http://mas-e.cds1.yospace.com/mas/{mmid}/{fid}?trans=json',
video_id, transform_source=js_to_json)
formats = []
for f in mas_data:
diff --git a/yt_dlp/extractor/reverbnation.py b/yt_dlp/extractor/reverbnation.py
index 06b6c3c2f..ddf8c3753 100644
--- a/yt_dlp/extractor/reverbnation.py
+++ b/yt_dlp/extractor/reverbnation.py
@@ -24,9 +24,9 @@ class ReverbNationIE(InfoExtractor):
song_id = self._match_id(url)
api_res = self._download_json(
- 'https://api.reverbnation.com/song/%s' % song_id,
+ f'https://api.reverbnation.com/song/{song_id}',
song_id,
- note='Downloading information of song %s' % song_id
+ note=f'Downloading information of song {song_id}',
)
THUMBNAILS = ('thumbnail', 'image')
@@ -36,7 +36,7 @@ class ReverbNationIE(InfoExtractor):
if api_res.get(thumb_key):
thumbnails.append({
'url': api_res[thumb_key],
- 'preference': quality(thumb_key)
+ 'preference': quality(thumb_key),
})
return {
diff --git a/yt_dlp/extractor/ridehome.py b/yt_dlp/extractor/ridehome.py
index 78f838ac1..58722bb95 100644
--- a/yt_dlp/extractor/ridehome.py
+++ b/yt_dlp/extractor/ridehome.py
@@ -55,7 +55,7 @@ class RideHomeIE(InfoExtractor):
'release_date': '20220108',
'release_timestamp': 1641672000,
'duration': 2789.38122,
- 'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com/images/.*\.jpeg$'
+ 'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com/images/.*\.jpeg$',
},
}],
}, {
@@ -81,7 +81,7 @@ class RideHomeIE(InfoExtractor):
'release_date': '20211026',
'release_timestamp': 1635272124,
'duration': 2266.30531,
- 'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com/images/.*\.jpeg$'
+ 'thumbnail': r're:^https?://content\.production\.cdn\.art19\.com/images/.*\.jpeg$',
},
}],
}]
diff --git a/yt_dlp/extractor/rinsefm.py b/yt_dlp/extractor/rinsefm.py
index f87b895df..5bc2eb883 100644
--- a/yt_dlp/extractor/rinsefm.py
+++ b/yt_dlp/extractor/rinsefm.py
@@ -39,8 +39,8 @@ class RinseFMIE(RinseFMBaseIE):
'title': 'Club Glow - 15/12/2023 - 20:00',
'thumbnail': r're:^https://.+\.(?:jpg|JPG)$',
'release_timestamp': 1702598400,
- 'release_date': '20231215'
- }
+ 'release_date': '20231215',
+ },
}]
def _real_extract(self, url):
@@ -58,22 +58,22 @@ class RinseFMArtistPlaylistIE(RinseFMBaseIE):
'info_dict': {
'id': 'resources',
'title': '[re]sources',
- 'description': '[re]sources est un label parisien piloté par le DJ et producteur Tommy Kid.'
+ 'description': '[re]sources est un label parisien piloté par le DJ et producteur Tommy Kid.',
},
- 'playlist_mincount': 40
+ 'playlist_mincount': 40,
}, {
'url': 'https://rinse.fm/shows/ivy/',
'info_dict': {
'id': 'ivy',
'title': '[IVY]',
- 'description': 'A dedicated space for DNB/Turbo House and 4x4.'
+ 'description': 'A dedicated space for DNB/Turbo House and 4x4.',
},
- 'playlist_mincount': 7
+ 'playlist_mincount': 7,
}]
def _entries(self, data):
for episode in traverse_obj(data, (
- 'props', 'pageProps', 'episodes', lambda _, v: determine_ext(v['fileUrl']) in MEDIA_EXTENSIONS.audio)
+ 'props', 'pageProps', 'episodes', lambda _, v: determine_ext(v['fileUrl']) in MEDIA_EXTENSIONS.audio),
):
yield self._parse_entry(episode)
diff --git a/yt_dlp/extractor/rmcdecouverte.py b/yt_dlp/extractor/rmcdecouverte.py
index bc59ed07e..e7b1e2297 100644
--- a/yt_dlp/extractor/rmcdecouverte.py
+++ b/yt_dlp/extractor/rmcdecouverte.py
@@ -1,9 +1,7 @@
+import urllib.parse
+
from .brightcove import BrightcoveLegacyIE
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urlparse,
-)
from ..utils import smuggle_url
@@ -59,7 +57,7 @@ class RMCDecouverteIE(InfoExtractor):
webpage = self._download_webpage(url, display_id)
brightcove_legacy_url = BrightcoveLegacyIE._extract_brightcove_url(webpage)
if brightcove_legacy_url:
- brightcove_id = compat_parse_qs(compat_urlparse.urlparse(
+ brightcove_id = urllib.parse.parse_qs(urllib.parse.urlparse(
brightcove_legacy_url).query)['@videoPlayer'][0]
else:
brightcove_id = self._search_regex(
diff --git a/yt_dlp/extractor/rockstargames.py b/yt_dlp/extractor/rockstargames.py
index 16622430c..02abd482c 100644
--- a/yt_dlp/extractor/rockstargames.py
+++ b/yt_dlp/extractor/rockstargames.py
@@ -19,7 +19,7 @@ class RockstarGamesIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'timestamp': 1464876000,
'upload_date': '20160602',
- }
+ },
}, {
'url': 'http://www.rockstargames.com/videos#/?video=48',
'only_matching': True,
diff --git a/yt_dlp/extractor/rokfin.py b/yt_dlp/extractor/rokfin.py
index 3bc5f3cab..be174655e 100644
--- a/yt_dlp/extractor/rokfin.py
+++ b/yt_dlp/extractor/rokfin.py
@@ -45,7 +45,7 @@ class RokfinIE(InfoExtractor):
'dislike_count': int,
'like_count': int,
'duration': 213,
- }
+ },
}, {
'url': 'https://rokfin.com/post/223/Julian-Assange-Arrested-Streaming-In-Real-Time',
'info_dict': {
@@ -63,7 +63,7 @@ class RokfinIE(InfoExtractor):
'dislike_count': int,
'like_count': int,
'tags': ['FreeThinkingMedia^', 'RealProgressives^'],
- }
+ },
}, {
'url': 'https://www.rokfin.com/stream/10543/Its-A-Crazy-Mess-Regional-Director-Blows-Whistle-On-Pfizers-Vaccine-Trial-Data',
'info_dict': {
@@ -86,7 +86,7 @@ class RokfinIE(InfoExtractor):
'dislike_count': int,
'like_count': int,
'tags': ['FreeThinkingMedia^'],
- }
+ },
}, {
'url': 'https://rokfin.com/post/126703/Brave-New-World--Aldous-Huxley-DEEPDIVE--Chpts-13--Quite-Frankly--Jay-Dyer',
'info_dict': {
@@ -106,7 +106,7 @@ class RokfinIE(InfoExtractor):
'tags': ['FreeThinkingMedia^', 'OpenMind^'],
'description': 'md5:cb04e32e68326c9b2b251b297bacff35',
'duration': 3100,
- }
+ },
}, {
'url': 'https://rokfin.com/stream/31332/The-Grayzone-live-on-Nordstream-blame-game',
'info_dict': {
@@ -126,7 +126,7 @@ class RokfinIE(InfoExtractor):
'release_date': '20230310',
'upload_date': '20230310',
'tags': ['FreeThinkingMedia^'],
- }
+ },
}]
def _real_extract(self, url):
@@ -203,7 +203,7 @@ class RokfinIE(InfoExtractor):
'parent': 'root',
'like_count': int_or_none(comment.get('numLikes')),
'dislike_count': int_or_none(comment.get('numDislikes')),
- 'timestamp': unified_timestamp(comment.get('postedAt'))
+ 'timestamp': unified_timestamp(comment.get('postedAt')),
}
pages_total = int_or_none(raw_comments.get('totalPages')) or None
@@ -246,7 +246,7 @@ class RokfinIE(InfoExtractor):
'code': urllib.parse.parse_qs(urllib.parse.urldefrag(urlh.url).fragment).get('code')[0],
'client_id': 'web',
'grant_type': 'authorization_code',
- 'redirect_uri': 'https://rokfin.com/silent-check-sso.html'
+ 'redirect_uri': 'https://rokfin.com/silent-check-sso.html',
}))
def _authentication_active(self):
@@ -276,7 +276,7 @@ class RokfinIE(InfoExtractor):
data=urlencode_postdata({
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
- 'client_id': 'web'
+ 'client_id': 'web',
}))
headers['authorization'] = self._get_auth_token()
if headers['authorization'] is None:
@@ -399,7 +399,7 @@ class RokfinSearchIE(SearchInfoExtractor):
'info_dict': {
'id': '"zelenko"',
'title': '"zelenko"',
- }
+ },
}]
_db_url = None
_db_access_key = None
diff --git a/yt_dlp/extractor/roosterteeth.py b/yt_dlp/extractor/roosterteeth.py
index 5c622399d..8b0099ec3 100644
--- a/yt_dlp/extractor/roosterteeth.py
+++ b/yt_dlp/extractor/roosterteeth.py
@@ -83,7 +83,7 @@ class RoosterTeethBaseIE(InfoExtractor):
'availability': self._availability(
needs_premium=sub_only, needs_subscription=sub_only, needs_auth=sub_only,
is_private=False, is_unlisted=False),
- 'tags': attributes.get('genres')
+ 'tags': attributes.get('genres'),
}
@@ -254,7 +254,7 @@ class RoosterTeethIE(RoosterTeethBaseIE):
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
if self._parse_json(e.cause.response.read().decode(), display_id).get('access') is False:
self.raise_login_required(
- '%s is only available for FIRST members' % display_id)
+ f'{display_id} is only available for FIRST members')
raise
# XXX: additional ad-free URL at video_data['links']['download'] but often gives 403 errors
@@ -281,7 +281,7 @@ class RoosterTeethIE(RoosterTeethBaseIE):
'display_id': display_id,
'formats': formats,
'subtitles': subtitles,
- **self._extract_video_info(episode)
+ **self._extract_video_info(episode),
}
diff --git a/yt_dlp/extractor/rottentomatoes.py b/yt_dlp/extractor/rottentomatoes.py
index e35717522..c229a1c4b 100644
--- a/yt_dlp/extractor/rottentomatoes.py
+++ b/yt_dlp/extractor/rottentomatoes.py
@@ -19,7 +19,7 @@ class RottenTomatoesIE(InfoExtractor):
'id': '11028566',
'ext': 'mp4',
'title': 'Toy Story 3',
- 'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.'
+ 'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.',
},
'skip': 'No longer available',
}, {
@@ -30,7 +30,7 @@ class RottenTomatoesIE(InfoExtractor):
'title': 'Toy Story 3: Trailer 2',
'description': '',
'thumbnail': r're:^https?://.*\.jpg$',
- 'duration': 149.941
+ 'duration': 149.941,
},
}, {
'url': 'http://www.rottentomatoes.com/m/toy_story_3',
diff --git a/yt_dlp/extractor/rozhlas.py b/yt_dlp/extractor/rozhlas.py
index 411a62519..4a10720ab 100644
--- a/yt_dlp/extractor/rozhlas.py
+++ b/yt_dlp/extractor/rozhlas.py
@@ -23,8 +23,8 @@ class RozhlasIE(InfoExtractor):
'id': '3421320',
'ext': 'mp3',
'title': 'Echo Pavla Klusáka (30.06.2015 21:00)',
- 'description': 'Osmdesátiny Terryho Rileyho jsou skvělou příležitostí proletět se elektronickými i akustickými díly zakladatatele minimalismu, který je aktivní už přes padesát let'
- }
+ 'description': 'Osmdesátiny Terryho Rileyho jsou skvělou příležitostí proletět se elektronickými i akustickými díly zakladatatele minimalismu, který je aktivní už přes padesát let',
+ },
}, {
'url': 'http://prehravac.rozhlas.cz/audio/3421320/embed',
'only_matching': True,
@@ -34,7 +34,7 @@ class RozhlasIE(InfoExtractor):
audio_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://prehravac.rozhlas.cz/audio/%s' % audio_id, audio_id)
+ f'http://prehravac.rozhlas.cz/audio/{audio_id}', audio_id)
title = self._html_search_regex(
r'<h3>(.+?)</h3>\s*<p[^>]*>.*?</p>\s*<div[^>]+id=["\']player-track',
@@ -48,7 +48,7 @@ class RozhlasIE(InfoExtractor):
return {
'id': audio_id,
- 'url': 'http://media.rozhlas.cz/_audio/%s.mp3' % audio_id,
+ 'url': f'http://media.rozhlas.cz/_audio/{audio_id}.mp3',
'title': title,
'description': description,
'duration': duration,
@@ -110,7 +110,7 @@ class RozhlasVltavaIE(RozhlasBaseIE):
'artist': 'Aleš Stuchlý',
'channel_id': 'radio-wave',
},
- }]
+ }],
}, {
'url': 'https://wave.rozhlas.cz/poslechnete-si-neklid-podcastovy-thriller-o-vine-strachu-a-vztahu-ktery-zasel-8554744',
'info_dict': {
@@ -183,7 +183,7 @@ class RozhlasVltavaIE(RozhlasBaseIE):
'chapter': 'Neklid #5',
'chapter_number': 5,
},
- }]
+ }],
}, {
'url': 'https://dvojka.rozhlas.cz/karel-siktanc-cerny-jezdec-bily-kun-napinava-pohadka-o-tajemnem-prizraku-8946969',
'info_dict': {
@@ -220,7 +220,7 @@ class RozhlasVltavaIE(RozhlasBaseIE):
'duration': ('duration', {int_or_none}),
'artist': ('meta', 'ga', 'contentAuthor'),
'channel_id': ('meta', 'ga', 'contentCreator'),
- })
+ }),
}
def _real_extract(self, url):
@@ -321,7 +321,7 @@ class MujRozhlasIE(RozhlasBaseIE):
'timestamp': ('attributes', 'since', {unified_timestamp}),
'modified_timestamp': ('attributes', 'updated', {unified_timestamp}),
'thumbnail': ('attributes', 'asset', 'url', {url_or_none}),
- })
+ }),
}
def _entries(self, api_url, playlist_id):
diff --git a/yt_dlp/extractor/rte.py b/yt_dlp/extractor/rte.py
index 729804d23..42ee9d2bd 100644
--- a/yt_dlp/extractor/rte.py
+++ b/yt_dlp/extractor/rte.py
@@ -35,7 +35,7 @@ class RteBaseIE(InfoExtractor):
error_info = self._parse_json(ee.cause.response.read().decode(), item_id, fatal=False)
if error_info:
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, error_info['message']),
+ '{} said: {}'.format(self.IE_NAME, error_info['message']),
expected=True)
raise
diff --git a/yt_dlp/extractor/rtl2.py b/yt_dlp/extractor/rtl2.py
index 07e1aa3ce..86e383a01 100644
--- a/yt_dlp/extractor/rtl2.py
+++ b/yt_dlp/extractor/rtl2.py
@@ -13,7 +13,7 @@ class RTL2IE(InfoExtractor):
'id': 'folge-203-0',
'ext': 'f4v',
'title': 'GRIP sucht den Sommerkönig',
- 'description': 'md5:e3adbb940fd3c6e76fa341b8748b562f'
+ 'description': 'md5:e3adbb940fd3c6e76fa341b8748b562f',
},
'params': {
# rtmp download
@@ -26,7 +26,7 @@ class RTL2IE(InfoExtractor):
'id': 'anna-erwischt-alex',
'ext': 'mp4',
'title': 'Anna erwischt Alex!',
- 'description': 'Anna nimmt ihrem Vater nicht ab, dass er nicht spielt. Und tatsächlich erwischt sie ihn auf frischer Tat.'
+ 'description': 'Anna nimmt ihrem Vater nicht ab, dass er nicht spielt. Und tatsächlich erwischt sie ihn auf frischer Tat.',
},
'params': {
# rtmp download
diff --git a/yt_dlp/extractor/rtlnl.py b/yt_dlp/extractor/rtlnl.py
index 724cb64e9..4537b3dc4 100644
--- a/yt_dlp/extractor/rtlnl.py
+++ b/yt_dlp/extractor/rtlnl.py
@@ -57,7 +57,7 @@ class RtlNlIE(InfoExtractor):
'thumbnail': r're:^https?://screenshots\.rtl\.nl/(?:[^/]+/)*sz=[0-9]+x[0-9]+/uuid=84ae5571-ac25-4225-ae0c-ef8d9efb2aed$',
'upload_date': '20150215',
'description': 'Er zijn nieuwe beelden vrijgegeven die vlak na de aanslag in Kopenhagen zijn gemaakt. Op de video is goed te zien hoe omstanders zich bekommeren om één van de slachtoffers, terwijl de eerste agenten ter plaatse komen.',
- }
+ },
}, {
# empty synopsis and missing episodes (see https://github.com/ytdl-org/youtube-dl/issues/6275)
# best format available nettv
@@ -99,14 +99,14 @@ class RtlNlIE(InfoExtractor):
def _real_extract(self, url):
uuid = self._match_id(url)
info = self._download_json(
- 'http://www.rtl.nl/system/s4m/vfd/version=2/uuid=%s/fmt=adaptive/' % uuid,
+ f'http://www.rtl.nl/system/s4m/vfd/version=2/uuid={uuid}/fmt=adaptive/',
uuid)
material = info['material'][0]
title = info['abstracts'][0]['name']
subtitle = material.get('title')
if subtitle:
- title += ' - %s' % subtitle
+ title += f' - {subtitle}'
description = material.get('synopsis')
meta = info.get('meta', {})
@@ -129,7 +129,7 @@ class RtlNlIE(InfoExtractor):
r'/sz=([0-9]+)', meta[p], 'thumbnail width', fatal=False)),
'height': int_or_none(self._search_regex(
r'/sz=[0-9]+x([0-9]+)',
- meta[p], 'thumbnail height', fatal=False))
+ meta[p], 'thumbnail height', fatal=False)),
})
return {
@@ -196,7 +196,7 @@ class RTLLuTeleVODIE(RTLLuBaseIE):
'ext': 'mp4',
'thumbnail': 'https://replay-assets.rtl.lu/2021/11/16/d3647fc4-470d-11ec-adc2-3a00abd6e90f_00008.jpg',
'description': 'md5:b1db974408cc858c9fd241812e4a2a14',
- }
+ },
}, {
'url': 'https://www.rtl.lu/video/3295215',
'info_dict': {
@@ -205,7 +205,7 @@ class RTLLuTeleVODIE(RTLLuBaseIE):
'ext': 'mp4',
'thumbnail': 'https://replay-assets.rtl.lu/2022/06/28/0000_3295215_0000.jpg',
'description': 'md5:85bcd4e0490aa6ec969d9bf16927437b',
- }
+ },
}]
@@ -221,7 +221,7 @@ class RTLLuArticleIE(RTLLuBaseIE):
'thumbnail': 'https://static.rtl.lu/rtl2008.lu/nt/p/2022/06/28/19/e4b37d66ddf00bab4c45617b91a5bb9b.jpeg',
'description': 'md5:5eab4a2a911c1fff7efc1682a38f9ef7',
'title': 'md5:40aa85f135578fbd549d3c9370321f99',
- }
+ },
}, {
# 5minutes
'url': 'https://5minutes.rtl.lu/espace-frontaliers/frontaliers-en-questions/a/1853173.html',
@@ -231,7 +231,7 @@ class RTLLuArticleIE(RTLLuBaseIE):
'description': 'md5:ac031da0740e997a5cf4633173634fee',
'title': 'md5:87e17722ed21af0f24be3243f4ec0c46',
'thumbnail': 'https://replay-assets.rtl.lu/2022/01/26/screenshot_20220126104933_3274749_12b249833469b0d6e4440a1dec83cdfa.jpg',
- }
+ },
}, {
# today.lu
'url': 'https://today.rtl.lu/entertainment/news/a/1936203.html',
@@ -241,7 +241,7 @@ class RTLLuArticleIE(RTLLuBaseIE):
'title': 'Once Upon A Time...zu Lëtzebuerg: The Three Witches\' Tower',
'description': 'The witchy theme continues in the latest episode of Once Upon A Time...',
'thumbnail': 'https://replay-assets.rtl.lu/2022/07/02/screenshot_20220702122859_3290019_412dc5185951b7f6545a4039c8be9235.jpg',
- }
+ },
}]
@@ -256,7 +256,7 @@ class RTLLuLiveIE(RTLLuBaseIE):
'live_status': 'is_live',
'title': r're:RTL - Télé LIVE \d{4}-\d{2}-\d{2} \d{2}:\d{2}',
'thumbnail': 'https://static.rtl.lu/livestream/channel1.jpg',
- }
+ },
}, {
# Tele:live-2
'url': 'https://www.rtl.lu/tele/live-2',
@@ -266,7 +266,7 @@ class RTLLuLiveIE(RTLLuBaseIE):
'live_status': 'is_live',
'title': r're:RTL - Télé LIVE \d{4}-\d{2}-\d{2} \d{2}:\d{2}',
'thumbnail': 'https://static.rtl.lu/livestream/channel2.jpg',
- }
+ },
}, {
# Radio:lauschteren
'url': 'https://www.rtl.lu/radio/lauschteren',
@@ -276,7 +276,7 @@ class RTLLuLiveIE(RTLLuBaseIE):
'live_status': 'is_live',
'title': r're:RTL - Radio LIVE \d{4}-\d{2}-\d{2} \d{2}:\d{2}',
'thumbnail': 'https://static.rtl.lu/livestream/rtlradiowebtv.jpg',
- }
+ },
}]
@@ -290,5 +290,5 @@ class RTLLuRadioIE(RTLLuBaseIE):
'description': 'md5:f855a4f3e3235393ae47ed1db5d934b9',
'title': '5 vir 12 - Stau um Stau',
'thumbnail': 'https://static.rtl.lu/rtlg//2022/06/24/c9c19e5694a14be46a3647a3760e1f62.jpg',
- }
+ },
}]
diff --git a/yt_dlp/extractor/rtnews.py b/yt_dlp/extractor/rtnews.py
index 6be9945f7..558511f1c 100644
--- a/yt_dlp/extractor/rtnews.py
+++ b/yt_dlp/extractor/rtnews.py
@@ -14,7 +14,7 @@ class RTNewsIE(InfoExtractor):
'id': '546301',
'title': 'Crowds gather to greet deported Djokovic as he returns to Serbia (VIDEO)',
'description': 'md5:1d5bfe1a988d81fd74227cfdf93d314d',
- 'thumbnail': 'https://cdni.rt.com/files/2022.01/article/61e587a085f540102c3386c1.png'
+ 'thumbnail': 'https://cdni.rt.com/files/2022.01/article/61e587a085f540102c3386c1.png',
},
}, {
'url': 'https://www.rt.com/shows/in-question/535980-plot-to-assassinate-julian-assange/',
@@ -23,7 +23,7 @@ class RTNewsIE(InfoExtractor):
'id': '535980',
'title': 'The plot to assassinate Julian Assange',
'description': 'md5:55279ce5e4441dc1d16e2e4a730152cd',
- 'thumbnail': 'https://cdni.rt.com/files/2021.09/article/615226f42030274e8879b53d.png'
+ 'thumbnail': 'https://cdni.rt.com/files/2021.09/article/615226f42030274e8879b53d.png',
},
'playlist': [{
'info_dict': {
@@ -31,7 +31,7 @@ class RTNewsIE(InfoExtractor):
'ext': 'mp4',
'title': '6152271d85f5400464496162',
},
- }]
+ }],
}]
def _entries(self, webpage):
@@ -46,12 +46,12 @@ class RTNewsIE(InfoExtractor):
}
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ playlist_id = self._match_id(url)
+ webpage = self._download_webpage(url, playlist_id)
return {
'_type': 'playlist',
- 'id': id,
+ 'id': playlist_id,
'entries': self._entries(webpage),
'title': self._og_search_title(webpage),
'description': self._og_search_description(webpage),
@@ -71,9 +71,9 @@ class RTDocumentryIE(InfoExtractor):
'description': 'md5:647c76984b7cb9a8b52a567e87448d88',
'thumbnail': 'https://cdni.rt.com/rtd-files/films/escobars-hitman/escobars-hitman_11.jpg',
'average_rating': 8.53,
- 'duration': 3134.0
+ 'duration': 3134.0,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://rtd.rt.com/shows/the-kalashnikova-show-military-secrets-anna-knishenko/iskander-tactical-system-natos-headache/',
'info_dict': {
@@ -86,9 +86,9 @@ class RTDocumentryIE(InfoExtractor):
'duration': 274.0,
'timestamp': 1605726000,
'view_count': int,
- 'upload_date': '20201118'
+ 'upload_date': '20201118',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://rtd.rt.com/series/i-am-hacked-trailer/introduction-to-safe-digital-life-ep2/',
'info_dict': {
@@ -101,29 +101,29 @@ class RTDocumentryIE(InfoExtractor):
'duration': 1524.0,
'timestamp': 1636977600,
'view_count': int,
- 'upload_date': '20211115'
+ 'upload_date': '20211115',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
ld_json = self._search_json_ld(webpage, None, fatal=False)
if not ld_json:
self.raise_no_formats('No video/audio found at the provided url.', expected=True)
media_json = self._parse_json(
self._search_regex(r'(?s)\'Med\'\s*:\s*\[\s*({.+})\s*\]\s*};', webpage, 'media info'),
- id, transform_source=js_to_json)
+ video_id, transform_source=js_to_json)
if 'title' not in ld_json and 'title' in media_json:
ld_json['title'] = media_json['title']
formats = [{'url': src['file']} for src in media_json.get('sources') or [] if src.get('file')]
return {
- 'id': id,
+ 'id': video_id,
'thumbnail': media_json.get('image'),
'formats': formats,
- **ld_json
+ **ld_json,
}
@@ -144,23 +144,23 @@ class RTDocumentryPlaylistIE(InfoExtractor):
},
}]
- def _entries(self, webpage, id):
+ def _entries(self, webpage, playlist_id):
video_urls = set(re.findall(r'list-2__link\s*"\s*href="([^"]+)"', webpage))
for v_url in video_urls:
- if id not in v_url:
+ if playlist_id not in v_url:
continue
yield self.url_result(
- 'https://rtd.rt.com%s' % v_url,
+ f'https://rtd.rt.com{v_url}',
ie=RTDocumentryIE.ie_key())
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ playlist_id = self._match_id(url)
+ webpage = self._download_webpage(url, playlist_id)
return {
'_type': 'playlist',
- 'id': id,
- 'entries': self._entries(webpage, id),
+ 'id': playlist_id,
+ 'entries': self._entries(webpage, playlist_id),
}
@@ -174,20 +174,20 @@ class RuptlyIE(InfoExtractor):
'ext': 'mp4',
'title': 'Japan: Double trouble! Tokyo zoo presents adorable panda twins | Video Ruptly',
'description': 'md5:85a8da5fdb31486f0562daf4360ce75a',
- 'thumbnail': 'https://storage.ruptly.tv/thumbnails/20220112-020/i6JQKnTNpYuqaXsR/i6JQKnTNpYuqaXsR.jpg'
+ 'thumbnail': 'https://storage.ruptly.tv/thumbnails/20220112-020/i6JQKnTNpYuqaXsR/i6JQKnTNpYuqaXsR.jpg',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
m3u8_url = self._search_regex(r'preview_url"\s?:\s?"(https?://storage\.ruptly\.tv/video_projects/.+\.m3u8)"', webpage, 'm3u8 url', fatal=False)
if not m3u8_url:
self.raise_no_formats('No video/audio found at the provided url.', expected=True)
- formats, subs = self._extract_m3u8_formats_and_subtitles(m3u8_url, id, ext='mp4')
+ formats, subs = self._extract_m3u8_formats_and_subtitles(m3u8_url, video_id, ext='mp4')
return {
- 'id': id,
+ 'id': video_id,
'formats': formats,
'subtitles': subs,
'title': self._og_search_title(webpage),
diff --git a/yt_dlp/extractor/rtp.py b/yt_dlp/extractor/rtp.py
index ec78d0a66..944e8636a 100644
--- a/yt_dlp/extractor/rtp.py
+++ b/yt_dlp/extractor/rtp.py
@@ -35,7 +35,7 @@ class RTPIE(InfoExtractor):
data = self._RX_OBFUSCATION.sub(
lambda m: json.dumps(
base64.b64decode(urllib.parse.unquote(
- ''.join(self._parse_json(m.group(1), video_id))
+ ''.join(self._parse_json(m.group(1), video_id)),
)).decode('iso-8859-1')),
data)
return js_to_json(data)
diff --git a/yt_dlp/extractor/rtrfm.py b/yt_dlp/extractor/rtrfm.py
index 7381d8202..7421e4249 100644
--- a/yt_dlp/extractor/rtrfm.py
+++ b/yt_dlp/extractor/rtrfm.py
@@ -56,8 +56,8 @@ class RTRFMIE(InfoExtractor):
url = None
self.raise_no_formats('Expired or no episode on this date', expected=True)
return {
- 'id': '%s-%s' % (show, date),
- 'title': '%s %s' % (title, date),
+ 'id': f'{show}-{date}',
+ 'title': f'{title} {date}',
'series': title,
'url': url,
'release_date': date,
diff --git a/yt_dlp/extractor/rts.py b/yt_dlp/extractor/rts.py
index bce5cba82..dc1e2d3b4 100644
--- a/yt_dlp/extractor/rts.py
+++ b/yt_dlp/extractor/rts.py
@@ -1,7 +1,6 @@
import re
from .srgssr import SRGSSRIE
-from ..compat import compat_str
from ..utils import (
determine_ext,
int_or_none,
@@ -110,7 +109,7 @@ class RTSIE(SRGSSRIE): # XXX: Do not subclass from concrete IE
{
'url': 'http://pages.rts.ch/emissions/passe-moi-les-jumelles/5624065-entre-ciel-et-mer.html',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
@@ -120,7 +119,7 @@ class RTSIE(SRGSSRIE): # XXX: Do not subclass from concrete IE
def download_json(internal_id):
return self._download_json(
- 'http://www.rts.ch/a/%s.html?f=json/article' % internal_id,
+ f'http://www.rts.ch/a/{internal_id}.html?f=json/article',
display_id)
all_info = download_json(media_id)
@@ -149,7 +148,7 @@ class RTSIE(SRGSSRIE): # XXX: Do not subclass from concrete IE
r'(?s)<iframe[^>]+class="srg-player"[^>]+src="[^"]+urn:([^"]+)"',
page)
if videos:
- entries = [self.url_result('srgssr:%s' % video_urn, 'SRGSSR') for video_urn in videos]
+ entries = [self.url_result(f'srgssr:{video_urn}', 'SRGSSR') for video_urn in videos]
if entries:
return self.playlist_result(entries, media_id, all_info.get('title'))
@@ -196,7 +195,7 @@ class RTSIE(SRGSSRIE): # XXX: Do not subclass from concrete IE
'tbr': extract_bitrate(format_url),
})
- download_base = 'http://rtsww%s-d.rts.ch/' % ('-a' if media_type == 'audio' else '')
+ download_base = 'http://rtsww{}-d.rts.ch/'.format('-a' if media_type == 'audio' else '')
for media in info.get('media', []):
media_url = media.get('url')
if not media_url or re.match(r'https?://', media_url):
@@ -215,7 +214,7 @@ class RTSIE(SRGSSRIE): # XXX: Do not subclass from concrete IE
self._check_formats(formats, media_id)
duration = info.get('duration') or info.get('cutout') or info.get('cutduration')
- if isinstance(duration, compat_str):
+ if isinstance(duration, str):
duration = parse_duration(duration)
return {
diff --git a/yt_dlp/extractor/rtvcplay.py b/yt_dlp/extractor/rtvcplay.py
index e7dcd5fd6..5b0eee965 100644
--- a/yt_dlp/extractor/rtvcplay.py
+++ b/yt_dlp/extractor/rtvcplay.py
@@ -213,7 +213,7 @@ class RTVCPlayEmbedIE(RTVCPlayBaseIE):
'title': 'Tráiler: Señoritas',
'thumbnail': r're:^https?://.*\.(?:jpg|png)',
'ext': 'mp4',
- }
+ },
}]
def _real_extract(self, url):
@@ -235,7 +235,7 @@ class RTVCPlayEmbedIE(RTVCPlayBaseIE):
'title': 'title',
'description': 'description',
'thumbnail': ('image', ..., 'thumbnail', 'path'),
- }, get_all=False)
+ }, get_all=False),
}
@@ -282,5 +282,5 @@ class RTVCKalturaIE(RTVCPlayBaseIE):
'title': 'title',
'description': 'description',
'thumbnail': ('channel', 'image', 'logo', 'path'),
- })
+ }),
}
diff --git a/yt_dlp/extractor/rtve.py b/yt_dlp/extractor/rtve.py
index a99a266c6..7e0b666ab 100644
--- a/yt_dlp/extractor/rtve.py
+++ b/yt_dlp/extractor/rtve.py
@@ -3,7 +3,6 @@ import io
import struct
from .common import InfoExtractor
-from ..compat import compat_b64decode
from ..utils import (
ExtractorError,
determine_ext,
@@ -62,14 +61,14 @@ class RTVEALaCartaIE(InfoExtractor):
}]
def _real_initialize(self):
- user_agent_b64 = base64.b64encode(self.get_param('http_headers')['User-Agent'].encode('utf-8')).decode('utf-8')
+ user_agent_b64 = base64.b64encode(self.get_param('http_headers')['User-Agent'].encode()).decode('utf-8')
self._manager = self._download_json(
'http://www.rtve.es/odin/loki/' + user_agent_b64,
None, 'Fetching manager info')['manager']
@staticmethod
def _decrypt_url(png):
- encrypted_data = io.BytesIO(compat_b64decode(png)[8:])
+ encrypted_data = io.BytesIO(base64.b64decode(png)[8:])
while True:
length = struct.unpack('!I', encrypted_data.read(4))[0]
chunk_type = encrypted_data.read(4)
@@ -111,7 +110,7 @@ class RTVEALaCartaIE(InfoExtractor):
def _extract_png_formats(self, video_id):
png = self._download_webpage(
- 'http://www.rtve.es/ztnr/movil/thumbnail/%s/videos/%s.png' % (self._manager, video_id),
+ f'http://www.rtve.es/ztnr/movil/thumbnail/{self._manager}/videos/{video_id}.png',
video_id, 'Downloading url information', query={'q': 'v2'})
q = qualities(['Media', 'Alta', 'HQ', 'HD_READY', 'HD_FULL'])
formats = []
@@ -135,7 +134,7 @@ class RTVEALaCartaIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
info = self._download_json(
- 'http://www.rtve.es/api/videos/%s/config/alacarta_videos.json' % video_id,
+ f'http://www.rtve.es/api/videos/{video_id}/config/alacarta_videos.json',
video_id)['page']['items'][0]
if info['state'] == 'DESPU':
raise ExtractorError('The video is no longer available', expected=True)
@@ -194,7 +193,7 @@ class RTVEAudioIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE
'title': 'Ignatius Farray',
'thumbnail': r're:https?://.+/1613243011863.jpg',
'duration': 3559.559,
- 'series': 'En Radio 3'
+ 'series': 'En Radio 3',
},
}, {
'url': 'https://www.rtve.es/play/audios/frankenstein-o-el-moderno-prometeo/capitulo-26-ultimo-muerte-victor-juan-jose-plans-mary-shelley/6082623/',
@@ -205,7 +204,7 @@ class RTVEAudioIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE
'title': 'Capítulo 26 y último: La muerte de Victor',
'thumbnail': r're:https?://.+/1632147445707.jpg',
'duration': 3174.086,
- 'series': 'Frankenstein o el moderno Prometeo'
+ 'series': 'Frankenstein o el moderno Prometeo',
},
}]
@@ -217,8 +216,7 @@ class RTVEAudioIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE
media url
"""
png = self._download_webpage(
- 'http://www.rtve.es/ztnr/movil/thumbnail/%s/audios/%s.png' %
- (self._manager, audio_id),
+ f'http://www.rtve.es/ztnr/movil/thumbnail/{self._manager}/audios/{audio_id}.png',
audio_id, 'Downloading url information', query={'q': 'v2'})
q = qualities(['Media', 'Alta', 'HQ', 'HD_READY', 'HD_FULL'])
formats = []
@@ -242,7 +240,7 @@ class RTVEAudioIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE
def _real_extract(self, url):
audio_id = self._match_id(url)
info = self._download_json(
- 'https://www.rtve.es/api/audios/%s.json' % audio_id,
+ f'https://www.rtve.es/api/audios/{audio_id}.json',
audio_id)['page']['items'][0]
return {
@@ -288,7 +286,7 @@ class RTVELiveIE(RTVEALaCartaIE): # XXX: Do not subclass from concrete IE
},
'params': {
'skip_download': 'live stream',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/rtvs.py b/yt_dlp/extractor/rtvs.py
index defb8d741..927da5778 100644
--- a/yt_dlp/extractor/rtvs.py
+++ b/yt_dlp/extractor/rtvs.py
@@ -21,7 +21,7 @@ class RTVSIE(InfoExtractor):
'duration': 2854,
'thumbnail': 'https://www.rtvs.sk/media/a501/image/file/2/0000/b1R8.rtvs.jpg',
'display_id': '135331',
- }
+ },
}, {
# tv archive
'url': 'http://www.rtvs.sk/televizia/archiv/8249/63118',
@@ -34,7 +34,7 @@ class RTVSIE(InfoExtractor):
'timestamp': 1428555900,
'upload_date': '20150409',
'duration': 4986,
- }
+ },
}, {
# tv archive
'url': 'https://www.rtvs.sk/televizia/archiv/18083?utm_source=web&utm_medium=rozcestnik&utm_campaign=Robin',
@@ -48,7 +48,7 @@ class RTVSIE(InfoExtractor):
'duration': 831,
'upload_date': '20211111',
'thumbnail': 'https://www.rtvs.sk/media/a501/image/file/2/0916/robin.jpg',
- }
+ },
}]
def _real_extract(self, url):
@@ -80,5 +80,5 @@ class RTVSIE(InfoExtractor):
'duration': parse_duration(traverse_obj(data, ('playlist', 0, 'length'))),
'thumbnail': traverse_obj(data, ('playlist', 0, 'image')),
'timestamp': unified_timestamp(traverse_obj(data, ('playlist', 0, 'datetime_create'))),
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/rtvslo.py b/yt_dlp/extractor/rtvslo.py
index 39ace7cc6..e71d01d1e 100644
--- a/yt_dlp/extractor/rtvslo.py
+++ b/yt_dlp/extractor/rtvslo.py
@@ -87,8 +87,8 @@ class RTVSLOIE(InfoExtractor):
},
}, {
'url': 'https://4d.rtvslo.si/arhiv/dnevnik/174842550',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
def _real_extract(self, url):
@@ -127,7 +127,7 @@ class RTVSLOIE(InfoExtractor):
'format_note': 'Sign language interpretation', 'preference': -10,
'language': (
'slv' if f.get('language') == 'eng' and f.get('acodec') != 'none'
- else f.get('language'))
+ else f.get('language')),
})
for mediafile in traverse_obj(media, ('mediaFiles', lambda _, v: url_or_none(v['streams']['https']))):
diff --git a/yt_dlp/extractor/rule34video.py b/yt_dlp/extractor/rule34video.py
index 11095b262..3630f5e40 100644
--- a/yt_dlp/extractor/rule34video.py
+++ b/yt_dlp/extractor/rule34video.py
@@ -38,8 +38,8 @@ class Rule34VideoIE(InfoExtractor):
'uploader': 'Sweet HMV',
'uploader_url': 'https://rule34video.com/members/22119/',
'categories': ['3D', 'MMD', 'iwara'],
- 'tags': 'mincount:10'
- }
+ 'tags': 'mincount:10',
+ },
},
{
'url': 'https://rule34video.com/videos/3065296/lara-in-trouble-ep-7-wildeerstudio/',
@@ -61,8 +61,8 @@ class Rule34VideoIE(InfoExtractor):
'uploader': 'CerZule',
'uploader_url': 'https://rule34video.com/members/36281/',
'categories': ['3D', 'Tomb Raider'],
- 'tags': 'mincount:40'
- }
+ 'tags': 'mincount:40',
+ },
},
]
diff --git a/yt_dlp/extractor/rumble.py b/yt_dlp/extractor/rumble.py
index 837a324e6..db780a2cf 100644
--- a/yt_dlp/extractor/rumble.py
+++ b/yt_dlp/extractor/rumble.py
@@ -37,7 +37,7 @@ class RumbleEmbedIE(InfoExtractor):
'duration': 234,
'uploader': 'WMAR',
'live_status': 'not_live',
- }
+ },
}, {
'url': 'https://rumble.com/embed/vslb7v',
'md5': '7418035de1a30a178b8af34dc2b6a52b',
@@ -53,7 +53,7 @@ class RumbleEmbedIE(InfoExtractor):
'duration': 901,
'uploader': 'CTNews',
'live_status': 'not_live',
- }
+ },
}, {
'url': 'https://rumble.com/embed/vunh1h',
'info_dict': {
@@ -73,12 +73,12 @@ class RumbleEmbedIE(InfoExtractor):
{
'url': r're:https://.+\.vtt',
'name': 'English',
- 'ext': 'vtt'
- }
- ]
+ 'ext': 'vtt',
+ },
+ ],
},
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://rumble.com/embed/v1essrt',
'info_dict': {
@@ -93,7 +93,7 @@ class RumbleEmbedIE(InfoExtractor):
'uploader': 'Lofi Girl',
'live_status': 'is_live',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://rumble.com/embed/v1amumr',
'info_dict': {
@@ -110,7 +110,7 @@ class RumbleEmbedIE(InfoExtractor):
'uploader': 'Rumble Events',
'live_status': 'was_live',
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
'url': 'https://rumble.com/embed/ufe9n.v5pv5f',
'only_matching': True,
@@ -133,7 +133,7 @@ class RumbleEmbedIE(InfoExtractor):
'uploader': 'Mr Producer Media',
'upload_date': '20220610',
'live_status': 'not_live',
- }
+ },
},
]
@@ -198,7 +198,7 @@ class RumbleEmbedIE(InfoExtractor):
'filesize': 'size',
'width': 'w',
'height': 'h',
- }, expected_type=lambda x: int(x) or None)
+ }, expected_type=lambda x: int(x) or None),
})
subtitles = {
@@ -258,7 +258,7 @@ class RumbleIE(InfoExtractor):
'dislike_count': int,
'view_count': int,
'live_status': 'not_live',
- }
+ },
}, {
'url': 'http://www.rumble.com/vDMUM1?key=value',
'only_matching': True,
@@ -353,7 +353,7 @@ class RumbleIE(InfoExtractor):
r'<span data-js="rumbles_up_votes">\s*([\d,.KM]+)', webpage, 'like count', default=None)),
'dislike_count': parse_count(self._search_regex(
r'<span data-js="rumbles_down_votes">\s*([\d,.KM]+)', webpage, 'dislike count', default=None)),
- 'description': clean_html(get_element_by_class('media-description', webpage))
+ 'description': clean_html(get_element_by_class('media-description', webpage)),
}
@@ -377,7 +377,7 @@ class RumbleChannelIE(InfoExtractor):
def entries(self, url, playlist_id):
for page in itertools.count(1):
try:
- webpage = self._download_webpage(f'{url}?page={page}', playlist_id, note='Downloading page %d' % page)
+ webpage = self._download_webpage(f'{url}?page={page}', playlist_id, note=f'Downloading page {page}')
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 404:
break
diff --git a/yt_dlp/extractor/rutube.py b/yt_dlp/extractor/rutube.py
index eb12f32fa..d389b3209 100644
--- a/yt_dlp/extractor/rutube.py
+++ b/yt_dlp/extractor/rutube.py
@@ -1,9 +1,6 @@
import itertools
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..utils import (
bool_or_none,
determine_ext,
@@ -21,7 +18,7 @@ class RutubeBaseIE(InfoExtractor):
query = {}
query['format'] = 'json'
return self._download_json(
- 'http://rutube.ru/api/video/%s/' % video_id,
+ f'http://rutube.ru/api/video/{video_id}/',
video_id, 'Downloading video JSON',
'Unable to download video JSON', query=query)
@@ -44,7 +41,7 @@ class RutubeBaseIE(InfoExtractor):
'thumbnail': video.get('thumbnail_url'),
'duration': duration,
'uploader': try_get(video, lambda x: x['author']['name']),
- 'uploader_id': compat_str(uploader_id) if uploader_id else None,
+ 'uploader_id': str(uploader_id) if uploader_id else None,
'timestamp': unified_timestamp(video.get('created_ts')),
'categories': [category] if category else None,
'age_limit': age_limit,
@@ -63,7 +60,7 @@ class RutubeBaseIE(InfoExtractor):
query = {}
query['format'] = 'json'
return self._download_json(
- 'http://rutube.ru/api/play/options/%s/' % video_id,
+ f'http://rutube.ru/api/play/options/{video_id}/',
video_id, 'Downloading options JSON',
'Unable to download options JSON',
headers=self.geo_verification_headers(), query=query)
@@ -171,7 +168,7 @@ class RutubeIE(RutubeBaseIE):
@classmethod
def suitable(cls, url):
- return False if RutubePlaylistIE.suitable(url) else super(RutubeIE, cls).suitable(url)
+ return False if RutubePlaylistIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -236,7 +233,7 @@ class RutubePlaylistBaseIE(RutubeBaseIE):
page = self._download_json(
next_page_url or self._next_page_url(
pagenum, playlist_id, *args, **kwargs),
- playlist_id, 'Downloading page %s' % pagenum)
+ playlist_id, f'Downloading page {pagenum}')
results = page.get('results')
if not results or not isinstance(results, list):
@@ -335,7 +332,7 @@ class RutubePlaylistIE(RutubePlaylistBaseIE):
def suitable(cls, url):
from ..utils import int_or_none, parse_qs
- if not super(RutubePlaylistIE, cls).suitable(url):
+ if not super().suitable(url):
return False
params = parse_qs(url)
return params.get('pl_type', [None])[0] and int_or_none(params.get('pl_id', [None])[0])
diff --git a/yt_dlp/extractor/rutv.py b/yt_dlp/extractor/rutv.py
index 726d49111..9bd26ad03 100644
--- a/yt_dlp/extractor/rutv.py
+++ b/yt_dlp/extractor/rutv.py
@@ -124,18 +124,18 @@ class RUTVIE(InfoExtractor):
is_live = video_type == 'live'
json_data = self._download_json(
- 'http://player.vgtrk.com/iframe/data%s/id/%s' % ('live' if is_live else 'video', video_id),
+ 'http://player.vgtrk.com/iframe/data{}/id/{}'.format('live' if is_live else 'video', video_id),
video_id, 'Downloading JSON')
if json_data['errors']:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, json_data['errors']), expected=True)
+ raise ExtractorError('{} said: {}'.format(self.IE_NAME, json_data['errors']), expected=True)
playlist = json_data['data']['playlist']
medialist = playlist['medialist']
media = medialist[0]
if media['errors']:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, media['errors']), expected=True)
+ raise ExtractorError('{} said: {}'.format(self.IE_NAME, media['errors']), expected=True)
view_count = int_or_none(playlist.get('count_views'))
priority_transport = playlist['priority_transport']
@@ -175,12 +175,12 @@ class RUTVIE(InfoExtractor):
continue
else:
fmt = {
- 'url': url
+ 'url': url,
}
fmt.update({
'width': int_or_none(quality, default=height, invscale=width, scale=height),
'height': int_or_none(quality, default=height),
- 'format_id': '%s-%s' % (transport, quality),
+ 'format_id': f'{transport}-{quality}',
'source_preference': preference,
})
formats.append(fmt)
diff --git a/yt_dlp/extractor/ruutu.py b/yt_dlp/extractor/ruutu.py
index dc61387be..2dddb39f9 100644
--- a/yt_dlp/extractor/ruutu.py
+++ b/yt_dlp/extractor/ruutu.py
@@ -1,8 +1,8 @@
import json
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_urlparse
from ..utils import (
ExtractorError,
determine_ext,
@@ -164,7 +164,7 @@ class RuutuIE(InfoExtractor):
video_id = self._match_id(url)
video_xml = self._download_xml(
- '%s/media-xml-cache' % self._API_BASE, video_id,
+ f'{self._API_BASE}/media-xml-cache', video_id,
query={'id': video_id})
formats = []
@@ -182,8 +182,8 @@ class RuutuIE(InfoExtractor):
processed_urls.append(video_url)
ext = determine_ext(video_url)
auth_video_url = url_or_none(self._download_webpage(
- '%s/auth/access/v2' % self._API_BASE, video_id,
- note='Downloading authenticated %s stream URL' % ext,
+ f'{self._API_BASE}/auth/access/v2', video_id,
+ note=f'Downloading authenticated {ext} stream URL',
fatal=False, query={'stream': video_url}))
if auth_video_url:
processed_urls.append(auth_video_url)
@@ -209,16 +209,16 @@ class RuutuIE(InfoExtractor):
'vcodec': 'none',
})
else:
- proto = compat_urllib_parse_urlparse(video_url).scheme
+ proto = urllib.parse.urlparse(video_url).scheme
if not child.tag.startswith('HTTP') and proto != 'rtmp':
continue
preference = -1 if proto == 'rtmp' else 1
label = child.get('label')
tbr = int_or_none(child.get('bitrate'))
- format_id = '%s-%s' % (proto, label if label else tbr) if label or tbr else proto
+ format_id = f'{proto}-{label if label else tbr}' if label or tbr else proto
if not self._is_valid_url(video_url, video_id, format_id):
continue
- width, height = [int_or_none(x) for x in child.get('resolution', 'x').split('x')[:2]]
+ width, height = (int_or_none(x) for x in child.get('resolution', 'x').split('x')[:2])
formats.append({
'format_id': format_id,
'url': video_url,
@@ -242,7 +242,7 @@ class RuutuIE(InfoExtractor):
self.report_drm(video_id)
ns_st_cds = pv('ns_st_cds')
if ns_st_cds != 'free':
- raise ExtractorError('This video is %s.' % ns_st_cds, expected=True)
+ raise ExtractorError(f'This video is {ns_st_cds}.', expected=True)
themes = pv('themes')
diff --git a/yt_dlp/extractor/ruv.py b/yt_dlp/extractor/ruv.py
index 12499d6ca..5e55a4059 100644
--- a/yt_dlp/extractor/ruv.py
+++ b/yt_dlp/extractor/ruv.py
@@ -131,10 +131,10 @@ class RuvSpilaIE(InfoExtractor):
},
}, {
'url': 'https://www.ruv.is/ungruv/spila/ungruv/28046/8beuph',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.ruv.is/krakkaruv/spila/krakkafrettir/30712/9jbgb0',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -153,7 +153,7 @@ class RuvSpilaIE(InfoExtractor):
}
}
}
- }''' % (series_id, display_id)})['data']['Program']
+ }''' % (series_id, display_id)})['data']['Program'] # noqa: UP031
episode = program['episodes'][0]
subs = {}
@@ -182,5 +182,5 @@ class RuvSpilaIE(InfoExtractor):
'timestamp': unified_timestamp(episode.get('firstrun')),
'formats': formats,
'age_limit': episode.get('rating'),
- 'chapters': clips
+ 'chapters': clips,
}
diff --git a/yt_dlp/extractor/s4c.py b/yt_dlp/extractor/s4c.py
index 67eff723b..6eb8b2b2c 100644
--- a/yt_dlp/extractor/s4c.py
+++ b/yt_dlp/extractor/s4c.py
@@ -12,7 +12,7 @@ class S4CIE(InfoExtractor):
'title': 'Y Swn',
'description': 'md5:f7681a30e4955b250b3224aa9fe70cf0',
'duration': 5340,
- 'thumbnail': 'https://www.s4c.cymru/amg/1920x1080/Y_Swn_2023S4C_099_ii.jpg'
+ 'thumbnail': 'https://www.s4c.cymru/amg/1920x1080/Y_Swn_2023S4C_099_ii.jpg',
},
}, {
'url': 'https://www.s4c.cymru/clic/programme/856636948',
@@ -22,7 +22,7 @@ class S4CIE(InfoExtractor):
'title': 'Am Dro',
'duration': 2880,
'description': 'md5:100d8686fc9a632a0cb2db52a3433ffe',
- 'thumbnail': 'https://www.s4c.cymru/amg/1920x1080/Am_Dro_2022-23S4C_P6_4005.jpg'
+ 'thumbnail': 'https://www.s4c.cymru/amg/1920x1080/Am_Dro_2022-23S4C_P6_4005.jpg',
},
}]
@@ -94,7 +94,7 @@ class S4CSeriesIE(InfoExtractor):
'https://www.s4c.cymru/df/series_details', series_id, query={
'lang': 'e',
'series_id': series_id,
- 'show_prog_in_series': 'Y'
+ 'show_prog_in_series': 'Y',
}, note='Downloading series details JSON')
return self.playlist_result(
diff --git a/yt_dlp/extractor/safari.py b/yt_dlp/extractor/safari.py
index 17dff0afa..86f34df47 100644
--- a/yt_dlp/extractor/safari.py
+++ b/yt_dlp/extractor/safari.py
@@ -1,11 +1,8 @@
import json
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
update_url_query,
@@ -34,9 +31,9 @@ class SafariBaseIE(InfoExtractor):
return
redirect_url = urlh.url
- parsed_url = compat_urlparse.urlparse(redirect_url)
- qs = compat_parse_qs(parsed_url.query)
- next_uri = compat_urlparse.urljoin(
+ parsed_url = urllib.parse.urlparse(redirect_url)
+ qs = urllib.parse.parse_qs(parsed_url.query)
+ next_uri = urllib.parse.urljoin(
'https://api.oreilly.com', qs['next'][0])
auth, urlh = self._download_json_handle(
@@ -54,7 +51,7 @@ class SafariBaseIE(InfoExtractor):
if (not auth.get('logged_in') and not auth.get('redirect_uri')
and credentials):
raise ExtractorError(
- 'Unable to login: %s' % credentials, expected=True)
+ f'Unable to login: {credentials}', expected=True)
# oreilly serves two same instances of the following cookies
# in Set-Cookie header and expects first one to be actually set
@@ -62,7 +59,7 @@ class SafariBaseIE(InfoExtractor):
self._apply_first_set_cookie_header(urlh, cookie)
_, urlh = self._download_webpage_handle(
- auth.get('redirect_uri') or next_uri, None, 'Completing login',)
+ auth.get('redirect_uri') or next_uri, None, 'Completing login')
if is_logged(urlh):
self.LOGGED_IN = True
@@ -124,7 +121,7 @@ class SafariIE(SafariBaseIE):
partner_id = self._PARTNER_ID
ui_id = self._UICONF_ID
else:
- video_id = '%s-%s' % (mobj.group('course_id'), mobj.group('part'))
+ video_id = '{}-{}'.format(mobj.group('course_id'), mobj.group('part'))
webpage, urlh = self._download_webpage_handle(url, video_id)
@@ -144,14 +141,14 @@ class SafariIE(SafariBaseIE):
group='id')
query = {
- 'wid': '_%s' % partner_id,
+ 'wid': f'_{partner_id}',
'uiconf_id': ui_id,
'flashvars[referenceId]': reference_id,
}
if self.LOGGED_IN:
kaltura_session = self._download_json(
- '%s/player/kaltura_session/?reference_id=%s' % (self._API_BASE, reference_id),
+ f'{self._API_BASE}/player/kaltura_session/?reference_id={reference_id}',
video_id, 'Downloading kaltura session JSON',
'Unable to download kaltura session JSON', fatal=False,
headers={'Accept': 'application/json'})
@@ -180,7 +177,7 @@ class SafariApiIE(SafariBaseIE):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
part = self._download_json(
- url, '%s/%s' % (mobj.group('course_id'), mobj.group('part')),
+ url, '{}/{}'.format(mobj.group('course_id'), mobj.group('part')),
'Downloading part JSON')
web_url = part['web_url']
if 'library/view' in web_url:
@@ -236,18 +233,18 @@ class SafariCourseIE(SafariBaseIE):
@classmethod
def suitable(cls, url):
return (False if SafariIE.suitable(url) or SafariApiIE.suitable(url)
- else super(SafariCourseIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
course_id = self._match_id(url)
course_json = self._download_json(
- '%s/book/%s/?override_format=%s' % (self._API_BASE, course_id, self._API_FORMAT),
+ f'{self._API_BASE}/book/{course_id}/?override_format={self._API_FORMAT}',
course_id, 'Downloading course JSON')
if 'chapters' not in course_json:
raise ExtractorError(
- 'No chapters found for course %s' % course_id, expected=True)
+ f'No chapters found for course {course_id}', expected=True)
entries = [
self.url_result(chapter, SafariApiIE.ie_key())
diff --git a/yt_dlp/extractor/saitosan.py b/yt_dlp/extractor/saitosan.py
index a5f05e1d0..4ed919572 100644
--- a/yt_dlp/extractor/saitosan.py
+++ b/yt_dlp/extractor/saitosan.py
@@ -47,15 +47,15 @@ class SaitosanIE(InfoExtractor):
base += '&sid=' + sid
self._download_webpage(base, b_id, note='Polling socket')
- payload = '420["room_start_join",{"room_id":"%s"}]' % b_id
- payload = '%s:%s' % (len(payload), payload)
+ payload = f'420["room_start_join",{{"room_id":"{b_id}"}}]'
+ payload = f'{len(payload)}:{payload}'
self._download_webpage(base, b_id, data=payload, note='Polling socket with payload')
response = self._download_socket_json(base, b_id, note='Polling socket')
if not response.get('ok'):
err = response.get('error') or {}
raise ExtractorError(
- '%s said: %s - %s' % (self.IE_NAME, err.get('code', '?'), err.get('msg', 'Unknown')) if err
+ '{} said: {} - {}'.format(self.IE_NAME, err.get('code', '?'), err.get('msg', 'Unknown')) if err
else 'The socket reported that the broadcast could not be joined. Maybe it\'s offline or the URL is incorrect',
expected=True, video_id=b_id)
@@ -71,5 +71,5 @@ class SaitosanIE(InfoExtractor):
'formats': self._extract_m3u8_formats(m3u8_url, b_id, 'mp4', live=True),
'thumbnail': m3u8_url.replace('av.m3u8', 'thumb'),
'uploader': try_get(b_data, lambda x: x['broadcast_user']['name']), # same as title
- 'is_live': True
+ 'is_live': True,
}
diff --git a/yt_dlp/extractor/samplefocus.py b/yt_dlp/extractor/samplefocus.py
index e9f5c227b..36ceb0254 100644
--- a/yt_dlp/extractor/samplefocus.py
+++ b/yt_dlp/extractor/samplefocus.py
@@ -28,10 +28,10 @@ class SampleFocusIE(InfoExtractor):
},
}, {
'url': 'https://samplefocus.com/samples/dababy-style-bass-808',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://samplefocus.com/samples/young-chop-kick',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -76,7 +76,7 @@ class SampleFocusIE(InfoExtractor):
def extract_count(klass):
return int_or_none(self._html_search_regex(
- r'<span[^>]+class=(?:["\'])?%s-count[^>]*>(\d+)' % klass,
+ rf'<span[^>]+class=(?:["\'])?{klass}-count[^>]*>(\d+)',
webpage, klass, fatal=False))
return {
@@ -90,7 +90,7 @@ class SampleFocusIE(InfoExtractor):
r'<a[^>]+href=(["\'])/license\1[^>]*>(?P<license>[^<]+)<',
webpage, 'license', fatal=False, group='license'),
'uploader_id': uploader_id,
- 'like_count': extract_count('sample-%s-favorites' % sample_id),
+ 'like_count': extract_count(f'sample-{sample_id}-favorites'),
'comment_count': extract_count('comments'),
'comments': comments,
'categories': categories,
diff --git a/yt_dlp/extractor/sapo.py b/yt_dlp/extractor/sapo.py
index beffaee59..2b8c078ea 100644
--- a/yt_dlp/extractor/sapo.py
+++ b/yt_dlp/extractor/sapo.py
@@ -64,7 +64,7 @@ class SapoIE(InfoExtractor):
video_id = mobj.group('id')
item = self._download_xml(
- 'http://rd3.videos.sapo.pt/%s/rss2' % video_id, video_id).find('./channel/item')
+ f'http://rd3.videos.sapo.pt/{video_id}/rss2', video_id).find('./channel/item')
title = item.find('./title').text
description = item.find('./{http://videos.sapo.pt/mrss/}synopse').text
diff --git a/yt_dlp/extractor/sbscokr.py b/yt_dlp/extractor/sbscokr.py
index 001d19ee1..93cf14fd0 100644
--- a/yt_dlp/extractor/sbscokr.py
+++ b/yt_dlp/extractor/sbscokr.py
@@ -94,7 +94,7 @@ class SBSCoKrIE(InfoExtractor):
formats = []
for stream in traverse_obj(details, (
- 'vod', 'source', 'mediasourcelist', lambda _, v: v['mediaurl'] or v['mediarscuse']
+ 'vod', 'source', 'mediasourcelist', lambda _, v: v['mediaurl'] or v['mediarscuse'],
), default=[source]):
if not stream.get('mediaurl'):
new_source = traverse_obj(
@@ -108,7 +108,7 @@ class SBSCoKrIE(InfoExtractor):
'format_id': stream.get('mediarscuse'),
'format_note': stream.get('medianame'),
**parse_resolution(stream.get('quality')),
- 'preference': int_or_none(stream.get('mediarscuse'))
+ 'preference': int_or_none(stream.get('mediarscuse')),
})
caption_url = traverse_obj(details, ('vod', 'source', 'subtitle', {url_or_none}))
diff --git a/yt_dlp/extractor/screencast.py b/yt_dlp/extractor/screencast.py
index df5e79bef..44b58911f 100644
--- a/yt_dlp/extractor/screencast.py
+++ b/yt_dlp/extractor/screencast.py
@@ -1,7 +1,6 @@
-import urllib.request
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_parse_qs
from ..utils import ExtractorError
@@ -16,7 +15,7 @@ class ScreencastIE(InfoExtractor):
'title': 'Color Measurement with Ocean Optics Spectrometers',
'description': 'md5:240369cde69d8bed61349a199c5fb153',
'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
- }
+ },
}, {
'url': 'http://www.screencast.com/t/V2uXehPJa1ZI',
'md5': 'e8e4b375a7660a9e7e35c33973410d34',
@@ -26,7 +25,7 @@ class ScreencastIE(InfoExtractor):
'title': 'The Amadeus Spectrometer',
'description': 're:^In this video, our friends at.*To learn more about Amadeus, visit',
'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
- }
+ },
}, {
'url': 'http://www.screencast.com/t/aAB3iowa',
'md5': 'dedb2734ed00c9755761ccaee88527cd',
@@ -36,7 +35,7 @@ class ScreencastIE(InfoExtractor):
'title': 'Google Earth Export',
'description': 'Provides a demo of a CommunityViz export to Google Earth, one of the 3D viewing options.',
'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
- }
+ },
}, {
'url': 'http://www.screencast.com/t/X3ddTrYh',
'md5': '669ee55ff9c51988b4ebc0877cc8b159',
@@ -46,7 +45,7 @@ class ScreencastIE(InfoExtractor):
'title': 'Toolkit 6 User Group Webinar (2014-03-04) - Default Judgment and First Impression',
'description': 'md5:7b9f393bc92af02326a5c5889639eab0',
'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
- }
+ },
}, {
'url': 'http://screencast.com/t/aAB3iowa',
'only_matching': True,
@@ -71,8 +70,8 @@ class ScreencastIE(InfoExtractor):
if flash_vars_s:
flash_vars_s = flash_vars_s.replace(',', '&')
if flash_vars_s:
- flash_vars = compat_parse_qs(flash_vars_s)
- video_url_raw = urllib.request.quote(
+ flash_vars = urllib.parse.parse_qs(flash_vars_s)
+ video_url_raw = urllib.parse.quote(
flash_vars['content'][0])
video_url = video_url_raw.replace('http%3A', 'http:')
diff --git a/yt_dlp/extractor/screencastomatic.py b/yt_dlp/extractor/screencastomatic.py
index 28e25e9d8..3af9135a9 100644
--- a/yt_dlp/extractor/screencastomatic.py
+++ b/yt_dlp/extractor/screencastomatic.py
@@ -23,7 +23,7 @@ class ScreencastOMaticIE(InfoExtractor):
'description': 'as the title says! also: some general info re 1) VCE philosophy and 2) distance learning.',
'duration': 369,
'upload_date': '20141216',
- }
+ },
}, {
'url': 'http://screencast-o-matic.com/player/c2lD3BeOPl',
'only_matching': True,
diff --git a/yt_dlp/extractor/scrippsnetworks.py b/yt_dlp/extractor/scrippsnetworks.py
index 85d51cd59..d770368a7 100644
--- a/yt_dlp/extractor/scrippsnetworks.py
+++ b/yt_dlp/extractor/scrippsnetworks.py
@@ -56,10 +56,10 @@ class ScrippsNetworksWatchIE(AWSIE):
site_id, video_id = mobj.group('site', 'id')
aws_identity_id_json = json.dumps({
- 'IdentityId': '%s:7655847c-0ae7-4d9b-80d6-56c062927eb3' % self._AWS_REGION
- }).encode('utf-8')
+ 'IdentityId': f'{self._AWS_REGION}:7655847c-0ae7-4d9b-80d6-56c062927eb3',
+ }).encode()
token = self._download_json(
- 'https://cognito-identity.%s.amazonaws.com/' % self._AWS_REGION, video_id,
+ f'https://cognito-identity.{self._AWS_REGION}.amazonaws.com/', video_id,
data=aws_identity_id_json,
headers={
'Accept': '*/*',
@@ -85,11 +85,11 @@ class ScrippsNetworksWatchIE(AWSIE):
def get(key):
return xpath_text(
- sts, './/{https://sts.amazonaws.com/doc/2011-06-15/}%s' % key,
+ sts, f'.//{{https://sts.amazonaws.com/doc/2011-06-15/}}{key}',
fatal=True)
mcp_id = self._aws_execute_api({
- 'uri': '/1/web/brands/%s/episodes/scrid/%s' % (self._SNI_TABLE[site_id], video_id),
+ 'uri': f'/1/web/brands/{self._SNI_TABLE[site_id]}/episodes/scrid/{video_id}',
'access_key': get('AccessKeyId'),
'secret_key': get('SecretAccessKey'),
'session_token': get('SessionToken'),
@@ -97,7 +97,7 @@ class ScrippsNetworksWatchIE(AWSIE):
return self.url_result(
smuggle_url(
- 'anvato:anvato_scripps_app_web_prod_0837996dbe373629133857ae9eb72e740424d80a:%s' % mcp_id,
+ f'anvato:anvato_scripps_app_web_prod_0837996dbe373629133857ae9eb72e740424d80a:{mcp_id}',
{'geo_countries': ['US']}),
AnvatoIE.ie_key(), video_id=mcp_id)
diff --git a/yt_dlp/extractor/scrolller.py b/yt_dlp/extractor/scrolller.py
index 4f9fa1440..a50822e10 100644
--- a/yt_dlp/extractor/scrolller.py
+++ b/yt_dlp/extractor/scrolller.py
@@ -14,7 +14,7 @@ class ScrolllerIE(InfoExtractor):
'thumbnail': 'https://zepto.scrolller.com/a-helping-hand-3ty9q8x094-540x960.jpg',
'title': 'A helping hand',
'age_limit': 0,
- }
+ },
}, {
'url': 'https://scrolller.com/tigers-chasing-a-drone-c5d1f2so6j',
'info_dict': {
@@ -23,7 +23,7 @@ class ScrolllerIE(InfoExtractor):
'thumbnail': 'https://zepto.scrolller.com/tigers-chasing-a-drone-az9pkpguwe-540x303.jpg',
'title': 'Tigers chasing a drone',
'age_limit': 0,
- }
+ },
}, {
'url': 'https://scrolller.com/baby-rhino-smells-something-9chhugsv9p',
'info_dict': {
@@ -32,7 +32,7 @@ class ScrolllerIE(InfoExtractor):
'thumbnail': 'https://atto.scrolller.com/hmm-whats-that-smell-bh54mf2c52-300x224.jpg',
'title': 'Baby rhino smells something',
'age_limit': 0,
- }
+ },
}, {
'url': 'https://scrolller.com/its-all-fun-and-games-cco8jjmoh7',
'info_dict': {
@@ -41,7 +41,7 @@ class ScrolllerIE(InfoExtractor):
'thumbnail': 'https://atto.scrolller.com/its-all-fun-and-games-3amk9vg7m3-540x649.jpg',
'title': 'It\'s all fun and games...',
'age_limit': 0,
- }
+ },
}, {
'url': 'https://scrolller.com/may-the-force-be-with-you-octokuro-yeytg1fs7a',
'info_dict': {
@@ -50,7 +50,7 @@ class ScrolllerIE(InfoExtractor):
'thumbnail': 'https://thumbs2.redgifs.com/DarkStarchyNautilus-poster.jpg',
'title': 'May the force be with you (Octokuro)',
'age_limit': 18,
- }
+ },
}]
def _real_extract(self, url):
@@ -68,7 +68,7 @@ class ScrolllerIE(InfoExtractor):
height
}
}
- }''' % video_id
+ }''' % video_id, # noqa: UP031
}
video_data = self._download_json(
@@ -98,5 +98,5 @@ class ScrolllerIE(InfoExtractor):
'title': video_data.get('title'),
'thumbnails': thumbnails,
'formats': formats,
- 'age_limit': 18 if video_data.get('isNsfw') else 0
+ 'age_limit': 18 if video_data.get('isNsfw') else 0,
}
diff --git a/yt_dlp/extractor/scte.py b/yt_dlp/extractor/scte.py
index fc91d60e1..39711325f 100644
--- a/yt_dlp/extractor/scte.py
+++ b/yt_dlp/extractor/scte.py
@@ -41,7 +41,7 @@ class SCTEBaseIE(InfoExtractor):
r'(?s)<[^>]+class=["\']AsiError["\'][^>]*>(.+?)</',
response, 'error message', default=None)
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
raise ExtractorError('Unable to log in')
@@ -66,9 +66,9 @@ class SCTEIE(SCTEBaseIE):
title = self._search_regex(r'<h1>(.+?)</h1>', webpage, 'title')
context_id = self._search_regex(r'context-(\d+)', webpage, video_id)
- content_base = 'https://learning.scte.org/pluginfile.php/%s/mod_scorm/content/8/' % context_id
+ content_base = f'https://learning.scte.org/pluginfile.php/{context_id}/mod_scorm/content/8/'
context = decode_packed_codes(self._download_webpage(
- '%smobile/data.js' % content_base, video_id))
+ f'{content_base}mobile/data.js', video_id))
data = self._parse_xml(
self._search_regex(
diff --git a/yt_dlp/extractor/senategov.py b/yt_dlp/extractor/senategov.py
index 7ff0cf5b7..cddca09d0 100644
--- a/yt_dlp/extractor/senategov.py
+++ b/yt_dlp/extractor/senategov.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
parse_qs,
@@ -68,7 +65,7 @@ class SenateISVPIE(InfoExtractor):
'info_dict': {
'id': 'commerce011514',
'ext': 'mp4',
- 'title': 'Integrated Senate Video Player'
+ 'title': 'Integrated Senate Video Player',
},
'params': {
# m3u8 download
@@ -80,8 +77,8 @@ class SenateISVPIE(InfoExtractor):
'info_dict': {
'id': 'intel090613',
'ext': 'mp4',
- 'title': 'Integrated Senate Video Player'
- }
+ 'title': 'Integrated Senate Video Player',
+ },
}, {
# From http://www.c-span.org/video/?96791-1
'url': 'http://www.senate.gov/isvp?type=live&comm=banking&filename=banking012715',
@@ -91,7 +88,7 @@ class SenateISVPIE(InfoExtractor):
def _real_extract(self, url):
url, smuggled_data = unsmuggle_url(url, {})
- qs = compat_parse_qs(self._match_valid_url(url).group('qs'))
+ qs = urllib.parse.parse_qs(self._match_valid_url(url).group('qs'))
if not qs.get('filename') or not qs.get('type') or not qs.get('comm'):
raise ExtractorError('Invalid URL', expected=True)
@@ -114,13 +111,13 @@ class SenateISVPIE(InfoExtractor):
formats = []
if video_type == 'arch':
filename = video_id if '.' in video_id else video_id + '.mp4'
- m3u8_url = compat_urlparse.urljoin(domain, 'i/' + filename + '/master.m3u8')
+ m3u8_url = urllib.parse.urljoin(domain, 'i/' + filename + '/master.m3u8')
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', m3u8_id='m3u8')
else:
hdcore_sign = 'hdcore=3.1.0'
url_params = (domain, video_id, stream_num)
f4m_url = f'%s/z/%s_1@%s/manifest.f4m?{hdcore_sign}' % url_params
- m3u8_url = '%s/i/%s_1@%s/master.m3u8' % url_params
+ m3u8_url = '{}/i/{}_1@{}/master.m3u8'.format(*url_params)
for entry in self._extract_f4m_formats(f4m_url, video_id, f4m_id='f4m'):
# URLs without the extra param induce an 404 error
entry.update({'extra_param_to_segment_url': hdcore_sign})
@@ -196,5 +193,5 @@ class SenateGovIE(InfoExtractor):
'description': self._og_search_description(webpage, default=None),
'thumbnail': self._og_search_thumbnail(webpage, default=None),
'age_limit': self._rta_search(webpage),
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/sendtonews.py b/yt_dlp/extractor/sendtonews.py
index 99fcf51f1..ecf4b27d7 100644
--- a/yt_dlp/extractor/sendtonews.py
+++ b/yt_dlp/extractor/sendtonews.py
@@ -19,7 +19,7 @@ class SendtoNewsIE(InfoExtractor):
# From http://cleveland.cbslocal.com/2016/05/16/indians-score-season-high-15-runs-in-blowout-win-over-reds-rapid-reaction/
'url': 'http://embed.sendtonews.com/player2/embedplayer.php?SC=GxfCe0Zo7D-175909-5588&type=single&autoplay=on&sound=YES',
'info_dict': {
- 'id': 'GxfCe0Zo7D-175909-5588'
+ 'id': 'GxfCe0Zo7D-175909-5588',
},
'playlist_count': 8,
# test the first video only to prevent lengthy tests
@@ -75,7 +75,7 @@ class SendtoNewsIE(InfoExtractor):
if not tbr:
continue
f.update({
- 'format_id': '%s-%d' % (determine_protocol(f), tbr),
+ 'format_id': f'{determine_protocol(f)}-{tbr}',
'tbr': tbr,
})
@@ -98,7 +98,7 @@ class SendtoNewsIE(InfoExtractor):
'timestamp': parse_iso8601(video.get('S_sysDate'), delimiter=' '),
# 'tbr' was explicitly set to be preferred over 'height' originally,
# So this is being kept unless someone can confirm this is unnecessary
- '_format_sort_fields': ('tbr', 'res')
+ '_format_sort_fields': ('tbr', 'res'),
})
entries.append(info_dict)
diff --git a/yt_dlp/extractor/servus.py b/yt_dlp/extractor/servus.py
index dda195883..117f18081 100644
--- a/yt_dlp/extractor/servus.py
+++ b/yt_dlp/extractor/servus.py
@@ -39,7 +39,7 @@ class ServusIE(InfoExtractor):
'episode': 'Episode 8 - Vie Ferrate – Klettersteige in den Alpen',
'episode_number': 8,
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://www.servustv.com/natur/v/aa-1xg5xwmgw2112/',
'only_matching': True,
diff --git a/yt_dlp/extractor/sevenplus.py b/yt_dlp/extractor/sevenplus.py
index 6c688d150..6e1fbe713 100644
--- a/yt_dlp/extractor/sevenplus.py
+++ b/yt_dlp/extractor/sevenplus.py
@@ -2,7 +2,6 @@ import json
import re
from .brightcove import BrightcoveNewBaseIE
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -31,7 +30,7 @@ class SevenPlusIE(BrightcoveNewBaseIE):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://7plus.com.au/UUUU?episode-id=AUMS43-001',
'only_matching': True,
@@ -71,7 +70,7 @@ class SevenPlusIE(BrightcoveNewBaseIE):
'idToken': id_token,
'platformId': 'web',
'regSource': '7plus',
- }).encode('utf-8')) or {}
+ }).encode()) or {}
self.token = token_resp.get('token')
if not self.token:
self.report_warning('Unable to log in: Could not extract auth token')
@@ -120,7 +119,7 @@ class SevenPlusIE(BrightcoveNewBaseIE):
if value:
info[dst_key] = value
info['series'] = try_get(
- item, lambda x: x['seriesLogo']['name'], compat_str)
+ item, lambda x: x['seriesLogo']['name'], str)
mobj = re.search(r'^S(\d+)\s+E(\d+)\s+-\s+(.+)$', info['title'])
if mobj:
info.update({
diff --git a/yt_dlp/extractor/sexu.py b/yt_dlp/extractor/sexu.py
index 989b63c72..71b1076e6 100644
--- a/yt_dlp/extractor/sexu.py
+++ b/yt_dlp/extractor/sexu.py
@@ -15,7 +15,7 @@ class SexuIE(InfoExtractor):
'categories': list, # NSFW
'thumbnail': r're:https?://.*\.jpg$',
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/seznamzpravy.py b/yt_dlp/extractor/seznamzpravy.py
index b31d566df..6f2bfcd27 100644
--- a/yt_dlp/extractor/seznamzpravy.py
+++ b/yt_dlp/extractor/seznamzpravy.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_urlparse,
-)
from ..utils import (
int_or_none,
parse_codecs,
@@ -13,7 +11,7 @@ from ..utils import (
def _raw_id(src_url):
- return compat_urllib_parse_urlparse(src_url).path.split('/')[-1]
+ return urllib.parse.urlparse(src_url).path.split('/')[-1]
class SeznamZpravyIE(InfoExtractor):
@@ -68,7 +66,7 @@ class SeznamZpravyIE(InfoExtractor):
f = {
'url': urljoin(sdn_url, relative_url),
- 'format_id': 'http-%s' % format_id,
+ 'format_id': f'http-{format_id}',
'tbr': int_or_none(format_data.get('bandwidth'), scale=1000),
'width': int_or_none(width),
'height': int_or_none(height),
@@ -79,7 +77,7 @@ class SeznamZpravyIE(InfoExtractor):
pls = sdn_data.get('pls', {})
def get_url(format_id):
- return try_get(pls, lambda x: x[format_id]['url'], compat_str)
+ return try_get(pls, lambda x: x[format_id]['url'], str)
dash_rel_url = get_url('dash')
if dash_rel_url:
diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py
index 89aee2728..f0a3b6b7d 100644
--- a/yt_dlp/extractor/shahid.py
+++ b/yt_dlp/extractor/shahid.py
@@ -63,17 +63,17 @@ class ShahidIE(ShahidBaseIE):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
'url': 'https://shahid.mbc.net/ar/movies/%D8%A7%D9%84%D9%82%D9%86%D8%A7%D8%B5%D8%A9/movie-151746',
- 'only_matching': True
+ 'only_matching': True,
}, {
# shahid plus subscriber only
'url': 'https://shahid.mbc.net/ar/series/%D9%85%D8%B1%D8%A7%D9%8A%D8%A7-2011-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/episode-90511',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://shahid.mbc.net/en/shows/Ramez-Fi-Al-Shallal-season-1-episode-1/episode-359319',
- 'only_matching': True
+ 'only_matching': True,
}]
def _perform_login(self, username, password):
@@ -84,7 +84,7 @@ class ShahidIE(ShahidBaseIE):
'email': username,
'password': password,
'basic': 'false',
- }).encode('utf-8'), headers={
+ }).encode(), headers={
'Content-Type': 'application/json; charset=UTF-8',
})['user']
except ExtractorError as e:
@@ -127,7 +127,7 @@ class ShahidIE(ShahidBaseIE):
# })['productModel']
response = self._download_json(
- 'http://api.shahid.net/api/v1_1/%s/%s' % (page_type, video_id),
+ f'http://api.shahid.net/api/v1_1/{page_type}/{video_id}',
video_id, 'Downloading video JSON', query={
'apiKey': 'sh@hid0nlin3',
'hash': 'b2wMCTHpSmyxGqQjJFOycRmLSex+BpTK/ooxy6vHaqs=',
@@ -136,7 +136,7 @@ class ShahidIE(ShahidBaseIE):
error = data.get('error')
if error:
raise ExtractorError(
- '%s returned error: %s' % (self.IE_NAME, '\n'.join(error.values())),
+ '{} returned error: {}'.format(self.IE_NAME, '\n'.join(error.values())),
expected=True)
video = data[page_type]
@@ -175,7 +175,7 @@ class ShahidShowIE(ShahidBaseIE):
'playlist_mincount': 32,
}, {
'url': 'https://shahid.mbc.net/ar/series/How-to-live-Longer-(The-Big-Think)/series-291861',
- 'only_matching': True
+ 'only_matching': True,
}]
_PAGE_SIZE = 30
@@ -196,7 +196,7 @@ class ShahidShowIE(ShahidBaseIE):
'pageSize': 30,
'sorts': [{
'order': 'DESC',
- 'type': 'SORTDATE'
+ 'type': 'SORTDATE',
}],
})
for product in playlist.get('productList', {}).get('products', []):
diff --git a/yt_dlp/extractor/shemaroome.py b/yt_dlp/extractor/shemaroome.py
index cca86ed6c..284b2f89c 100644
--- a/yt_dlp/extractor/shemaroome.py
+++ b/yt_dlp/extractor/shemaroome.py
@@ -1,8 +1,7 @@
+import base64
+
from .common import InfoExtractor
from ..aes import aes_cbc_decrypt, unpad_pkcs7
-from ..compat import (
- compat_b64decode,
-)
from ..utils import (
ExtractorError,
bytes_to_intlist,
@@ -24,8 +23,8 @@ class ShemarooMeIE(InfoExtractor):
'description': 'md5:2782c4127807103cf5a6ae2ca33645ce',
},
'params': {
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}, {
'url': 'https://www.shemaroome.com/shows/jurm-aur-jazbaat/laalach',
'info_dict': {
@@ -37,9 +36,9 @@ class ShemarooMeIE(InfoExtractor):
'release_date': '20210507',
},
'params': {
- 'skip_download': True
+ 'skip_download': True,
},
- 'skip': 'Premium videos cannot be downloaded yet.'
+ 'skip': 'Premium videos cannot be downloaded yet.',
}, {
'url': 'https://www.shemaroome.com/shows/jai-jai-jai-bajrang-bali/jai-jai-jai-bajrang-bali-episode-99',
'info_dict': {
@@ -51,8 +50,8 @@ class ShemarooMeIE(InfoExtractor):
'release_date': '20110101',
},
'params': {
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}]
def _real_extract(self, url):
@@ -69,8 +68,8 @@ class ShemarooMeIE(InfoExtractor):
data_json = self._download_json('https://www.shemaroome.com/users/user_all_lists', video_id, data=data.encode())
if not data_json.get('status'):
raise ExtractorError('Premium videos cannot be downloaded yet.', expected=True)
- url_data = bytes_to_intlist(compat_b64decode(data_json['new_play_url']))
- key = bytes_to_intlist(compat_b64decode(data_json['key']))
+ url_data = bytes_to_intlist(base64.b64decode(data_json['new_play_url']))
+ key = bytes_to_intlist(base64.b64decode(data_json['key']))
iv = [0] * 16
m3u8_url = unpad_pkcs7(intlist_to_bytes(aes_cbc_decrypt(url_data, key, iv))).decode('ascii')
headers = {'stream_key': data_json['stream_key']}
diff --git a/yt_dlp/extractor/showroomlive.py b/yt_dlp/extractor/showroomlive.py
index ab1895311..303f0b3cc 100644
--- a/yt_dlp/extractor/showroomlive.py
+++ b/yt_dlp/extractor/showroomlive.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -24,18 +23,18 @@ class ShowRoomLiveIE(InfoExtractor):
r'(?:profile|room)\?room_id\=(\d+)'), webpage, 'room_id')
room = self._download_json(
- urljoin(url, '/api/room/profile?room_id=%s' % room_id),
+ urljoin(url, f'/api/room/profile?room_id={room_id}'),
broadcaster_id)
is_live = room.get('is_onlive')
if is_live is not True:
- raise ExtractorError('%s is offline' % broadcaster_id, expected=True)
+ raise ExtractorError(f'{broadcaster_id} is offline', expected=True)
uploader = room.get('performer_name') or broadcaster_id
title = room.get('room_name') or room.get('main_name') or uploader
streaming_url_list = self._download_json(
- urljoin(url, '/api/live/streaming_url?room_id=%s' % room_id),
+ urljoin(url, f'/api/live/streaming_url?room_id={room_id}'),
broadcaster_id)['streaming_url_list']
formats = []
@@ -68,7 +67,7 @@ class ShowRoomLiveIE(InfoExtractor):
})
return {
- 'id': compat_str(room.get('live_id') or broadcaster_id),
+ 'id': str(room.get('live_id') or broadcaster_id),
'title': title,
'description': room.get('description'),
'timestamp': int_or_none(room.get('current_live_started_at')),
diff --git a/yt_dlp/extractor/sibnet.py b/yt_dlp/extractor/sibnet.py
index 73bb75d8f..c3fb72e69 100644
--- a/yt_dlp/extractor/sibnet.py
+++ b/yt_dlp/extractor/sibnet.py
@@ -8,10 +8,10 @@ class SibnetEmbedIE(InfoExtractor):
_WEBPAGE_TESTS = [{
'url': 'https://phpbb3.x-tk.ru/bbcode-video-sibnet-t24.html',
'info_dict': {
- 'id': 'shell', # FIXME?
+ 'id': 'shell', # FIXME: Non unique ID?
'ext': 'mp4',
'age_limit': 0,
'thumbnail': 'https://video.sibnet.ru/upload/cover/video_1887072_0.jpg',
'title': 'КВН Москва не сразу строилась - Девушка впервые играет в Mortal Kombat',
- }
+ },
}]
diff --git a/yt_dlp/extractor/simplecast.py b/yt_dlp/extractor/simplecast.py
index ec349ddf9..f6bb1306a 100644
--- a/yt_dlp/extractor/simplecast.py
+++ b/yt_dlp/extractor/simplecast.py
@@ -19,7 +19,7 @@ class SimplecastBaseIE(InfoExtractor):
def _call_search_api(self, resource, resource_id, resource_url):
return self._download_json(
- 'https://api.simplecast.com/%ss/search' % resource, resource_id,
+ f'https://api.simplecast.com/{resource}s/search', resource_id,
data=urlencode_postdata({'url': resource_url}))
def _parse_episode(self, episode):
@@ -33,7 +33,7 @@ class SimplecastBaseIE(InfoExtractor):
season_id = None
if season_href:
season_id = self._search_regex(
- r'https?://api.simplecast.com/seasons/(%s)' % self._UUID_REGEX,
+ rf'https?://api.simplecast.com/seasons/({self._UUID_REGEX})',
season_href, 'season id', default=None)
webpage_url = episode.get('episode_url')
@@ -65,7 +65,7 @@ class SimplecastBaseIE(InfoExtractor):
class SimplecastIE(SimplecastBaseIE):
IE_NAME = 'simplecast'
- _VALID_URL = r'https?://(?:api\.simplecast\.com/episodes|player\.simplecast\.com)/(?P<id>%s)' % SimplecastBaseIE._UUID_REGEX
+ _VALID_URL = rf'https?://(?:api\.simplecast\.com/episodes|player\.simplecast\.com)/(?P<id>{SimplecastBaseIE._UUID_REGEX})'
_EMBED_REGEX = [rf'''(?x)<iframe[^>]+src=["\']
(?P<url>https?://(?:
embed\.simplecast\.com/[0-9a-f]{8}|
diff --git a/yt_dlp/extractor/sina.py b/yt_dlp/extractor/sina.py
index eeb9ebb44..974af1bc5 100644
--- a/yt_dlp/extractor/sina.py
+++ b/yt_dlp/extractor/sina.py
@@ -28,7 +28,7 @@ class SinaIE(InfoExtractor):
'id': '250576622',
'ext': 'mp4',
'title': '现场:克鲁兹宣布退选 特朗普将稳获提名',
- }
+ },
},
{
'url': 'http://video.sina.com.cn/v/b/101314253-1290078633.html',
@@ -66,8 +66,7 @@ class SinaIE(InfoExtractor):
webpage = self._download_webpage(url, pseudo_id)
error = get_element_by_attribute('class', 'errtitle', webpage)
if error:
- raise ExtractorError('%s said: %s' % (
- self.IE_NAME, clean_html(error)), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {clean_html(error)}', expected=True)
video_id = self._search_regex(
r"video_id\s*:\s*'(\d+)'", webpage, 'video id')
@@ -75,7 +74,7 @@ class SinaIE(InfoExtractor):
'http://s.video.sina.com.cn/video/h5play',
video_id, query={'video_id': video_id})
if video_data['code'] != 1:
- raise ExtractorError('%s said: %s' % (
+ raise ExtractorError('{} said: {}'.format(
self.IE_NAME, video_data['message']), expected=True)
else:
video_data = video_data['data']
diff --git a/yt_dlp/extractor/sixplay.py b/yt_dlp/extractor/sixplay.py
index 44619a16c..6037a3511 100644
--- a/yt_dlp/extractor/sixplay.py
+++ b/yt_dlp/extractor/sixplay.py
@@ -1,7 +1,4 @@
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..utils import (
determine_ext,
int_or_none,
@@ -44,9 +41,9 @@ class SixPlayIE(InfoExtractor):
}.get(domain, ('6play', 'm6web'))
data = self._download_json(
- 'https://pc.middleware.6play.fr/6play/v2/platforms/m6group_web/services/%s/videos/clip_%s' % (service, video_id),
+ f'https://pc.middleware.6play.fr/6play/v2/platforms/m6group_web/services/{service}/videos/clip_{video_id}',
video_id, headers={
- 'x-customer-name': consumer_name
+ 'x-customer-name': consumer_name,
}, query={
'csa': 5,
'with': 'clips',
@@ -82,7 +79,7 @@ class SixPlayIE(InfoExtractor):
asset_url = urlh.url
asset_url = asset_url.replace('_drmnp.ism/', '_unpnp.ism/')
for i in range(3, 0, -1):
- asset_url = asset_url = asset_url.replace('_sd1/', '_sd%d/' % i)
+ asset_url = asset_url.replace('_sd1/', f'_sd{i}/')
m3u8_formats = self._extract_m3u8_formats(
asset_url, video_id, 'mp4', 'm3u8_native',
m3u8_id='hls', fatal=False)
@@ -107,7 +104,7 @@ class SixPlayIE(InfoExtractor):
def get(getter):
for src in (data, clip_data):
- v = try_get(src, getter, compat_str)
+ v = try_get(src, getter, str)
if v:
return v
diff --git a/yt_dlp/extractor/skeb.py b/yt_dlp/extractor/skeb.py
index 54dfdc441..bc5ec3da7 100644
--- a/yt_dlp/extractor/skeb.py
+++ b/yt_dlp/extractor/skeb.py
@@ -20,8 +20,8 @@ class SkebIE(InfoExtractor):
'subtitles': {
'jpn': [{
'url': r're:https://skeb.+',
- 'ext': 'vtt'
- }]
+ 'ext': 'vtt',
+ }],
},
'width': 720,
'height': 405,
@@ -48,8 +48,8 @@ class SkebIE(InfoExtractor):
'subtitles': {
'jpn': [{
'url': r're:https://skeb.+',
- 'ext': 'vtt'
- }]
+ 'ext': 'vtt',
+ }],
},
'duration': 98,
'ext': 'mp3',
@@ -70,8 +70,8 @@ class SkebIE(InfoExtractor):
}, {
'id': '486431',
'title': 'ヒロ。\n\n私のキャラク... by 諸々',
- }]
- }
+ }],
+ },
}]
def _real_extract(self, url):
@@ -106,7 +106,7 @@ class SkebIE(InfoExtractor):
if width is not None and height is not None:
# the longest side is at most 720px for non-client viewers
max_size = max(width, height)
- width, height = list(x * 720 // max_size for x in (width, height))
+ width, height = (x * 720 // max_size for x in (width, height))
entries.append({
**parent,
'id': str(item['id']),
@@ -116,7 +116,7 @@ class SkebIE(InfoExtractor):
'jpn': [{
'url': item.get('vtt_url'),
'ext': 'vtt',
- }]
+ }],
} if item.get('vtt_url') else None,
'width': width,
'height': height,
diff --git a/yt_dlp/extractor/sky.py b/yt_dlp/extractor/sky.py
index 574ac219c..5c9e4f35c 100644
--- a/yt_dlp/extractor/sky.py
+++ b/yt_dlp/extractor/sky.py
@@ -94,7 +94,7 @@ class SkyNewsStoryIE(SkyBaseIE):
'upload_date': '20211027',
'timestamp': 1635317494,
'uploader_id': '6058004172001',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/skyit.py b/yt_dlp/extractor/skyit.py
index 42d30f7c4..6e2973232 100644
--- a/yt_dlp/extractor/skyit.py
+++ b/yt_dlp/extractor/skyit.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_urllib_parse_urlparse,
-)
from ..utils import (
dict_get,
int_or_none,
@@ -56,7 +54,7 @@ class SkyItPlayerIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- domain = compat_parse_qs(compat_urllib_parse_urlparse(
+ domain = urllib.parse.parse_qs(urllib.parse.urlparse(
url).query).get('domain', [None])[0]
token = dict_get(self._TOKEN_MAP, (domain, 'sky'))
video = self._download_json(
@@ -64,7 +62,7 @@ class SkyItPlayerIE(InfoExtractor):
video_id, query={
'caller': 'sky',
'id': video_id,
- 'token': token
+ 'token': token,
}, headers=self.geo_verification_headers())
return self._parse_video(video, video_id)
diff --git a/yt_dlp/extractor/skylinewebcams.py b/yt_dlp/extractor/skylinewebcams.py
index 197407c18..102a2f9b2 100644
--- a/yt_dlp/extractor/skylinewebcams.py
+++ b/yt_dlp/extractor/skylinewebcams.py
@@ -15,7 +15,7 @@ class SkylineWebcamsIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/skynewsarabia.py b/yt_dlp/extractor/skynewsarabia.py
index 234703cf7..7ac24437f 100644
--- a/yt_dlp/extractor/skynewsarabia.py
+++ b/yt_dlp/extractor/skynewsarabia.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
parse_duration,
parse_iso8601,
@@ -10,7 +9,7 @@ class SkyNewsArabiaBaseIE(InfoExtractor):
_IMAGE_BASE_URL = 'http://www.skynewsarabia.com/web/images'
def _call_api(self, path, value):
- return self._download_json('http://api.skynewsarabia.com/web/rest/v2/%s/%s.json' % (path, value), value)
+ return self._download_json(f'http://api.skynewsarabia.com/web/rest/v2/{path}/{value}.json', value)
def _get_limelight_media_id(self, url):
return self._search_regex(r'/media/[^/]+/([a-z0-9]{32})', url, 'limelight media id')
@@ -19,11 +18,11 @@ class SkyNewsArabiaBaseIE(InfoExtractor):
return self._IMAGE_BASE_URL + image_path_template.format(width=width, height=height)
def _extract_video_info(self, video_data):
- video_id = compat_str(video_data['id'])
+ video_id = str(video_data['id'])
topic = video_data.get('topicTitle')
return {
'_type': 'url_transparent',
- 'url': 'limelight:media:%s' % self._get_limelight_media_id(video_data['videoUrl'][0]['url']),
+ 'url': 'limelight:media:{}'.format(self._get_limelight_media_id(video_data['videoUrl'][0]['url'])),
'id': video_id,
'title': video_data['headline'],
'description': video_data.get('summary'),
@@ -32,7 +31,7 @@ class SkyNewsArabiaBaseIE(InfoExtractor):
'duration': parse_duration(video_data.get('runTime')),
'tags': video_data.get('tags', []),
'categories': [topic] if topic else [],
- 'webpage_url': 'http://www.skynewsarabia.com/web/video/%s' % video_id,
+ 'webpage_url': f'http://www.skynewsarabia.com/web/video/{video_id}',
'ie_key': 'LimelightMedia',
}
@@ -101,7 +100,7 @@ class SkyNewsArabiaArticleIE(SkyNewsArabiaBaseIE):
topic = article_data.get('topicTitle')
return {
'_type': 'url_transparent',
- 'url': 'limelight:media:%s' % self._get_limelight_media_id(media_asset['videoUrl'][0]['url']),
+ 'url': 'limelight:media:{}'.format(self._get_limelight_media_id(media_asset['videoUrl'][0]['url'])),
'id': article_id,
'title': article_data['headline'],
'description': article_data.get('summary'),
diff --git a/yt_dlp/extractor/skynewsau.py b/yt_dlp/extractor/skynewsau.py
index 43a9c82cf..617738a7c 100644
--- a/yt_dlp/extractor/skynewsau.py
+++ b/yt_dlp/extractor/skynewsau.py
@@ -22,21 +22,21 @@ class SkyNewsAUIE(InfoExtractor):
'tags': ['fblink', 'msn', 'usa', 'world', 'yt'],
'upload_date': '20211015',
},
- 'params': {'skip_download': True, 'format': 'bv'}
+ 'params': {'skip_download': True, 'format': 'bv'},
}]
_API_KEY = '6krsj3w249nk779d8fukqx9f'
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
embedcode = self._search_regex(r'embedcode\s?=\s?\"([^\"]+)\"', webpage, 'embedcode')
data_json = self._download_json(
- f'https://content.api.news/v3/videos/brightcove/{embedcode}?api_key={self._API_KEY}', id)['content']
+ f'https://content.api.news/v3/videos/brightcove/{embedcode}?api_key={self._API_KEY}', video_id)['content']
return {
- 'id': id,
+ 'id': video_id,
'_type': 'url_transparent',
- 'url': 'https://players.brightcove.net/%s/default_default/index.html?videoId=%s' % tuple(embedcode.split('-')),
+ 'url': 'https://players.brightcove.net/{}/default_default/index.html?videoId={}'.format(*tuple(embedcode.split('-'))),
'ie_key': 'BrightcoveNew',
'title': data_json.get('caption'),
'upload_date': unified_strdate(try_get(data_json, lambda x: x['date']['created'])),
diff --git a/yt_dlp/extractor/slideshare.py b/yt_dlp/extractor/slideshare.py
index ab9dad0ec..302b6e15c 100644
--- a/yt_dlp/extractor/slideshare.py
+++ b/yt_dlp/extractor/slideshare.py
@@ -1,9 +1,7 @@
import json
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
get_element_by_id,
@@ -32,12 +30,12 @@ class SlideshareIE(InfoExtractor):
webpage, 'slideshare object')
info = json.loads(slideshare_obj)
if info['slideshow']['type'] != 'video':
- raise ExtractorError('Webpage type is "%s": only video extraction is supported for Slideshare' % info['slideshow']['type'], expected=True)
+ raise ExtractorError('Webpage type is "{}": only video extraction is supported for Slideshare'.format(info['slideshow']['type']), expected=True)
doc = info['doc']
bucket = info['jsplayer']['video_bucket']
ext = info['jsplayer']['video_extension']
- video_url = compat_urlparse.urljoin(bucket, doc + '-SD.' + ext)
+ video_url = urllib.parse.urljoin(bucket, doc + '-SD.' + ext)
description = get_element_by_id('slideshow-description-paragraph', webpage) or self._html_search_regex(
r'(?s)<p[^>]+itemprop="description"[^>]*>(.+?)</p>', webpage,
'description', fatal=False)
diff --git a/yt_dlp/extractor/slideslive.py b/yt_dlp/extractor/slideslive.py
index a1328dee2..e684ac7b8 100644
--- a/yt_dlp/extractor/slideslive.py
+++ b/yt_dlp/extractor/slideslive.py
@@ -532,7 +532,7 @@ class SlidesLiveIE(InfoExtractor):
}, note='Downloading video slides info', errnote='Failed to download video slides info') or {}
for slide_id, slide in enumerate(traverse_obj(slides, ('slides', ...)), 1):
- if not traverse_obj(slide, ('video', 'service')) == 'yoda':
+ if traverse_obj(slide, ('video', 'service')) != 'yoda':
continue
video_path = traverse_obj(slide, ('video', 'id'))
cdn_hostname = traverse_obj(service_data, (
diff --git a/yt_dlp/extractor/slutload.py b/yt_dlp/extractor/slutload.py
index 8e6e89c9a..c04898b1e 100644
--- a/yt_dlp/extractor/slutload.py
+++ b/yt_dlp/extractor/slutload.py
@@ -11,7 +11,7 @@ class SlutloadIE(InfoExtractor):
'ext': 'mp4',
'title': 'virginie baisee en cam',
'age_limit': 18,
- 'thumbnail': r're:https?://.*?\.jpg'
+ 'thumbnail': r're:https?://.*?\.jpg',
},
}, {
# mobile site
@@ -29,14 +29,14 @@ class SlutloadIE(InfoExtractor):
video_id = self._match_id(url)
embed_page = self._download_webpage(
- 'http://www.slutload.com/embed_player/%s' % video_id, video_id,
+ f'http://www.slutload.com/embed_player/{video_id}', video_id,
'Downloading embed page', fatal=False)
if embed_page:
def extract(what):
return self._html_search_regex(
- r'data-video-%s=(["\'])(?P<url>(?:(?!\1).)+)\1' % what,
- embed_page, 'video %s' % what, default=None, group='url')
+ rf'data-video-{what}=(["\'])(?P<url>(?:(?!\1).)+)\1',
+ embed_page, f'video {what}', default=None, group='url')
video_url = extract('url')
if video_url:
@@ -47,11 +47,11 @@ class SlutloadIE(InfoExtractor):
'url': video_url,
'title': title,
'thumbnail': extract('preview'),
- 'age_limit': 18
+ 'age_limit': 18,
}
webpage = self._download_webpage(
- 'http://www.slutload.com/video/_/%s/' % video_id, video_id)
+ f'http://www.slutload.com/video/_/{video_id}/', video_id)
title = self._html_search_regex(
r'<h1><strong>([^<]+)</strong>', webpage, 'title').strip()
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
diff --git a/yt_dlp/extractor/snotr.py b/yt_dlp/extractor/snotr.py
index 6889f1929..859e5e837 100644
--- a/yt_dlp/extractor/snotr.py
+++ b/yt_dlp/extractor/snotr.py
@@ -30,7 +30,7 @@ class SnotrIE(InfoExtractor):
'filesize_approx': 8500000,
'description': 'The top 10 George W. Bush moments, brought to you by David Letterman!',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/sohu.py b/yt_dlp/extractor/sohu.py
index a41ad303a..23faee1f1 100644
--- a/yt_dlp/extractor/sohu.py
+++ b/yt_dlp/extractor/sohu.py
@@ -1,11 +1,8 @@
import base64
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_urlencode,
-)
from ..utils import (
ExtractorError,
float_or_none,
@@ -51,7 +48,7 @@ class SohuIE(InfoExtractor):
'upload_date': '20150305',
'thumbnail': 'http://e3f49eaa46b57.cdn.sohucs.com//group1/M10/83/FA/MTAuMTAuODguODA=/6_14cbccdde5eg104SysCutcloud_78693464_7_0b.jpg',
'tags': ['爱范儿', '爱范品', 'MWC', '手机'],
- }
+ },
}, {
'note': 'Multipart video',
'url': 'http://my.tv.sohu.com/pl/8384802/78910339.shtml',
@@ -71,22 +68,22 @@ class SohuIE(InfoExtractor):
'ext': 'mp4',
'duration': 294,
'title': '【神探苍实战秘籍】第13期 战争之影 赫卡里姆',
- }
+ },
}, {
'info_dict': {
'id': '78910339_part2',
'ext': 'mp4',
'duration': 300,
'title': '【神探苍实战秘籍】第13期 战争之影 赫卡里姆',
- }
+ },
}, {
'info_dict': {
'id': '78910339_part3',
'ext': 'mp4',
'duration': 150,
'title': '【神探苍实战秘籍】第13期 战争之影 赫卡里姆',
- }
- }]
+ },
+ }],
}, {
'note': 'Video with title containing dash',
'url': 'http://my.tv.sohu.com/us/249884221/78932792.shtml',
@@ -101,8 +98,8 @@ class SohuIE(InfoExtractor):
'tags': [],
},
'params': {
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}]
def _real_extract(self, url):
@@ -115,7 +112,7 @@ class SohuIE(InfoExtractor):
return self._download_json(
base_data_url + vid_id, video_id,
- 'Downloading JSON data for %s' % vid_id,
+ f'Downloading JSON data for {vid_id}',
headers=self.geo_verification_headers())
mobj = self._match_valid_url(url)
@@ -133,18 +130,18 @@ class SohuIE(InfoExtractor):
if vid_data['play'] != 1:
if vid_data.get('status') == 12:
raise ExtractorError(
- '%s said: There\'s something wrong in the video.' % self.IE_NAME,
+ f'{self.IE_NAME} said: There\'s something wrong in the video.',
expected=True)
else:
self.raise_geo_restricted(
- '%s said: The video is only licensed to users in Mainland China.' % self.IE_NAME)
+ f'{self.IE_NAME} said: The video is only licensed to users in Mainland China.')
formats_json = {}
for format_id in ('nor', 'high', 'super', 'ori', 'h2644k', 'h2654k'):
- vid_id = vid_data['data'].get('%sVid' % format_id)
+ vid_id = vid_data['data'].get(f'{format_id}Vid')
if not vid_id:
continue
- vid_id = compat_str(vid_id)
+ vid_id = str(vid_id)
formats_json[format_id] = vid_data if vid == vid_id else _fetch_data(vid_id, mytv)
part_count = vid_data['data']['totalBlocks']
@@ -162,7 +159,7 @@ class SohuIE(InfoExtractor):
su = data['su']
video_url = 'newflv.sohu.ccgslb.net'
- cdnId = None
+ cdn_id = None
retries = 0
while 'newflv.sohu.ccgslb.net' in video_url:
@@ -174,20 +171,19 @@ class SohuIE(InfoExtractor):
'rb': 1,
}
- if cdnId is not None:
- params['idc'] = cdnId
+ if cdn_id is not None:
+ params['idc'] = cdn_id
- download_note = 'Downloading %s video URL part %d of %d' % (
- format_id, i + 1, part_count)
+ download_note = f'Downloading {format_id} video URL part {i + 1} of {part_count}'
if retries > 0:
- download_note += ' (retry #%d)' % retries
+ download_note += f' (retry #{retries})'
part_info = self._parse_json(self._download_webpage(
- 'http://%s/?%s' % (allot, compat_urllib_parse_urlencode(params)),
+ f'http://{allot}/?{urllib.parse.urlencode(params)}',
video_id, download_note), video_id)
video_url = part_info['url']
- cdnId = part_info.get('nid')
+ cdn_id = part_info.get('nid')
retries += 1
if retries > 5:
@@ -204,7 +200,7 @@ class SohuIE(InfoExtractor):
})
playlist.append({
- 'id': '%s_part%d' % (video_id, i + 1),
+ 'id': f'{video_id}_part{i + 1}',
'title': title,
'duration': vid_data['data']['clipsDuration'][i],
'formats': formats,
@@ -269,7 +265,7 @@ class SohuVIE(InfoExtractor):
'upload_date': '20150305',
'thumbnail': 'http://e3f49eaa46b57.cdn.sohucs.com//group1/M10/83/FA/MTAuMTAuODguODA=/6_14cbccdde5eg104SysCutcloud_78693464_7_0b.jpg',
'tags': ['爱范儿', '爱范品', 'MWC', '手机'],
- }
+ },
}, {
'note': 'Multipart video',
'url': 'https://tv.sohu.com/v/dXMvMjQyNTYyMTYzLzc4OTEwMzM5LnNodG1s.html?src=pl',
diff --git a/yt_dlp/extractor/sonyliv.py b/yt_dlp/extractor/sonyliv.py
index 7c914acbe..a0a051e97 100644
--- a/yt_dlp/extractor/sonyliv.py
+++ b/yt_dlp/extractor/sonyliv.py
@@ -73,7 +73,7 @@ class SonyLIVIE(InfoExtractor):
if c == 'x':
t[i] = str(n)
elif c == 'y':
- t[i] = '{:x}'.format(3 & n | 8)
+ t[i] = f'{3 & n | 8:x}'
return ''.join(t) + '-' + str(int(time.time() * 1000))
def _perform_login(self, username, password):
@@ -121,7 +121,7 @@ class SonyLIVIE(InfoExtractor):
def _call_api(self, version, path, video_id):
try:
return self._download_json(
- 'https://apiv2.sonyliv.com/AGL/%s/A/ENG/WEB/%s' % (version, path),
+ f'https://apiv2.sonyliv.com/AGL/{version}/A/ENG/WEB/{path}',
video_id, headers=self._HEADERS)['resultObj']
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 406 and self._parse_json(
@@ -146,7 +146,7 @@ class SonyLIVIE(InfoExtractor):
self.report_drm(video_id)
dash_url = content['videoURL']
headers = {
- 'x-playback-session-id': '%s-%d' % (uuid.uuid4().hex, time.time() * 1000)
+ 'x-playback-session-id': '%s-%d' % (uuid.uuid4().hex, time.time() * 1000),
}
formats = self._extract_mpd_formats(
dash_url, video_id, mpd_id='dash', headers=headers, fatal=False)
diff --git a/yt_dlp/extractor/soundcloud.py b/yt_dlp/extractor/soundcloud.py
index 358146171..0f7368435 100644
--- a/yt_dlp/extractor/soundcloud.py
+++ b/yt_dlp/extractor/soundcloud.py
@@ -4,13 +4,11 @@ import json
import re
from .common import InfoExtractor, SearchInfoExtractor
-from ..compat import compat_str
from ..networking import HEADRequest
from ..networking.exceptions import HTTPError
from ..utils import (
KNOWN_EXTENSIONS,
ExtractorError,
- error_to_compat_str,
float_or_none,
int_or_none,
join_nonempty,
@@ -113,7 +111,7 @@ class SoundcloudBaseIE(InfoExtractor):
self._update_client_id()
continue
elif non_fatal:
- self.report_warning(error_to_compat_str(e))
+ self.report_warning(str(e))
return False
raise
@@ -196,24 +194,20 @@ class SoundcloudBaseIE(InfoExtractor):
t = clid # _CLIENT_ID
d = '-'.join([str(mInt) for mInt in [a, i, s, w, u, l, b, k]])
- p = n + y + d + r + e + t + d + n
- h = p
+ h = n + y + d + r + e + t + d + n
m = 8011470
- f = 0
- for f in range(f, len(h)):
+ for f in range(len(h)):
m = (m >> 1) + ((1 & m) << 23)
m += ord(h[f])
m &= 16777215
# c is not even needed
- out = str(y) + ':' + str(d) + ':' + format(m, 'x') + ':' + str(c)
-
- return out
+ return f'{y}:{d}:{m:x}:{c}'
def _extract_info_dict(self, info, full_title=None, secret_token=None, extract_flat=False):
- track_id = compat_str(info['id'])
+ track_id = str(info['id'])
title = info['title']
format_urls = set()
@@ -342,12 +336,12 @@ class SoundcloudBaseIE(InfoExtractor):
thumbnails = []
artwork_url = info.get('artwork_url')
thumbnail = artwork_url or user.get('avatar_url')
- if isinstance(thumbnail, compat_str):
+ if isinstance(thumbnail, str):
if re.search(self._IMAGE_REPL_RE, thumbnail):
for image_id, size in self._ARTWORK_MAP.items():
i = {
'id': image_id,
- 'url': re.sub(self._IMAGE_REPL_RE, '-%s.jpg' % image_id, thumbnail),
+ 'url': re.sub(self._IMAGE_REPL_RE, f'-{image_id}.jpg', thumbnail),
}
if image_id == 'tiny' and not artwork_url:
size = 18
@@ -363,7 +357,7 @@ class SoundcloudBaseIE(InfoExtractor):
thumbnails = [{'url': thumbnail}]
def extract_count(key):
- return int_or_none(info.get('%s_count' % key))
+ return int_or_none(info.get(f'{key}_count'))
return {
'id': track_id,
@@ -382,7 +376,7 @@ class SoundcloudBaseIE(InfoExtractor):
'comment_count': extract_count('comment'),
'repost_count': extract_count('reposts'),
'genres': traverse_obj(info, ('genre', {str}, {lambda x: x or None}, all)),
- 'formats': formats if not extract_flat else None
+ 'formats': formats if not extract_flat else None,
}
@classmethod
@@ -434,7 +428,7 @@ class SoundcloudIE(SoundcloudBaseIE):
'thumbnail': 'https://i1.sndcdn.com/artworks-000031955188-rwb18x-original.jpg',
'uploader_url': 'https://soundcloud.com/ethmusic',
'genres': [],
- }
+ },
},
# geo-restricted
{
@@ -467,7 +461,7 @@ class SoundcloudIE(SoundcloudBaseIE):
'id': '123998367',
'ext': 'mp3',
'title': 'Youtube - Dl Test Video \'\' Ä↭',
- 'description': 'test chars: \"\'/\\ä↭',
+ 'description': 'test chars: "\'/\\ä↭',
'uploader': 'jaimeMF',
'uploader_id': '69767071',
'timestamp': 1386604920,
@@ -491,7 +485,7 @@ class SoundcloudIE(SoundcloudBaseIE):
'id': '123998367',
'ext': 'mp3',
'title': 'Youtube - Dl Test Video \'\' Ä↭',
- 'description': 'test chars: \"\'/\\ä↭',
+ 'description': 'test chars: "\'/\\ä↭',
'uploader': 'jaimeMF',
'uploader_id': '69767071',
'timestamp': 1386604920,
@@ -630,10 +624,10 @@ class SoundcloudIE(SoundcloudBaseIE):
if token:
query['secret_token'] = token
else:
- full_title = resolve_title = '%s/%s' % mobj.group('uploader', 'title')
+ full_title = resolve_title = '{}/{}'.format(*mobj.group('uploader', 'title'))
token = mobj.group('token')
if token:
- resolve_title += '/%s' % token
+ resolve_title += f'/{token}'
info_json_url = self._resolv_url(self._BASE_URL + resolve_title)
info = self._download_json(
@@ -644,13 +638,13 @@ class SoundcloudIE(SoundcloudBaseIE):
class SoundcloudPlaylistBaseIE(SoundcloudBaseIE):
def _extract_set(self, playlist, token=None):
- playlist_id = compat_str(playlist['id'])
+ playlist_id = str(playlist['id'])
tracks = playlist.get('tracks') or []
- if not all([t.get('permalink_url') for t in tracks]) and token:
+ if not all(t.get('permalink_url') for t in tracks) and token:
tracks = self._download_json(
self._API_V2_BASE + 'tracks', playlist_id,
'Downloading tracks', query={
- 'ids': ','.join([compat_str(t['id']) for t in tracks]),
+ 'ids': ','.join([str(t['id']) for t in tracks]),
'playlistId': playlist_id,
'playlistSecretToken': token,
}, headers=self._HEADERS)
@@ -700,7 +694,7 @@ class SoundcloudSetIE(SoundcloudPlaylistBaseIE):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
- full_title = '%s/sets/%s' % mobj.group('uploader', 'slug_title')
+ full_title = '{}/sets/{}'.format(*mobj.group('uploader', 'slug_title'))
token = mobj.group('token')
if token:
full_title += '/' + token
@@ -709,8 +703,8 @@ class SoundcloudSetIE(SoundcloudPlaylistBaseIE):
self._BASE_URL + full_title), full_title, headers=self._HEADERS)
if 'errors' in info:
- msgs = (compat_str(err['error_message']) for err in info['errors'])
- raise ExtractorError('unable to download video webpage: %s' % ','.join(msgs))
+ msgs = (str(err['error_message']) for err in info['errors'])
+ raise ExtractorError('unable to download video webpage: {}'.format(','.join(msgs)))
return self._extract_set(info, token)
@@ -853,7 +847,7 @@ class SoundcloudUserIE(SoundcloudPagedPlaylistBaseIE):
return self._extract_playlist(
self._API_V2_BASE + self._BASE_URL_MAP[resource] % user['id'],
str_or_none(user.get('id')),
- '%s (%s)' % (user['username'], resource.capitalize()))
+ '{} ({})'.format(user['username'], resource.capitalize()))
class SoundcloudUserPermalinkIE(SoundcloudPagedPlaylistBaseIE):
@@ -897,8 +891,8 @@ class SoundcloudTrackStationIE(SoundcloudPagedPlaylistBaseIE):
r'soundcloud:track-stations:(\d+)', track['id'], 'track id')
return self._extract_playlist(
- self._API_V2_BASE + 'stations/%s/tracks' % track['id'],
- track_id, 'Track station: %s' % track['title'])
+ self._API_V2_BASE + 'stations/{}/tracks'.format(track['id']),
+ track_id, 'Track station: {}'.format(track['title']))
class SoundcloudRelatedIE(SoundcloudPagedPlaylistBaseIE):
@@ -946,7 +940,7 @@ class SoundcloudRelatedIE(SoundcloudPagedPlaylistBaseIE):
return self._extract_playlist(
self._API_V2_BASE + self._BASE_URL_MAP[relation] % track['id'], str(track['id']),
- '%s (%s)' % (track.get('title') or slug, relation.capitalize()))
+ '{} ({})'.format(track.get('title') or slug, relation.capitalize()))
class SoundcloudPlaylistIE(SoundcloudPlaylistBaseIE):
diff --git a/yt_dlp/extractor/soundgasm.py b/yt_dlp/extractor/soundgasm.py
index 9e59c7c0e..b524e245d 100644
--- a/yt_dlp/extractor/soundgasm.py
+++ b/yt_dlp/extractor/soundgasm.py
@@ -15,7 +15,7 @@ class SoundgasmIE(InfoExtractor):
'title': 'Piano sample',
'description': 'Royalty Free Sample Music',
'uploader': 'ytdl',
- }
+ },
}
def _real_extract(self, url):
@@ -69,6 +69,6 @@ class SoundgasmProfileIE(InfoExtractor):
entries = [
self.url_result(audio_url, 'Soundgasm')
- for audio_url in re.findall(r'href="([^"]+/u/%s/[^"]+)' % profile_id, webpage)]
+ for audio_url in re.findall(rf'href="([^"]+/u/{profile_id}/[^"]+)', webpage)]
return self.playlist_result(entries, profile_id)
diff --git a/yt_dlp/extractor/southpark.py b/yt_dlp/extractor/southpark.py
index e23f192a1..3d661a86a 100644
--- a/yt_dlp/extractor/southpark.py
+++ b/yt_dlp/extractor/southpark.py
@@ -87,7 +87,7 @@ class SouthParkDeIE(SouthParkIE): # XXX: Do not subclass from concrete IE
'id': 'e99d45ea-ed00-11e0-aca6-0026b9414f30',
'ext': 'mp4',
'title': 'Zahnfee Cartman',
- 'description': 'md5:b917eec991d388811d911fd1377671ac'
+ 'description': 'md5:b917eec991d388811d911fd1377671ac',
},
}, {
# episode
@@ -102,7 +102,7 @@ class SouthParkDeIE(SouthParkIE): # XXX: Do not subclass from concrete IE
def _get_feed_url(self, uri, url=None):
video_id = self._id_from_uri(uri)
config = self._download_json(
- 'http://media.mtvnservices.com/pmt/e1/access/index.html?uri=%s&configtype=edge&ref=%s' % (uri, url), video_id)
+ f'http://media.mtvnservices.com/pmt/e1/access/index.html?uri={uri}&configtype=edge&ref={url}', video_id)
return self._remove_template_parameter(config['feedWithQueryParams'])
def _get_feed_query(self, uri):
diff --git a/yt_dlp/extractor/spankbang.py b/yt_dlp/extractor/spankbang.py
index c73f7971d..6805a72de 100644
--- a/yt_dlp/extractor/spankbang.py
+++ b/yt_dlp/extractor/spankbang.py
@@ -37,7 +37,7 @@ class SpankBangIE(InfoExtractor):
'timestamp': 1617109572,
'upload_date': '20210330',
'age_limit': 18,
- }
+ },
}, {
# 480p only
'url': 'http://spankbang.com/1vt0/video/solvane+gangbang',
@@ -72,12 +72,12 @@ class SpankBangIE(InfoExtractor):
mobj = self._match_valid_url(url)
video_id = mobj.group('id') or mobj.group('id_2')
webpage = self._download_webpage(
- url.replace('/%s/embed' % video_id, '/%s/video' % video_id),
+ url.replace(f'/{video_id}/embed', f'/{video_id}/video'),
video_id, headers={'Cookie': 'country=US'})
if re.search(r'<[^>]+\b(?:id|class)=["\']video_removed', webpage):
raise ExtractorError(
- 'Video %s is not available' % video_id, expected=True)
+ f'Video {video_id} is not available', expected=True)
formats = []
@@ -104,8 +104,7 @@ class SpankBangIE(InfoExtractor):
STREAM_URL_PREFIX = 'stream_url_'
for mobj in re.finditer(
- r'%s(?P<id>[^\s=]+)\s*=\s*(["\'])(?P<url>(?:(?!\2).)+)\2'
- % STREAM_URL_PREFIX, webpage):
+ rf'{STREAM_URL_PREFIX}(?P<id>[^\s=]+)\s*=\s*(["\'])(?P<url>(?:(?!\2).)+)\2', webpage):
extract_format(mobj.group('id', 'url'))
if not formats:
@@ -159,7 +158,7 @@ class SpankBangIE(InfoExtractor):
'view_count': view_count,
'formats': formats,
'age_limit': age_limit,
- }, info
+ }, info,
)
diff --git a/yt_dlp/extractor/spiegel.py b/yt_dlp/extractor/spiegel.py
index 3701e295a..0397f83fe 100644
--- a/yt_dlp/extractor/spiegel.py
+++ b/yt_dlp/extractor/spiegel.py
@@ -4,7 +4,7 @@ from .jwplatform import JWPlatformIE
class SpiegelIE(InfoExtractor):
_UUID_RE = r'[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}'
- _VALID_URL = r'https?://(?:www\.)?(?:spiegel|manager-magazin)\.de(?:/[^/]+)+/[^/]*-(?P<id>[0-9]+|%s)(?:-embed|-iframe)?(?:\.html)?(?:$|[#?])' % _UUID_RE
+ _VALID_URL = rf'https?://(?:www\.)?(?:spiegel|manager-magazin)\.de(?:/[^/]+)+/[^/]*-(?P<id>[0-9]+|{_UUID_RE})(?:-embed|-iframe)?(?:\.html)?(?:$|[#?])'
_TESTS = [{
'url': 'http://www.spiegel.de/video/vulkan-tungurahua-in-ecuador-ist-wieder-aktiv-video-1259285.html',
'md5': '50c7948883ec85a3e431a0a44b7ad1d6',
@@ -45,7 +45,7 @@ class SpiegelIE(InfoExtractor):
'_type': 'url_transparent',
'id': video_id,
'display_id': video_id,
- 'url': 'jwplatform:%s' % media_id,
+ 'url': f'jwplatform:{media_id}',
'title': self._og_search_title(webpage, default=None),
'ie_key': JWPlatformIE.ie_key(),
}
diff --git a/yt_dlp/extractor/sport5.py b/yt_dlp/extractor/sport5.py
index 44b4067de..6c45c3ec4 100644
--- a/yt_dlp/extractor/sport5.py
+++ b/yt_dlp/extractor/sport5.py
@@ -27,7 +27,7 @@ class Sport5IE(InfoExtractor):
'categories': list,
},
'skip': 'Blocked outside of Israel',
- }
+ },
]
def _real_extract(self, url):
@@ -39,13 +39,13 @@ class Sport5IE(InfoExtractor):
video_id = self._html_search_regex(r'clipId=([\w-]+)', webpage, 'video id')
metadata = self._download_xml(
- 'http://sport5-metadata-rr-d.nsacdn.com/vod/vod/%s/HDS/metadata.xml' % video_id,
+ f'http://sport5-metadata-rr-d.nsacdn.com/vod/vod/{video_id}/HDS/metadata.xml',
video_id)
error = metadata.find('./Error')
if error is not None:
raise ExtractorError(
- '%s returned error: %s - %s' % (
+ '{} returned error: {} - {}'.format(
self.IE_NAME,
error.find('./Name').text,
error.find('./Description').text),
diff --git a/yt_dlp/extractor/sportdeutschland.py b/yt_dlp/extractor/sportdeutschland.py
index 30dbcf370..2d6acb876 100644
--- a/yt_dlp/extractor/sportdeutschland.py
+++ b/yt_dlp/extractor/sportdeutschland.py
@@ -24,7 +24,7 @@ class SportDeutschlandIE(InfoExtractor):
'duration': 32447,
'upload_date': '20230114',
'timestamp': 1673733618,
- }
+ },
}, {
'url': 'https://sportdeutschland.tv/deutscherbadmintonverband/bwf-tour-1-runde-feld-1-yonex-gainward-german-open-2022-0',
'info_dict': {
@@ -40,7 +40,7 @@ class SportDeutschlandIE(InfoExtractor):
'duration': 41097,
'upload_date': '20220309',
'timestamp': 1646860727.0,
- }
+ },
}, {
'url': 'https://sportdeutschland.tv/ggcbremen/formationswochenende-latein-2023',
'info_dict': {
@@ -66,8 +66,8 @@ class SportDeutschlandIE(InfoExtractor):
'upload_date': '20230225',
'timestamp': 1677349909,
'live_status': 'was_live',
- }
- }]
+ },
+ }],
}, {
'url': 'https://sportdeutschland.tv/dtb/gymnastik-international-tag-1',
'info_dict': {
@@ -99,7 +99,7 @@ class SportDeutschlandIE(InfoExtractor):
**traverse_obj(video, {
'id': 'id',
'duration': ('duration', {lambda x: float(x) > 0 and float(x)}),
- 'timestamp': ('created_at', {unified_timestamp})
+ 'timestamp': ('created_at', {unified_timestamp}),
}),
}
@@ -120,7 +120,7 @@ class SportDeutschlandIE(InfoExtractor):
'is_live': 'currently_live',
'was_live': 'was_live',
'channel_url': ('profile', 'slug', {lambda x: f'https://sportdeutschland.tv/{x}'}),
- }, get_all=False)
+ }, get_all=False),
}
parts = traverse_obj(meta, (('livestream', ('videos', ...)), ))
diff --git a/yt_dlp/extractor/spotify.py b/yt_dlp/extractor/spotify.py
index 55ce36aea..de67a6114 100644
--- a/yt_dlp/extractor/spotify.py
+++ b/yt_dlp/extractor/spotify.py
@@ -39,7 +39,7 @@ class SpotifyBaseIE(InfoExtractor):
'persistedQuery': {
'sha256Hash': self._OPERATION_HASHES[operation],
},
- })
+ }),
}, headers={'authorization': 'Bearer ' + self._ACCESS_TOKEN},
**kwargs)['data']
@@ -115,7 +115,7 @@ class SpotifyIE(SpotifyBaseIE):
'duration': 2083.605,
'release_date': '20201217',
'series': "The Guardian's Audio Long Reads",
- }
+ },
}, {
'url': 'https://open.spotify.com/embed/episode/4TvCsKKs2thXmarHigWvXE?si=7eatS8AbQb6RxqO2raIuWA',
'only_matching': True,
@@ -124,7 +124,7 @@ class SpotifyIE(SpotifyBaseIE):
def _real_extract(self, url):
episode_id = self._match_id(url)
episode = self._call_api('Episode', episode_id, {
- 'uri': 'spotify:episode:' + episode_id
+ 'uri': 'spotify:episode:' + episode_id,
})['episode']
return self._extract_episode(
episode, try_get(episode, lambda x: x['podcast']['name']))
diff --git a/yt_dlp/extractor/spreaker.py b/yt_dlp/extractor/spreaker.py
index 36a9bd291..d1df45969 100644
--- a/yt_dlp/extractor/spreaker.py
+++ b/yt_dlp/extractor/spreaker.py
@@ -1,7 +1,6 @@
import itertools
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
float_or_none,
int_or_none,
@@ -16,26 +15,26 @@ def _extract_episode(data, episode_id=None):
title = data['title']
download_url = data['download_url']
- series = try_get(data, lambda x: x['show']['title'], compat_str)
- uploader = try_get(data, lambda x: x['author']['fullname'], compat_str)
+ series = try_get(data, lambda x: x['show']['title'], str)
+ uploader = try_get(data, lambda x: x['author']['fullname'], str)
thumbnails = []
for image in ('image_original', 'image_medium', 'image'):
- image_url = url_or_none(data.get('%s_url' % image))
+ image_url = url_or_none(data.get(f'{image}_url'))
if image_url:
thumbnails.append({'url': image_url})
def stats(key):
return int_or_none(try_get(
data,
- (lambda x: x['%ss_count' % key],
- lambda x: x['stats']['%ss' % key])))
+ (lambda x: x[f'{key}s_count'],
+ lambda x: x['stats'][f'{key}s'])))
def duration(key):
return float_or_none(data.get(key), scale=1000)
return {
- 'id': compat_str(episode_id or data['episode_id']),
+ 'id': str(episode_id or data['episode_id']),
'url': download_url,
'display_id': data.get('permalink'),
'title': title,
@@ -97,7 +96,7 @@ class SpreakerIE(InfoExtractor):
def _real_extract(self, url):
episode_id = self._match_id(url)
data = self._download_json(
- 'https://api.spreaker.com/v2/episodes/%s' % episode_id,
+ f'https://api.spreaker.com/v2/episodes/{episode_id}',
episode_id)['response']['episode']
return _extract_episode(data, episode_id)
@@ -116,7 +115,7 @@ class SpreakerPageIE(InfoExtractor):
(r'data-episode_id=["\'](?P<id>\d+)',
r'episode_id\s*:\s*(?P<id>\d+)'), webpage, 'episode id')
return self.url_result(
- 'https://api.spreaker.com/episode/%s' % episode_id,
+ f'https://api.spreaker.com/episode/{episode_id}',
ie=SpreakerIE.ie_key(), video_id=episode_id)
@@ -133,8 +132,8 @@ class SpreakerShowIE(InfoExtractor):
def _entries(self, show_id):
for page_num in itertools.count(1):
episodes = self._download_json(
- 'https://api.spreaker.com/show/%s/episodes' % show_id,
- show_id, note='Downloading JSON page %d' % page_num, query={
+ f'https://api.spreaker.com/show/{show_id}/episodes',
+ show_id, note=f'Downloading JSON page {page_num}', query={
'page': page_num,
'max_per_page': 100,
})
@@ -169,5 +168,5 @@ class SpreakerShowPageIE(InfoExtractor):
show_id = self._search_regex(
r'show_id\s*:\s*(?P<id>\d+)', webpage, 'show id')
return self.url_result(
- 'https://api.spreaker.com/show/%s' % show_id,
+ f'https://api.spreaker.com/show/{show_id}',
ie=SpreakerShowIE.ie_key(), video_id=show_id)
diff --git a/yt_dlp/extractor/springboardplatform.py b/yt_dlp/extractor/springboardplatform.py
index bdb8ef496..cd3261d55 100644
--- a/yt_dlp/extractor/springboardplatform.py
+++ b/yt_dlp/extractor/springboardplatform.py
@@ -52,8 +52,7 @@ class SpringboardPlatformIE(InfoExtractor):
index = mobj.group('index') or mobj.group('index_2')
video = self._download_xml(
- 'http://cms.springboardplatform.com/xml_feeds_advanced/index/%s/rss3/%s'
- % (index, video_id), video_id)
+ f'http://cms.springboardplatform.com/xml_feeds_advanced/index/{index}/rss3/{video_id}', video_id)
item = xpath_element(video, './/item', 'item', fatal=True)
@@ -66,7 +65,7 @@ class SpringboardPlatformIE(InfoExtractor):
if 'error_video.mp4' in video_url:
raise ExtractorError(
- 'Video %s no longer exists' % video_id, expected=True)
+ f'Video {video_id} no longer exists', expected=True)
duration = int_or_none(content.get('duration'))
tbr = int_or_none(content.get('bitrate'))
diff --git a/yt_dlp/extractor/srgssr.py b/yt_dlp/extractor/srgssr.py
index 145f25e9f..c01fd12cc 100644
--- a/yt_dlp/extractor/srgssr.py
+++ b/yt_dlp/extractor/srgssr.py
@@ -48,7 +48,7 @@ class SRGSSRIE(InfoExtractor):
def _get_tokenized_src(self, url, video_id, format_id):
token = self._download_json(
'http://tp.srgssr.ch/akahd/token?acl=*',
- video_id, 'Downloading %s token' % format_id, fatal=False) or {}
+ video_id, f'Downloading {format_id} token', fatal=False) or {}
auth_params = try_get(token, lambda x: x['token']['authparams'])
if auth_params:
url += ('?' if '?' not in url else '&') + auth_params
@@ -57,8 +57,7 @@ class SRGSSRIE(InfoExtractor):
def _get_media_data(self, bu, media_type, media_id):
query = {'onlyChapters': True} if media_type == 'video' else {}
full_media_data = self._download_json(
- 'https://il.srgssr.ch/integrationlayer/2.0/%s/mediaComposition/%s/%s.json'
- % (bu, media_type, media_id),
+ f'https://il.srgssr.ch/integrationlayer/2.0/{bu}/mediaComposition/{media_type}/{media_id}.json',
media_id, query=query)['chapterList']
try:
media_data = next(
@@ -73,7 +72,7 @@ class SRGSSRIE(InfoExtractor):
self.raise_geo_restricted(
msg=message, countries=self._GEO_COUNTRIES)
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, message), expected=True)
+ f'{self.IE_NAME} said: {message}', expected=True)
return media_data
@@ -119,7 +118,7 @@ class SRGSSRIE(InfoExtractor):
# whole episode.
if int_or_none(media_data.get('position')) == 0:
for p in ('S', 'H'):
- podcast_url = media_data.get('podcast%sdUrl' % p)
+ podcast_url = media_data.get(f'podcast{p}dUrl')
if not podcast_url:
continue
quality = p + 'D'
@@ -207,7 +206,7 @@ class SRGSSRPlayIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
'url': 'http://play.swissinfo.ch/play/tv/business/video/why-people-were-against-tax-reforms?id=42960270',
'info_dict': {
@@ -223,7 +222,7 @@ class SRGSSRPlayIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.srf.ch/play/tv/popupvideoplayer?id=c4dba0ca-e75b-43b2-a34f-f708a4932e01',
'only_matching': True,
@@ -244,4 +243,4 @@ class SRGSSRPlayIE(InfoExtractor):
bu = mobj.group('bu')
media_type = mobj.group('type') or mobj.group('type_2')
media_id = mobj.group('id')
- return self.url_result('srgssr:%s:%s:%s' % (bu[:3], media_type, media_id), 'SRGSSR')
+ return self.url_result(f'srgssr:{bu[:3]}:{media_type}:{media_id}', 'SRGSSR')
diff --git a/yt_dlp/extractor/srmediathek.py b/yt_dlp/extractor/srmediathek.py
index f0b3b585f..fc63d9b1a 100644
--- a/yt_dlp/extractor/srmediathek.py
+++ b/yt_dlp/extractor/srmediathek.py
@@ -43,7 +43,7 @@ class SRMediathekIE(ARDMediathekBaseIE):
webpage = self._download_webpage(url, video_id)
if '>Der gew&uuml;nschte Beitrag ist leider nicht mehr verf&uuml;gbar.<' in webpage:
- raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
+ raise ExtractorError(f'Video {video_id} is no longer available', expected=True)
media_collection_url = self._search_regex(
r'data-mediacollection-ardplayer="([^"]+)"', webpage, 'media collection url')
diff --git a/yt_dlp/extractor/stageplus.py b/yt_dlp/extractor/stageplus.py
index 77e4362fc..639907269 100644
--- a/yt_dlp/extractor/stageplus.py
+++ b/yt_dlp/extractor/stageplus.py
@@ -468,7 +468,7 @@ fragment BannerFields on Banner {
}, data=json.dumps({
'query': self._GRAPHQL_QUERY,
'variables': {'videoId': concert_id},
- 'operationName': 'videoDetailPage'
+ 'operationName': 'videoDetailPage',
}, separators=(',', ':')).encode())['data']['node']
metadata = traverse_obj(data, {
diff --git a/yt_dlp/extractor/stanfordoc.py b/yt_dlp/extractor/stanfordoc.py
index be0f4afc1..ab41091cf 100644
--- a/yt_dlp/extractor/stanfordoc.py
+++ b/yt_dlp/extractor/stanfordoc.py
@@ -19,7 +19,7 @@ class StanfordOpenClassroomIE(InfoExtractor):
'id': 'PracticalUnix_intro-environment',
'ext': 'mp4',
'title': 'Intro Environment',
- }
+ },
}
def _real_extract(self, url):
@@ -34,12 +34,12 @@ class StanfordOpenClassroomIE(InfoExtractor):
'upload_date': None,
}
- baseUrl = 'http://openclassroom.stanford.edu/MainFolder/courses/' + course + '/videos/'
- xmlUrl = baseUrl + video + '.xml'
- mdoc = self._download_xml(xmlUrl, info['id'])
+ base_url = 'http://openclassroom.stanford.edu/MainFolder/courses/' + course + '/videos/'
+ xml_url = base_url + video + '.xml'
+ mdoc = self._download_xml(xml_url, info['id'])
try:
info['title'] = mdoc.findall('./title')[0].text
- info['url'] = baseUrl + mdoc.findall('./videoFile')[0].text
+ info['url'] = base_url + mdoc.findall('./videoFile')[0].text
except IndexError:
raise ExtractorError('Invalid metadata XML file')
return info
@@ -66,7 +66,7 @@ class StanfordOpenClassroomIE(InfoExtractor):
links = orderedSet(re.findall(r'<a href="(VideoPage\.php\?[^"]+)">', coursepage))
info['entries'] = [self.url_result(
- 'http://openclassroom.stanford.edu/MainFolder/%s' % unescapeHTML(l)
+ f'http://openclassroom.stanford.edu/MainFolder/{unescapeHTML(l)}',
) for l in links]
return info
else: # Root page
@@ -78,12 +78,12 @@ class StanfordOpenClassroomIE(InfoExtractor):
}
info['title'] = info['id']
- rootURL = 'http://openclassroom.stanford.edu/MainFolder/HomePage.php'
- rootpage = self._download_webpage(rootURL, info['id'],
+ root_url = 'http://openclassroom.stanford.edu/MainFolder/HomePage.php'
+ rootpage = self._download_webpage(root_url, info['id'],
errnote='Unable to download course info page')
links = orderedSet(re.findall(r'<a href="(CoursePage\.php\?[^"]+)">', rootpage))
info['entries'] = [self.url_result(
- 'http://openclassroom.stanford.edu/MainFolder/%s' % unescapeHTML(l)
+ f'http://openclassroom.stanford.edu/MainFolder/{unescapeHTML(l)}',
) for l in links]
return info
diff --git a/yt_dlp/extractor/startrek.py b/yt_dlp/extractor/startrek.py
index 94efb589c..c59187173 100644
--- a/yt_dlp/extractor/startrek.py
+++ b/yt_dlp/extractor/startrek.py
@@ -22,7 +22,7 @@ class StarTrekIE(InfoExtractor):
}, {
'url': 'https://media.startrek.com/2022/06/16/2043801155561/1069981_hls/trr_snw_107_v4-c4bfc25d/stream_vtt.m3u8',
}]},
- }
+ },
}, {
'url': 'https://www.startrek.com/videos/watch-ethan-peck-and-gia-sandhu-beam-down-to-the-ready-room',
'md5': 'f5ad74fbb86e91e0882fc0a333178d1d',
@@ -38,7 +38,7 @@ class StarTrekIE(InfoExtractor):
'subtitles': {'en-US': [{
'url': r're:https://(?:intl|www)\.startrek\.com/sites/default/files/video/captions/2022-06/TRR_SNW_105_v5\.vtt',
}]},
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/startv.py b/yt_dlp/extractor/startv.py
index 312a4fde0..8cb576530 100644
--- a/yt_dlp/extractor/startv.py
+++ b/yt_dlp/extractor/startv.py
@@ -1,7 +1,4 @@
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..utils import (
ExtractorError,
clean_html,
@@ -11,14 +8,14 @@ from ..utils import (
class StarTVIE(InfoExtractor):
- _VALID_URL = r"""(?x)
+ _VALID_URL = r'''(?x)
https?://(?:www\.)?startv\.com\.tr/
(?:
(?:dizi|program)/(?:[^/?#&]+)/(?:bolumler|fragmanlar|ekstralar)|
video/arsiv/(?:dizi|program)/(?:[^/?#&]+)
)/
(?P<id>[^/?#&]+)
- """
+ '''
IE_NAME = 'startv'
_TESTS = [
{
@@ -32,41 +29,41 @@ class StarTVIE(InfoExtractor):
'description': 'md5:3a8049f05a75c2e8747116a673275de4',
'thumbnail': r're:^https?://.*\.jpg(?:\?.*?)?$',
'timestamp': 1569281400,
- 'upload_date': '20190923'
+ 'upload_date': '20190923',
},
},
{
'url': 'https://www.startv.com.tr/video/arsiv/dizi/avlu/44-bolum',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/dizi/cocuk/fragmanlar/5-bolum-fragmani',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/dizi/cocuk/ekstralar/5-bolumun-nefes-kesen-final-sahnesi',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/program/burcu-ile-haftasonu/bolumler/1-bolum',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/program/burcu-ile-haftasonu/fragmanlar/2-fragman',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/video/arsiv/program/buyukrisk/14-bolumde-hangi-unlu-ne-sordu-',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/video/arsiv/program/buyukrisk/buyuk-risk-334-bolum',
- 'only_matching': True
+ 'only_matching': True,
},
{
'url': 'https://www.startv.com.tr/video/arsiv/program/dada/dada-58-bolum',
- 'only_matching': True
- }
+ 'only_matching': True,
+ },
]
def _real_extract(self, url):
@@ -80,7 +77,7 @@ class StarTVIE(InfoExtractor):
if not info:
raise ExtractorError('Failed to extract API data')
- video_id = compat_str(info.get('id'))
+ video_id = str(info.get('id'))
title = info.get('title') or self._og_search_title(webpage)
description = clean_html(info.get('description')) or self._og_search_description(webpage, default=None)
thumbnail = self._proto_relative_url(
@@ -96,5 +93,5 @@ class StarTVIE(InfoExtractor):
'description': description,
'thumbnail': thumbnail,
'timestamp': int_or_none(info.get('release_date')),
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/steam.py b/yt_dlp/extractor/steam.py
index 63da9662a..b7f8ac3ae 100644
--- a/yt_dlp/extractor/steam.py
+++ b/yt_dlp/extractor/steam.py
@@ -10,7 +10,7 @@ from ..utils import (
class SteamIE(InfoExtractor):
- _VALID_URL = r"""(?x)
+ _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
@@ -18,7 +18,7 @@ class SteamIE(InfoExtractor):
(?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 = 'http://store.steampowered.com/video/%s/'
_AGECHECK_TEMPLATE = 'http://store.steampowered.com/agecheck/video/%s/?snr=1_agecheck_agecheck__age-gate&ageDay=1&ageMonth=January&ageYear=1970'
_TESTS = [{
@@ -31,7 +31,7 @@ class SteamIE(InfoExtractor):
'ext': 'mp4',
'title': 'Terraria video 256785003',
'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com',
- }
+ },
},
{
'md5': '6a294ee0c4b1f47f5bb76a65e31e3592',
@@ -40,8 +40,8 @@ class SteamIE(InfoExtractor):
'ext': 'mp4',
'title': 'Terraria video 2040428',
'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com',
- }
- }
+ },
+ },
],
'info_dict': {
'id': '105600',
@@ -49,7 +49,7 @@ class SteamIE(InfoExtractor):
},
'params': {
'playlistend': 2,
- }
+ },
}, {
'url': 'https://store.steampowered.com/app/271590/Grand_Theft_Auto_V/',
'info_dict': {
@@ -61,13 +61,13 @@ class SteamIE(InfoExtractor):
def _real_extract(self, url):
m = self._match_valid_url(url)
- fileID = m.group('fileID')
- if fileID:
+ file_id = m.group('fileID')
+ if file_id:
video_url = url
- playlist_id = fileID
+ playlist_id = file_id
else:
- gameID = m.group('gameID')
- playlist_id = gameID
+ game_id = m.group('gameID')
+ playlist_id = game_id
video_url = self._VIDEO_PAGE_TEMPLATE % playlist_id
self._set_cookie('steampowered.com', 'wants_mature_content', '1')
@@ -99,7 +99,7 @@ class SteamIE(InfoExtractor):
entry['thumbnail'] = movie.get('data-poster')
for quality in ('', '-hd'):
for ext in ('webm', 'mp4'):
- video_url = movie.get('data-%s%s-source' % (ext, quality))
+ video_url = movie.get(f'data-{ext}{quality}-source')
if video_url:
formats.append({
'format_id': ext + quality,
diff --git a/yt_dlp/extractor/stitcher.py b/yt_dlp/extractor/stitcher.py
index 46a15e6a1..09ebabb28 100644
--- a/yt_dlp/extractor/stitcher.py
+++ b/yt_dlp/extractor/stitcher.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
clean_html,
@@ -37,7 +36,7 @@ class StitcherBaseIE(InfoExtractor):
def _extract_episode(self, episode, audio_url, show_info):
info = {
- 'id': compat_str(episode['id']),
+ 'id': str(episode['id']),
'display_id': episode.get('slug'),
'title': episode['title'].strip(),
'description': self._extract_description(episode),
@@ -126,7 +125,7 @@ class StitcherShowIE(StitcherBaseIE):
def _real_extract(self, url):
show_slug = self._match_id(url)
data = self._call_api(
- 'search/show/%s/allEpisodes' % show_slug, show_slug, {'count': 10000})
+ f'search/show/{show_slug}/allEpisodes', show_slug, {'count': 10000})
show = try_get(data, lambda x: x['shows'][0], dict) or {}
show_info = self._extract_show_info(show)
diff --git a/yt_dlp/extractor/storyfire.py b/yt_dlp/extractor/storyfire.py
index 20a70a7bc..d5430e6c0 100644
--- a/yt_dlp/extractor/storyfire.py
+++ b/yt_dlp/extractor/storyfire.py
@@ -14,8 +14,8 @@ class StoryFireBaseIE(InfoExtractor):
def _call_api(self, path, video_id, resource, query=None):
return self._download_json(
- 'https://storyfire.com/app/%s/%s' % (path, video_id), video_id,
- 'Downloading %s JSON metadata' % resource, query=query)
+ f'https://storyfire.com/app/{path}/{video_id}', video_id,
+ f'Downloading {resource} JSON metadata', query=query)
def _parse_video(self, video):
title = video['title']
@@ -69,7 +69,7 @@ class StoryFireIE(StoryFireBaseIE):
'params': {
'skip_download': True,
},
- 'expected_warnings': ['Unable to download JSON metadata']
+ 'expected_warnings': ['Unable to download JSON metadata'],
}
def _real_extract(self, url):
@@ -92,7 +92,7 @@ class StoryFireUserIE(StoryFireBaseIE):
def _fetch_page(self, user_id, page):
videos = self._call_api(
- 'publicVideos', user_id, 'page %d' % (page + 1), {
+ 'publicVideos', user_id, f'page {page + 1}', {
'skip': page * self._PAGE_SIZE,
})['videos']
for video in videos:
diff --git a/yt_dlp/extractor/streamable.py b/yt_dlp/extractor/streamable.py
index c303ac53a..62ae0ee46 100644
--- a/yt_dlp/extractor/streamable.py
+++ b/yt_dlp/extractor/streamable.py
@@ -25,7 +25,7 @@ class StreamableIE(InfoExtractor):
'upload_date': '20160208',
'duration': 61.516,
'view_count': int,
- }
+ },
},
# older video without bitrate, width/height, codecs, etc. info
{
@@ -40,7 +40,7 @@ class StreamableIE(InfoExtractor):
'upload_date': '20150311',
'duration': 12,
'view_count': int,
- }
+ },
},
{
'url': 'https://streamable.com/e/dnd1',
@@ -49,7 +49,7 @@ class StreamableIE(InfoExtractor):
{
'url': 'https://streamable.com/s/okkqk/drxjds',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
@@ -59,7 +59,7 @@ class StreamableIE(InfoExtractor):
# to return video info like the title properly sometimes, and doesn't
# include info like the video duration
video = self._download_json(
- 'https://ajax.streamable.com/videos/%s' % video_id, video_id)
+ f'https://ajax.streamable.com/videos/{video_id}', video_id)
# Format IDs:
# 0 The video is being uploaded
@@ -99,5 +99,5 @@ class StreamableIE(InfoExtractor):
'timestamp': float_or_none(video.get('date_added')),
'duration': float_or_none(video.get('duration')),
'view_count': int_or_none(video.get('plays')),
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/streamcz.py b/yt_dlp/extractor/streamcz.py
index c4537ba8d..4122ba886 100644
--- a/yt_dlp/extractor/streamcz.py
+++ b/yt_dlp/extractor/streamcz.py
@@ -23,7 +23,7 @@ class StreamCZIE(InfoExtractor):
'description': 'md5:8f5f09b9b7bc67df910486cdd88f7165',
'duration': 1369.6,
'view_count': int,
- }
+ },
}, {
'url': 'https://www.stream.cz/kdo-to-mluvi/kdo-to-mluvi-velke-odhaleni-prinasi-novy-porad-uz-od-25-srpna-64087937',
'md5': '41fd358000086a1ccdb068c77809b158',
@@ -35,7 +35,7 @@ class StreamCZIE(InfoExtractor):
'description': 'md5:97a811000a6460266029d6c1c2ebcd59',
'duration': 50.2,
'view_count': int,
- }
+ },
}, {
'url': 'https://www.stream.cz/tajemno/znicehonic-jim-skrz-strechu-prolitnul-zahadny-predmet-badatele-vse-objasnili-64147267',
'md5': '3ee4d0be040e8f4a543e67e509d55e3f',
@@ -47,7 +47,7 @@ class StreamCZIE(InfoExtractor):
'description': 'md5:4b8ada6718d34bb011c4e04ca4bc19bf',
'duration': 442.84,
'view_count': int,
- }
+ },
}]
def _extract_formats(self, spl_url, video):
@@ -86,9 +86,9 @@ class StreamCZIE(InfoExtractor):
perex
duration
views
- }'''
- }).encode('utf-8'),
- headers={'Content-Type': 'application/json;charset=UTF-8'}
+ }''',
+ }).encode(),
+ headers={'Content-Type': 'application/json;charset=UTF-8'},
)['data']['episode']
spl_url = data['spl'] + 'spl2,3'
@@ -105,7 +105,7 @@ class StreamCZIE(InfoExtractor):
for ext, sub_url in subs.get('urls').items():
subtitles.setdefault(subs['language'], []).append({
'ext': ext,
- 'url': urljoin(spl_url, sub_url)
+ 'url': urljoin(spl_url, sub_url),
})
formats = list(self._extract_formats(spl_url, video))
diff --git a/yt_dlp/extractor/streetvoice.py b/yt_dlp/extractor/streetvoice.py
index a32c8bc37..60056f7cb 100644
--- a/yt_dlp/extractor/streetvoice.py
+++ b/yt_dlp/extractor/streetvoice.py
@@ -33,7 +33,7 @@ class StreetVoiceIE(InfoExtractor):
'track': '流浪',
'track_id': '123688',
'album': '2010',
- }
+ },
}, {
'url': 'http://tw.streetvoice.com/skippylu/songs/94440/',
'only_matching': True,
@@ -41,7 +41,7 @@ class StreetVoiceIE(InfoExtractor):
def _real_extract(self, url):
song_id = self._match_id(url)
- base_url = 'https://streetvoice.com/api/v4/song/%s/' % song_id
+ base_url = f'https://streetvoice.com/api/v4/song/{song_id}/'
song = self._download_json(base_url, song_id, query={
'fields': 'album,comments_count,created_at,id,image,length,likes_count,name,nickname,plays_count,profile,share_count,synopsis,user,username',
})
@@ -51,7 +51,7 @@ class StreetVoiceIE(InfoExtractor):
for suffix, format_id in [('hls/file', 'hls'), ('file', 'http'), ('file/original', 'original')]:
f_url = (self._download_json(
base_url + suffix + '/', song_id,
- 'Downloading %s format URL' % format_id,
+ f'Downloading {format_id} format URL',
data=b'', fatal=False) or {}).get('file')
if not f_url:
continue
@@ -86,7 +86,7 @@ class StreetVoiceIE(InfoExtractor):
'timestamp': parse_iso8601(song.get('created_at')),
'uploader': try_get(user, lambda x: x['profile']['nickname']),
'uploader_id': str_or_none(user.get('id')),
- 'uploader_url': urljoin(url, '/%s/' % username) if username else None,
+ 'uploader_url': urljoin(url, f'/{username}/') if username else None,
'view_count': get_count('plays'),
'like_count': get_count('likes'),
'comment_count': get_count('comments'),
diff --git a/yt_dlp/extractor/stretchinternet.py b/yt_dlp/extractor/stretchinternet.py
index e438dee11..232837d8d 100644
--- a/yt_dlp/extractor/stretchinternet.py
+++ b/yt_dlp/extractor/stretchinternet.py
@@ -12,7 +12,7 @@ class StretchInternetIE(InfoExtractor):
# 'timestamp': 1575668361,
# 'upload_date': '20191206',
'uploader_id': '99997',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/stripchat.py b/yt_dlp/extractor/stripchat.py
index a847925e4..31c8afbc6 100644
--- a/yt_dlp/extractor/stripchat.py
+++ b/yt_dlp/extractor/stripchat.py
@@ -22,7 +22,7 @@ class StripchatIE(InfoExtractor):
'skip': 'Room is offline',
}, {
'url': 'https://stripchat.com/Rakhijaan@xh',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/stv.py b/yt_dlp/extractor/stv.py
index 0ab780100..c489e4237 100644
--- a/yt_dlp/extractor/stv.py
+++ b/yt_dlp/extractor/stv.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
float_or_none,
int_or_none,
@@ -21,7 +20,7 @@ class STVPlayerIE(InfoExtractor):
'ext': 'mp4',
'upload_date': '20170301',
'title': '60 seconds on set with Laura Norton',
- 'description': "How many questions can Laura - a.k.a Kerry Wyatt - answer in 60 seconds? Let\'s find out!",
+ 'description': "How many questions can Laura - a.k.a Kerry Wyatt - answer in 60 seconds? Let's find out!",
'timestamp': 1488388054,
'uploader_id': '1486976045',
},
@@ -47,13 +46,13 @@ class STVPlayerIE(InfoExtractor):
api_path, resp = None, {}
for k, v in player_api_cache.items():
- if k.startswith('/episodes/') or k.startswith('/shortform/'):
+ if k.startswith(('/episodes/', '/shortform/')):
api_path, resp = k, v
break
else:
episode_id = str_or_none(try_get(
props, lambda x: x['pageProps']['episodeId']))
- api_path = '/%s/%s' % (self._PTYPE_MAP[ptype], episode_id or video_id)
+ api_path = f'/{self._PTYPE_MAP[ptype]}/{episode_id or video_id}'
result = resp.get('results')
if not result:
@@ -62,7 +61,7 @@ class STVPlayerIE(InfoExtractor):
result = resp['results']
video = result['video']
- video_id = compat_str(video['id'])
+ video_id = str(video['id'])
subtitles = {}
_subtitles = result.get('_subtitles') or {}
diff --git a/yt_dlp/extractor/substack.py b/yt_dlp/extractor/substack.py
index 6ee3f75e1..30cb322dc 100644
--- a/yt_dlp/extractor/substack.py
+++ b/yt_dlp/extractor/substack.py
@@ -18,7 +18,7 @@ class SubstackIE(InfoExtractor):
'thumbnail': 'md5:bec758a34d8ee9142d43bcebdf33af18',
'uploader': 'Maybe Baby',
'uploader_id': '33628',
- }
+ },
}, {
'url': 'https://haleynahman.substack.com/p/-dear-danny-i-found-my-boyfriends?s=r',
'md5': '0a63eacec877a1171a62cfa69710fcea',
@@ -30,7 +30,7 @@ class SubstackIE(InfoExtractor):
'thumbnail': 'md5:daa40b6b79249417c14ff8103db29639',
'uploader': 'Maybe Baby',
'uploader_id': '33628',
- }
+ },
}, {
'url': 'https://andrewzimmern.substack.com/p/mussels-with-black-bean-sauce-recipe',
'md5': 'fd3c07077b02444ff0130715b5f632bb',
@@ -42,7 +42,7 @@ class SubstackIE(InfoExtractor):
'thumbnail': 'md5:e30bfaa9da40e82aa62354263a9dd232',
'uploader': "Andrew Zimmern's Spilled Milk ",
'uploader_id': '577659',
- }
+ },
}]
@classmethod
@@ -54,7 +54,7 @@ class SubstackIE(InfoExtractor):
if mobj:
parsed = urllib.parse.urlparse(url)
yield parsed._replace(netloc=f'{mobj.group("subdomain")}.substack.com').geturl()
- raise cls.StopExtraction()
+ raise cls.StopExtraction
def _extract_video_formats(self, video_id, url):
formats, subtitles = [], {}
diff --git a/yt_dlp/extractor/sunporno.py b/yt_dlp/extractor/sunporno.py
index 501156e51..6078d5000 100644
--- a/yt_dlp/extractor/sunporno.py
+++ b/yt_dlp/extractor/sunporno.py
@@ -22,7 +22,7 @@ class SunPornoIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'duration': 302,
'age_limit': 18,
- }
+ },
}, {
'url': 'http://embeds.sunporno.com/embed/807778',
'only_matching': True,
@@ -32,7 +32,7 @@ class SunPornoIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://www.sunporno.com/videos/%s' % video_id, video_id)
+ f'http://www.sunporno.com/videos/{video_id}', video_id)
title = self._html_extract_title(webpage)
description = self._html_search_meta(
diff --git a/yt_dlp/extractor/sverigesradio.py b/yt_dlp/extractor/sverigesradio.py
index 01a07b399..944ce18c4 100644
--- a/yt_dlp/extractor/sverigesradio.py
+++ b/yt_dlp/extractor/sverigesradio.py
@@ -51,7 +51,7 @@ class SverigesRadioBaseIE(InfoExtractor):
query['quality'] = quality
audio_url_data = self._download_json(
self._BASE_URL + 'getaudiourl', audio_id,
- 'Downloading %s format JSON metadata' % quality,
+ f'Downloading {quality} format JSON metadata',
fatal=False, query=query) or {}
audio_url = audio_url_data.get('audioUrl')
if not audio_url or audio_url in urls:
diff --git a/yt_dlp/extractor/svt.py b/yt_dlp/extractor/svt.py
index 573147a45..38782abac 100644
--- a/yt_dlp/extractor/svt.py
+++ b/yt_dlp/extractor/svt.py
@@ -2,7 +2,6 @@ import json
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
determine_ext,
dict_get,
@@ -100,7 +99,7 @@ class SVTBaseIE(InfoExtractor):
class SVTIE(SVTBaseIE):
_VALID_URL = r'https?://(?:www\.)?svt\.se/wd\?(?:.*?&)?widgetId=(?P<widget_id>\d+)&.*?\barticleId=(?P<id>\d+)'
- _EMBED_REGEX = [r'(?:<iframe src|href)="(?P<url>%s[^"]*)"' % _VALID_URL]
+ _EMBED_REGEX = [rf'(?:<iframe src|href)="(?P<url>{_VALID_URL}[^"]*)"']
_TEST = {
'url': 'http://www.svt.se/wd?widgetId=23991&sectionId=541&articleId=2900353&type=embed&contextSectionId=123&autostart=false',
'md5': '33e9a5d8f646523ce0868ecfb0eed77d',
@@ -119,7 +118,7 @@ class SVTIE(SVTBaseIE):
article_id = mobj.group('id')
info = self._download_json(
- 'http://www.svt.se/wd?widgetId=%s&articleId=%s&format=json&type=embed&output=json' % (widget_id, article_id),
+ f'http://www.svt.se/wd?widgetId={widget_id}&articleId={article_id}&format=json&type=embed&output=json',
article_id)
info_dict = self._extract_video(info['video'], article_id)
@@ -159,7 +158,7 @@ class SVTPlayIE(SVTPlayBaseIE):
'subtitles': {
'sv': [{
'ext': 'vtt',
- }]
+ }],
},
},
'params': {
@@ -181,7 +180,7 @@ class SVTPlayIE(SVTPlayBaseIE):
'episode': '1. Farlig kryssning',
'series': 'Rederiet',
'subtitles': {
- 'sv': 'count:3'
+ 'sv': 'count:3',
},
},
'params': {
@@ -236,7 +235,7 @@ class SVTPlayIE(SVTPlayBaseIE):
def _extract_by_video_id(self, video_id, webpage=None):
data = self._download_json(
- 'https://api.svt.se/videoplayer-api/video/%s' % video_id,
+ f'https://api.svt.se/videoplayer-api/video/{video_id}',
video_id, headers=self.geo_verification_headers())
info_dict = self._extract_video(data, video_id)
if not info_dict.get('title'):
@@ -281,7 +280,7 @@ class SVTPlayIE(SVTPlayBaseIE):
svt_id = try_get(
data, lambda x: x['statistics']['dataLake']['content']['id'],
- compat_str)
+ str)
if not svt_id:
nextjs_data = self._search_nextjs_data(webpage, video_id, fatal=False)
@@ -323,7 +322,7 @@ class SVTSeriesIE(SVTPlayBaseIE):
@classmethod
def suitable(cls, url):
- return False if SVTIE.suitable(url) or SVTPlayIE.suitable(url) else super(SVTSeriesIE, cls).suitable(url)
+ return False if SVTIE.suitable(url) or SVTPlayIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
series_slug, season_id = self._match_valid_url(url).groups()
@@ -349,7 +348,7 @@ class SVTSeriesIE(SVTPlayBaseIE):
name
shortDescription
}
-}''' % series_slug,
+}''' % series_slug, # noqa: UP031
})['data']['listablesBySlug'][0]
season_name = None
@@ -368,7 +367,7 @@ class SVTSeriesIE(SVTPlayBaseIE):
for item in items:
video = item.get('item') or {}
content_id = video.get('videoSvtId')
- if not content_id or not isinstance(content_id, compat_str):
+ if not content_id or not isinstance(content_id, str):
continue
entries.append(self.url_result(
'svt:' + content_id, SVTPlayIE.ie_key(), content_id))
@@ -377,7 +376,7 @@ class SVTSeriesIE(SVTPlayBaseIE):
season_name = season_name or season_id
if title and season_name:
- title = '%s - %s' % (title, season_name)
+ title = f'{title} - {season_name}'
elif season_id:
title = season_id
@@ -401,32 +400,32 @@ class SVTPageIE(SVTBaseIE):
'id': 'jXvk42E',
'title': 'Försvarsmakten om trafikkaoset på E22: Kunde inte varit där snabbare',
'ext': 'mp4',
- "duration": 80,
+ 'duration': 80,
'age_limit': 0,
'timestamp': 1704370009,
'episode': 'Försvarsmakten om trafikkaoset på E22: Kunde inte varit där snabbare',
'series': 'Lokala Nyheter Skåne',
- 'upload_date': '20240104'
+ 'upload_date': '20240104',
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.svt.se/nyheter/svtforum/2023-tungt-ar-for-svensk-media',
'info_dict': {
'title': '2023 tungt år för svensk media',
'id': 'ewqAZv4',
'ext': 'mp4',
- "duration": 3074,
+ 'duration': 3074,
'age_limit': 0,
'series': '',
'timestamp': 1702980479,
'upload_date': '20231219',
- 'episode': 'Mediestudier'
+ 'episode': 'Mediestudier',
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.svt.se/sport/ishockey/bakom-masken-lehners-kamp-mot-mental-ohalsa',
'info_dict': {
@@ -434,7 +433,7 @@ class SVTPageIE(SVTBaseIE):
'title': 'Bakom masken – Lehners kamp mot mental ohälsa',
},
'playlist_count': 4,
- 'skip': 'Video is gone'
+ 'skip': 'Video is gone',
}, {
'url': 'https://www.svt.se/nyheter/utrikes/svenska-andrea-ar-en-mil-fran-branderna-i-kalifornien',
'info_dict': {
@@ -442,7 +441,7 @@ class SVTPageIE(SVTBaseIE):
'title': 'Svenska Andrea redo att fly sitt hem i Kalifornien',
},
'playlist_count': 2,
- 'skip': 'Video is gone'
+ 'skip': 'Video is gone',
}, {
# only programTitle
'url': 'http://www.svt.se/sport/ishockey/jagr-tacklar-giroux-under-intervjun',
@@ -453,7 +452,7 @@ class SVTPageIE(SVTBaseIE):
'duration': 27,
'age_limit': 0,
},
- 'skip': 'Video is gone'
+ 'skip': 'Video is gone',
}, {
'url': 'https://www.svt.se/nyheter/lokalt/vast/svt-testar-tar-nagon-upp-skrapet-1',
'only_matching': True,
@@ -464,7 +463,7 @@ class SVTPageIE(SVTBaseIE):
@classmethod
def suitable(cls, url):
- return False if SVTIE.suitable(url) or SVTPlayIE.suitable(url) else super(SVTPageIE, cls).suitable(url)
+ return False if SVTIE.suitable(url) or SVTPlayIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
@@ -479,7 +478,7 @@ class SVTPageIE(SVTBaseIE):
def entries():
for video_id in set(traverse_obj(data, (
- 'page', (('topMedia', 'svtId'), ('body', ..., 'video', 'svtId')), {str}
+ 'page', (('topMedia', 'svtId'), ('body', ..., 'video', 'svtId')), {str},
))):
info = self._extract_video(
self._download_json(f'https://api.svt.se/video/{video_id}', video_id), video_id)
diff --git a/yt_dlp/extractor/swearnet.py b/yt_dlp/extractor/swearnet.py
index aeaff28f2..b4835c5ad 100644
--- a/yt_dlp/extractor/swearnet.py
+++ b/yt_dlp/extractor/swearnet.py
@@ -17,7 +17,7 @@ class SwearnetEpisodeIE(InfoExtractor):
'title': 'Episode 1 - Grilled Cheese Sammich',
'season_number': 1,
'thumbnail': 'https://cdn.vidyard.com/thumbnails/232819/_RX04IKIq60a2V6rIRqq_Q_small.jpg',
- }
+ },
}]
def _get_formats_and_subtitle(self, video_source, video_id):
@@ -32,7 +32,7 @@ class SwearnetEpisodeIE(InfoExtractor):
else:
formats.extend({
'url': video_mp4.get('url'),
- 'ext': 'mp4'
+ 'ext': 'mp4',
} for video_mp4 in value)
return formats, subtitles
@@ -42,7 +42,7 @@ class SwearnetEpisodeIE(InfoExtractor):
for caption in caption_json:
subs.setdefault(caption.get('language') or 'und', []).append({
'url': caption.get('vttUrl'),
- 'name': caption.get('name')
+ 'name': caption.get('name'),
})
return subs
@@ -75,5 +75,5 @@ class SwearnetEpisodeIE(InfoExtractor):
'season_number': int_or_none(season_number),
'episode_number': int_or_none(episode_number),
'thumbnails': [{'url': thumbnail_url}
- for thumbnail_url in traverse_obj(json_data, ('thumbnailUrls', ...))]
+ for thumbnail_url in traverse_obj(json_data, ('thumbnailUrls', ...))],
}
diff --git a/yt_dlp/extractor/syfy.py b/yt_dlp/extractor/syfy.py
index 29e5e573f..a32b50080 100644
--- a/yt_dlp/extractor/syfy.py
+++ b/yt_dlp/extractor/syfy.py
@@ -30,9 +30,9 @@ class SyfyIE(AdobePassIE):
def _real_extract(self, url):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id)
- syfy_mpx = list(self._parse_json(self._search_regex(
+ syfy_mpx = next(iter(self._parse_json(self._search_regex(
r'jQuery\.extend\(Drupal\.settings\s*,\s*({.+?})\);', webpage, 'drupal settings'),
- display_id)['syfy']['syfy_mpx'].values())[0]
+ display_id)['syfy']['syfy_mpx'].values()))
video_id = syfy_mpx['mpxGUID']
title = syfy_mpx['episodeTitle']
query = {
diff --git a/yt_dlp/extractor/syvdk.py b/yt_dlp/extractor/syvdk.py
index 287fb264b..ec166831c 100644
--- a/yt_dlp/extractor/syvdk.py
+++ b/yt_dlp/extractor/syvdk.py
@@ -13,8 +13,8 @@ class SYVDKIE(InfoExtractor):
'display_id': 'isabella-arendt-stiller-op-for-de-konservative-2',
'ext': 'mp3',
'title': 'Isabella Arendt stiller op for De Konservative',
- 'description': 'md5:f5fa6a431813bf37284f3412ad7c6c06'
- }
+ 'description': 'md5:f5fa6a431813bf37284f3412ad7c6c06',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/tagesschau.py b/yt_dlp/extractor/tagesschau.py
index c69c13d0b..4c537dfd1 100644
--- a/yt_dlp/extractor/tagesschau.py
+++ b/yt_dlp/extractor/tagesschau.py
@@ -141,10 +141,10 @@ class TagesschauIE(InfoExtractor):
if not formats:
continue
entries.append({
- 'id': '%s-%d' % (display_id, num),
+ 'id': f'{display_id}-{num}',
'title': try_get(video, lambda x: x['mc']['_title']),
'duration': int_or_none(try_get(video, lambda x: x['mc']['_duration'])),
- 'formats': formats
+ 'formats': formats,
})
if not entries:
diff --git a/yt_dlp/extractor/taptap.py b/yt_dlp/extractor/taptap.py
index 56f2f0ef4..e4c31da4e 100644
--- a/yt_dlp/extractor/taptap.py
+++ b/yt_dlp/extractor/taptap.py
@@ -31,9 +31,9 @@ class TapTapBaseIE(InfoExtractor):
# h265 playlist contains both h265 and h264 formats
video_url = traverse_obj(video_data, ('play_url', ('url_h265', 'url'), {url_or_none}, any))
formats = self._extract_m3u8_formats(video_url, video_id, fatal=False)
- for format in formats:
- if re.search(r'^(hev|hvc|hvt)\d', format.get('vcodec', '')):
- format['format_id'] = join_nonempty(format.get('format_id'), 'h265', delim='_')
+ for fmt in formats:
+ if re.search(r'^(hev|hvc|hvt)\d', fmt.get('vcodec', '')):
+ fmt['format_id'] = join_nonempty(fmt.get('format_id'), 'h265', delim='_')
return {
'id': str(video_id),
@@ -41,7 +41,7 @@ class TapTapBaseIE(InfoExtractor):
**traverse_obj(video_data, ({
'duration': ('info', 'duration', {int_or_none}),
'thumbnail': ('thumbnail', ('original_url', 'url'), {url_or_none}),
- }), get_all=False)
+ }), get_all=False),
}
def _real_extract(self, url):
@@ -54,8 +54,8 @@ class TapTapBaseIE(InfoExtractor):
metainfo = traverse_obj(data, self._META_PATH)
entries = [{
**metainfo,
- **self._extract_video(id)
- } for id in set(traverse_obj(data, self._ID_PATH))]
+ **self._extract_video(id_),
+ } for id_ in set(traverse_obj(data, self._ID_PATH))]
return self.playlist_result(entries, **metainfo, id=video_id)
@@ -100,7 +100,7 @@ class TapTapMomentIE(TapTapBaseIE):
'uploader': '乌酱',
'uploader_id': '532896',
'thumbnail': r're:^https?://.*\.(png|jpg)',
- }
+ },
}],
'params': {'skip_download': 'm3u8'},
}, {
@@ -131,7 +131,7 @@ class TapTapMomentIE(TapTapBaseIE):
'uploader': '崩坏:星穹铁道',
'uploader_id': '414732580',
'thumbnail': r're:^https?://.*\.(png|jpg)',
- }
+ },
}],
'params': {'skip_download': 'm3u8'},
}, {
@@ -176,7 +176,7 @@ class TapTapAppIE(TapTapBaseIE):
'description': 'md5:e345f39a5fea5de2a46923f70d5f76ab',
'duration': 26,
'thumbnail': r're:^https?://.*\.(png|jpg)',
- }
+ },
}, {
'info_dict': {
'id': '4058462',
@@ -185,7 +185,7 @@ class TapTapAppIE(TapTapBaseIE):
'description': 'md5:e345f39a5fea5de2a46923f70d5f76ab',
'duration': 295,
'thumbnail': r're:^https?://.*\.(png|jpg)',
- }
+ },
}],
'params': {'skip_download': 'm3u8'},
}]
@@ -221,7 +221,7 @@ class TapTapAppIntlIE(TapTapIntlBase):
'description': 'md5:418285f9c15347fc3cf3e3a3c649f182',
'duration': 78,
'thumbnail': r're:^https?://.*\.(png|jpg)',
- }
+ },
}],
'params': {'skip_download': 'm3u8'},
}]
@@ -269,7 +269,7 @@ class TapTapPostIntlIE(TapTapIntlBase):
'uploader': 'TapTap Editor',
'uploader_id': '80224473',
'thumbnail': r're:^https?://.*\.(png|jpg)',
- }
+ },
}],
'params': {'skip_download': 'm3u8'},
}]
diff --git a/yt_dlp/extractor/tbs.py b/yt_dlp/extractor/tbs.py
index 4e178593f..9b9aa50d3 100644
--- a/yt_dlp/extractor/tbs.py
+++ b/yt_dlp/extractor/tbs.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .turner import TurnerBaseIE
-from ..compat import (
- compat_parse_qs,
- compat_urllib_parse_urlparse,
-)
from ..utils import (
float_or_none,
int_or_none,
@@ -27,7 +24,7 @@ class TBSIE(TurnerBaseIE):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}, {
'url': 'http://www.tbs.com/shows/search-party/season-1/episode-1/explicit-the-mysterious-disappearance-of-the-girl-no-one-knew',
'only_matching': True,
@@ -42,20 +39,20 @@ class TBSIE(TurnerBaseIE):
drupal_settings = self._parse_json(self._search_regex(
r'<script[^>]+?data-drupal-selector="drupal-settings-json"[^>]*?>({.+?})</script>',
webpage, 'drupal setting'), display_id)
- isLive = 'watchtnt' in path or 'watchtbs' in path
- video_data = next(v for v in drupal_settings['turner_playlist'] if isLive or v.get('url') == path)
+ is_live = 'watchtnt' in path or 'watchtbs' in path
+ video_data = next(v for v in drupal_settings['turner_playlist'] if is_live or v.get('url') == path)
media_id = video_data['mediaID']
title = video_data['title']
- tokenizer_query = compat_parse_qs(compat_urllib_parse_urlparse(
+ tokenizer_query = urllib.parse.parse_qs(urllib.parse.urlparse(
drupal_settings['ngtv_token_url']).query)
info = self._extract_ngtv_info(
media_id, tokenizer_query, {
'url': url,
'site_name': site[:3].upper(),
- 'auth_required': video_data.get('authRequired') == '1' or isLive,
- 'is_live': isLive
+ 'auth_required': video_data.get('authRequired') == '1' or is_live,
+ 'is_live': is_live,
})
thumbnails = []
@@ -84,6 +81,6 @@ class TBSIE(TurnerBaseIE):
'season_number': int_or_none(video_data.get('season')),
'episode_number': int_or_none(video_data.get('episode')),
'thumbnails': thumbnails,
- 'is_live': isLive
+ 'is_live': is_live,
})
return info
diff --git a/yt_dlp/extractor/tbsjp.py b/yt_dlp/extractor/tbsjp.py
index 77ddeca32..32f9cfbde 100644
--- a/yt_dlp/extractor/tbsjp.py
+++ b/yt_dlp/extractor/tbsjp.py
@@ -92,8 +92,8 @@ class TBSJPProgramIE(InfoExtractor):
'categories': ['エンタメ', 'ミライカプセル', '会社', '働く', 'バラエティ', '動画'],
'description': '幼少期の夢は大人になって、どう成長したのだろうか?\nそしてその夢は今後、どのように広がっていくのか?\nいま話題の会社で働く人の「夢の成長」を描く',
'series': 'ミライカプセル -I have a dream-',
- 'title': 'ミライカプセル -I have a dream-'
- }
+ 'title': 'ミライカプセル -I have a dream-',
+ },
}]
def _real_extract(self, url):
@@ -126,7 +126,7 @@ class TBSJPPlaylistIE(InfoExtractor):
'info_dict': {
'title': 'まもなく配信終了',
'id': '184f9970e7ba48e4915f1b252c55015e',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/teachable.py b/yt_dlp/extractor/teachable.py
index 778fa1263..0d39be6d5 100644
--- a/yt_dlp/extractor/teachable.py
+++ b/yt_dlp/extractor/teachable.py
@@ -29,7 +29,7 @@ class TeachableBaseIE(InfoExtractor):
'courses.workitdaily.com': 'workitdaily',
}
- _VALID_URL_SUB_TUPLE = (_URL_PREFIX, '|'.join(re.escape(site) for site in _SITES.keys()))
+ _VALID_URL_SUB_TUPLE = (_URL_PREFIX, '|'.join(re.escape(site) for site in _SITES))
def _real_initialize(self):
self._logged_in = False
@@ -43,8 +43,8 @@ class TeachableBaseIE(InfoExtractor):
return
login_page, urlh = self._download_webpage_handle(
- 'https://%s/sign_in' % site, None,
- 'Downloading %s login page' % site)
+ f'https://{site}/sign_in', None,
+ f'Downloading {site} login page')
def is_logged(webpage):
return any(re.search(p, webpage) for p in (
@@ -73,7 +73,7 @@ class TeachableBaseIE(InfoExtractor):
post_url = urljoin(login_url, post_url)
response = self._download_webpage(
- post_url, None, 'Logging in to %s' % site,
+ post_url, None, f'Logging in to {site}',
data=urlencode_postdata(login_form),
headers={
'Content-Type': 'application/x-www-form-urlencoded',
@@ -82,8 +82,8 @@ class TeachableBaseIE(InfoExtractor):
if '>I accept the new Privacy Policy<' in response:
raise ExtractorError(
- 'Unable to login: %s asks you to accept new Privacy Policy. '
- 'Go to https://%s/ and accept.' % (site, site), expected=True)
+ f'Unable to login: {site} asks you to accept new Privacy Policy. '
+ f'Go to https://{site}/ and accept.', expected=True)
# Successful login
if is_logged(response):
@@ -93,7 +93,7 @@ class TeachableBaseIE(InfoExtractor):
message = get_element_by_class('alert', response)
if message is not None:
raise ExtractorError(
- 'Unable to login: %s' % clean_html(message), expected=True)
+ f'Unable to login: {clean_html(message)}', expected=True)
raise ExtractorError('Unable to log in')
@@ -102,11 +102,11 @@ class TeachableIE(TeachableBaseIE):
_WORKING = False
_VALID_URL = r'''(?x)
(?:
- %shttps?://(?P<site_t>[^/]+)|
- https?://(?:www\.)?(?P<site>%s)
+ {}https?://(?P<site_t>[^/]+)|
+ https?://(?:www\.)?(?P<site>{})
)
/courses/[^/]+/lectures/(?P<id>\d+)
- ''' % TeachableBaseIE._VALID_URL_SUB_TUPLE
+ '''.format(*TeachableBaseIE._VALID_URL_SUB_TUPLE)
_TESTS = [{
'url': 'https://gns3.teachable.com/courses/gns3-certified-associate/lectures/6842364',
@@ -146,7 +146,7 @@ class TeachableIE(TeachableBaseIE):
if cls._is_teachable(webpage):
if re.match(r'https?://[^/]+/(?:courses|p)', url):
yield f'{cls._URL_PREFIX}{url}'
- raise cls.StopExtraction()
+ raise cls.StopExtraction
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -178,7 +178,7 @@ class TeachableIE(TeachableBaseIE):
chapter = None
chapter_number = None
section_item = self._search_regex(
- r'(?s)(?P<li><li[^>]+\bdata-lecture-id=["\']%s[^>]+>.+?</li>)' % video_id,
+ rf'(?s)(?P<li><li[^>]+\bdata-lecture-id=["\']{video_id}[^>]+>.+?</li>)',
webpage, 'section item', default=None, group='li')
if section_item:
chapter_number = int_or_none(self._search_regex(
@@ -211,11 +211,11 @@ class TeachableIE(TeachableBaseIE):
class TeachableCourseIE(TeachableBaseIE):
_VALID_URL = r'''(?x)
(?:
- %shttps?://(?P<site_t>[^/]+)|
- https?://(?:www\.)?(?P<site>%s)
+ {}https?://(?P<site_t>[^/]+)|
+ https?://(?:www\.)?(?P<site>{})
)
/(?:courses|p)/(?:enrolled/)?(?P<id>[^/?#&]+)
- ''' % TeachableBaseIE._VALID_URL_SUB_TUPLE
+ '''.format(*TeachableBaseIE._VALID_URL_SUB_TUPLE)
_TESTS = [{
'url': 'http://v1.upskillcourses.com/courses/essential-web-developer-course/',
'info_dict': {
@@ -242,8 +242,7 @@ class TeachableCourseIE(TeachableBaseIE):
@classmethod
def suitable(cls, url):
- return False if TeachableIE.suitable(url) else super(
- TeachableCourseIE, cls).suitable(url)
+ return False if TeachableIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -259,7 +258,7 @@ class TeachableCourseIE(TeachableBaseIE):
webpage = self._download_webpage(url, course_id)
- url_base = 'https://%s/' % site
+ url_base = f'https://{site}/'
entries = []
diff --git a/yt_dlp/extractor/teachertube.py b/yt_dlp/extractor/teachertube.py
index 740240993..4c26d31e0 100644
--- a/yt_dlp/extractor/teachertube.py
+++ b/yt_dlp/extractor/teachertube.py
@@ -50,7 +50,7 @@ class TeacherTubeIE(InfoExtractor):
r'<div\b[^>]+\bclass=["\']msgBox error[^>]+>([^<]+)', webpage,
'error', default=None)
if error:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error}', expected=True)
title = self._html_search_meta('title', webpage, 'title', fatal=True)
TITLE_SUFFIX = ' - TeacherTube'
@@ -70,7 +70,7 @@ class TeacherTubeIE(InfoExtractor):
formats = [
{
'url': media_url,
- 'quality': quality(determine_ext(media_url))
+ 'quality': quality(determine_ext(media_url)),
} for media_url in set(media_urls)
]
@@ -102,7 +102,7 @@ class TeacherTubeUserIE(InfoExtractor):
_TEST = {
'url': 'http://www.teachertube.com/user/profile/rbhagwati2',
'info_dict': {
- 'id': 'rbhagwati2'
+ 'id': 'rbhagwati2',
},
'playlist_mincount': 179,
}
@@ -115,10 +115,10 @@ class TeacherTubeUserIE(InfoExtractor):
webpage = self._download_webpage(url, user_id)
urls.extend(re.findall(self._MEDIA_RE, webpage))
- pages = re.findall(r'/ajax-user/user-videos/%s\?page=([0-9]+)' % user_id, webpage)[:-1]
+ pages = re.findall(rf'/ajax-user/user-videos/{user_id}\?page=([0-9]+)', webpage)[:-1]
for p in pages:
- more = 'http://www.teachertube.com/ajax-user/user-videos/%s?page=%s' % (user_id, p)
- webpage = self._download_webpage(more, user_id, 'Downloading page %s/%s' % (p, len(pages)))
+ more = f'http://www.teachertube.com/ajax-user/user-videos/{user_id}?page={p}'
+ webpage = self._download_webpage(more, user_id, f'Downloading page {p}/{len(pages)}')
video_urls = re.findall(self._MEDIA_RE, webpage)
urls.extend(video_urls)
diff --git a/yt_dlp/extractor/ted.py b/yt_dlp/extractor/ted.py
index 0969bbb03..8544c8bed 100644
--- a/yt_dlp/extractor/ted.py
+++ b/yt_dlp/extractor/ted.py
@@ -46,11 +46,11 @@ class TedTalkIE(TedBaseIE):
webpage = self._download_webpage(url, display_id)
talk_info = self._search_nextjs_data(webpage, display_id)['props']['pageProps']['videoData']
video_id = talk_info['id']
- playerData = self._parse_json(talk_info.get('playerData'), video_id)
+ player_data = self._parse_json(talk_info.get('playerData'), video_id)
http_url = None
formats, subtitles = [], {}
- for format_id, resources in (playerData.get('resources') or {}).items():
+ for format_id, resources in (player_data.get('resources') or {}).items():
if format_id == 'hls':
stream_url = url_or_none(try_get(resources, lambda x: x['stream']))
if not stream_url:
@@ -71,7 +71,7 @@ class TedTalkIE(TedBaseIE):
bitrate = int_or_none(resource.get('bitrate'))
formats.append({
'url': h264_url,
- 'format_id': '%s-%sk' % (format_id, bitrate),
+ 'format_id': f'{format_id}-{bitrate}k',
'tbr': bitrate,
})
if re.search(r'\d+k', h264_url):
@@ -81,7 +81,7 @@ class TedTalkIE(TedBaseIE):
if not streamer:
continue
formats.extend({
- 'format_id': '%s-%s' % (format_id, resource.get('name')),
+ 'format_id': '{}-{}'.format(format_id, resource.get('name')),
'url': streamer,
'play_path': resource['file'],
'ext': 'flv',
@@ -98,7 +98,7 @@ class TedTalkIE(TedBaseIE):
continue
bitrate_url = re.sub(r'\d+k', bitrate, http_url)
if not self._is_valid_url(
- bitrate_url, video_id, '%s bitrate' % bitrate):
+ bitrate_url, video_id, f'{bitrate} bitrate'):
continue
f = m3u8_format.copy()
f.update({
@@ -119,12 +119,12 @@ class TedTalkIE(TedBaseIE):
})
if not formats:
- external = playerData.get('external') or {}
+ external = player_data.get('external') or {}
service = external.get('service') or ''
ext_url = external.get('code') if service.lower() == 'youtube' else None
return self.url_result(ext_url or external['uri'])
- thumbnail = playerData.get('thumb') or self._og_search_property('image', webpage)
+ thumbnail = player_data.get('thumb') or self._og_search_property('image', webpage)
if thumbnail:
# trim thumbnail resize parameters
thumbnail = thumbnail.split('?')[0]
@@ -141,7 +141,7 @@ class TedTalkIE(TedBaseIE):
'view_count': str_to_int(talk_info.get('viewedCount')),
'upload_date': unified_strdate(talk_info.get('publishedAt')),
'release_date': unified_strdate(talk_info.get('recordedOn')),
- 'tags': try_get(playerData, lambda x: x['targeting']['tag'].split(',')),
+ 'tags': try_get(player_data, lambda x: x['targeting']['tag'].split(',')),
}
@@ -153,7 +153,7 @@ class TedSeriesIE(TedBaseIE):
'id': '3',
'title': 'Small Thing Big Idea',
'series': 'Small Thing Big Idea',
- 'description': 'md5:6869ca52cec661aef72b3e9f7441c55c'
+ 'description': 'md5:6869ca52cec661aef72b3e9f7441c55c',
},
'playlist_mincount': 16,
}, {
@@ -163,7 +163,7 @@ class TedSeriesIE(TedBaseIE):
'title': 'The Way We Work Season 2',
'series': 'The Way We Work',
'description': 'md5:59469256e533e1a48c4aa926a382234c',
- 'season_number': 2
+ 'season_number': 2,
},
'playlist_mincount': 8,
}]
@@ -194,7 +194,7 @@ class TedPlaylistIE(TedBaseIE):
'info_dict': {
'id': '171',
'title': 'The most popular talks of all time',
- 'description': 'md5:d2f22831dc86c7040e733a3cb3993d78'
+ 'description': 'md5:d2f22831dc86c7040e733a3cb3993d78',
},
'playlist_mincount': 25,
}]
diff --git a/yt_dlp/extractor/tele13.py b/yt_dlp/extractor/tele13.py
index 1705c2d55..c5ca208fb 100644
--- a/yt_dlp/extractor/tele13.py
+++ b/yt_dlp/extractor/tele13.py
@@ -36,7 +36,7 @@ class Tele13IE(InfoExtractor):
'uploader_id': 'UCnLY_3ezwNcDSC_Wc6suZxw',
},
'add_ie': ['Youtube'],
- }
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/telecaribe.py b/yt_dlp/extractor/telecaribe.py
index 91118a1a4..539187690 100644
--- a/yt_dlp/extractor/telecaribe.py
+++ b/yt_dlp/extractor/telecaribe.py
@@ -37,7 +37,7 @@ class TelecaribePlayIE(InfoExtractor):
},
'params': {
'skip_download': 'Livestream',
- }
+ },
}, {
'url': 'https://www.play.telecaribe.co/liveplus',
'info_dict': {
diff --git a/yt_dlp/extractor/telecinco.py b/yt_dlp/extractor/telecinco.py
index a3f0c7cda..7a9dcd71c 100644
--- a/yt_dlp/extractor/telecinco.py
+++ b/yt_dlp/extractor/telecinco.py
@@ -29,7 +29,7 @@ class TelecincoIE(InfoExtractor):
'title': 'Con Martín Berasategui, hacer un bacalao al pil-pil es fácil y divertido',
'duration': 662,
},
- }]
+ }],
}, {
'url': 'http://www.cuatro.com/deportes/futbol/barcelona/Leo_Messi-Champions-Roma_2_2052780128.html',
'md5': 'c86fe0d99e3bdb46b7950d38bf6ef12a',
diff --git a/yt_dlp/extractor/telegraaf.py b/yt_dlp/extractor/telegraaf.py
index 13e9515f8..c0bcdc206 100644
--- a/yt_dlp/extractor/telegraaf.py
+++ b/yt_dlp/extractor/telegraaf.py
@@ -40,11 +40,11 @@ class TelegraafIE(InfoExtractor):
videoId
}
}
-}''' % article_id,
+}''' % article_id, # noqa: UP031
})['data']['article']['videos'][0]['videoId']
item = self._download_json(
- 'https://content.tmgvideo.nl/playlist/item=%s/playlist.json' % video_id,
+ f'https://content.tmgvideo.nl/playlist/item={video_id}/playlist.json',
video_id)['items'][0]
title = item['title']
@@ -62,7 +62,7 @@ class TelegraafIE(InfoExtractor):
formats.extend(self._extract_mpd_formats(
manifest_url, video_id, mpd_id='dash', fatal=False))
else:
- self.report_warning('Unknown adaptive format %s' % ext)
+ self.report_warning(f'Unknown adaptive format {ext}')
for location in locations.get('progressive', []):
src = try_get(location, lambda x: x['sources'][0]['src'])
if not src:
@@ -72,7 +72,7 @@ class TelegraafIE(InfoExtractor):
'url': src,
'width': int_or_none(location.get('width')),
'height': int_or_none(location.get('height')),
- 'format_id': 'http' + ('-%s' % label if label else ''),
+ 'format_id': 'http' + (f'-{label}' if label else ''),
})
return {
diff --git a/yt_dlp/extractor/telegram.py b/yt_dlp/extractor/telegram.py
index 5ec54857d..4ec8bcc75 100644
--- a/yt_dlp/extractor/telegram.py
+++ b/yt_dlp/extractor/telegram.py
@@ -62,7 +62,7 @@ class TelegramEmbedIE(InfoExtractor):
},
'params': {
'noplaylist': True,
- }
+ },
}, {
# 2-video post with 'single' query param
'url': 'https://t.me/vorposte/29342?single',
diff --git a/yt_dlp/extractor/telemb.py b/yt_dlp/extractor/telemb.py
index a71b14c27..664464844 100644
--- a/yt_dlp/extractor/telemb.py
+++ b/yt_dlp/extractor/telemb.py
@@ -18,7 +18,7 @@ class TeleMBIE(InfoExtractor):
'title': 'Mons - Cook with Danielle : des cours de cuisine en anglais ! - Les reportages',
'description': 'md5:bc5225f47b17c309761c856ad4776265',
'thumbnail': r're:^http://.*\.(?:jpg|png)$',
- }
+ },
},
{
# non-ASCII characters in download URL
@@ -31,7 +31,7 @@ class TeleMBIE(InfoExtractor):
'title': 'Havré - Incendie mortel - Les reportages',
'description': 'md5:5e54cb449acb029c2b7734e2d946bd4a',
'thumbnail': r're:^http://.*\.(?:jpg|png)$',
- }
+ },
},
]
@@ -46,7 +46,7 @@ class TeleMBIE(InfoExtractor):
for video_url in re.findall(r'file\s*:\s*"([^"]+)"', webpage):
fmt = {
'url': video_url,
- 'format_id': video_url.split(':')[0]
+ 'format_id': video_url.split(':')[0],
}
rtmp = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+))/(?P<playpath>mp4:.+)$', video_url)
if rtmp:
diff --git a/yt_dlp/extractor/telemundo.py b/yt_dlp/extractor/telemundo.py
index 84b24dead..7f4cecd51 100644
--- a/yt_dlp/extractor/telemundo.py
+++ b/yt_dlp/extractor/telemundo.py
@@ -19,7 +19,7 @@ class TelemundoIE(InfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://www.telemundo.com/shows/al-rojo-vivo/empleo/video/personajes-de-times-square-piden-que-la-ciudad-de-nueva-york-los-deje-volver-trabajar-tmvo9816272',
'only_matching': True,
@@ -46,5 +46,5 @@ class TelemundoIE(InfoExtractor):
'formats': formats,
'timestamp': date,
'uploader': 'Telemundo',
- 'uploader_id': self._search_regex(r'https?:\/\/(?:[^/]+\/){3}video\/(?P<id>[^\/]+)', m3u8_url, 'Akamai account', fatal=False)
+ 'uploader_id': self._search_regex(r'https?:\/\/(?:[^/]+\/){3}video\/(?P<id>[^\/]+)', m3u8_url, 'Akamai account', fatal=False),
}
diff --git a/yt_dlp/extractor/telequebec.py b/yt_dlp/extractor/telequebec.py
index 08a083714..7f5d5d29b 100644
--- a/yt_dlp/extractor/telequebec.py
+++ b/yt_dlp/extractor/telequebec.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
int_or_none,
smuggle_url,
@@ -72,7 +71,7 @@ class TeleQuebecIE(TeleQuebecBaseIE):
product = media.get('product') or {}
season = product.get('season') or {}
info.update({
- 'description': try_get(media, lambda x: x['descriptions'][-1]['text'], compat_str),
+ 'description': try_get(media, lambda x: x['descriptions'][-1]['text'], str),
'series': try_get(season, lambda x: x['serie']['titre']),
'season': season.get('name'),
'season_number': int_or_none(season.get('seasonNo')),
@@ -108,14 +107,14 @@ class TeleQuebecSquatIE(InfoExtractor):
video_id = self._match_id(url)
video = self._download_json(
- 'https://squat.api.telequebec.tv/v1/videos/%s' % video_id,
+ f'https://squat.api.telequebec.tv/v1/videos/{video_id}',
video_id)
media_id = video['sourceId']
return {
'_type': 'url_transparent',
- 'url': 'http://zonevideo.telequebec.tv/media/%s' % media_id,
+ 'url': f'http://zonevideo.telequebec.tv/media/{media_id}',
'ie_key': TeleQuebecIE.ie_key(),
'id': media_id,
'title': video.get('titre'),
diff --git a/yt_dlp/extractor/teletask.py b/yt_dlp/extractor/teletask.py
index fd831f580..050196c57 100644
--- a/yt_dlp/extractor/teletask.py
+++ b/yt_dlp/extractor/teletask.py
@@ -20,7 +20,7 @@ class TeleTaskIE(InfoExtractor):
'ext': 'mp4',
'title': 'Duplicate Detection',
'upload_date': '20141218',
- }
+ },
}, {
'md5': 'e1e7218c5f0e4790015a437fcf6c71b4',
'info_dict': {
@@ -28,8 +28,8 @@ class TeleTaskIE(InfoExtractor):
'ext': 'mp4',
'title': 'Duplicate Detection',
'upload_date': '20141218',
- }
- }]
+ },
+ }],
}
def _real_extract(self, url):
@@ -42,7 +42,7 @@ class TeleTaskIE(InfoExtractor):
r'Date:</td><td>([^<]+)</td>', webpage, 'date', fatal=False))
entries = [{
- 'id': '%s-%s' % (lecture_id, format_id),
+ 'id': f'{lecture_id}-{format_id}',
'url': video_url,
'title': title,
'upload_date': upload_date,
diff --git a/yt_dlp/extractor/telewebion.py b/yt_dlp/extractor/telewebion.py
index 380c84d98..b65116024 100644
--- a/yt_dlp/extractor/telewebion.py
+++ b/yt_dlp/extractor/telewebion.py
@@ -72,7 +72,7 @@ class TelewebionIE(InfoExtractor):
result = self._download_json('https://graph.telewebion.com/graphql', video_id, note, data=json.dumps({
'operationName': operation,
'query': f'query {operation}{parameters} @cacheControl(maxAge: 60) {{{query}\n}}\n',
- 'variables': {name: value for name, (_, value) in (variables or {}).items()}
+ 'variables': {name: value for name, (_, value) in (variables or {}).items()},
}, separators=(',', ':')).encode(), headers={
'Content-Type': 'application/json',
'Accept': 'application/json',
diff --git a/yt_dlp/extractor/tempo.py b/yt_dlp/extractor/tempo.py
index 71e54eb0c..4cd16f240 100644
--- a/yt_dlp/extractor/tempo.py
+++ b/yt_dlp/extractor/tempo.py
@@ -20,8 +20,8 @@ class IVXPlayerIE(InfoExtractor):
'upload_date': '20221204',
'title': 'Film Indonesia di Disney Content Showcase Asia Pacific 2022',
'timestamp': 1670151746,
- 'thumbnail': 'https://ivx-image.ivideosmart.com/serve/image/video/2366065?width=300'
- }
+ 'thumbnail': 'https://ivx-image.ivideosmart.com/serve/image/video/2366065?width=300',
+ },
}]
_WEBPAGE_TESTS = [{
'url': 'https://www.cantika.com/video/31737/film-indonesia-di-disney-content-showcase-asia-pacific-2022',
@@ -32,8 +32,8 @@ class IVXPlayerIE(InfoExtractor):
'title': 'Serial Indonesia di Disney Content Showcase Asia Pacific 2022',
'timestamp': 1670639416,
'upload_date': '20221210',
- 'thumbnail': 'https://ivx-image.ivideosmart.com/serve/image/video/2374200?width=300'
- }
+ 'thumbnail': 'https://ivx-image.ivideosmart.com/serve/image/video/2374200?width=300',
+ },
}, {
'url': 'https://www.gooto.com/video/11437/wuling-suv-ramai-dikunjungi-di-giias-2018',
'info_dict': {
@@ -44,8 +44,8 @@ class IVXPlayerIE(InfoExtractor):
'description': 'md5:6d901483d0aacc664aecb4489719aafa',
'duration': 75,
'timestamp': 1534011263,
- 'thumbnail': 'https://ivx-image.ivideosmart.com/serve/image/video/892109?width=300'
- }
+ 'thumbnail': 'https://ivx-image.ivideosmart.com/serve/image/video/892109?width=300',
+ },
}]
@classmethod
@@ -56,7 +56,7 @@ class IVXPlayerIE(InfoExtractor):
webpage)
if mobj:
yield f'ivxplayer:{mobj.group("video_id")}:{mobj.group("player_key")}'
- raise cls.StopExtraction()
+ raise cls.StopExtraction
def _real_extract(self, url):
video_id, player_key = self._match_valid_url(url).group('video_id', 'player_key')
@@ -74,7 +74,7 @@ class IVXPlayerIE(InfoExtractor):
'timestamp': parse_iso8601(traverse_obj(json_data, ('ivx', 'published_at'))),
'formats': formats,
'subtitles': subtitles,
- 'thumbnail': traverse_obj(json_data, ('ivx', 'thumbnail_url'))
+ 'thumbnail': traverse_obj(json_data, ('ivx', 'thumbnail_url')),
}
@@ -93,7 +93,7 @@ class TempoIE(InfoExtractor):
'timestamp': 1658907970,
'upload_date': '20220727',
'tags': ['Anies Baswedan', ' PTUN', ' PTUN | Pengadilan Tata Usaha Negara', ' PTUN Batalkan UMP DKI', ' UMP DKI'],
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/tencent.py b/yt_dlp/extractor/tencent.py
index ae2cb483f..fc2b07ac2 100644
--- a/yt_dlp/extractor/tencent.py
+++ b/yt_dlp/extractor/tencent.py
@@ -25,7 +25,7 @@ class TencentBaseIE(InfoExtractor):
if api_response.get('code') != '0.0' and msg is not None:
if msg in (
'您所在区域暂无此内容版权(如设置VPN请关闭后重试)',
- 'This content is not available in your area due to copyright restrictions. Please choose other videos.'
+ 'This content is not available in your area due to copyright restrictions. Please choose other videos.',
):
self.raise_geo_restricted()
raise ExtractorError(f'Tencent said: {msg}')
diff --git a/yt_dlp/extractor/tennistv.py b/yt_dlp/extractor/tennistv.py
index c1b4a3312..197d7892d 100644
--- a/yt_dlp/extractor/tennistv.py
+++ b/yt_dlp/extractor/tennistv.py
@@ -47,7 +47,7 @@ class TennisTVIE(InfoExtractor):
_HEADERS = {
'origin': 'https://www.tennistv.com',
'referer': 'https://www.tennistv.com/',
- 'content-Type': 'application/x-www-form-urlencoded'
+ 'content-Type': 'application/x-www-form-urlencoded',
}
def _perform_login(self, username, password):
@@ -58,7 +58,7 @@ class TennisTVIE(InfoExtractor):
'redirect_uri': 'https://tennistv.com',
'response_mode': 'fragment',
'response_type': 'code',
- 'scope': 'openid'
+ 'scope': 'openid',
})
post_url = self._html_search_regex(r'action=["\']([^"\']+?)["\']\s+method=["\']post["\']', login_page, 'login POST url')
@@ -67,7 +67,7 @@ class TennisTVIE(InfoExtractor):
headers=self._HEADERS, data=urlencode_postdata({
'username': username,
'password': password,
- 'submitAction': 'Log In'
+ 'submitAction': 'Log In',
}))
if 'Your username or password was incorrect' in temp_page:
raise ExtractorError('Your username or password was incorrect', expected=True)
@@ -82,14 +82,14 @@ class TennisTVIE(InfoExtractor):
'response_type': 'code',
'scope': 'openid',
'nonce': random_uuidv4(),
- 'prompt': 'none'
+ 'prompt': 'none',
})
self.get_token(None, {
'code': urllib.parse.parse_qs(handle.url)['code'][-1],
'grant_type': 'authorization_code',
'client_id': 'tennis-tv-web',
- 'redirect_uri': 'https://www.tennistv.com/resources/v1.1.10/html/silent-check-sso.html'
+ 'redirect_uri': 'https://www.tennistv.com/resources/v1.1.10/html/silent-check-sso.html',
})
def get_token(self, video_id, payload):
@@ -109,7 +109,7 @@ class TennisTVIE(InfoExtractor):
self.raise_login_required()
self.access_token, self.refresh_token = cookies['access_token'].value, cookies['refresh_token'].value
- def _download_session_json(self, video_id, entryid,):
+ def _download_session_json(self, video_id, entryid):
return self._download_json(
f'https://atppayments.streamamg.com/api/v1/session/ksession/?lang=en&apijwttoken={self.access_token}&entryId={entryid}',
video_id, 'Downloading ksession token', 'Failed to download ksession token', headers=self._HEADERS)
@@ -126,7 +126,7 @@ class TennisTVIE(InfoExtractor):
self.get_token(video_id, {
'grant_type': 'refresh_token',
'refresh_token': self.refresh_token,
- 'client_id': 'tennis-tv-web'
+ 'client_id': 'tennis-tv-web',
})
k_session = self._download_session_json(video_id, entryid).get('KSession')
if k_session is None:
diff --git a/yt_dlp/extractor/tenplay.py b/yt_dlp/extractor/tenplay.py
index 11cc5705e..d8c556ace 100644
--- a/yt_dlp/extractor/tenplay.py
+++ b/yt_dlp/extractor/tenplay.py
@@ -45,11 +45,11 @@ class TenPlayIE(InfoExtractor):
'timestamp': 1600770600,
'upload_date': '20200922',
'uploader': 'Channel 10',
- 'uploader_id': '2199827728001'
+ 'uploader_id': '2199827728001',
},
'params': {
'skip_download': True,
- }
+ },
}, {
'url': 'https://10play.com.au/how-to-stay-married/web-extras/season-1/terrys-talks-ep-1-embracing-change/tpv190915ylupc',
'only_matching': True,
@@ -63,7 +63,7 @@ class TenPlayIE(InfoExtractor):
'MA': 15,
'MA15+': 15,
'R': 18,
- 'X': 18
+ 'X': 18,
}
def _get_bearer_token(self, video_id):
diff --git a/yt_dlp/extractor/testurl.py b/yt_dlp/extractor/testurl.py
index 3cf001776..31e3c4d9c 100644
--- a/yt_dlp/extractor/testurl.py
+++ b/yt_dlp/extractor/testurl.py
@@ -30,7 +30,7 @@ class TestURLIE(InfoExtractor):
), None)
if not extractor:
raise ExtractorError(
- 'Found multiple matching extractors: %s' % ' '.join(ie.IE_NAME for ie in matching_extractors),
+ 'Found multiple matching extractors: {}'.format(' '.join(ie.IE_NAME for ie in matching_extractors)),
expected=True)
else:
extractor = matching_extractors[0]
diff --git a/yt_dlp/extractor/tf1.py b/yt_dlp/extractor/tf1.py
index aba4927ae..5bade3ae5 100644
--- a/yt_dlp/extractor/tf1.py
+++ b/yt_dlp/extractor/tf1.py
@@ -43,7 +43,7 @@ class TF1IE(InfoExtractor):
'season': 'Season 3',
'tags': 'count:13',
'episode': 'Episode 21',
- 'duration': 2312
+ 'duration': 2312,
},
'params': {'skip_download': 'm3u8'},
}, {
@@ -62,7 +62,7 @@ class TF1IE(InfoExtractor):
'variables': json.dumps({
'programSlug': program_slug,
'slug': slug,
- })
+ }),
})['data']['videoBySlug']
wat_id = video['streamId']
diff --git a/yt_dlp/extractor/tfo.py b/yt_dlp/extractor/tfo.py
index d417f50e1..0d1b25217 100644
--- a/yt_dlp/extractor/tfo.py
+++ b/yt_dlp/extractor/tfo.py
@@ -16,7 +16,7 @@ class TFOIE(InfoExtractor):
'ext': 'mp4',
'title': 'Video Game Hackathon',
'description': 'md5:558afeba217c6c8d96c60e5421795c07',
- }
+ },
}
def _real_extract(self, url):
@@ -31,7 +31,7 @@ class TFOIE(InfoExtractor):
if infos.get('success') == 0:
if infos.get('code') == 'ErrGeoBlocked':
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
- raise ExtractorError('%s said: %s' % (self.IE_NAME, clean_html(infos['msg'])), expected=True)
+ raise ExtractorError('{} said: {}'.format(self.IE_NAME, clean_html(infos['msg'])), expected=True)
video_data = infos['data']
return {
diff --git a/yt_dlp/extractor/theguardian.py b/yt_dlp/extractor/theguardian.py
index fb6407715..a9e499064 100644
--- a/yt_dlp/extractor/theguardian.py
+++ b/yt_dlp/extractor/theguardian.py
@@ -26,8 +26,8 @@ class TheGuardianPodcastIE(InfoExtractor):
'description': 'md5:cfd3df2791d394d2ab62cd571d5207ee',
'creator': 'Stephen Buranyi',
'thumbnail': 'md5:73c12558fcb3b0e2a59422bfb33b3f79',
- 'release_date': '20231103'
- }
+ 'release_date': '20231103',
+ },
}, {
'url': 'https://www.theguardian.com/news/audio/2023/oct/30/the-trials-of-robert-habeck-is-the-worlds-most-powerful-green-politician-doomed-to-fail-podcast',
'md5': 'd1771744681789b4cd7da2a08e487702',
@@ -38,8 +38,8 @@ class TheGuardianPodcastIE(InfoExtractor):
'description': 'md5:1b5cf6582d1771c6b7077784b5456994',
'creator': 'Philip Oltermann',
'thumbnail': 'md5:6e5c5ec43843e956e20be793722e9080',
- 'release_date': '20231030'
- }
+ 'release_date': '20231030',
+ },
}, {
'url': 'https://www.theguardian.com/football/audio/2023/nov/06/arsenal-feel-hard-done-by-and-luton-hold-liverpool-football-weekly',
'md5': 'a2fcff6f8e060a95b1483295273dc35e',
@@ -50,8 +50,8 @@ class TheGuardianPodcastIE(InfoExtractor):
'description': 'md5:286a9fbddaeb7c83cc65d1c4a5330b2a',
'creator': 'Max Rushden',
'thumbnail': 'md5:93eb7d6440f1bb94eb3a6cad63f48afd',
- 'release_date': '20231106'
- }
+ 'release_date': '20231106',
+ },
}, {
'url': 'https://www.theguardian.com/politics/audio/2023/nov/02/the-covid-inquiry-politics-weekly-uk-podcast',
'md5': '06a0f7e9701a80c8064a5d35690481ec',
@@ -62,8 +62,8 @@ class TheGuardianPodcastIE(InfoExtractor):
'description': 'md5:207c98859c14903582b17d25b014046e',
'creator': 'Gaby Hinsliff',
'thumbnail': 'md5:28932a7b5a25b057be330d2ed70ea7f3',
- 'release_date': '20231102'
- }
+ 'release_date': '20231102',
+ },
}]
def _real_extract(self, url):
@@ -88,25 +88,25 @@ class TheGuardianPodcastPlaylistIE(InfoExtractor):
'info_dict': {
'id': 'theguardianswomensfootballweekly',
'title': "The Guardian's Women's Football Weekly",
- 'description': 'md5:e2cc021311e582d29935a73614a43f51'
+ 'description': 'md5:e2cc021311e582d29935a73614a43f51',
},
- 'playlist_mincount': 69
+ 'playlist_mincount': 69,
}, {
'url': 'https://www.theguardian.com/news/series/todayinfocus?page=2',
'info_dict': {
'id': 'todayinfocus',
'title': 'Today in Focus',
- 'description': 'md5:0f097764fc0d359e0b6eb537be0387e2'
+ 'description': 'md5:0f097764fc0d359e0b6eb537be0387e2',
},
- 'playlist_mincount': 1261
+ 'playlist_mincount': 1261,
}, {
'url': 'https://www.theguardian.com/news/series/the-audio-long-read',
'info_dict': {
'id': 'the-audio-long-read',
'title': 'The Audio Long Read',
- 'description': 'md5:5462994a27527309562b25b6defc4ef3'
+ 'description': 'md5:5462994a27527309562b25b6defc4ef3',
},
- 'playlist_mincount': 996
+ 'playlist_mincount': 996,
}]
def _entries(self, url, playlist_id):
@@ -117,8 +117,7 @@ class TheGuardianPodcastPlaylistIE(InfoExtractor):
break
episodes = get_elements_html_by_class('fc-item--type-media', webpage)
- for url_path in traverse_obj(episodes, (..., {extract_attributes}, 'data-id')):
- yield url_path
+ yield from traverse_obj(episodes, (..., {extract_attributes}, 'data-id'))
def _real_extract(self, url):
podcast_id = self._match_id(url)
diff --git a/yt_dlp/extractor/theholetv.py b/yt_dlp/extractor/theholetv.py
index a13f83bff..a3a702428 100644
--- a/yt_dlp/extractor/theholetv.py
+++ b/yt_dlp/extractor/theholetv.py
@@ -12,8 +12,8 @@ class TheHoleTvIE(InfoExtractor):
'ext': 'mp4',
'title': 'Сергей Орлов — Громкий вопрос',
'thumbnail': 'https://assets-cdn.the-hole.tv/images/t8gan4n6zn627e7wni11b2uemqts',
- 'description': 'md5:45741a9202331f995d9fb76996759379'
- }
+ 'description': 'md5:45741a9202331f995d9fb76996759379',
+ },
}]
def _real_extract(self, url):
@@ -31,5 +31,5 @@ class TheHoleTvIE(InfoExtractor):
'description': self._og_search_description(webpage),
'thumbnail': player_attrs.get('data-player-poster-value'),
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
diff --git a/yt_dlp/extractor/theintercept.py b/yt_dlp/extractor/theintercept.py
index 99f0d42ef..dcdca8f81 100644
--- a/yt_dlp/extractor/theintercept.py
+++ b/yt_dlp/extractor/theintercept.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -20,7 +19,7 @@ class TheInterceptIE(InfoExtractor):
'timestamp': 1450429239,
'upload_date': '20151218',
'comment_count': int,
- }
+ },
}]
def _real_extract(self, url):
@@ -35,8 +34,8 @@ class TheInterceptIE(InfoExtractor):
if post['slug'] == display_id:
return {
'_type': 'url_transparent',
- 'url': 'jwplatform:%s' % post['fov_videoid'],
- 'id': compat_str(post['ID']),
+ 'url': 'jwplatform:{}'.format(post['fov_videoid']),
+ 'id': str(post['ID']),
'display_id': display_id,
'title': post['title'],
'description': post.get('excerpt'),
diff --git a/yt_dlp/extractor/theplatform.py b/yt_dlp/extractor/theplatform.py
index eeb33a660..7c1769c2d 100644
--- a/yt_dlp/extractor/theplatform.py
+++ b/yt_dlp/extractor/theplatform.py
@@ -1,4 +1,3 @@
-import binascii
import hashlib
import hmac
import re
@@ -42,8 +41,7 @@ class ThePlatformBaseIE(OnceIE):
if exception.get('value') == 'GeoLocationBlocked':
self.raise_geo_restricted(error_element.attrib['abstract'])
elif error_element.attrib['src'].startswith(
- 'http://link.theplatform.%s/s/errorFiles/Unavailable.'
- % self._TP_TLD):
+ f'http://link.theplatform.{self._TP_TLD}/s/errorFiles/Unavailable.'):
raise ExtractorError(
error_element.attrib['abstract'], expected=True)
@@ -70,7 +68,7 @@ class ThePlatformBaseIE(OnceIE):
return formats, subtitles
def _download_theplatform_metadata(self, path, video_id):
- info_url = 'http://link.theplatform.%s/s/%s?format=preview' % (self._TP_TLD, path)
+ info_url = f'http://link.theplatform.{self._TP_TLD}/s/{path}?format=preview'
return self._download_json(info_url, video_id)
def _parse_theplatform_metadata(self, info):
@@ -140,7 +138,7 @@ class ThePlatformIE(ThePlatformBaseIE, AdobePassIE):
<meta\s+
property=(["'])(?:og:video(?::(?:secure_)?url)?|twitter:player)\1\s+
content=(["'])(?P<url>https?://player\.theplatform\.com/p/.+?)\2''',
- r'(?s)<(?:iframe|script)[^>]+src=(["\'])(?P<url>(?:https?:)?//player\.theplatform\.com/p/.+?)\1'
+ r'(?s)<(?:iframe|script)[^>]+src=(["\'])(?P<url>(?:https?:)?//player\.theplatform\.com/p/.+?)\1',
]
_TESTS = [{
@@ -225,17 +223,14 @@ class ThePlatformIE(ThePlatformBaseIE, AdobePassIE):
flags = '10' if include_qs else '00'
expiration_date = '%x' % (int(time.time()) + life)
- def str_to_hex(str):
- return binascii.b2a_hex(str.encode('ascii')).decode('ascii')
-
- def hex_to_bytes(hex):
- return binascii.a2b_hex(hex.encode('ascii'))
+ def str_to_hex(str_data):
+ return str_data.encode('ascii').hex()
relative_path = re.match(r'https?://link\.theplatform\.com/s/([^?]+)', url).group(1)
- clear_text = hex_to_bytes(flags + expiration_date + str_to_hex(relative_path))
+ clear_text = bytes.fromhex(flags + expiration_date + str_to_hex(relative_path))
checksum = hmac.new(sig_key.encode('ascii'), clear_text, hashlib.sha1).hexdigest()
sig = flags + expiration_date + checksum + str_to_hex(sig_secret)
- return '%s&sig=%s' % (url, sig)
+ return f'{url}&sig={sig}'
def _real_extract(self, url):
url, smuggled_data = unsmuggle_url(url, {})
@@ -274,7 +269,7 @@ class ThePlatformIE(ThePlatformBaseIE, AdobePassIE):
break
if feed_id is None:
raise ExtractorError('Unable to find feed id')
- return self.url_result('http://feed.theplatform.com/f/%s/%s?byGuid=%s' % (
+ return self.url_result('http://feed.theplatform.com/f/{}/{}?byGuid={}'.format(
provider_id, feed_id, qs_dict['guid'][0]))
if smuggled_data.get('force_smil_url', False):
@@ -298,13 +293,10 @@ class ThePlatformIE(ThePlatformBaseIE, AdobePassIE):
config_url = config_url.replace('swf/', 'config/')
config_url = config_url.replace('onsite/', 'onsite/config/')
config = self._download_json(config_url, video_id, 'Downloading config')
- if 'releaseUrl' in config:
- release_url = config['releaseUrl']
- else:
- release_url = 'http://link.theplatform.com/s/%s?mbr=true' % path
+ release_url = config.get('releaseUrl') or f'http://link.theplatform.com/s/{path}?mbr=true'
smil_url = release_url + '&formats=MPEG4&manifest=f4m'
else:
- smil_url = 'http://link.theplatform.com/s/%s?mbr=true' % path
+ smil_url = f'http://link.theplatform.com/s/{path}?mbr=true'
sig = smuggled_data.get('sig')
if sig:
@@ -387,7 +379,7 @@ class ThePlatformFeedIE(ThePlatformBaseIE):
if asset_type in asset_types_query:
query.update(asset_types_query[asset_type])
cur_formats, cur_subtitles = self._extract_theplatform_smil(update_url_query(
- main_smil_url or smil_url, query), video_id, 'Downloading SMIL data for %s' % asset_type)
+ main_smil_url or smil_url, query), video_id, f'Downloading SMIL data for {asset_type}')
formats.extend(cur_formats)
subtitles = self._merge_subtitles(subtitles, cur_subtitles)
@@ -400,7 +392,7 @@ class ThePlatformFeedIE(ThePlatformBaseIE):
timestamp = int_or_none(entry.get('media$availableDate'), scale=1000)
categories = [item['media$name'] for item in entry.get('media$categories', [])]
- ret = self._extract_theplatform_metadata('%s/%s' % (provider_id, first_video_id), video_id)
+ ret = self._extract_theplatform_metadata(f'{provider_id}/{first_video_id}', video_id)
subtitles = self._merge_subtitles(subtitles, ret['subtitles'])
ret.update({
'id': video_id,
diff --git a/yt_dlp/extractor/thestar.py b/yt_dlp/extractor/thestar.py
index 293c34c06..38aa695a5 100644
--- a/yt_dlp/extractor/thestar.py
+++ b/yt_dlp/extractor/thestar.py
@@ -18,7 +18,7 @@ class TheStarIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/794267642001/default_default/index.html?videoId=%s'
diff --git a/yt_dlp/extractor/theweatherchannel.py b/yt_dlp/extractor/theweatherchannel.py
index d1921e4f9..424f778a2 100644
--- a/yt_dlp/extractor/theweatherchannel.py
+++ b/yt_dlp/extractor/theweatherchannel.py
@@ -24,7 +24,7 @@ class TheWeatherChannelIE(ThePlatformIE): # XXX: Do not subclass from concrete
'timestamp': 1689967343,
'display_id': 'invest-95l-in-atlantic-has-a-medium-chance-of-development',
'duration': 34.0,
- }
+ },
}, {
'url': 'https://weather.com/en-CA/international/videos/video/unidentified-object-falls-from-sky-in-india',
'only_matching': True,
@@ -34,7 +34,7 @@ class TheWeatherChannelIE(ThePlatformIE): # XXX: Do not subclass from concrete
asset_name, locale, display_id = self._match_valid_url(url).groups()
if not locale:
locale = 'en-US'
- video_data = list(self._download_json(
+ video_data = next(iter(self._download_json(
'https://weather.com/api/v1/p/redux-dal', display_id, data=json.dumps([{
'name': 'getCMSAssetsUrlConfig',
'params': {
@@ -44,10 +44,10 @@ class TheWeatherChannelIE(ThePlatformIE): # XXX: Do not subclass from concrete
'$in': asset_name,
},
},
- }
+ },
}]).encode(), headers={
'Content-Type': 'application/json',
- })['dal']['getCMSAssetsUrlConfig'].values())[0]['data'][0]
+ })['dal']['getCMSAssetsUrlConfig'].values()))['data'][0]
video_id = video_data['id']
seo_meta = video_data.get('seometa', {})
title = video_data.get('title') or seo_meta['title']
diff --git a/yt_dlp/extractor/thisamericanlife.py b/yt_dlp/extractor/thisamericanlife.py
index 9a3d79840..8b5d1e616 100644
--- a/yt_dlp/extractor/thisamericanlife.py
+++ b/yt_dlp/extractor/thisamericanlife.py
@@ -22,11 +22,11 @@ class ThisAmericanLifeIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'http://www.thisamericanlife.org/radio-archives/episode/%s' % video_id, video_id)
+ f'http://www.thisamericanlife.org/radio-archives/episode/{video_id}', video_id)
return {
'id': video_id,
- 'url': 'http://stream.thisamericanlife.org/{0}/stream/{0}_64k.m3u8'.format(video_id),
+ 'url': f'http://stream.thisamericanlife.org/{video_id}/stream/{video_id}_64k.m3u8',
'protocol': 'm3u8_native',
'ext': 'm4a',
'acodec': 'aac',
diff --git a/yt_dlp/extractor/thisvid.py b/yt_dlp/extractor/thisvid.py
index 04b083811..2fd6ae83a 100644
--- a/yt_dlp/extractor/thisvid.py
+++ b/yt_dlp/extractor/thisvid.py
@@ -27,7 +27,7 @@ class ThisVidIE(InfoExtractor):
'uploader': 'jeanslevisjeans',
'display_id': 'sitting-on-ball-tight-jeans',
'age_limit': 18,
- }
+ },
}, {
'url': 'https://thisvid.com/embed/3533241/',
'md5': '839becb572995687e11a69dc4358a386',
@@ -40,7 +40,7 @@ class ThisVidIE(InfoExtractor):
'uploader': 'jeanslevisjeans',
'display_id': 'sitting-on-ball-tight-jeans',
'age_limit': 18,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/threeqsdn.py b/yt_dlp/extractor/threeqsdn.py
index f7a13d2c3..45fdef907 100644
--- a/yt_dlp/extractor/threeqsdn.py
+++ b/yt_dlp/extractor/threeqsdn.py
@@ -14,7 +14,7 @@ class ThreeQSDNIE(InfoExtractor):
IE_NAME = '3qsdn'
IE_DESC = '3Q SDN'
_VALID_URL = r'https?://playout\.3qsdn\.com/(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
- _EMBED_REGEX = [r'<iframe[^>]+\b(?:data-)?src=(["\'])(?P<url>%s.*?)\1' % _VALID_URL]
+ _EMBED_REGEX = [rf'<iframe[^>]+\b(?:data-)?src=(["\'])(?P<url>{_VALID_URL}.*?)\1']
_TESTS = [{
# https://player.3qsdn.com/demo.html
'url': 'https://playout.3qsdn.com/7201c779-6b3c-11e7-a40e-002590c750be',
@@ -121,7 +121,7 @@ class ThreeQSDNIE(InfoExtractor):
height = int_or_none(s.get('height'))
formats.append({
'ext': ext,
- 'format_id': join_nonempty('http', ext, height and '%dp' % height),
+ 'format_id': join_nonempty('http', ext, height and f'{height}p'),
'height': height,
'source_preference': 0,
'url': src,
@@ -152,5 +152,5 @@ class ThreeQSDNIE(InfoExtractor):
# It seems like this would be correctly handled by default
# However, unless someone can confirm this, the old
# behaviour is being kept as-is
- '_format_sort_fields': ('res', 'source_preference')
+ '_format_sort_fields': ('res', 'source_preference'),
}
diff --git a/yt_dlp/extractor/threespeak.py b/yt_dlp/extractor/threespeak.py
index dbd509087..32ea17786 100644
--- a/yt_dlp/extractor/threespeak.py
+++ b/yt_dlp/extractor/threespeak.py
@@ -24,32 +24,32 @@ class ThreeSpeakIE(InfoExtractor):
'duration': 2703.867833,
'filesize': 1620054781,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
json_str = self._html_search_regex(r'JSON\.parse\(\'([^\']+)\'\)', webpage, 'json')
# The json string itself is escaped. Hence the double parsing
- data_json = self._parse_json(self._parse_json(f'"{json_str}"', id), id)
- video_json = self._parse_json(data_json['json_metadata'], id)
+ data_json = self._parse_json(self._parse_json(f'"{json_str}"', video_id), video_id)
+ video_json = self._parse_json(data_json['json_metadata'], video_id)
formats, subtitles = [], {}
og_m3u8 = self._html_search_regex(r'<meta\s?property=\"ogvideo\"\s?content=\"([^\"]+)\">', webpage, 'og m3u8', fatal=False)
if og_m3u8:
- https_frmts, https_subs = self._extract_m3u8_formats_and_subtitles(og_m3u8, id, fatal=False, m3u8_id='https')
+ https_frmts, https_subs = self._extract_m3u8_formats_and_subtitles(og_m3u8, video_id, fatal=False, m3u8_id='https')
formats.extend(https_frmts)
subtitles = self._merge_subtitles(subtitles, https_subs)
ipfs_m3u8 = try_get(video_json, lambda x: x['video']['info']['ipfs'])
if ipfs_m3u8:
- ipfs_frmts, ipfs_subs = self._extract_m3u8_formats_and_subtitles(f'https://ipfs.3speak.tv/ipfs/{ipfs_m3u8}',
- id, fatal=False, m3u8_id='ipfs')
+ ipfs_frmts, ipfs_subs = self._extract_m3u8_formats_and_subtitles(
+ f'https://ipfs.3speak.tv/ipfs/{ipfs_m3u8}', video_id, fatal=False, m3u8_id='ipfs')
formats.extend(ipfs_frmts)
subtitles = self._merge_subtitles(subtitles, ipfs_subs)
mp4_file = try_get(video_json, lambda x: x['video']['info']['file'])
if mp4_file:
formats.append({
- 'url': f'https://threespeakvideo.b-cdn.net/{id}/{mp4_file}',
+ 'url': f'https://threespeakvideo.b-cdn.net/{video_id}/{mp4_file}',
'ext': 'mp4',
'format_id': 'https-mp4',
'duration': try_get(video_json, lambda x: x['video']['info']['duration']),
@@ -58,7 +58,7 @@ class ThreeSpeakIE(InfoExtractor):
'format_note': 'Original file',
})
return {
- 'id': id,
+ 'id': video_id,
'title': data_json.get('title') or data_json.get('root_title'),
'uploader': data_json.get('author'),
'description': try_get(video_json, lambda x: x['video']['content']['description']),
@@ -82,12 +82,12 @@ class ThreeSpeakUserIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ playlist_id = self._match_id(url)
+ webpage = self._download_webpage(url, playlist_id)
entries = [
self.url_result(
- 'https://3speak.tv/watch?v=%s' % video,
+ f'https://3speak.tv/watch?v={video}',
ie=ThreeSpeakIE.ie_key())
for video in re.findall(r'data-payout\s?\=\s?\"([^\"]+)\"', webpage) if video
]
- return self.playlist_result(entries, id)
+ return self.playlist_result(entries, playlist_id)
diff --git a/yt_dlp/extractor/tiktok.py b/yt_dlp/extractor/tiktok.py
index 7bcfdedbe..dc74d4a1f 100644
--- a/yt_dlp/extractor/tiktok.py
+++ b/yt_dlp/extractor/tiktok.py
@@ -5,10 +5,10 @@ import random
import re
import string
import time
+import urllib.parse
import uuid
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_urlparse
from ..networking import HEADRequest
from ..utils import (
ExtractorError,
@@ -121,7 +121,7 @@ class TikTokBaseIE(InfoExtractor):
if webpage_cookies.get('sid_tt'):
self._set_cookie(self._API_HOSTNAME, 'sid_tt', webpage_cookies['sid_tt'].value)
return self._download_json(
- 'https://%s/aweme/v1/%s/' % (self._API_HOSTNAME, ep), video_id=video_id,
+ f'https://{self._API_HOSTNAME}/aweme/v1/{ep}/', video_id=video_id,
fatal=fatal, note=note, errnote=errnote, headers={
'User-Agent': self._APP_USER_AGENT,
'Accept': 'application/json',
@@ -138,7 +138,7 @@ class TikTokBaseIE(InfoExtractor):
'channel': 'googleplay',
'aid': self._APP_INFO['aid'],
'app_name': self._APP_INFO['app_name'],
- 'version_code': ''.join((f'{int(v):02d}' for v in self._APP_INFO['app_version'].split('.'))),
+ 'version_code': ''.join(f'{int(v):02d}' for v in self._APP_INFO['app_version'].split('.')),
'version_name': self._APP_INFO['app_version'],
'manifest_version_code': self._APP_INFO['manifest_app_version'],
'update_version_code': self._APP_INFO['manifest_app_version'],
@@ -254,7 +254,7 @@ class TikTokBaseIE(InfoExtractor):
'ext': 'srt',
'data': '\n\n'.join(
f'{i + 1}\n{srt_subtitles_timecode(line["start_time"] / 1000)} --> {srt_subtitles_timecode(line["end_time"] / 1000)}\n{line["text"]}'
- for i, line in enumerate(caption_json['utterances']) if line.get('text'))
+ for i, line in enumerate(caption_json['utterances']) if line.get('text')),
})
# feed endpoint subs
if not subtitles:
@@ -382,7 +382,7 @@ class TikTokBaseIE(InfoExtractor):
auth_cookie = self._get_cookies(self._WEBPAGE_HOST).get('sid_tt')
if auth_cookie:
for f in formats:
- self._set_cookie(compat_urllib_parse_urlparse(f['url']).hostname, 'sid_tt', auth_cookie.value)
+ self._set_cookie(urllib.parse.urlparse(f['url']).hostname, 'sid_tt', auth_cookie.value)
thumbnails = []
for cover_id in ('cover', 'ai_dynamic_cover', 'animated_cover', 'ai_dynamic_cover_bak',
@@ -402,7 +402,7 @@ class TikTokBaseIE(InfoExtractor):
contained_music_author = traverse_obj(
music_info, ('matched_song', 'author'), ('matched_pgc_sound', 'author'), 'author', expected_type=str)
- is_generic_og_trackname = music_info.get('is_original_sound') and music_info.get('title') == 'original sound - %s' % music_info.get('owner_handle')
+ is_generic_og_trackname = music_info.get('is_original_sound') and music_info.get('title') == 'original sound - {}'.format(music_info.get('owner_handle'))
if is_generic_og_trackname:
music_track, music_author = contained_music_track or 'original sound', contained_music_author
else:
@@ -792,7 +792,7 @@ class TikTokIE(TikTokBaseIE):
'expected_warnings': ['Unable to find video in feed'],
}, {
# 1080p format
- 'url': 'https://www.tiktok.com/@tatemcrae/video/7107337212743830830', # FIXME
+ 'url': 'https://www.tiktok.com/@tatemcrae/video/7107337212743830830', # FIXME: Web can only get audio
'md5': '982512017a8a917124d5a08c8ae79621',
'info_dict': {
'id': '7107337212743830830',
@@ -846,7 +846,7 @@ class TikTokIE(TikTokBaseIE):
}, {
# Auto-captions available
'url': 'https://www.tiktok.com/@hankgreen1/video/7047596209028074758',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -1059,17 +1059,17 @@ class TikTokSoundIE(TikTokBaseListIE):
'url': 'https://www.tiktok.com/music/Build-a-Btch-6956990112127585029?lang=en',
'playlist_mincount': 100,
'info_dict': {
- 'id': '6956990112127585029'
+ 'id': '6956990112127585029',
},
- 'expected_warnings': ['Retrying']
+ 'expected_warnings': ['Retrying'],
}, {
# Actual entries are less than listed video count
'url': 'https://www.tiktok.com/music/jiefei-soap-remix-7036843036118469381',
'playlist_mincount': 2182,
'info_dict': {
- 'id': '7036843036118469381'
+ 'id': '7036843036118469381',
},
- 'expected_warnings': ['Retrying']
+ 'expected_warnings': ['Retrying'],
}]
@@ -1085,11 +1085,11 @@ class TikTokEffectIE(TikTokBaseListIE):
'info_dict': {
'id': '1258156',
},
- 'expected_warnings': ['Retrying']
+ 'expected_warnings': ['Retrying'],
}, {
# Different entries between mobile and web, depending on region
'url': 'https://www.tiktok.com/sticker/Elf-Friend-479565',
- 'only_matching': True
+ 'only_matching': True,
}]
@@ -1106,16 +1106,16 @@ class TikTokTagIE(TikTokBaseListIE):
'id': '46294678',
'title': 'hello2018',
},
- 'expected_warnings': ['Retrying']
+ 'expected_warnings': ['Retrying'],
}, {
'url': 'https://tiktok.com/tag/fypシ?is_copy_url=0&is_from_webapp=v1',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
display_id = self._match_id(url)
webpage = self._download_webpage(url, display_id, headers={
- 'User-Agent': 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)'
+ 'User-Agent': 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
})
tag_id = self._html_search_regex(r'snssdk\d*://challenge/detail/(\d+)', webpage, 'tag ID')
return self.playlist_result(self._entries(tag_id, display_id), tag_id, display_id)
@@ -1129,17 +1129,17 @@ class TikTokCollectionIE(TikTokBaseIE):
'url': 'https://www.tiktok.com/@imanoreotwe/collection/count-test-7371330159376370462',
'info_dict': {
'id': '7371330159376370462',
- 'title': 'imanoreotwe-count-test'
+ 'title': 'imanoreotwe-count-test',
},
- 'playlist_count': 9
+ 'playlist_count': 9,
}, {
# tests returning multiple pages of a large collection
'url': 'https://www.tiktok.com/@imanoreotwe/collection/%F0%9F%98%82-7111887189571160875',
'info_dict': {
'id': '7111887189571160875',
- 'title': 'imanoreotwe-%F0%9F%98%82'
+ 'title': 'imanoreotwe-%F0%9F%98%82',
},
- 'playlist_mincount': 100
+ 'playlist_mincount': 100,
}]
_API_BASE_URL = 'https://www.tiktok.com/api/collection/item_list/'
_PAGE_COUNT = 30
diff --git a/yt_dlp/extractor/tmz.py b/yt_dlp/extractor/tmz.py
index edd16bc5b..c9f23a867 100644
--- a/yt_dlp/extractor/tmz.py
+++ b/yt_dlp/extractor/tmz.py
@@ -174,8 +174,7 @@ class TMZIE(InfoExtractor):
# see https://developers.google.com/youtube/iframe_api_reference#Video_Queueing_Functions
match_obj = re.search(r'\.cueVideoById\(\s*(?P<quote>[\'"])(?P<id>.*?)(?P=quote)', webpage)
if match_obj:
- res = self.url_result(match_obj.group('id'))
- return res
+ return self.url_result(match_obj.group('id'))
# try to extract from twitter
blockquote_el = get_element_by_attribute('class', 'twitter-tweet', webpage)
if blockquote_el:
@@ -185,8 +184,7 @@ class TMZIE(InfoExtractor):
if matches:
for _, match in matches:
if '/status/' in match:
- res = self.url_result(match)
- return res
+ return self.url_result(match)
raise ExtractorError('No video found!')
if id not in jsonld:
jsonld['id'] = url
diff --git a/yt_dlp/extractor/tnaflix.py b/yt_dlp/extractor/tnaflix.py
index 535e6c8f0..22832e068 100644
--- a/yt_dlp/extractor/tnaflix.py
+++ b/yt_dlp/extractor/tnaflix.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
fix_xml_ampersands,
float_or_none,
@@ -64,7 +63,7 @@ class TNAFlixNetworkBaseIE(InfoExtractor):
height = int_or_none(xpath_text(timeline, './imageHeight', 'thumbnail height'))
return [{
- 'url': self._proto_relative_url(pattern_el.text.replace('#', compat_str(i)), 'http:'),
+ 'url': self._proto_relative_url(pattern_el.text.replace('#', str(i)), 'http:'),
'width': width,
'height': height,
} for i in range(first, last + 1)]
@@ -138,7 +137,7 @@ class TNAFlixNetworkBaseIE(InfoExtractor):
thumbnails = self._extract_thumbnails(cfg_xml) or []
thumbnails.append({
- 'url': self._proto_relative_url(xpath_text(cfg_xml, './startThumb', 'thumbnail'), 'http:')
+ 'url': self._proto_relative_url(xpath_text(cfg_xml, './startThumb', 'thumbnail'), 'http:'),
})
# check for EMPFlix-style JSON and extract
@@ -239,7 +238,7 @@ class TNAFlixIE(TNAEMPFlixBaseIE):
'duration': 91,
'age_limit': 18,
'categories': list,
- }
+ },
}, {
# non-anonymous uploader, categories
'url': 'https://www.tnaflix.com/teen-porn/Educational-xxx-video/video6538',
@@ -255,7 +254,7 @@ class TNAFlixIE(TNAEMPFlixBaseIE):
'age_limit': 18,
'uploader': 'bobwhite39',
'categories': list,
- }
+ },
}, {
'url': 'https://www.tnaflix.com/amateur-porn/bunzHD-Ms.Donk/video358632',
'only_matching': True,
@@ -278,7 +277,7 @@ class EMPFlixIE(TNAEMPFlixBaseIE):
'duration': 83,
'age_limit': 18,
'categories': list,
- }
+ },
}, {
'url': 'http://www.empflix.com/videos/[AROMA][ARMD-718]-Aoi-Yoshino-Sawa-25826.html',
'only_matching': True,
@@ -313,7 +312,7 @@ class MovieFapIE(TNAFlixNetworkBaseIE):
'comment_count': int,
'average_rating': float,
'categories': ['Amateur', 'Masturbation', 'Mature', 'Flashing'],
- }
+ },
}, {
# quirky single-format case where the extension is given as fid, but the video is really an flv
'url': 'http://www.moviefap.com/videos/e5da0d3edce5404418f5/jeune-couple-russe.html',
diff --git a/yt_dlp/extractor/toggle.py b/yt_dlp/extractor/toggle.py
index 70737337c..de2e03f17 100644
--- a/yt_dlp/extractor/toggle.py
+++ b/yt_dlp/extractor/toggle.py
@@ -26,7 +26,7 @@ class ToggleIE(InfoExtractor):
},
'params': {
'skip_download': 'm3u8 download',
- }
+ },
}, {
'note': 'DRM-protected video',
'url': 'http://www.mewatch.sg/en/movies/dug-s-special-mission/341413',
@@ -40,7 +40,7 @@ class ToggleIE(InfoExtractor):
},
'params': {
'skip_download': 'DRM-protected wvm download',
- }
+ },
}, {
# this also tests correct video id extraction
'note': 'm3u8 links are geo-restricted, but Android/mp4 is okay',
@@ -56,7 +56,7 @@ class ToggleIE(InfoExtractor):
'params': {
'skip_download': 'DRM-protected wvm download',
},
- 'skip': 'm3u8 links are geo-restricted'
+ 'skip': 'm3u8 links are geo-restricted',
}, {
'url': 'http://video.toggle.sg/en/clips/seraph-sun-aloysius-will-suddenly-sing-some-old-songs-in-high-pitch-on-set/343331',
'only_matching': True,
@@ -92,14 +92,14 @@ class ToggleIE(InfoExtractor):
'LocaleLanguage': '',
'LocaleCountry': '',
'LocaleDevice': '',
- 'LocaleUserState': 0
+ 'LocaleUserState': 0,
},
'Platform': 0,
'SiteGuid': 0,
'DomainID': '0',
'UDID': '',
'ApiUser': self._API_USER,
- 'ApiPass': self._API_PASS
+ 'ApiPass': self._API_PASS,
},
'MediaID': video_id,
'mediaType': 0,
@@ -107,7 +107,7 @@ class ToggleIE(InfoExtractor):
info = self._download_json(
'http://tvpapi.as.tvinci.com/v2_9/gateways/jsonpostgw.aspx?m=GetMediaInfo',
- video_id, 'Downloading video info json', data=json.dumps(params).encode('utf-8'))
+ video_id, 'Downloading video info json', data=json.dumps(params).encode())
title = info['MediaName']
@@ -122,8 +122,8 @@ class ToggleIE(InfoExtractor):
if ext == 'm3u8':
m3u8_formats = self._extract_m3u8_formats(
video_url, video_id, ext='mp4', m3u8_id=vid_format,
- note='Downloading %s m3u8 information' % vid_format,
- errnote='Failed to download %s m3u8 information' % vid_format,
+ note=f'Downloading {vid_format} m3u8 information',
+ errnote=f'Failed to download {vid_format} m3u8 information',
fatal=False)
for f in m3u8_formats:
# Apple FairPlay Streaming
@@ -133,14 +133,14 @@ class ToggleIE(InfoExtractor):
elif ext == 'mpd':
formats.extend(self._extract_mpd_formats(
video_url, video_id, mpd_id=vid_format,
- note='Downloading %s MPD manifest' % vid_format,
- errnote='Failed to download %s MPD manifest' % vid_format,
+ note=f'Downloading {vid_format} MPD manifest',
+ errnote=f'Failed to download {vid_format} MPD manifest',
fatal=False))
elif ext == 'ism':
formats.extend(self._extract_ism_formats(
video_url, video_id, ism_id=vid_format,
- note='Downloading %s ISM manifest' % vid_format,
- errnote='Failed to download %s ISM manifest' % vid_format,
+ note=f'Downloading {vid_format} ISM manifest',
+ errnote=f'Failed to download {vid_format} ISM manifest',
fatal=False))
elif ext == 'mp4':
formats.append({
diff --git a/yt_dlp/extractor/tonline.py b/yt_dlp/extractor/tonline.py
index 33b9a32e4..cfbd36b2f 100644
--- a/yt_dlp/extractor/tonline.py
+++ b/yt_dlp/extractor/tonline.py
@@ -15,13 +15,13 @@ class TOnlineIE(InfoExtractor):
'ext': 'mp4',
'title': 'Drittes Remis! Zidane: "Es muss etwas passieren"',
'description': 'Es läuft nicht rund bei Real Madrid. Das 1:1 gegen den SD Eibar war das dritte Unentschieden in Folge in der Liga.',
- }
+ },
}
def _real_extract(self, url):
video_id = self._match_id(url)
video_data = self._download_json(
- 'http://www.t-online.de/tv/id_%s/tid_json_video' % video_id, video_id)
+ f'http://www.t-online.de/tv/id_{video_id}/tid_json_video', video_id)
title = video_data['subtitle']
formats = []
diff --git a/yt_dlp/extractor/toongoggles.py b/yt_dlp/extractor/toongoggles.py
index 1b8fc3acd..bfeb16a6e 100644
--- a/yt_dlp/extractor/toongoggles.py
+++ b/yt_dlp/extractor/toongoggles.py
@@ -18,7 +18,7 @@ class ToonGogglesIE(InfoExtractor):
'description': 'Bernard decides to play football in order to be better than Lloyd and tries to beat him no matter how, he even cheats.',
'upload_date': '20160718',
'timestamp': 1468879330,
- }
+ },
}, {
'url': 'http://www.toongoggles.com/shows/227759/om-nom-stories-around-the-world',
'info_dict': {
diff --git a/yt_dlp/extractor/toutv.py b/yt_dlp/extractor/toutv.py
index ced1224fa..cbd2c9c26 100644
--- a/yt_dlp/extractor/toutv.py
+++ b/yt_dlp/extractor/toutv.py
@@ -61,7 +61,7 @@ class TouTvIE(RadioCanadaIE): # XXX: Do not subclass from concrete IE
def _real_extract(self, url):
path = self._match_id(url)
metadata = self._download_json(
- 'https://services.radio-canada.ca/toutv/presentation/%s' % path, path, query={
+ f'https://services.radio-canada.ca/toutv/presentation/{path}', path, query={
'client_key': self._CLIENT_KEY,
'device': 'web',
'version': 4,
diff --git a/yt_dlp/extractor/toypics.py b/yt_dlp/extractor/toypics.py
index ccb2ef816..5b625a380 100644
--- a/yt_dlp/extractor/toypics.py
+++ b/yt_dlp/extractor/toypics.py
@@ -16,7 +16,7 @@ class ToypicsIE(InfoExtractor):
'title': "Chance-Bulge'd, 2",
'age_limit': 18,
'uploader': 'kidsune',
- }
+ },
}
def _real_extract(self, url):
@@ -70,10 +70,10 @@ class ToypicsUserIE(InfoExtractor):
urls = []
page_count = (video_count + PAGE_SIZE + 1) // PAGE_SIZE
for n in range(1, page_count + 1):
- lpage_url = url + '/public/%d' % n
+ lpage_url = url + f'/public/{n}'
lpage = self._download_webpage(
lpage_url, username,
- note='Downloading page %d/%d' % (n, page_count))
+ note=f'Downloading page {n}/{page_count}')
urls.extend(
re.findall(
r'<div[^>]+class=["\']preview[^>]+>\s*<a[^>]+href="(https?://videos\.toypics\.net/view/[^"]+)"',
@@ -86,5 +86,5 @@ class ToypicsUserIE(InfoExtractor):
'_type': 'url',
'url': eurl,
'ie_key': 'Toypics',
- } for eurl in urls]
+ } for eurl in urls],
}
diff --git a/yt_dlp/extractor/traileraddict.py b/yt_dlp/extractor/traileraddict.py
index 5c4a138c4..81c936558 100644
--- a/yt_dlp/extractor/traileraddict.py
+++ b/yt_dlp/extractor/traileraddict.py
@@ -14,7 +14,7 @@ class TrailerAddictIE(InfoExtractor):
'ext': 'mp4',
'title': 'Prince Avalanche Trailer',
'description': 'Trailer for Prince Avalanche.\n\nTwo highway road workers spend the summer of 1988 away from their city lives. The isolated landscape becomes a place of misadventure as the men find themselves at odds with each other and the women they left behind.',
- }
+ },
}
def _real_extract(self, url):
@@ -39,7 +39,7 @@ class TrailerAddictIE(InfoExtractor):
else:
fvar = 'fvar'
- info_url = 'http://www.traileraddict.com/%s.php?tid=%s' % (fvar, str(video_id))
+ info_url = f'http://www.traileraddict.com/{fvar}.php?tid={video_id!s}'
info_webpage = self._download_webpage(info_url, video_id, 'Downloading the info webpage')
final_url = self._search_regex(r'&fileurl=(.+)',
diff --git a/yt_dlp/extractor/trovo.py b/yt_dlp/extractor/trovo.py
index 545a67275..7d800ae34 100644
--- a/yt_dlp/extractor/trovo.py
+++ b/yt_dlp/extractor/trovo.py
@@ -63,7 +63,7 @@ class TrovoIE(TrovoBaseIE):
'title': '💥IGRAMO IGRICE UPADAJTE💥2500/5000 2022-06-28 22:01',
'live_status': 'is_live',
},
- 'skip': 'May not be live'
+ 'skip': 'May not be live',
}]
def _real_extract(self, url):
@@ -77,7 +77,7 @@ class TrovoIE(TrovoBaseIE):
},
})
if live_info.get('isLive') == 0:
- raise ExtractorError('%s is offline' % username, expected=True)
+ raise ExtractorError(f'{username} is offline', expected=True)
program_info = live_info['programInfo']
program_id = program_info['id']
title = program_info['title']
diff --git a/yt_dlp/extractor/trtcocuk.py b/yt_dlp/extractor/trtcocuk.py
index f27f5a1e3..0c899f1e7 100644
--- a/yt_dlp/extractor/trtcocuk.py
+++ b/yt_dlp/extractor/trtcocuk.py
@@ -15,7 +15,7 @@ class TrtCocukVideoIE(InfoExtractor):
'title': 'Kaptan Pengu ve Arkadaşları 1 Bölüm İzle TRT Çocuk',
'release_date': '20201209',
'release_timestamp': 1607513774,
- }
+ },
}, {
'url': 'https://www.trtcocuk.net.tr/video/sef-rokanin-lezzet-dunyasi-17',
'info_dict': {
@@ -23,7 +23,7 @@ class TrtCocukVideoIE(InfoExtractor):
'ext': 'mp4',
'series': '"Şef Roka\'nın Lezzet Dünyası"',
'title': 'Şef Roka\'nın Lezzet Dünyası 17 Bölüm İzle TRT Çocuk',
- }
+ },
}]
def _real_extract(self, url):
@@ -44,5 +44,5 @@ class TrtCocukVideoIE(InfoExtractor):
'season_number': int_or_none(nuxtjs_data.get('season')),
'release_timestamp': parse_iso8601(nuxtjs_data.get('publishedDate')),
'series': traverse_obj(nuxtjs_data, ('show', 0, 'title')),
- 'title': self._html_extract_title(webpage) # TODO: get better title
+ 'title': self._html_extract_title(webpage), # TODO: get better title
}
diff --git a/yt_dlp/extractor/trtworld.py b/yt_dlp/extractor/trtworld.py
index dbb72a4fe..d33b9aae0 100644
--- a/yt_dlp/extractor/trtworld.py
+++ b/yt_dlp/extractor/trtworld.py
@@ -16,7 +16,7 @@ class TrtWorldIE(InfoExtractor):
'release_date': '20231202',
'thumbnail': 'https://cdn-i.pr.trt.com.tr/trtworld/17647563_0-0-1920-1080.jpeg',
'description': 'md5:0a975c04257fb529c8f99c7b76a2cf12',
- }
+ },
}, {
'url': 'https://www.trtworld.com/video/one-offs/frames-from-anatolia-recreating-a-james-bond-scene-in-istanbuls-grand-bazaar-14541780',
'info_dict': {
@@ -27,7 +27,7 @@ class TrtWorldIE(InfoExtractor):
'release_date': '20230819',
'thumbnail': 'https://cdn-i.pr.trt.com.tr/trtworld/16939810_0-0-1920-1080.jpeg',
'description': 'md5:4050e21570cc3c40b6c9badae800a94f',
- }
+ },
}, {
'url': 'https://www.trtworld.com/video/the-newsmakers/can-sudan-find-peace-amidst-failed-transition-to-democracy-12904760',
'info_dict': {
@@ -36,8 +36,8 @@ class TrtWorldIE(InfoExtractor):
'title': 'Can Sudan find peace amidst failed transition to democracy?',
'release_timestamp': 1681972747,
'release_date': '20230420',
- 'thumbnail': 'http://cdni0.trtworld.com/w768/q70/154214_NMYOUTUBETEMPLATE1_1681833018736.jpg'
- }
+ 'thumbnail': 'http://cdni0.trtworld.com/w768/q70/154214_NMYOUTUBETEMPLATE1_1681833018736.jpg',
+ },
}, {
'url': 'https://www.trtworld.com/video/africa-matters/locals-learning-to-cope-with-rising-tides-of-kenyas-great-lakes-16059545',
'info_dict': {
@@ -66,7 +66,7 @@ class TrtWorldIE(InfoExtractor):
'tags': [],
'live_status': 'not_live',
'like_count': int,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/trueid.py b/yt_dlp/extractor/trueid.py
index efedac180..183364b40 100644
--- a/yt_dlp/extractor/trueid.py
+++ b/yt_dlp/extractor/trueid.py
@@ -31,7 +31,7 @@ class TrueIDIE(InfoExtractor):
'upload_date': '20200916',
'release_date': '20200630',
},
- 'expected_warnings': ['Video is geo restricted.']
+ 'expected_warnings': ['Video is geo restricted.'],
}, {
'url': 'https://trueid.id/series/zZOBVPb62EwR/qXY73rwyl7oj/one-piece-ep-1/',
'md5': '1c6d976049bc3c89a8a25aed2c3fb081',
@@ -51,7 +51,7 @@ class TrueIDIE(InfoExtractor):
'upload_date': '20210112',
'release_date': '20210131',
},
- 'expected_warnings': ['Video is geo restricted.']
+ 'expected_warnings': ['Video is geo restricted.'],
}, {
'url': 'https://vn.trueid.net/series/7DNPM7Bpa9wv/pwLgEQ4Xbda2/haikyu-vua-bong-chuyen-phan-1/',
'info_dict': {
@@ -69,7 +69,7 @@ class TrueIDIE(InfoExtractor):
'upload_date': '20210818',
'release_date': '20210818',
},
- 'expected_warnings': ['Video is geo restricted.']
+ 'expected_warnings': ['Video is geo restricted.'],
}, {
'url': 'https://trueid.ph/series/l8rvvAw7Jwv8/l8rvvAw7Jwv8/naruto-trailer/',
'only_matching': True,
diff --git a/yt_dlp/extractor/trutv.py b/yt_dlp/extractor/trutv.py
index ea0f2f40e..cbfe67af2 100644
--- a/yt_dlp/extractor/trutv.py
+++ b/yt_dlp/extractor/trutv.py
@@ -32,7 +32,7 @@ class TruTVIE(TurnerBaseIE):
display_id = clip_slug
data = self._download_json(
- 'https://api.trutv.com/v2/web/%s/%s/%s' % (path, series_slug, display_id),
+ f'https://api.trutv.com/v2/web/{path}/{series_slug}/{display_id}',
display_id)
video_data = data['episode'] if video_id else data['info']
media_id = video_data['mediaId']
diff --git a/yt_dlp/extractor/tube8.py b/yt_dlp/extractor/tube8.py
index 5f15b4581..7267bf2bd 100644
--- a/yt_dlp/extractor/tube8.py
+++ b/yt_dlp/extractor/tube8.py
@@ -1,8 +1,8 @@
import re
+import urllib.parse
from .common import InfoExtractor
from ..aes import aes_decrypt_text
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
determine_ext,
format_field,
@@ -100,12 +100,12 @@ class Tube8IE(InfoExtractor):
r'flashvars\.video_url\s*=\s*(["\'])(?P<url>http.+?)\1',
webpage, 'video url', default=None, group='url')
if video_url:
- extract_format(compat_urllib_parse_unquote(video_url))
+ extract_format(urllib.parse.unquote(video_url))
if not formats:
if 'title="This video is no longer available"' in webpage:
self.raise_no_formats(
- 'Video %s is no longer available' % video_id, expected=True)
+ f'Video {video_id} is no longer available', expected=True)
if not title:
title = self._html_search_regex(
@@ -153,8 +153,8 @@ class Tube8IE(InfoExtractor):
tags_str = self._search_regex(
r'(?s)Tags:\s*</dt>\s*<dd>(.+?)</(?!a)',
webpage, 'tags', fatal=False)
- tags = [t for t in re.findall(
- r'<a[^>]+href=[^>]+>([^<]+)', tags_str)] if tags_str else None
+ tags = list(re.findall(
+ r'<a[^>]+href=[^>]+>([^<]+)', tags_str)) if tags_str else None
info.update({
'description': description,
diff --git a/yt_dlp/extractor/tubetugraz.py b/yt_dlp/extractor/tubetugraz.py
index a351e4e55..e13375f0a 100644
--- a/yt_dlp/extractor/tubetugraz.py
+++ b/yt_dlp/extractor/tubetugraz.py
@@ -28,7 +28,7 @@ class TubeTuGrazBaseIE(InfoExtractor):
'lang': 'de',
'_eventId_proceed': '',
'j_username': username,
- 'j_password': password
+ 'j_password': password,
}))
if not urlh or urlh.url == 'https://tube.tugraz.at/paella/ui/index.html':
return
@@ -53,33 +53,33 @@ class TubeTuGrazBaseIE(InfoExtractor):
self.report_warning('unable to login: incorrect TFA code')
def _extract_episode(self, episode_info):
- id = episode_info.get('id')
+ video_id = episode_info.get('id')
formats = list(self._extract_formats(
- traverse_obj(episode_info, ('mediapackage', 'media', 'track')), id))
+ traverse_obj(episode_info, ('mediapackage', 'media', 'track')), video_id))
title = traverse_obj(episode_info, ('mediapackage', 'title'), 'dcTitle')
series_title = traverse_obj(episode_info, ('mediapackage', 'seriestitle'))
creator = ', '.join(variadic(traverse_obj(
episode_info, ('mediapackage', 'creators', 'creator'), 'dcCreator', default='')))
return {
- 'id': id,
+ 'id': video_id,
'title': title,
'creator': creator or None,
'duration': traverse_obj(episode_info, ('mediapackage', 'duration'), 'dcExtent'),
'series': series_title,
'series_id': traverse_obj(episode_info, ('mediapackage', 'series'), 'dcIsPartOf'),
'episode': series_title and title,
- 'formats': formats
+ 'formats': formats,
}
- def _set_format_type(self, formats, type):
+ def _set_format_type(self, formats, fmt_type):
for f in formats:
- f['format_note'] = type
- if not type.startswith(self._FORMAT_TYPES[0]):
+ f['format_note'] = fmt_type
+ if not fmt_type.startswith(self._FORMAT_TYPES[0]):
f['preference'] = -2
return formats
- def _extract_formats(self, format_list, id):
+ def _extract_formats(self, format_list, video_id):
has_hls, has_dash = False, False
for format_info in format_list or []:
@@ -87,7 +87,7 @@ class TubeTuGrazBaseIE(InfoExtractor):
if url is None:
continue
- type = format_info.get('type') or 'unknown'
+ fmt_type = format_info.get('type') or 'unknown'
transport = (format_info.get('transport') or 'https').lower()
if transport == 'https':
@@ -100,10 +100,10 @@ class TubeTuGrazBaseIE(InfoExtractor):
}]
elif transport == 'hls':
has_hls, formats = True, self._extract_m3u8_formats(
- url, id, 'mp4', fatal=False, note=f'downloading {type} HLS manifest')
+ url, video_id, 'mp4', fatal=False, note=f'downloading {fmt_type} HLS manifest')
elif transport == 'dash':
has_dash, formats = True, self._extract_mpd_formats(
- url, id, fatal=False, note=f'downloading {type} DASH manifest')
+ url, video_id, fatal=False, note=f'downloading {fmt_type} DASH manifest')
else:
# RTMP, HDS, SMOOTH, and unknown formats
# - RTMP url fails on every tested entry until now
@@ -111,21 +111,21 @@ class TubeTuGrazBaseIE(InfoExtractor):
# - SMOOTH url 404's on every tested entry until now
continue
- yield from self._set_format_type(formats, type)
+ yield from self._set_format_type(formats, fmt_type)
# TODO: Add test for these
- for type in self._FORMAT_TYPES:
+ for fmt_type in self._FORMAT_TYPES:
if not has_hls:
hls_formats = self._extract_m3u8_formats(
- f'https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_{id}_{type}.smil/playlist.m3u8',
- id, 'mp4', fatal=False, note=f'Downloading {type} HLS manifest', errnote=False) or []
- yield from self._set_format_type(hls_formats, type)
+ f'https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_{video_id}_{fmt_type}.smil/playlist.m3u8',
+ video_id, 'mp4', fatal=False, note=f'Downloading {fmt_type} HLS manifest', errnote=False) or []
+ yield from self._set_format_type(hls_formats, fmt_type)
if not has_dash:
dash_formats = self._extract_mpd_formats(
- f'https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_{id}_{type}.smil/manifest_mpm4sav_mvlist.mpd',
- id, fatal=False, note=f'Downloading {type} DASH manifest', errnote=False)
- yield from self._set_format_type(dash_formats, type)
+ f'https://wowza.tugraz.at/matterhorn_engage/smil:engage-player_{video_id}_{fmt_type}.smil/manifest_mpm4sav_mvlist.mpd',
+ video_id, fatal=False, note=f'Downloading {fmt_type} DASH manifest', errnote=False)
+ yield from self._set_format_type(dash_formats, fmt_type)
class TubeTuGrazIE(TubeTuGrazBaseIE):
@@ -148,7 +148,7 @@ class TubeTuGrazIE(TubeTuGrazBaseIE):
'creator': 'Safran C',
'duration': 3295818,
'series_id': 'b1192fff-2aa7-4bf0-a5cf-7b15c3bd3b34',
- }
+ },
}, {
'url': 'https://tube.tugraz.at/paella/ui/watch.html?id=2df6d787-e56a-428d-8ef4-d57f07eef238',
'md5': 'de0d854a56bf7318d2b693fe1adb89a5',
@@ -158,7 +158,7 @@ class TubeTuGrazIE(TubeTuGrazBaseIE):
'ext': 'mp4',
},
'expected_warnings': ['Extractor failed to obtain "title"'],
- }
+ },
]
def _real_extract(self, url):
@@ -193,7 +193,7 @@ class TubeTuGrazSeriesIE(TubeTuGrazBaseIE):
'series': '[209351] Strassenwesen',
'creator': 'Neuhold R',
'duration': 6127024,
- }
+ },
},
{
'info_dict': {
@@ -205,7 +205,7 @@ class TubeTuGrazSeriesIE(TubeTuGrazBaseIE):
'series': '[209351] Strassenwesen',
'creator': 'Neuhold R',
'duration': 5374422,
- }
+ },
},
{
'info_dict': {
@@ -217,7 +217,7 @@ class TubeTuGrazSeriesIE(TubeTuGrazBaseIE):
'series': '[209351] Strassenwesen',
'creator': 'Neuhold R',
'duration': 5566404,
- }
+ },
},
{
'info_dict': {
@@ -229,24 +229,25 @@ class TubeTuGrazSeriesIE(TubeTuGrazBaseIE):
'series': '[209351] Strassenwesen',
'creator': 'Neuhold R',
'duration': 5420200,
- }
- }
+ },
+ },
],
- 'min_playlist_count': 4
+ 'min_playlist_count': 4,
}]
def _real_extract(self, url):
- id = self._match_id(url)
- episodes_data = self._download_json(self._API_EPISODE, id, query={'sid': id}, note='Downloading episode list')
+ playlist_id = self._match_id(url)
+ episodes_data = self._download_json(
+ self._API_EPISODE, playlist_id, query={'sid': playlist_id}, note='Downloading episode list')
series_data = self._download_json(
- 'https://tube.tugraz.at/series/series.json', id, fatal=False,
+ 'https://tube.tugraz.at/series/series.json', playlist_id, fatal=False,
note='downloading series metadata', errnote='failed to download series metadata',
query={
- 'seriesId': id,
+ 'seriesId': playlist_id,
'count': 1,
- 'sort': 'TITLE'
+ 'sort': 'TITLE',
})
return self.playlist_result(
- map(self._extract_episode, episodes_data['search-results']['result']), id,
+ map(self._extract_episode, episodes_data['search-results']['result']), playlist_id,
traverse_obj(series_data, ('catalogs', 0, 'http://purl.org/dc/terms/', 'title', 0, 'value')))
diff --git a/yt_dlp/extractor/tubitv.py b/yt_dlp/extractor/tubitv.py
index 78be86d58..9d9ddae72 100644
--- a/yt_dlp/extractor/tubitv.py
+++ b/yt_dlp/extractor/tubitv.py
@@ -56,7 +56,7 @@ class TubiTvIE(InfoExtractor):
'description': 'A stand up comedian is forced to look at the decisions in his life while on a one week trip to the west coast.',
'uploader_id': 'bc168bee0d18dd1cb3b86c68706ab434',
},
- 'skip': 'Content Unavailable'
+ 'skip': 'Content Unavailable',
}, {
'url': 'http://tubitv.com/tv-shows/321886/s01_e01_on_nom_stories',
'only_matching': True,
@@ -70,7 +70,7 @@ class TubiTvIE(InfoExtractor):
'uploader_id': 'd8fed30d4f24fcb22ec294421b9defc2',
'release_year': 1979,
},
- 'skip': 'Content Unavailable'
+ 'skip': 'Content Unavailable',
}]
# DRM formats are included only to raise appropriate error
@@ -155,7 +155,7 @@ class TubiTvShowIE(InfoExtractor):
'playlist_mincount': 390,
'info_dict': {
'id': 'the-joy-of-painting-with-bob-ross',
- }
+ },
}]
def _entries(self, show_url, show_name):
@@ -165,7 +165,7 @@ class TubiTvShowIE(InfoExtractor):
r'window\.__data\s*=\s*({[^<]+});\s*</script>',
show_webpage, 'data'), show_name, transform_source=js_to_json)['video']
- for episode_id in show_json['fullContentById'].keys():
+ for episode_id in show_json['fullContentById']:
if traverse_obj(show_json, ('byId', episode_id, 'type')) == 's':
continue
yield self.url_result(
diff --git a/yt_dlp/extractor/tumblr.py b/yt_dlp/extractor/tumblr.py
index f2d0c5901..7f851bf63 100644
--- a/yt_dlp/extractor/tumblr.py
+++ b/yt_dlp/extractor/tumblr.py
@@ -28,7 +28,7 @@ class TumblrIE(InfoExtractor):
'repost_count': int,
'age_limit': 0,
'tags': ['Orphan Black', 'Tatiana Maslany', 'Interview', 'Video', 'OB S1 DVD Extras'],
- }
+ },
}, {
'note': 'multiple formats',
'url': 'https://maskofthedragon.tumblr.com/post/626907179849564160/mona-talking-in-english',
@@ -65,7 +65,7 @@ class TumblrIE(InfoExtractor):
'repost_count': int,
'age_limit': 0,
'tags': [],
- }
+ },
}, {
'note': 'dashboard only (original post)',
'url': 'https://jujanon.tumblr.com/post/159704441298/my-baby-eating',
@@ -82,7 +82,7 @@ class TumblrIE(InfoExtractor):
'repost_count': int,
'age_limit': 0,
'tags': ['crabs', 'my video', 'my pets'],
- }
+ },
}, {
'note': 'dashboard only (reblog)',
'url': 'https://bartlebyshop.tumblr.com/post/180294460076/duality-of-bird',
@@ -99,7 +99,7 @@ class TumblrIE(InfoExtractor):
'repost_count': int,
'age_limit': 0,
'tags': [],
- }
+ },
}, {
'note': 'dashboard only (external)',
'url': 'https://afloweroutofstone.tumblr.com/post/675661759168823296/the-blues-remembers-everything-the-country-forgot',
@@ -149,7 +149,7 @@ class TumblrIE(InfoExtractor):
'uploader': 'naked-yogi',
},
# 'add_ie': ['Vidme'],
- 'skip': 'dead embedded video host'
+ 'skip': 'dead embedded video host',
}, {
'url': 'https://prozdvoices.tumblr.com/post/673201091169681408/what-recording-voice-acting-sounds-like',
'md5': 'a0063fc8110e6c9afe44065b4ea68177',
@@ -363,8 +363,8 @@ class TumblrIE(InfoExtractor):
# if it's a reblog, og:description will be the reblogger's comment, not the uploader's.
# content_json is always the op, so if it exists but has no text, there's no description
if content_json:
- description = '\n\n'.join((
- item.get('text') for item in content_json if item.get('type') == 'text')) or None
+ description = '\n\n'.join(
+ item.get('text') for item in content_json if item.get('type') == 'text') or None
else:
description = self._og_search_description(webpage, default=None)
uploader_id = traverse_obj(post_json, 'reblogged_root_name', 'blog_name')
diff --git a/yt_dlp/extractor/tunein.py b/yt_dlp/extractor/tunein.py
index fd2fe132c..05bb2a9fa 100644
--- a/yt_dlp/extractor/tunein.py
+++ b/yt_dlp/extractor/tunein.py
@@ -117,7 +117,7 @@ class TuneInPodcastIE(TuneInBaseIE):
'playlist_mincount': 200,
}, {
'url': 'https://tunein.com/embed/player/p191660/',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://tunein.com/podcasts/World-News/BBC-News-p14/',
'info_dict': {
@@ -230,5 +230,5 @@ class TuneInShortenerIE(InfoExtractor):
if url_parsed.port == 443:
url = url_parsed._replace(netloc=url_parsed.hostname).url
- self.to_screen('Following redirect: %s' % url)
+ self.to_screen(f'Following redirect: {url}')
return self.url_result(url)
diff --git a/yt_dlp/extractor/turner.py b/yt_dlp/extractor/turner.py
index b27db87bf..8b79a8ba9 100644
--- a/yt_dlp/extractor/turner.py
+++ b/yt_dlp/extractor/turner.py
@@ -1,7 +1,6 @@
import re
from .adobepass import AdobePassIE
-from ..compat import compat_str
from ..utils import (
ExtractorError,
determine_ext,
@@ -79,7 +78,7 @@ class TurnerBaseIE(AdobePassIE):
ext = determine_ext(video_url)
if video_url.startswith('/mp4:protected/'):
continue
- # TODO Correct extraction for these files
+ # TODO: Correct extraction for these files
# protected_path_data = path_data.get('protected')
# if not protected_path_data or not rtmp_src:
# continue
@@ -160,7 +159,7 @@ class TurnerBaseIE(AdobePassIE):
'height': int(mobj.group('height')),
'tbr': int_or_none(mobj.group('bitrate')),
})
- elif isinstance(format_id, compat_str):
+ elif isinstance(format_id, str):
if format_id.isdigit():
f['tbr'] = int(format_id)
else:
@@ -187,7 +186,7 @@ class TurnerBaseIE(AdobePassIE):
'scc': 'scc',
'webvtt': 'vtt',
'smptett': 'tt',
- }.get(source.get('format'))
+ }.get(source.get('format')),
})
thumbnails.extend({
@@ -219,7 +218,7 @@ class TurnerBaseIE(AdobePassIE):
def _extract_ngtv_info(self, media_id, tokenizer_query, ap_data=None):
is_live = ap_data.get('is_live')
streams_data = self._download_json(
- 'http://medium.ngtv.io/media/%s/tv' % media_id,
+ f'http://medium.ngtv.io/media/{media_id}/tv',
media_id)['media']['tv']
duration = None
chapters = []
diff --git a/yt_dlp/extractor/tv2.py b/yt_dlp/extractor/tv2.py
index 9b19e7995..601ecab78 100644
--- a/yt_dlp/extractor/tv2.py
+++ b/yt_dlp/extractor/tv2.py
@@ -52,10 +52,10 @@ class TV2IE(InfoExtractor):
format_urls = []
for protocol in self._PROTOCOLS:
try:
- data = self._download_json('https://api.sumo.tv2.no/play/%s?stream=%s' % (video_id, protocol),
+ data = self._download_json(f'https://api.sumo.tv2.no/play/{video_id}?stream={protocol}',
video_id, 'Downloading playabck JSON',
headers={'content-type': 'application/json'},
- data='{"device":{"id":"1-1-1","name":"Nettleser (HTML)"}}'.encode())['playback']
+ data=b'{"device":{"id":"1-1-1","name":"Nettleser (HTML)"}}')['playback']
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 401:
error = self._parse_json(e.cause.response.read().decode(), video_id)['error']
@@ -71,7 +71,7 @@ class TV2IE(InfoExtractor):
video_url = item.get('url')
if not video_url or video_url in format_urls:
continue
- format_id = '%s-%s' % (protocol.lower(), item.get('type'))
+ format_id = '{}-{}'.format(protocol.lower(), item.get('type'))
if not self._is_valid_url(video_url, video_id, format_id):
continue
format_urls.append(video_url)
@@ -97,9 +97,9 @@ class TV2IE(InfoExtractor):
self.report_drm(video_id)
thumbnails = [{
- 'id': type,
+ 'id': thumb_type,
'url': thumb_url,
- } for type, thumb_url in (asset.get('images') or {}).items()]
+ } for thumb_type, thumb_url in (asset.get('images') or {}).items()]
return {
'id': video_id,
@@ -151,7 +151,7 @@ class TV2ArticleIE(InfoExtractor):
assets.append(asset)
entries = [
- self.url_result('http://www.tv2.no/v/%s' % asset_id, 'TV2')
+ self.url_result(f'http://www.tv2.no/v/{asset_id}', 'TV2')
for asset_id in assets]
title = remove_end(self._og_search_title(webpage), ' - TV2.no')
@@ -196,7 +196,7 @@ class KatsomoIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- api_base = 'http://%s/api/web/asset/%s' % (self._API_DOMAIN, video_id)
+ api_base = f'http://{self._API_DOMAIN}/api/web/asset/{video_id}'
asset = self._download_json(
api_base + '.json', video_id,
@@ -209,7 +209,7 @@ class KatsomoIE(InfoExtractor):
for protocol in self._PROTOCOLS:
try:
data = self._download_json(
- api_base + '/play.json?protocol=%s&videoFormat=SMIL+ISMUSP' % protocol,
+ api_base + f'/play.json?protocol={protocol}&videoFormat=SMIL+ISMUSP',
video_id, 'Downloading play JSON')['playback']
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 401:
@@ -232,7 +232,7 @@ class KatsomoIE(InfoExtractor):
video_url = item.get('url')
if not video_url or video_url in format_urls:
continue
- format_id = '%s-%s' % (protocol.lower(), item.get('mediaFormat'))
+ format_id = '{}-{}'.format(protocol.lower(), item.get('mediaFormat'))
if not self._is_valid_url(video_url, video_id, format_id):
continue
format_urls.append(video_url)
diff --git a/yt_dlp/extractor/tv24ua.py b/yt_dlp/extractor/tv24ua.py
index 89905acdb..2787de4da 100644
--- a/yt_dlp/extractor/tv24ua.py
+++ b/yt_dlp/extractor/tv24ua.py
@@ -15,7 +15,7 @@ class TV24UAVideoIE(InfoExtractor):
'ext': 'mp4',
'title': 'У Харкові ворожа ракета прилетіла в будинок, де слухали пісні про "офіцерів-росіян"',
'thumbnail': r're:^https?://.*\.jpe?g',
- }
+ },
}, {
'url': 'https://24tv.ua/news/showPlayer.do?videoUrl=2022/07/2074790&objectId=2074790&w=640&h=360',
'only_matching': True,
@@ -32,7 +32,7 @@ class TV24UAVideoIE(InfoExtractor):
'ext': 'mp4',
'title': 'Росіяни руйнують Бородянку на Київщині та стріляють з літаків по мешканцях: шокуючі фото',
'thumbnail': r're:^https?://.*\.jpe?g',
- }
+ },
},
{
'url': 'https://24tv.ua/vipalyuyut-nashi-mista-sela-dsns-pokazali-motoroshni-naslidki_n1883966',
@@ -43,7 +43,7 @@ class TV24UAVideoIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpe?g',
},
'params': {'allowed_extractors': ['Generic', '24tv.ua']},
- }
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/tv2dk.py b/yt_dlp/extractor/tv2dk.py
index 35e92f10c..9cd7606b0 100644
--- a/yt_dlp/extractor/tv2dk.py
+++ b/yt_dlp/extractor/tv2dk.py
@@ -82,7 +82,7 @@ class TV2DKIE(InfoExtractor):
def add_entry(partner_id, kaltura_id):
entries.append(self.url_result(
- 'kaltura:%s:%s' % (partner_id, kaltura_id), 'Kaltura',
+ f'kaltura:{partner_id}:{kaltura_id}', 'Kaltura',
video_id=kaltura_id))
for video_el in re.findall(r'(?s)<[^>]+\bdata-entryid\s*=[^>]*>', webpage):
diff --git a/yt_dlp/extractor/tv2hu.py b/yt_dlp/extractor/tv2hu.py
index cd35ff5fb..3fd41a3e9 100644
--- a/yt_dlp/extractor/tv2hu.py
+++ b/yt_dlp/extractor/tv2hu.py
@@ -1,4 +1,3 @@
-# encoding: utf-8
from .common import InfoExtractor
from ..utils import (
UnsupportedError,
@@ -44,14 +43,14 @@ class TV2HuIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- json_data = self._download_json(f'https://tv2play.hu/api/search/{id}', id)
+ video_id = self._match_id(url)
+ json_data = self._download_json(f'https://tv2play.hu/api/search/{video_id}', video_id)
if json_data['contentType'] == 'showpage':
ribbon_ids = traverse_obj(json_data, ('pages', ..., 'tabs', ..., 'ribbonIds'), get_all=False, expected_type=list)
entries = [self.url_result(f'https://tv2play.hu/szalag/{ribbon_id}',
ie=TV2HuSeriesIE.ie_key(), video_id=ribbon_id) for ribbon_id in ribbon_ids]
- return self.playlist_result(entries, playlist_id=id)
+ return self.playlist_result(entries, playlist_id=video_id)
elif json_data['contentType'] != 'video':
raise UnsupportedError(url)
@@ -88,17 +87,17 @@ class TV2HuSeriesIE(InfoExtractor):
'playlist_mincount': 284,
'info_dict': {
'id': '59',
- }
+ },
}]
def _real_extract(self, url):
- id = self._match_id(url)
- json_data = self._download_json(f'https://tv2play.hu/api/ribbons/{id}/0?size=100000', id)
+ playlist_id = self._match_id(url)
+ json_data = self._download_json(f'https://tv2play.hu/api/ribbons/{playlist_id}/0?size=100000', playlist_id)
entries = []
for card in json_data.get('cards', []):
video_id = card.get('slug')
if video_id:
- entries.append(self.url_result(f'https://tv2play.hu/{video_id}',
- ie=TV2HuIE.ie_key(), video_id=video_id))
+ entries.append(self.url_result(
+ f'https://tv2play.hu/{video_id}', TV2HuIE, video_id))
- return self.playlist_result(entries, playlist_id=id)
+ return self.playlist_result(entries, playlist_id=playlist_id)
diff --git a/yt_dlp/extractor/tv4.py b/yt_dlp/extractor/tv4.py
index 10a2fe6e2..73a446e11 100644
--- a/yt_dlp/extractor/tv4.py
+++ b/yt_dlp/extractor/tv4.py
@@ -76,7 +76,7 @@ class TV4IE(InfoExtractor):
{
'url': 'https://www.tv4play.se/program/nyheterna/avsnitt/13315940',
'only_matching': True,
- }
+ },
]
def _call_api(self, endpoint, video_id, headers=None, query={}):
diff --git a/yt_dlp/extractor/tv5unis.py b/yt_dlp/extractor/tv5unis.py
index 978255b17..88fd33482 100644
--- a/yt_dlp/extractor/tv5unis.py
+++ b/yt_dlp/extractor/tv5unis.py
@@ -32,7 +32,7 @@ class TV5UnisBaseIE(InfoExtractor):
}
}
}
-}''' % (self._GQL_QUERY_NAME, self._gql_args(groups)),
+}''' % (self._GQL_QUERY_NAME, self._gql_args(groups)), # noqa: UP031
})['data'][self._GQL_QUERY_NAME]
media_id = product['videoElement']['mediaId']
@@ -61,13 +61,13 @@ class TV5UnisVideoIE(TV5UnisBaseIE):
'ext': 'mp4',
'title': 'Watatatow',
'duration': 10.01,
- }
+ },
}
_GQL_QUERY_NAME = 'productById'
@staticmethod
def _gql_args(groups):
- return 'id: %s' % groups
+ return f'id: {groups}'
class TV5UnisIE(TV5UnisBaseIE):
@@ -80,7 +80,7 @@ class TV5UnisIE(TV5UnisBaseIE):
'id': 'e5ee23a586c44612a56aad61accf16ef',
'ext': 'mp4',
'title': 'Je ne peux pas lui résister',
- 'description': "Atys, le nouveau concierge de l'école, a réussi à ébranler la confiance de Mado en affirmant qu\'une médaille, ce n'est que du métal. Comme Mado essaie de lui prouver que ses valeurs sont solides, il veut la mettre à l'épreuve...",
+ 'description': "Atys, le nouveau concierge de l'école, a réussi à ébranler la confiance de Mado en affirmant qu'une médaille, ce n'est que du métal. Comme Mado essaie de lui prouver que ses valeurs sont solides, il veut la mettre à l'épreuve...",
'subtitles': {
'fr': 'count:1',
},
@@ -110,7 +110,7 @@ class TV5UnisIE(TV5UnisBaseIE):
@staticmethod
def _gql_args(groups):
- args = 'rootProductSlug: "%s"' % groups[0]
+ args = f'rootProductSlug: "{groups[0]}"'
if groups[1]:
- args += ', seasonNumber: %s, episodeNumber: %s' % groups[1:]
+ args += ', seasonNumber: {}, episodeNumber: {}'.format(*groups[1:])
return args
diff --git a/yt_dlp/extractor/tvanouvelles.py b/yt_dlp/extractor/tvanouvelles.py
index dbebda4f4..855c771ec 100644
--- a/yt_dlp/extractor/tvanouvelles.py
+++ b/yt_dlp/extractor/tvanouvelles.py
@@ -42,7 +42,7 @@ class TVANouvellesArticleIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if TVANouvellesIE.suitable(url) else super(TVANouvellesArticleIE, cls).suitable(url)
+ return False if TVANouvellesIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
@@ -51,7 +51,7 @@ class TVANouvellesArticleIE(InfoExtractor):
entries = [
self.url_result(
- 'http://www.tvanouvelles.ca/videos/%s' % mobj.group('id'),
+ 'http://www.tvanouvelles.ca/videos/{}'.format(mobj.group('id')),
ie=TVANouvellesIE.ie_key(), video_id=mobj.group('id'))
for mobj in re.finditer(
r'data-video-id=(["\'])?(?P<id>\d+)', webpage)]
diff --git a/yt_dlp/extractor/tvc.py b/yt_dlp/extractor/tvc.py
index caa76ab6f..7c8c922a6 100644
--- a/yt_dlp/extractor/tvc.py
+++ b/yt_dlp/extractor/tvc.py
@@ -24,7 +24,7 @@ class TVCIE(InfoExtractor):
video_id = self._match_id(url)
video = self._download_json(
- 'http://www.tvc.ru/video/json/id/%s' % video_id, video_id)
+ f'http://www.tvc.ru/video/json/id/{video_id}', video_id)
formats = []
for info in video.get('path', {}).get('quality', []):
diff --git a/yt_dlp/extractor/tver.py b/yt_dlp/extractor/tver.py
index 5f7896837..8105db41c 100644
--- a/yt_dlp/extractor/tver.py
+++ b/yt_dlp/extractor/tver.py
@@ -60,7 +60,7 @@ class TVerIE(InfoExtractor):
'platform_uid': self._PLATFORM_UID,
'platform_token': self._PLATFORM_TOKEN,
}, headers={
- 'x-tver-platform-type': 'web'
+ 'x-tver-platform-type': 'web',
})
episode_content = traverse_obj(
episode_info, ('result', 'episode', 'content')) or {}
diff --git a/yt_dlp/extractor/tvigle.py b/yt_dlp/extractor/tvigle.py
index 6c982193d..6b87ecac7 100644
--- a/yt_dlp/extractor/tvigle.py
+++ b/yt_dlp/extractor/tvigle.py
@@ -46,7 +46,7 @@ class TvigleIE(InfoExtractor):
}, {
'url': 'https://cloud.tvigle.ru/video/5267604/',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
@@ -63,7 +63,7 @@ class TvigleIE(InfoExtractor):
webpage, 'video id')
video_data = self._download_json(
- 'http://cloud.tvigle.ru/api/play/video/%s/' % video_id, display_id)
+ f'http://cloud.tvigle.ru/api/play/video/{video_id}/', display_id)
item = video_data['playlist']['items'][0]
@@ -76,7 +76,7 @@ class TvigleIE(InfoExtractor):
msg=error_message, countries=self._GEO_COUNTRIES)
else:
raise ExtractorError(
- '%s returned error: %s' % (self.IE_NAME, error_message),
+ f'{self.IE_NAME} returned error: {error_message}',
expected=True)
title = item['title']
@@ -115,7 +115,7 @@ class TvigleIE(InfoExtractor):
item, lambda x: x['video_files_size'][vcodec][format_id]))
formats.append({
'url': video_url,
- 'format_id': '%s-%s' % (vcodec, format_id),
+ 'format_id': f'{vcodec}-{format_id}',
'vcodec': vcodec,
'height': int_or_none(height),
'filesize': filesize,
diff --git a/yt_dlp/extractor/tviplayer.py b/yt_dlp/extractor/tviplayer.py
index 7e9b04d55..17f853509 100644
--- a/yt_dlp/extractor/tviplayer.py
+++ b/yt_dlp/extractor/tviplayer.py
@@ -14,7 +14,7 @@ class TVIPlayerIE(InfoExtractor):
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/61c8ee630cf2cc58e7d98d9f/',
'season_number': 8,
'season': 'Season 8',
- }
+ },
}, {
'url': 'https://tviplayer.iol.pt/programa/isabel/62b471090cf26256cd2a8594/video/62be445f0cf2ea4f0a5218e5',
'info_dict': {
@@ -25,7 +25,7 @@ class TVIPlayerIE(InfoExtractor):
'title': 'Isabel - Episódio 1',
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/62beac200cf2f9a86eab856b/',
'season_number': 1,
- }
+ },
}, {
# no /programa/
'url': 'https://tviplayer.iol.pt/video/62c4131c0cf2f9a86eac06bb',
@@ -37,7 +37,7 @@ class TVIPlayerIE(InfoExtractor):
'season': 'Season 2',
'duration': 148,
'season_number': 2,
- }
+ },
}, {
# episodio url
'url': 'https://tviplayer.iol.pt/programa/para-sempre/61716c360cf2365a5ed894c4/episodio/t1e187',
@@ -49,7 +49,7 @@ class TVIPlayerIE(InfoExtractor):
'thumbnail': 'https://www.iol.pt/multimedia/oratvi/multimedia/imagem/id/62eda30b0cf2ea367d48973b/',
'duration': 1250,
'season_number': 1,
- }
+ },
}]
def _real_initialize(self):
diff --git a/yt_dlp/extractor/tvn24.py b/yt_dlp/extractor/tvn24.py
index ac480580a..0dc43a9d4 100644
--- a/yt_dlp/extractor/tvn24.py
+++ b/yt_dlp/extractor/tvn24.py
@@ -18,7 +18,7 @@ class TVN24IE(InfoExtractor):
'title': '"Święta mają być wesołe, dlatego, ludziska, wszyscy pod jemiołę"',
'description': 'Wyjątkowe orędzie Artura Andrusa, jednego z gości Szkła kontaktowego.',
'thumbnail': 're:https?://.*[.]jpeg',
- }
+ },
}, {
# different layout
'url': 'https://tvnmeteo.tvn24.pl/magazyny/maja-w-ogrodzie,13/odcinki-online,1,4,1,0/pnacza-ptaki-i-iglaki-odc-691-hgtv-odc-29,1771763.html',
@@ -58,7 +58,7 @@ class TVN24IE(InfoExtractor):
def extract_json(attr, name, default=NO_DEFAULT, fatal=True):
return self._parse_json(
self._search_regex(
- r'\b%s=(["\'])(?P<json>(?!\1).+?)\1' % attr, webpage,
+ rf'\b{attr}=(["\'])(?P<json>(?!\1).+?)\1', webpage,
name, group='json', default=default, fatal=fatal) or '{}',
display_id, transform_source=unescapeHTML, fatal=fatal)
diff --git a/yt_dlp/extractor/tvnoe.py b/yt_dlp/extractor/tvnoe.py
index 917c46bd1..24a82623f 100644
--- a/yt_dlp/extractor/tvnoe.py
+++ b/yt_dlp/extractor/tvnoe.py
@@ -18,7 +18,7 @@ class TVNoeIE(InfoExtractor):
'series': 'Noční univerzita',
'title': 'prof. Tomáš Halík, Th.D. - Návrat náboženství a střet civilizací',
'description': 'md5:f337bae384e1a531a52c55ebc50fff41',
- }
+ },
}
def _real_extract(self, url):
@@ -40,7 +40,7 @@ class TVNoeIE(InfoExtractor):
'field-name-field-podnazev', webpage)),
'description': clean_html(get_element_by_class(
'field-name-body', webpage)),
- 'series': clean_html(get_element_by_class('title', webpage))
+ 'series': clean_html(get_element_by_class('title', webpage)),
})
return info_dict
diff --git a/yt_dlp/extractor/tvp.py b/yt_dlp/extractor/tvp.py
index f1ebf027a..da3082907 100644
--- a/yt_dlp/extractor/tvp.py
+++ b/yt_dlp/extractor/tvp.py
@@ -98,7 +98,7 @@ class TVPIE(InfoExtractor):
'playlist_mincount': 1800,
'params': {
'skip_download': True,
- }
+ },
}, {
# ABC-specific video embeding
# moved to https://bajkowakraina.tvp.pl/wideo/50981130,teleranek,51027049,zubr,51116450
@@ -221,7 +221,7 @@ class TVPIE(InfoExtractor):
if website_data.get('items_total_count') > website_data.get('items_per_page'):
for page in itertools.count(2):
page_website_data = self._parse_vue_website_data(
- self._download_webpage(url, page_id, note='Downloading page #%d' % page,
+ self._download_webpage(url, page_id, note=f'Downloading page #{page}',
query={'page': page}),
page_id)
if not page_website_data.get('videos') and not page_website_data.get('items'):
@@ -290,7 +290,7 @@ class TVPStreamIE(InfoExtractor):
def _real_extract(self, url):
channel_id = self._match_id(url)
- channel_url = self._proto_relative_url('//stream.tvp.pl/?channel_id=%s' % channel_id or 'default')
+ channel_url = self._proto_relative_url(f'//stream.tvp.pl/?channel_id={channel_id}' or 'default')
webpage = self._download_webpage(channel_url, channel_id or 'default', 'Downloading channel webpage')
channels = self._search_json(
r'window\.__channels\s*=', webpage, 'channel list', channel_id,
@@ -300,7 +300,7 @@ class TVPStreamIE(InfoExtractor):
return {
'_type': 'url_transparent',
'id': channel_id or channel['id'],
- 'url': 'tvp:%s' % audition['video_id'],
+ 'url': 'tvp:{}'.format(audition['video_id']),
'title': audition.get('title'),
'alt_title': channel.get('title'),
'is_live': True,
@@ -379,8 +379,7 @@ class TVPEmbedIE(InfoExtractor):
))
webpage = self._download_webpage(
- ('https://www.tvp.pl/sess/TVPlayer2/api.php?id=%s'
- + '&@method=getTvpConfig&@callback=%s') % (video_id, callback), video_id)
+ f'https://www.tvp.pl/sess/TVPlayer2/api.php?id={video_id}&@method=getTvpConfig&@callback={callback}', video_id)
# stripping JSONP padding
datastr = webpage[15 + len(callback):-3]
@@ -470,7 +469,7 @@ class TVPEmbedIE(InfoExtractor):
# vod.tvp.pl
if info.get('vortalName') == 'vod':
info_dict.update({
- 'title': '%s, %s' % (info.get('title'), info.get('subtitle')),
+ 'title': '{}, {}'.format(info.get('title'), info.get('subtitle')),
'series': info.get('title'),
'season': info.get('season'),
'episode_number': info.get('episode'),
diff --git a/yt_dlp/extractor/tvplay.py b/yt_dlp/extractor/tvplay.py
index 29185d34b..b4a927a1d 100644
--- a/yt_dlp/extractor/tvplay.py
+++ b/yt_dlp/extractor/tvplay.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -107,7 +107,7 @@ class TVPlayIE(InfoExtractor):
{
'url': 'mtg:418113',
'only_matching': True,
- }
+ },
]
def _real_extract(self, url):
@@ -118,13 +118,13 @@ class TVPlayIE(InfoExtractor):
if geo_country:
self._initialize_geo_bypass({'countries': [geo_country.upper()]})
video = self._download_json(
- 'http://playapi.mtgx.tv/v3/videos/%s' % video_id, video_id, 'Downloading video JSON')
+ f'http://playapi.mtgx.tv/v3/videos/{video_id}', video_id, 'Downloading video JSON')
title = video['title']
try:
streams = self._download_json(
- 'http://playapi.mtgx.tv/v3/videos/stream/%s' % video_id,
+ f'http://playapi.mtgx.tv/v3/videos/stream/{video_id}',
video_id, 'Downloading streams JSON')
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
@@ -143,7 +143,7 @@ class TVPlayIE(InfoExtractor):
formats.extend(self._extract_f4m_formats(
update_url_query(video_url, {
'hdcore': '3.5.0',
- 'plugin': 'aasp-3.5.0.151.81'
+ 'plugin': 'aasp-3.5.0.151.81',
}), video_id, f4m_id='hds', fatal=False))
elif ext == 'm3u8':
formats.extend(self._extract_m3u8_formats(
@@ -184,7 +184,7 @@ class TVPlayIE(InfoExtractor):
if sami_path:
lang = self._search_regex(
r'_([a-z]{2})\.xml', sami_path, 'lang',
- default=compat_urlparse.urlparse(url).netloc.rsplit('.', 1)[-1])
+ default=urllib.parse.urlparse(url).netloc.rsplit('.', 1)[-1])
subtitles[lang] = [{
'url': sami_path,
}]
@@ -250,7 +250,7 @@ class TVPlayHomeIE(InfoExtractor):
'description': 'md5:c6926e9710f1a126f028fbe121eddb79',
'duration': 2440,
},
- 'skip': '404'
+ 'skip': '404',
}, {
'url': 'https://play.tv3.lt/lives/tv6-lt,live-2838694/optibet-a-lygos-rungtynes-marijampoles-suduva--vilniaus-riteriai,programme-3422014',
'only_matching': True,
diff --git a/yt_dlp/extractor/tvplayer.py b/yt_dlp/extractor/tvplayer.py
index d43bdc2ff..7c47bc78e 100644
--- a/yt_dlp/extractor/tvplayer.py
+++ b/yt_dlp/extractor/tvplayer.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -21,7 +20,7 @@ class TVPlayerIE(InfoExtractor):
'params': {
# m3u8 download
'skip_download': True,
- }
+ },
}
def _real_extract(self, url):
@@ -48,7 +47,7 @@ class TVPlayerIE(InfoExtractor):
validate = context['validate']
platform = try_get(
- context, lambda x: x['platform']['key'], compat_str) or 'firefox'
+ context, lambda x: x['platform']['key'], str) or 'firefox'
try:
response = self._download_json(
@@ -66,7 +65,7 @@ class TVPlayerIE(InfoExtractor):
response = self._parse_json(
e.cause.response.read().decode(), resource_id)['tvplayer']['response']
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, response['error']), expected=True)
+ '{} said: {}'.format(self.IE_NAME, response['error']), expected=True)
raise
formats = self._extract_m3u8_formats(response['stream'], display_id, 'mp4')
diff --git a/yt_dlp/extractor/tweakers.py b/yt_dlp/extractor/tweakers.py
index 9249550c9..77a4136cc 100644
--- a/yt_dlp/extractor/tweakers.py
+++ b/yt_dlp/extractor/tweakers.py
@@ -19,13 +19,13 @@ class TweakersIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpe?g$',
'duration': 386,
'uploader_id': 's7JeEm',
- }
+ },
}
def _real_extract(self, url):
video_id = self._match_id(url)
video_data = self._download_json(
- 'https://tweakers.net/video/s1playlist/%s/1920/1080/playlist.json' % video_id,
+ f'https://tweakers.net/video/s1playlist/{video_id}/1920/1080/playlist.json',
video_id)['items'][0]
title = video_data['title']
diff --git a/yt_dlp/extractor/twentymin.py b/yt_dlp/extractor/twentymin.py
index 74f90b00b..a08c591d9 100644
--- a/yt_dlp/extractor/twentymin.py
+++ b/yt_dlp/extractor/twentymin.py
@@ -47,14 +47,14 @@ class TwentyMinutenIE(InfoExtractor):
video_id = self._match_id(url)
video = self._download_json(
- 'http://api.20min.ch/video/%s/show' % video_id,
+ f'http://api.20min.ch/video/{video_id}/show',
video_id)['content']
title = video['title']
formats = [{
'format_id': format_id,
- 'url': 'http://podcast.20min-tv.ch/podcast/20min/%s%s.mp4' % (video_id, p),
+ 'url': f'http://podcast.20min-tv.ch/podcast/20min/{video_id}{p}.mp4',
'quality': quality,
} for quality, (format_id, p) in enumerate([('sd', ''), ('hd', 'h')])]
@@ -64,7 +64,7 @@ class TwentyMinutenIE(InfoExtractor):
def extract_count(kind):
return try_get(
video,
- lambda x: int_or_none(x['communityobject']['thumbs_%s' % kind]))
+ lambda x: int_or_none(x['communityobject'][f'thumbs_{kind}']))
like_count = extract_count('up')
dislike_count = extract_count('down')
diff --git a/yt_dlp/extractor/twentythreevideo.py b/yt_dlp/extractor/twentythreevideo.py
index 290c3761e..af7c00686 100644
--- a/yt_dlp/extractor/twentythreevideo.py
+++ b/yt_dlp/extractor/twentythreevideo.py
@@ -16,7 +16,7 @@ class TwentyThreeVideoIE(InfoExtractor):
'upload_date': '20171221',
'uploader_id': '12258964',
'uploader': 'Rasmus Bysted',
- }
+ },
}, {
'url': 'https://bonnier-publications-danmark.23video.com/v.ihtml/player.html?token=f0dc46476e06e13afd5a1f84a29e31e8&source=embed&photo%5fid=36137620',
'only_matching': True,
@@ -24,7 +24,7 @@ class TwentyThreeVideoIE(InfoExtractor):
def _real_extract(self, url):
domain, query, photo_id = self._match_valid_url(url).groups()
- base_url = 'https://%s' % domain
+ base_url = f'https://{domain}'
photo_data = self._download_json(
base_url + '/api/photo/list?' + query, photo_id, query={
'format': 'json',
diff --git a/yt_dlp/extractor/twitcasting.py b/yt_dlp/extractor/twitcasting.py
index 28ea16cc2..53b408469 100644
--- a/yt_dlp/extractor/twitcasting.py
+++ b/yt_dlp/extractor/twitcasting.py
@@ -139,7 +139,7 @@ class TwitCastingIE(InfoExtractor):
webpage, 'datetime', None))
stream_server_data = self._download_json(
- 'https://twitcasting.tv/streamserver.php?target=%s&mode=client' % uploader_id, video_id,
+ f'https://twitcasting.tv/streamserver.php?target={uploader_id}&mode=client', video_id,
'Downloading live info', fatal=False)
is_live = any(f'data-{x}' in webpage for x in ['is-onlive="true"', 'live-type="live"', 'status="online"'])
@@ -189,7 +189,7 @@ class TwitCastingIE(InfoExtractor):
for mode, ws_url in streams.items():
formats.append({
'url': ws_url,
- 'format_id': 'ws-%s' % mode,
+ 'format_id': f'ws-{mode}',
'ext': 'mp4',
'quality': qq(mode),
'source_preference': -10,
@@ -244,8 +244,8 @@ class TwitCastingLiveIE(InfoExtractor):
def _real_extract(self, url):
uploader_id = self._match_id(url)
self.to_screen(
- 'Downloading live video of user {0}. '
- 'Pass "https://twitcasting.tv/{0}/show" to download the history'.format(uploader_id))
+ f'Downloading live video of user {uploader_id}. '
+ f'Pass "https://twitcasting.tv/{uploader_id}/show" to download the history')
is_live = traverse_obj(self._download_json(
f'https://frontendapi.twitcasting.tv/watch/user/{uploader_id}',
@@ -284,10 +284,10 @@ class TwitCastingUserIE(InfoExtractor):
}]
def _entries(self, uploader_id):
- base_url = next_url = 'https://twitcasting.tv/%s/show' % uploader_id
+ base_url = next_url = f'https://twitcasting.tv/{uploader_id}/show'
for page_num in itertools.count(1):
webpage = self._download_webpage(
- next_url, uploader_id, query={'filter': 'watchable'}, note='Downloading page %d' % page_num)
+ next_url, uploader_id, query={'filter': 'watchable'}, note=f'Downloading page {page_num}')
matches = re.finditer(
r'(?s)<a\s+class="tw-movie-thumbnail2"\s+href="(?P<url>/[^/"]+/movie/\d+)"', webpage)
for mobj in matches:
@@ -303,4 +303,4 @@ class TwitCastingUserIE(InfoExtractor):
def _real_extract(self, url):
uploader_id = self._match_id(url)
return self.playlist_result(
- self._entries(uploader_id), uploader_id, '%s - Live History' % uploader_id)
+ self._entries(uploader_id), uploader_id, f'{uploader_id} - Live History')
diff --git a/yt_dlp/extractor/twitch.py b/yt_dlp/extractor/twitch.py
index 80cba0915..44b19ad13 100644
--- a/yt_dlp/extractor/twitch.py
+++ b/yt_dlp/extractor/twitch.py
@@ -3,13 +3,9 @@ import itertools
import json
import random
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_parse_qs,
- compat_str,
- compat_urllib_parse_urlparse,
-)
from ..utils import (
ExtractorError,
UserNotLive,
@@ -64,7 +60,7 @@ class TwitchBaseIE(InfoExtractor):
def _perform_login(self, username, password):
def fail(message):
raise ExtractorError(
- 'Unable to login. Twitch said: %s' % message, expected=True)
+ f'Unable to login. Twitch said: {message}', expected=True)
def login_step(page, urlh, note, data):
form = self._hidden_inputs(page)
@@ -156,12 +152,12 @@ class TwitchBaseIE(InfoExtractor):
'persistedQuery': {
'version': 1,
'sha256Hash': self._OPERATION_HASHES[op['operationName']],
- }
+ },
}
return self._download_base_gql(video_id, ops, note)
def _download_access_token(self, video_id, token_kind, param_name):
- method = '%sPlaybackAccessToken' % token_kind
+ method = f'{token_kind}PlaybackAccessToken'
ops = {
'query': '''{
%s(
@@ -176,11 +172,11 @@ class TwitchBaseIE(InfoExtractor):
value
signature
}
- }''' % (method, param_name, video_id),
+ }''' % (method, param_name, video_id), # noqa: UP031
}
return self._download_base_gql(
video_id, ops,
- 'Downloading %s access token GraphQL' % token_kind)['data'][method]
+ f'Downloading {token_kind} access token GraphQL')['data'][method]
def _get_thumbnails(self, thumbnail):
return [{
@@ -242,8 +238,8 @@ class TwitchVodIE(TwitchBaseIE):
{
'start_time': 0,
'end_time': 17208,
- 'title': 'League of Legends'
- }
+ 'title': 'League of Legends',
+ },
],
'live_status': 'was_live',
},
@@ -301,25 +297,25 @@ class TwitchVodIE(TwitchBaseIE):
{
'start_time': 0,
'end_time': 573,
- 'title': 'League of Legends'
+ 'title': 'League of Legends',
},
{
'start_time': 573,
'end_time': 3922,
- 'title': 'Legends of Runeterra'
+ 'title': 'Legends of Runeterra',
},
{
'start_time': 3922,
'end_time': 11643,
- 'title': 'Art'
- }
+ 'title': 'Art',
+ },
],
'live_status': 'was_live',
'thumbnail': r're:^https?://.*\.jpg$',
'view_count': int,
},
'params': {
- 'skip_download': True
+ 'skip_download': True,
},
}, {
'note': 'Storyboards',
@@ -338,18 +334,18 @@ class TwitchVodIE(TwitchBaseIE):
{
'start_time': 0,
'end_time': 573,
- 'title': 'League of Legends'
+ 'title': 'League of Legends',
},
{
'start_time': 573,
'end_time': 3922,
- 'title': 'Legends of Runeterra'
+ 'title': 'Legends of Runeterra',
},
{
'start_time': 3922,
'end_time': 11643,
- 'title': 'Art'
- }
+ 'title': 'Art',
+ },
],
'live_status': 'was_live',
'thumbnail': r're:^https?://.*\.jpg$',
@@ -359,8 +355,8 @@ class TwitchVodIE(TwitchBaseIE):
},
'params': {
'format': 'mhtml',
- 'skip_download': True
- }
+ 'skip_download': True,
+ },
}, {
'note': 'VOD with single chapter',
'url': 'https://www.twitch.tv/videos/1536751224',
@@ -377,17 +373,17 @@ class TwitchVodIE(TwitchBaseIE):
{
'start_time': 0,
'end_time': 8353,
- 'title': 'League of Legends'
- }
+ 'title': 'League of Legends',
+ },
],
'live_status': 'was_live',
'thumbnail': r're:^https?://.*\.jpg$',
'view_count': int,
},
'params': {
- 'skip_download': True
+ 'skip_download': True,
},
- 'expected_warnings': ['Unable to download JSON metadata: HTTP Error 403: Forbidden']
+ 'expected_warnings': ['Unable to download JSON metadata: HTTP Error 403: Forbidden'],
}, {
'url': 'https://www.twitch.tv/tangotek/schedule?vodID=1822395420',
'only_matching': True,
@@ -488,7 +484,7 @@ class TwitchVodIE(TwitchBaseIE):
vod_id = info.get('id') or item_id
# id backward compatibility for download archives
if vod_id[0] != 'v':
- vod_id = 'v%s' % vod_id
+ vod_id = f'v{vod_id}'
thumbnail = url_or_none(info.get('previewThumbnailURL'))
is_live = None
if thumbnail:
@@ -503,8 +499,8 @@ class TwitchVodIE(TwitchBaseIE):
'description': info.get('description'),
'duration': int_or_none(info.get('lengthSeconds')),
'thumbnails': self._get_thumbnails(thumbnail),
- 'uploader': try_get(info, lambda x: x['owner']['displayName'], compat_str),
- 'uploader_id': try_get(info, lambda x: x['owner']['login'], compat_str),
+ 'uploader': try_get(info, lambda x: x['owner']['displayName'], str),
+ 'uploader_id': try_get(info, lambda x: x['owner']['login'], str),
'timestamp': unified_timestamp(info.get('publishedAt')),
'view_count': int_or_none(info.get('viewCount')),
'chapters': list(self._extract_chapters(info, item_id)),
@@ -559,8 +555,8 @@ class TwitchVodIE(TwitchBaseIE):
self._prefer_source(formats)
info['formats'] = formats
- parsed_url = compat_urllib_parse_urlparse(url)
- query = compat_parse_qs(parsed_url.query)
+ parsed_url = urllib.parse.urlparse(url)
+ query = urllib.parse.parse_qs(parsed_url.query)
if 't' in query:
info['start_time'] = parse_duration(query['t'][0])
@@ -568,7 +564,7 @@ class TwitchVodIE(TwitchBaseIE):
info['subtitles'] = {
'rechat': [{
'url': update_url_query(
- 'https://api.twitch.tv/v5/videos/%s/comments' % vod_id, {
+ f'https://api.twitch.tv/v5/videos/{vod_id}/comments', {
'client_id': self._CLIENT_ID,
}),
'ext': 'json',
@@ -587,7 +583,7 @@ def _make_video_result(node):
'_type': 'url_transparent',
'ie_key': TwitchVodIE.ie_key(),
'id': 'v' + video_id,
- 'url': 'https://www.twitch.tv/videos/%s' % video_id,
+ 'url': f'https://www.twitch.tv/videos/{video_id}',
'title': node.get('title'),
'thumbnail': node.get('previewThumbnailURL'),
'duration': float_or_none(node.get('lengthSeconds')),
@@ -638,7 +634,7 @@ class TwitchPlaylistBaseIE(TwitchBaseIE):
def _entries(self, channel_name, *args):
cursor = None
variables_common = self._make_variables(channel_name, *args)
- entries_key = '%ss' % self._ENTRY_KIND
+ entries_key = f'{self._ENTRY_KIND}s'
for page_num in itertools.count(1):
variables = variables_common.copy()
variables['limit'] = self._PAGE_LIMIT
@@ -649,7 +645,7 @@ class TwitchPlaylistBaseIE(TwitchBaseIE):
'operationName': self._OPERATION_NAME,
'variables': variables,
}],
- 'Downloading %ss GraphQL page %s' % (self._NODE_KIND, page_num),
+ f'Downloading {self._NODE_KIND}s GraphQL page {page_num}',
fatal=False)
if not page:
break
@@ -671,7 +667,7 @@ class TwitchPlaylistBaseIE(TwitchBaseIE):
if entry:
cursor = edge.get('cursor')
yield entry
- if not cursor or not isinstance(cursor, compat_str):
+ if not cursor or not isinstance(cursor, str):
break
@@ -765,7 +761,7 @@ class TwitchVideosIE(TwitchPlaylistBaseIE):
if any(ie.suitable(url) for ie in (
TwitchVideosClipsIE,
TwitchVideosCollectionsIE))
- else super(TwitchVideosIE, cls).suitable(url))
+ else super().suitable(url))
@staticmethod
def _make_variables(channel_name, broadcast_type, sort):
@@ -782,15 +778,15 @@ class TwitchVideosIE(TwitchPlaylistBaseIE):
def _real_extract(self, url):
channel_name = self._match_id(url)
qs = parse_qs(url)
- filter = qs.get('filter', ['all'])[0]
+ video_filter = qs.get('filter', ['all'])[0]
sort = qs.get('sort', ['time'])[0]
- broadcast = self._BROADCASTS.get(filter, self._DEFAULT_BROADCAST)
+ broadcast = self._BROADCASTS.get(video_filter, self._DEFAULT_BROADCAST)
return self.playlist_result(
self._entries(channel_name, broadcast.type, sort),
playlist_id=channel_name,
- playlist_title='%s - %s sorted by %s'
- % (channel_name, broadcast.label,
- self._SORTED_BY.get(sort, self._DEFAULT_SORTED_BY)))
+ playlist_title=(
+ f'{channel_name} - {broadcast.label} '
+ f'sorted by {self._SORTED_BY.get(sort, self._DEFAULT_SORTED_BY)}'))
class TwitchVideosClipsIE(TwitchPlaylistBaseIE):
@@ -828,11 +824,11 @@ class TwitchVideosClipsIE(TwitchPlaylistBaseIE):
_NODE_KIND = 'Clip'
@staticmethod
- def _make_variables(channel_name, filter):
+ def _make_variables(channel_name, channel_filter):
return {
'login': channel_name,
'criteria': {
- 'filter': filter,
+ 'filter': channel_filter,
},
}
@@ -858,12 +854,12 @@ class TwitchVideosClipsIE(TwitchPlaylistBaseIE):
def _real_extract(self, url):
channel_name = self._match_id(url)
qs = parse_qs(url)
- range = qs.get('range', ['7d'])[0]
- clip = self._RANGE.get(range, self._DEFAULT_CLIP)
+ date_range = qs.get('range', ['7d'])[0]
+ clip = self._RANGE.get(date_range, self._DEFAULT_CLIP)
return self.playlist_result(
self._entries(channel_name, clip.filter),
playlist_id=channel_name,
- playlist_title='%s - Clips %s' % (channel_name, clip.label))
+ playlist_title=f'{channel_name} - Clips {clip.label}')
class TwitchVideosCollectionsIE(TwitchPlaylistBaseIE):
@@ -907,7 +903,7 @@ class TwitchVideosCollectionsIE(TwitchPlaylistBaseIE):
'_type': 'url_transparent',
'ie_key': TwitchCollectionIE.ie_key(),
'id': collection_id,
- 'url': 'https://www.twitch.tv/collections/%s' % collection_id,
+ 'url': f'https://www.twitch.tv/collections/{collection_id}',
'title': node.get('title'),
'thumbnail': node.get('thumbnailURL'),
'duration': float_or_none(node.get('lengthSeconds')),
@@ -919,7 +915,7 @@ class TwitchVideosCollectionsIE(TwitchPlaylistBaseIE):
channel_name = self._match_id(url)
return self.playlist_result(
self._entries(channel_name), playlist_id=channel_name,
- playlist_title='%s - Collections' % channel_name)
+ playlist_title=f'{channel_name} - Collections')
class TwitchStreamIE(TwitchBaseIE):
@@ -996,7 +992,7 @@ class TwitchStreamIE(TwitchBaseIE):
TwitchVideosClipsIE,
TwitchVideosCollectionsIE,
TwitchClipsIE))
- else super(TwitchStreamIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
channel_name = self._match_id(url).lower()
@@ -1025,7 +1021,7 @@ class TwitchStreamIE(TwitchBaseIE):
if not user:
raise ExtractorError(
- '%s does not exist' % channel_name, expected=True)
+ f'{channel_name} does not exist', expected=True)
stream = user['stream']
@@ -1046,16 +1042,16 @@ class TwitchStreamIE(TwitchBaseIE):
sq_user = try_get(gql, lambda x: x[1]['data']['user'], dict) or {}
uploader = sq_user.get('displayName')
description = try_get(
- sq_user, lambda x: x['broadcastSettings']['title'], compat_str)
+ sq_user, lambda x: x['broadcastSettings']['title'], str)
thumbnail = url_or_none(try_get(
gql, lambda x: x[2]['data']['user']['stream']['previewImageURL'],
- compat_str))
+ str))
title = uploader or channel_name
stream_type = stream.get('type')
if stream_type in ['rerun', 'live']:
- title += ' (%s)' % stream_type
+ title += f' ({stream_type})'
return {
'id': stream_id,
@@ -1165,7 +1161,7 @@ class TwitchClipsIE(TwitchBaseIE):
}
viewCount
}
-}''' % video_id}, 'Downloading clip GraphQL', fatal=False)
+}''' % video_id}, 'Downloading clip GraphQL', fatal=False) # noqa: UP031
if data:
clip = try_get(data, lambda x: x['data']['clip'], dict) or clip
@@ -1213,7 +1209,7 @@ class TwitchClipsIE(TwitchBaseIE):
'view_count': int_or_none(clip.get('viewCount')),
'timestamp': unified_timestamp(clip.get('createdAt')),
'thumbnails': thumbnails,
- 'creator': try_get(clip, lambda x: x['broadcaster']['displayName'], compat_str),
- 'uploader': try_get(clip, lambda x: x['curator']['displayName'], compat_str),
- 'uploader_id': try_get(clip, lambda x: x['curator']['id'], compat_str),
+ 'creator': try_get(clip, lambda x: x['broadcaster']['displayName'], str),
+ 'uploader': try_get(clip, lambda x: x['curator']['displayName'], str),
+ 'uploader_id': try_get(clip, lambda x: x['curator']['id'], str),
}
diff --git a/yt_dlp/extractor/twitter.py b/yt_dlp/extractor/twitter.py
index 1a11162a0..4ed48ec5a 100644
--- a/yt_dlp/extractor/twitter.py
+++ b/yt_dlp/extractor/twitter.py
@@ -2,14 +2,10 @@ import functools
import json
import random
import re
+import urllib.parse
from .common import InfoExtractor
from .periscope import PeriscopeBaseIE, PeriscopeIE
-from ..compat import (
- compat_parse_qs,
- compat_urllib_parse_unquote,
- compat_urllib_parse_urlparse,
-)
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -46,9 +42,9 @@ class TwitterBaseIE(InfoExtractor):
'flow_context': {
'debug_overrides': {},
'start_location': {
- 'location': 'unknown'
- }
- }
+ 'location': 'unknown',
+ },
+ },
},
'subtask_versions': {
'action_list': 2,
@@ -91,8 +87,8 @@ class TwitterBaseIE(InfoExtractor):
'user_recommendations_list': 4,
'user_recommendations_urt': 1,
'wait_spinner': 3,
- 'web_modal': 1
- }
+ 'web_modal': 1,
+ },
}, separators=(',', ':')).encode()
def _extract_variant_formats(self, variant, video_id):
@@ -111,7 +107,7 @@ class TwitterBaseIE(InfoExtractor):
tbr = int_or_none(dict_get(variant, ('bitrate', 'bit_rate')), 1000) or None
f = {
'url': variant_url,
- 'format_id': 'http' + ('-%d' % tbr if tbr else ''),
+ 'format_id': 'http' + (f'-{tbr}' if tbr else ''),
'tbr': tbr,
}
self._search_dimensions_in_video_url(f, variant_url)
@@ -126,7 +122,7 @@ class TwitterBaseIE(InfoExtractor):
subtitles = {}
urls = []
for video_variant in vmap_data.findall('.//{http://twitter.com/schema/videoVMapV2.xsd}videoVariant'):
- video_variant.attrib['url'] = compat_urllib_parse_unquote(
+ video_variant.attrib['url'] = urllib.parse.unquote(
video_variant.attrib['url'])
urls.append(video_variant.attrib['url'])
fmts, subs = self._extract_variant_formats(
@@ -218,7 +214,7 @@ class TwitterBaseIE(InfoExtractor):
def build_login_json(*subtask_inputs):
return json.dumps({
'flow_token': self._flow_token,
- 'subtask_inputs': subtask_inputs
+ 'subtask_inputs': subtask_inputs,
}, separators=(',', ':')).encode()
def input_dict(subtask_id, text):
@@ -226,8 +222,8 @@ class TwitterBaseIE(InfoExtractor):
'subtask_id': subtask_id,
'enter_text': {
'text': text,
- 'link': 'next_link'
- }
+ 'link': 'next_link',
+ },
}
next_subtask = self._call_login_api(
@@ -240,8 +236,8 @@ class TwitterBaseIE(InfoExtractor):
'subtask_id': next_subtask,
'js_instrumentation': {
'response': '{}',
- 'link': 'next_link'
- }
+ 'link': 'next_link',
+ },
}))
elif next_subtask == 'LoginEnterUserIdentifierSSO':
@@ -253,12 +249,12 @@ class TwitterBaseIE(InfoExtractor):
'key': 'user_identifier',
'response_data': {
'text_data': {
- 'result': username
- }
- }
+ 'result': username,
+ },
+ },
}],
- 'link': 'next_link'
- }
+ 'link': 'next_link',
+ },
}))
elif next_subtask == 'LoginEnterAlternateIdentifierSubtask':
@@ -273,8 +269,8 @@ class TwitterBaseIE(InfoExtractor):
'subtask_id': next_subtask,
'enter_password': {
'password': password,
- 'link': 'next_link'
- }
+ 'link': 'next_link',
+ },
}))
elif next_subtask == 'AccountDuplicationCheck':
@@ -282,8 +278,8 @@ class TwitterBaseIE(InfoExtractor):
'Submitting account duplication check', headers, data=build_login_json({
'subtask_id': next_subtask,
'check_logged_in_account': {
- 'link': 'AccountDuplicationCheck_false'
- }
+ 'link': 'AccountDuplicationCheck_false',
+ },
}))
elif next_subtask == 'LoginTwoFactorAuthChallenge':
@@ -317,7 +313,7 @@ class TwitterBaseIE(InfoExtractor):
'x-twitter-client-language': 'en',
'x-twitter-active-user': 'yes',
} if self.is_logged_in else {
- 'x-guest-token': self._fetch_guest_token(video_id)
+ 'x-guest-token': self._fetch_guest_token(video_id),
})
allowed_status = {400, 401, 403, 404} if graphql else {403}
result = self._download_json(
@@ -388,7 +384,7 @@ class TwitterCardIE(InfoExtractor):
'repost_count': int,
'tags': ['PlutoFlyby'],
},
- 'params': {'format': '[protocol=https]'}
+ 'params': {'format': '[protocol=https]'},
},
{
'url': 'https://twitter.com/i/cards/tfw/v1/654001591733886977',
@@ -1221,7 +1217,7 @@ class TwitterIE(TwitterBaseIE):
'thumbnail': r're:https://pbs\.twimg\.com/amplify_video_thumb/.+',
'age_limit': 0,
'_old_archive_ids': ['twitter 1790637656616943991'],
- }
+ },
}, {
# onion route
'url': 'https://twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion/TwitterBlue/status/1484226494708662273',
@@ -1374,11 +1370,11 @@ class TwitterIE(TwitterBaseIE):
'responsive_web_media_download_video_enabled': False,
'responsive_web_graphql_skip_user_profile_image_extensions_enabled': False,
'responsive_web_graphql_timeline_navigation_enabled': True,
- 'responsive_web_enhance_cards_enabled': False
+ 'responsive_web_enhance_cards_enabled': False,
},
'fieldToggles': {
- 'withArticleRichContentState': False
- }
+ 'withArticleRichContentState': False,
+ },
}
def _call_syndication_api(self, twid):
@@ -1644,9 +1640,9 @@ class TwitterAmplifyIE(TwitterBaseIE):
def _find_dimension(target):
w = int_or_none(self._html_search_meta(
- 'twitter:%s:width' % target, webpage, fatal=False))
+ f'twitter:{target}:width', webpage, fatal=False))
h = int_or_none(self._html_search_meta(
- 'twitter:%s:height' % target, webpage, fatal=False))
+ f'twitter:{target}:height', webpage, fatal=False))
return w, h
if thumbnail:
@@ -1740,7 +1736,7 @@ class TwitterBroadcastIE(TwitterBaseIE, PeriscopeBaseIE):
m3u8_url = source.get('noRedirectPlaybackUrl') or source['location']
if '/live_video_stream/geoblocked/' in m3u8_url:
self.raise_geo_restricted()
- m3u8_id = compat_parse_qs(compat_urllib_parse_urlparse(
+ m3u8_id = urllib.parse.parse_qs(urllib.parse.urlparse(
m3u8_url).query).get('type', [None])[0]
state, width, height = self._extract_common_format_info(broadcast)
info['formats'] = self._extract_pscp_m3u8_formats(
@@ -1895,12 +1891,12 @@ class TwitterShortenerIE(TwitterBaseIE):
def _real_extract(self, url):
mobj = self._match_valid_url(url)
- eid, id = mobj.group('eid', 'id')
+ eid, shortcode = mobj.group('eid', 'id')
if eid:
- id = eid
- url = self._BASE_URL + id
- new_url = self._request_webpage(url, id, headers={'User-Agent': 'curl'}).url
- __UNSAFE_LINK = "https://twitter.com/safety/unsafe_link_warning?unsafe_link="
+ shortcode = eid
+ url = self._BASE_URL + shortcode
+ new_url = self._request_webpage(url, shortcode, headers={'User-Agent': 'curl'}).url
+ __UNSAFE_LINK = 'https://twitter.com/safety/unsafe_link_warning?unsafe_link='
if new_url.startswith(__UNSAFE_LINK):
- new_url = new_url.replace(__UNSAFE_LINK, "")
+ new_url = new_url.replace(__UNSAFE_LINK, '')
return self.url_result(new_url)
diff --git a/yt_dlp/extractor/txxx.py b/yt_dlp/extractor/txxx.py
index 77dabbc82..488c13b1a 100644
--- a/yt_dlp/extractor/txxx.py
+++ b/yt_dlp/extractor/txxx.py
@@ -85,7 +85,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.txxx.tube/contents/videos_sources/16574000/16574965/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://txxx.tube/videos/16574965/digital-desire-malena-morgan/',
'md5': 'c54e4ace54320aaf8e2a72df87859391',
@@ -101,7 +101,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.txxx.tube/contents/videos_sources/16574000/16574965/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://vxxx.com/video-68925/',
'md5': '1fcff3748b0c5b41fe41d0afa22409e1',
@@ -117,7 +117,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.vxxx.com/contents/videos_sources/68000/68925/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://hclips.com/videos/6291073/malena-morgan-masturbates-her-sweet/',
'md5': 'a5dd4f83363972ee043313cff85e7e26',
@@ -133,7 +133,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://hctn.nv7s.com/contents/videos_sources/6291000/6291073/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://hdzog.com/videos/67063/gorgeous-malena-morgan-will-seduce-you-at-the-first-glance/',
'md5': 'f8bdedafd45d1ec2875c43fe33a846d3',
@@ -149,7 +149,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.hdzog.com/contents/videos_sources/67000/67063/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://hdzog.tube/videos/67063/gorgeous-malena-morgan-will-seduce-you-at-the-first-glance/',
'md5': 'f8bdedafd45d1ec2875c43fe33a846d3',
@@ -165,7 +165,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.hdzog.com/contents/videos_sources/67000/67063/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://hotmovs.com/videos/8789287/unbelievable-malena-morgan-performing-in-incredible-masturantion/',
'md5': '71d32c51584876472db87e561171a386',
@@ -181,7 +181,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.hotmovs.com/contents/videos_sources/8789000/8789287/screenshots/10.jpg',
- }
+ },
}, {
'url': 'https://hotmovs.tube/videos/8789287/unbelievable-malena-morgan-performing-in-incredible-masturantion/',
'md5': '71d32c51584876472db87e561171a386',
@@ -197,7 +197,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.hotmovs.com/contents/videos_sources/8789000/8789287/screenshots/10.jpg',
- }
+ },
}, {
'url': 'https://inporn.com/video/517897/malena-morgan-solo/',
'md5': '344db467481edf78f193cdf5820a7cfb',
@@ -213,7 +213,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://iptn.m3pd.com/media/tn/sources/517897_1.jpg',
- }
+ },
}, {
'url': 'https://privatehomeclips.com/videos/3630599/malena-morgan-cam-show/',
'md5': 'ea657273e352493c5fb6357fbfa4f126',
@@ -229,7 +229,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://hctn.nv7s.com/contents/videos_sources/3630000/3630599/screenshots/15.jpg',
- }
+ },
}, {
'url': 'https://tubepornclassic.com/videos/1015455/mimi-rogers-full-body-massage-nude-compilation/',
'md5': '2e9a6cf610c9862e86e0ce24f08f4427',
@@ -245,7 +245,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.tubepornclassic.com/contents/videos_sources/1015000/1015455/screenshots/6.jpg',
- }
+ },
}, {
'url': 'https://upornia.com/videos/1498858/twistys-malena-morgan-starring-at-dr-morgan-baller/',
'md5': '7ff7033340bc88a173198b7c22600e4f',
@@ -261,7 +261,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.upornia.com/contents/videos_sources/1498000/1498858/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://upornia.tube/videos/1498858/twistys-malena-morgan-starring-at-dr-morgan-baller/',
'md5': '7ff7033340bc88a173198b7c22600e4f',
@@ -277,7 +277,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.upornia.com/contents/videos_sources/1498000/1498858/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://vjav.com/videos/11761/yui-hatano-in-if-yui-was-my-girlfriend2/',
'md5': '6de5bc1f13bdfc3491a77f23edb1676f',
@@ -293,7 +293,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.vjav.com/contents/videos_sources/11000/11761/screenshots/23.jpg',
- }
+ },
}, {
'url': 'https://vjav.tube/videos/11761/yui-hatano-in-if-yui-was-my-girlfriend2/',
'md5': '6de5bc1f13bdfc3491a77f23edb1676f',
@@ -309,7 +309,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.vjav.com/contents/videos_sources/11000/11761/screenshots/23.jpg',
- }
+ },
}, {
'url': 'https://voyeurhit.com/videos/332875/charlotte-stokely-elle-alexandra-malena-morgan-lingerie/',
'md5': '12b4666e9c3e60dafe9182e5d12aae33',
@@ -325,7 +325,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.voyeurhit.com/contents/videos_sources/332000/332875/screenshots/1.jpg',
- }
+ },
}, {
'url': 'https://voyeurhit.tube/videos/332875/charlotte-stokely-elle-alexandra-malena-morgan-lingerie/',
'md5': '12b4666e9c3e60dafe9182e5d12aae33',
@@ -341,7 +341,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://tn.voyeurhit.com/contents/videos_sources/332000/332875/screenshots/1.jpg',
- }
+ },
}]
_WEBPAGE_TESTS = [{
'url': 'https://pornzog.com/video/9125519/michelle-malone-dreamgirls-wild-wet-3/',
@@ -357,7 +357,7 @@ class TxxxIE(InfoExtractor):
'dislike_count': int,
'age_limit': 18,
'thumbnail': 'https://hctn.nv7s.com/contents/videos_sources/5119000/5119660/screenshots/1.jpg',
- }
+ },
}]
def _call_api(self, url, video_id, fatal=False, **kwargs):
@@ -415,7 +415,7 @@ class PornTopIE(InfoExtractor):
'timestamp': 1609455029,
'upload_date': '20201231',
'thumbnail': 'https://tn.porntop.com/media/tn/sources/101569_1.jpg',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/udemy.py b/yt_dlp/extractor/udemy.py
index 5c296051a..2801aa8c4 100644
--- a/yt_dlp/extractor/udemy.py
+++ b/yt_dlp/extractor/udemy.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_str, compat_urlparse
from ..networking import Request
from ..networking.exceptions import HTTPError
from ..utils import (
@@ -73,22 +73,21 @@ class UdemyIE(InfoExtractor):
course_id = course.get('id') or self._search_regex(
[
r'data-course-id=["\'](\d+)',
- r'&quot;courseId&quot;\s*:\s*(\d+)'
+ r'&quot;courseId&quot;\s*:\s*(\d+)',
], webpage, 'course id')
return course_id, course.get('title')
def _enroll_course(self, base_url, webpage, course_id):
def combine_url(base_url, url):
- return compat_urlparse.urljoin(base_url, url) if not url.startswith('http') else url
+ return urllib.parse.urljoin(base_url, url) if not url.startswith('http') else url
checkout_url = unescapeHTML(self._search_regex(
r'href=(["\'])(?P<url>(?:https?://(?:www\.)?udemy\.com)?/(?:payment|cart)/checkout/.+?)\1',
webpage, 'checkout url', group='url', default=None))
if checkout_url:
raise ExtractorError(
- 'Course %s is not free. You have to pay for it before you can download. '
- 'Use this URL to confirm purchase: %s'
- % (course_id, combine_url(base_url, checkout_url)),
+ f'Course {course_id} is not free. You have to pay for it before you can download. '
+ f'Use this URL to confirm purchase: {combine_url(base_url, checkout_url)}',
expected=True)
enroll_url = unescapeHTML(self._search_regex(
@@ -100,12 +99,11 @@ class UdemyIE(InfoExtractor):
course_id, 'Enrolling in the course',
headers={'Referer': base_url})
if '>You have enrolled in' in webpage:
- self.to_screen('%s: Successfully enrolled in the course' % course_id)
+ self.to_screen(f'{course_id}: Successfully enrolled in the course')
def _download_lecture(self, course_id, lecture_id):
return self._download_json(
- 'https://www.udemy.com/api-2.0/users/me/subscribed-courses/%s/lectures/%s?'
- % (course_id, lecture_id),
+ f'https://www.udemy.com/api-2.0/users/me/subscribed-courses/{course_id}/lectures/{lecture_id}?',
lecture_id, 'Downloading lecture JSON', query={
'fields[lecture]': 'title,description,view_html,asset',
'fields[asset]': 'asset_type,stream_url,thumbnail_url,download_urls,stream_urls,captions,data,course_is_drmed',
@@ -116,17 +114,17 @@ class UdemyIE(InfoExtractor):
return
error = response.get('error')
if error:
- error_str = 'Udemy returned error #%s: %s' % (error.get('code'), error.get('message'))
+ error_str = 'Udemy returned error #{}: {}'.format(error.get('code'), error.get('message'))
error_data = error.get('data')
if error_data:
- error_str += ' - %s' % error_data.get('formErrors')
+ error_str += ' - {}'.format(error_data.get('formErrors'))
raise ExtractorError(error_str, expected=True)
def _download_webpage_handle(self, *args, **kwargs):
headers = kwargs.get('headers', {}).copy()
headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'
kwargs['headers'] = headers
- ret = super(UdemyIE, self)._download_webpage_handle(
+ ret = super()._download_webpage_handle(
*args, **kwargs)
if not ret:
return ret
@@ -151,14 +149,14 @@ class UdemyIE(InfoExtractor):
headers['X-Udemy-Client-Id'] = cookie.value
elif cookie.name == 'access_token':
headers['X-Udemy-Bearer-Token'] = cookie.value
- headers['X-Udemy-Authorization'] = 'Bearer %s' % cookie.value
+ headers['X-Udemy-Authorization'] = f'Bearer {cookie.value}'
if isinstance(url_or_request, Request):
url_or_request.headers.update(headers)
else:
url_or_request = Request(url_or_request, headers=headers)
- response = super(UdemyIE, self)._download_json(url_or_request, *args, **kwargs)
+ response = super()._download_json(url_or_request, *args, **kwargs)
self._handle_error(response)
return response
@@ -195,7 +193,7 @@ class UdemyIE(InfoExtractor):
r'(?s)<div[^>]+class="form-errors[^"]*">(.+?)</div>',
response, 'error message', default=None)
if error:
- raise ExtractorError('Unable to login: %s' % error, expected=True)
+ raise ExtractorError(f'Unable to login: {error}', expected=True)
raise ExtractorError('Unable to log in')
def _real_extract(self, url):
@@ -226,7 +224,7 @@ class UdemyIE(InfoExtractor):
asset_type = asset.get('asset_type') or asset.get('assetType')
if asset_type != 'Video':
raise ExtractorError(
- 'Lecture %s is not a video' % lecture_id, expected=True)
+ f'Lecture {lecture_id} is not a video', expected=True)
stream_url = asset.get('stream_url') or asset.get('streamUrl')
if stream_url:
@@ -235,7 +233,7 @@ class UdemyIE(InfoExtractor):
if youtube_url:
return self.url_result(youtube_url, 'Youtube')
- video_id = compat_str(asset['id'])
+ video_id = str(asset['id'])
thumbnail = asset.get('thumbnail_url') or asset.get('thumbnailUrl')
duration = float_or_none(asset.get('data', {}).get('duration'))
@@ -287,7 +285,7 @@ class UdemyIE(InfoExtractor):
format_id = source.get('label')
f = {
'url': video_url,
- 'format_id': '%sp' % format_id,
+ 'format_id': f'{format_id}p',
'height': int_or_none(format_id),
}
if format_id:
@@ -316,7 +314,7 @@ class UdemyIE(InfoExtractor):
})
for url_kind in ('download', 'stream'):
- urls = asset.get('%s_urls' % url_kind)
+ urls = asset.get(f'{url_kind}_urls')
if isinstance(urls, dict):
extract_formats(urls.get('Video'))
@@ -328,7 +326,7 @@ class UdemyIE(InfoExtractor):
cc_url = url_or_none(cc.get('url'))
if not cc_url:
continue
- lang = try_get(cc, lambda x: x['locale']['locale'], compat_str)
+ lang = try_get(cc, lambda x: x['locale']['locale'], str)
sub_dict = (automatic_captions if cc.get('source') == 'auto'
else subtitles)
sub_dict.setdefault(lang or 'en', []).append({
@@ -363,7 +361,7 @@ class UdemyIE(InfoExtractor):
else:
formats.append(add_output_format_meta({
'url': src,
- 'format_id': '%dp' % height if height else None,
+ 'format_id': f'{height}p' if height else None,
'height': height,
}, res))
@@ -423,7 +421,7 @@ class UdemyCourseIE(UdemyIE): # XXX: Do not subclass from concrete IE
@classmethod
def suitable(cls, url):
- return False if UdemyIE.suitable(url) else super(UdemyCourseIE, cls).suitable(url)
+ return False if UdemyIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
course_path = self._match_id(url)
@@ -435,7 +433,7 @@ class UdemyCourseIE(UdemyIE): # XXX: Do not subclass from concrete IE
self._enroll_course(url, webpage, course_id)
response = self._download_json(
- 'https://www.udemy.com/api-2.0/courses/%s/cached-subscriber-curriculum-items' % course_id,
+ f'https://www.udemy.com/api-2.0/courses/{course_id}/cached-subscriber-curriculum-items',
course_id, 'Downloading course curriculum', query={
'fields[chapter]': 'title,object_index',
'fields[lecture]': 'title,asset',
diff --git a/yt_dlp/extractor/udn.py b/yt_dlp/extractor/udn.py
index d5849d29b..9970e4ff5 100644
--- a/yt_dlp/extractor/udn.py
+++ b/yt_dlp/extractor/udn.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urlparse
from ..utils import (
determine_ext,
int_or_none,
@@ -13,7 +13,7 @@ class UDNEmbedIE(InfoExtractor):
IE_DESC = '聯合影音'
_PROTOCOL_RELATIVE_VALID_URL = r'//video\.udn\.com/(?:embed|play)/news/(?P<id>\d+)'
_VALID_URL = r'https?:' + _PROTOCOL_RELATIVE_VALID_URL
- _EMBED_REGEX = [r'<iframe[^>]+src="(?:https?:)?(?P<url>%s)"' % _PROTOCOL_RELATIVE_VALID_URL]
+ _EMBED_REGEX = [rf'<iframe[^>]+src="(?:https?:)?(?P<url>{_PROTOCOL_RELATIVE_VALID_URL})"']
_TESTS = [{
'url': 'http://video.udn.com/embed/news/300040',
'info_dict': {
@@ -66,8 +66,8 @@ class UDNEmbedIE(InfoExtractor):
continue
video_url = self._download_webpage(
- compat_urlparse.urljoin(url, api_url), video_id,
- note='retrieve url for %s video' % video_type)
+ urllib.parse.urljoin(url, api_url), video_id,
+ note=f'retrieve url for {video_type} video')
ext = determine_ext(video_url)
if ext == 'm3u8':
diff --git a/yt_dlp/extractor/uktvplay.py b/yt_dlp/extractor/uktvplay.py
index ab22a8e2d..9abe3436f 100644
--- a/yt_dlp/extractor/uktvplay.py
+++ b/yt_dlp/extractor/uktvplay.py
@@ -18,7 +18,7 @@ class UKTVPlayIE(InfoExtractor):
# m3u8 download
'skip_download': True,
},
- 'expected_warnings': ['Failed to download MPD manifest']
+ 'expected_warnings': ['Failed to download MPD manifest'],
}, {
'url': 'https://uktvplay.uktv.co.uk/shows/africa/watch-online/5983349675001',
'only_matching': True,
diff --git a/yt_dlp/extractor/umg.py b/yt_dlp/extractor/umg.py
index 1da4ecdf8..b509fda88 100644
--- a/yt_dlp/extractor/umg.py
+++ b/yt_dlp/extractor/umg.py
@@ -20,7 +20,7 @@ class UMGDeIE(InfoExtractor):
'title': 'Jedes Wort ist Gold wert',
'timestamp': 1513591800,
'upload_date': '20171218',
- }
+ },
}
def _real_extract(self, url):
@@ -45,7 +45,7 @@ class UMGDeIE(InfoExtractor):
createdDate
}
}
-}''' % video_id})['data']['universalMusic']['video']
+}''' % video_id})['data']['universalMusic']['video'] # noqa: UP031
title = video_data['headline']
hls_url_template = 'http://mediadelivery.universal-music-services.de/vod/mp4:autofill/storage/' + '/'.join(list(video_id)) + '/content/%s/file/playlist.m3u8'
diff --git a/yt_dlp/extractor/unistra.py b/yt_dlp/extractor/unistra.py
index 6e872cd14..edc1f8554 100644
--- a/yt_dlp/extractor/unistra.py
+++ b/yt_dlp/extractor/unistra.py
@@ -27,7 +27,7 @@ class UnistraIE(InfoExtractor):
'title': 'Prix Louise Weiss 2014',
'description': 'md5:cc3a8735f079f4fb6b0b570fc10c135a',
},
- }
+ },
]
def _real_extract(self, url):
@@ -43,9 +43,9 @@ class UnistraIE(InfoExtractor):
for file_path in files:
format_id = 'HD' if file_path.endswith('-HD.mp4') else 'SD'
formats.append({
- 'url': 'http://vod-flash.u-strasbg.fr:8080%s' % file_path,
+ 'url': f'http://vod-flash.u-strasbg.fr:8080{file_path}',
'format_id': format_id,
- 'quality': quality(format_id)
+ 'quality': quality(format_id),
})
title = self._html_search_regex(
@@ -60,5 +60,5 @@ class UnistraIE(InfoExtractor):
'title': title,
'description': description,
'thumbnail': thumbnail,
- 'formats': formats
+ 'formats': formats,
}
diff --git a/yt_dlp/extractor/unity.py b/yt_dlp/extractor/unity.py
index 6d8bc0593..7d6ffb030 100644
--- a/yt_dlp/extractor/unity.py
+++ b/yt_dlp/extractor/unity.py
@@ -16,7 +16,7 @@ class UnityIE(InfoExtractor):
'uploader': 'Unity',
'uploader_id': 'Unity3D',
'upload_date': '20140926',
- }
+ },
}, {
'url': 'https://unity3d.com/learn/tutorials/projects/2d-ufo-tutorial/following-player-camera?playlist=25844',
'only_matching': True,
diff --git a/yt_dlp/extractor/uol.py b/yt_dlp/extractor/uol.py
index 068c2b87d..5198e89e8 100644
--- a/yt_dlp/extractor/uol.py
+++ b/yt_dlp/extractor/uol.py
@@ -1,8 +1,6 @@
+import urllib.parse
+
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urllib_parse_urlencode,
-)
from ..utils import (
clean_html,
int_or_none,
@@ -26,7 +24,7 @@ class UOLIE(InfoExtractor):
'description': 'md5:3f8c11a0c0556d66daf7e5b45ef823b2',
'timestamp': 1470421860,
'upload_date': '20160805',
- }
+ },
}, {
'url': 'http://tvuol.uol.com.br/video/incendio-destroi-uma-das-maiores-casas-noturnas-de-londres-04024E9A3268D4C95326',
'md5': '2850a0e8dfa0a7307e04a96c5bdc5bc2',
@@ -37,7 +35,7 @@ class UOLIE(InfoExtractor):
'description': 'Em Londres, um incêndio destruiu uma das maiores boates da cidade. Não há informações sobre vítimas.',
'timestamp': 1470674520,
'upload_date': '20160808',
- }
+ },
}, {
'url': 'http://mais.uol.com.br/static/uolplayer/index.html?mediaId=15951931',
'only_matching': True,
@@ -68,12 +66,12 @@ class UOLIE(InfoExtractor):
# https://api.mais.uol.com.br/apiuol/v4/player/data/[MEDIA_ID]
'https://api.mais.uol.com.br/apiuol/v3/media/detail/' + video_id,
video_id)['item']
- media_id = compat_str(video_data['mediaId'])
+ media_id = str(video_data['mediaId'])
title = video_data['title']
ver = video_data.get('revision', 2)
uol_formats = self._download_json(
- 'https://croupier.mais.uol.com.br/v3/formats/%s/jsonp' % media_id,
+ f'https://croupier.mais.uol.com.br/v3/formats/{media_id}/jsonp',
media_id)
quality = qualities(['mobile', 'WEBM', '360p', '720p', '1080p'])
formats = []
@@ -96,7 +94,7 @@ class UOLIE(InfoExtractor):
m3u8_formats = self._extract_m3u8_formats(
f_url, media_id, 'mp4', 'm3u8_native',
m3u8_id='hls', fatal=False)
- encoded_query = compat_urllib_parse_urlencode(query)
+ encoded_query = urllib.parse.urlencode(query)
for m3u8_f in m3u8_formats:
m3u8_f['extra_param_to_segment_url'] = encoded_query
m3u8_f['url'] = update_url_query(m3u8_f['url'], query)
diff --git a/yt_dlp/extractor/urort.py b/yt_dlp/extractor/urort.py
index f14d7cce6..06931293e 100644
--- a/yt_dlp/extractor/urort.py
+++ b/yt_dlp/extractor/urort.py
@@ -23,22 +23,22 @@ class UrortIE(InfoExtractor):
},
'params': {
'matchtitle': '^The Bomb$', # To test, we want just one video
- }
+ },
}
def _real_extract(self, url):
playlist_id = self._match_id(url)
- fstr = urllib.parse.quote("InternalBandUrl eq '%s'" % playlist_id)
- json_url = 'http://urort.p3.no/breeze/urort/TrackDTOViews?$filter=%s&$orderby=Released%%20desc&$expand=Tags%%2CFiles' % fstr
+ fstr = urllib.parse.quote(f"InternalBandUrl eq '{playlist_id}'")
+ json_url = f'http://urort.p3.no/breeze/urort/TrackDTOViews?$filter={fstr}&$orderby=Released%20desc&$expand=Tags%2CFiles'
songs = self._download_json(json_url, playlist_id)
entries = []
for s in songs:
formats = [{
'tbr': f.get('Quality'),
'ext': f['FileType'],
- 'format_id': '%s-%s' % (f['FileType'], f.get('Quality', '')),
- 'url': 'http://p3urort.blob.core.windows.net/tracks/%s' % f['FileRef'],
+ 'format_id': '{}-{}'.format(f['FileType'], f.get('Quality', '')),
+ 'url': 'http://p3urort.blob.core.windows.net/tracks/{}'.format(f['FileRef']),
'quality': 3 if f['FileType'] == 'mp3' else 2,
} for f in s['Files']]
e = {
@@ -46,7 +46,7 @@ class UrortIE(InfoExtractor):
'title': s['Title'],
'uploader_id': playlist_id,
'uploader': s.get('BandName', playlist_id),
- 'thumbnail': 'http://urort.p3.no/cloud/images/%s' % s['Image'],
+ 'thumbnail': 'http://urort.p3.no/cloud/images/{}'.format(s['Image']),
'upload_date': unified_strdate(s.get('Released')),
'formats': formats,
}
diff --git a/yt_dlp/extractor/urplay.py b/yt_dlp/extractor/urplay.py
index 928e6e1c2..79bb8a805 100644
--- a/yt_dlp/extractor/urplay.py
+++ b/yt_dlp/extractor/urplay.py
@@ -98,7 +98,7 @@ class URPlayIE(InfoExtractor):
file_http = v.get('location')
if file_http:
formats.extend(self._extract_wowza_formats(
- 'http://%s/%splaylist.m3u8' % (host, file_http),
+ f'http://{host}/{file_http}playlist.m3u8',
video_id, skip_protocols=['f4m', 'rtmp', 'rtsp']))
subtitles = {}
@@ -116,14 +116,14 @@ class URPlayIE(InfoExtractor):
for k, v in stream.items():
if (k in ('sd', 'hd') or not isinstance(v, dict)):
continue
- lang, sttl_url = (v.get(kk) for kk in ('language', 'location', ))
+ lang, sttl_url = (v.get(kk) for kk in ('language', 'location'))
if not sttl_url:
continue
lang = parse_lang_code(lang)
if not lang:
continue
sttl = subtitles.get(lang) or []
- sttl.append({'ext': k, 'url': sttl_url, })
+ sttl.append({'ext': k, 'url': sttl_url})
subtitles[lang] = sttl
image = urplayer_data.get('image') or {}
@@ -146,7 +146,7 @@ class URPlayIE(InfoExtractor):
return {
'id': video_id,
- 'title': '%s : %s' % (series_title, episode) if series_title else episode,
+ 'title': f'{series_title} : {episode}' if series_title else episode,
'description': urplayer_data.get('description'),
'thumbnails': thumbnails,
'timestamp': unified_timestamp(urplayer_data.get('publishedAt')),
diff --git a/yt_dlp/extractor/usatoday.py b/yt_dlp/extractor/usatoday.py
index 42a28c509..6ad18ff9d 100644
--- a/yt_dlp/extractor/usatoday.py
+++ b/yt_dlp/extractor/usatoday.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
get_element_by_attribute,
@@ -23,7 +22,7 @@ class USATodayIE(InfoExtractor):
'description': 'md5:7e50464fdf2126b0f533748d3c78d58f',
'uploader_id': '29906170001',
'upload_date': '20160313',
- }
+ },
}, {
# ui-video-data[asset_metadata][items][brightcoveaccount] = 28911775001
'url': 'https://www.usatoday.com/story/tech/science/2018/08/21/yellowstone-supervolcano-eruption-stop-worrying-its-blow/973633002/',
@@ -35,7 +34,7 @@ class USATodayIE(InfoExtractor):
'description': 'md5:3715e7927639a4f16b474e9391687c62',
'uploader_id': '28911775001',
'upload_date': '20180820',
- }
+ },
}]
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/default_default/index.html?videoId=%s'
@@ -51,7 +50,7 @@ class USATodayIE(InfoExtractor):
return {
'_type': 'url_transparent',
'url': self.BRIGHTCOVE_URL_TEMPLATE % (item.get('brightcoveaccount', '29906170001'), item.get('brightcoveid') or video_data['brightcove_id']),
- 'id': compat_str(video_data['id']),
+ 'id': str(video_data['id']),
'title': video_data['title'],
'thumbnail': video_data.get('thumbnail'),
'description': video_data.get('description'),
diff --git a/yt_dlp/extractor/ustream.py b/yt_dlp/extractor/ustream.py
index 046e3d768..33cf8f454 100644
--- a/yt_dlp/extractor/ustream.py
+++ b/yt_dlp/extractor/ustream.py
@@ -1,11 +1,8 @@
import random
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
encode_data_uri,
@@ -82,14 +79,14 @@ class UstreamIE(InfoExtractor):
extra_note = ''
conn_info = self._download_json(
- 'http://r%d-1-%s-recorded-lp-live.ums.ustream.tv/1/ustream' % (rnd(1e8), video_id),
+ f'http://r{rnd(1e8)}-1-{video_id}-recorded-lp-live.ums.ustream.tv/1/ustream',
video_id, note='Downloading connection info' + extra_note,
query={
'type': 'viewer',
'appId': app_id_ver[0],
'appVersion': app_id_ver[1],
- 'rsid': '%s:%s' % (num_to_hex(rnd(1e8)), num_to_hex(rnd(1e8))),
- 'rpin': '_rpin.%d' % rnd(1e15),
+ 'rsid': f'{num_to_hex(rnd(1e8))}:{num_to_hex(rnd(1e8))}',
+ 'rpin': f'_rpin.{rnd(1e15)}',
'referrer': url,
'media': video_id,
'application': 'recorded',
@@ -98,7 +95,7 @@ class UstreamIE(InfoExtractor):
connection_id = conn_info[0]['args'][0]['connectionId']
return self._download_json(
- 'http://%s/1/ustream?connectionId=%s' % (host, connection_id),
+ f'http://{host}/1/ustream?connectionId={connection_id}',
video_id, note='Downloading stream info' + extra_note)
def _get_streams(self, url, video_id, app_id_ver):
@@ -106,14 +103,14 @@ class UstreamIE(InfoExtractor):
for trial_count in range(3):
stream_info = self._get_stream_info(
url, video_id, app_id_ver,
- extra_note=' (try %d)' % (trial_count + 1) if trial_count > 0 else '')
+ extra_note=f' (try {trial_count + 1})' if trial_count > 0 else '')
if 'stream' in stream_info[0]['args'][0]:
return stream_info[0]['args'][0]['stream']
return []
def _parse_segmented_mp4(self, dash_stream_info):
def resolve_dash_template(template, idx, chunk_hash):
- return template.replace('%', compat_str(idx), 1).replace('%', chunk_hash)
+ return template.replace('%', str(idx), 1).replace('%', chunk_hash)
formats = []
for stream in dash_stream_info['streams']:
@@ -121,13 +118,13 @@ class UstreamIE(InfoExtractor):
provider = dash_stream_info['providers'][0]
fragments = [{
'url': resolve_dash_template(
- provider['url'] + stream['initUrl'], 0, dash_stream_info['hashes']['0'])
+ provider['url'] + stream['initUrl'], 0, dash_stream_info['hashes']['0']),
}]
for idx in range(dash_stream_info['videoLength'] // dash_stream_info['chunkTime']):
fragments.append({
'url': resolve_dash_template(
provider['url'] + stream['segmentUrl'], idx,
- dash_stream_info['hashes'][compat_str(idx // 10 * 10)])
+ dash_stream_info['hashes'][str(idx // 10 * 10)]),
})
content_type = stream['contentType']
kind = content_type.split('/')[0]
@@ -173,16 +170,16 @@ class UstreamIE(InfoExtractor):
r'ustream\.vars\.offAirContentVideoIds=([^;]+);', webpage,
'content video IDs'), video_id)
return self.playlist_result(
- map(lambda u: self.url_result('http://www.ustream.tv/recorded/' + u, 'Ustream'), content_video_ids),
+ (self.url_result('http://www.ustream.tv/recorded/' + u, 'Ustream') for u in content_video_ids),
video_id)
params = self._download_json(
- 'https://api.ustream.tv/videos/%s.json' % video_id, video_id)
+ f'https://api.ustream.tv/videos/{video_id}.json', video_id)
error = params.get('error')
if error:
raise ExtractorError(
- '%s returned error: %s' % (self.IE_NAME, error), expected=True)
+ f'{self.IE_NAME} returned error: {error}', expected=True)
video = params['video']
@@ -255,12 +252,12 @@ class UstreamChannelIE(InfoExtractor):
channel_id = self._html_search_meta('ustream:channel_id', webpage)
BASE = 'http://www.ustream.tv'
- next_url = '/ajax/socialstream/videos/%s/1.json' % channel_id
+ next_url = f'/ajax/socialstream/videos/{channel_id}/1.json'
video_ids = []
while next_url:
reply = self._download_json(
- compat_urlparse.urljoin(BASE, next_url), display_id,
- note='Downloading video information (next: %d)' % (len(video_ids) + 1))
+ urllib.parse.urljoin(BASE, next_url), display_id,
+ note=f'Downloading video information (next: {len(video_ids) + 1})')
video_ids.extend(re.findall(r'data-content-id="(\d.*)"', reply['data']))
next_url = reply['nextUrl']
diff --git a/yt_dlp/extractor/ustudio.py b/yt_dlp/extractor/ustudio.py
index f6ce5b357..b5da88b26 100644
--- a/yt_dlp/extractor/ustudio.py
+++ b/yt_dlp/extractor/ustudio.py
@@ -21,14 +21,14 @@ class UstudioIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'upload_date': '20111107',
'uploader': 'Tony Farley',
- }
+ },
}
def _real_extract(self, url):
video_id, display_id = self._match_valid_url(url).groups()
config = self._download_xml(
- 'http://v1.ustudio.com/embed/%s/ustudio/config.xml' % video_id,
+ f'http://v1.ustudio.com/embed/{video_id}/ustudio/config.xml',
display_id)
def extract(kind):
@@ -36,7 +36,7 @@ class UstudioIE(InfoExtractor):
'url': unescapeHTML(item.attrib['url']),
'width': int_or_none(item.get('width')),
'height': int_or_none(item.get('height')),
- } for item in config.findall('./qualities/quality/%s' % kind) if item.get('url')]
+ } for item in config.findall(f'./qualities/quality/{kind}') if item.get('url')]
formats = extract('video')
@@ -74,13 +74,13 @@ class UstudioEmbedIE(InfoExtractor):
'title': '5 Things IT Should Know About Video',
'description': 'md5:93d32650884b500115e158c5677d25ad',
'uploader_id': 'DeN7VdYRDKhP',
- }
+ },
}
def _real_extract(self, url):
uploader_id, video_id = self._match_valid_url(url).groups()
video_data = self._download_json(
- 'http://app.ustudio.com/embed/%s/%s/config.json' % (uploader_id, video_id),
+ f'http://app.ustudio.com/embed/{uploader_id}/{video_id}/config.json',
video_id)['videos'][0]
title = video_data['name']
@@ -92,7 +92,7 @@ class UstudioEmbedIE(InfoExtractor):
continue
height = int_or_none(quality.get('height'))
formats.append({
- 'format_id': '%s-%dp' % (ext, height) if height else ext,
+ 'format_id': f'{ext}-{height}p' if height else ext,
'url': quality_url,
'width': int_or_none(quality.get('width')),
'height': height,
diff --git a/yt_dlp/extractor/utreon.py b/yt_dlp/extractor/utreon.py
index 12a7e4984..fc44df97b 100644
--- a/yt_dlp/extractor/utreon.py
+++ b/yt_dlp/extractor/utreon.py
@@ -23,7 +23,7 @@ class UtreonIE(InfoExtractor):
'thumbnail': r're:^https?://.+\.jpg',
'release_date': '20210723',
'duration': 586,
- }
+ },
}, {
'url': 'https://utreon.com/v/jerJw5EOOVU',
'info_dict': {
@@ -35,7 +35,7 @@ class UtreonIE(InfoExtractor):
'thumbnail': r're:^https?://.+\.jpg',
'release_date': '20210723',
'duration': 60,
- }
+ },
}, {
'url': 'https://utreon.com/v/C4ZxXhYBBmE',
'info_dict': {
@@ -47,7 +47,7 @@ class UtreonIE(InfoExtractor):
'thumbnail': r're:^https?://.+\.jpg',
'release_date': '20210723',
'duration': 884,
- }
+ },
}, {
'url': 'https://utreon.com/v/Y-stEH-FBm8',
'info_dict': {
@@ -59,7 +59,7 @@ class UtreonIE(InfoExtractor):
'thumbnail': r're:^https?://.+\.jpg',
'release_date': '20210718',
'duration': 151,
- }
+ },
}, {
'url': 'https://playeur.com/v/Wzqp-UrxSeu',
'info_dict': {
@@ -71,7 +71,7 @@ class UtreonIE(InfoExtractor):
'release_date': '20240208',
'thumbnail': r're:^https?://.+\.jpg',
'duration': 262,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/veo.py b/yt_dlp/extractor/veo.py
index 205f8ea63..9d3c39f02 100644
--- a/yt_dlp/extractor/veo.py
+++ b/yt_dlp/extractor/veo.py
@@ -22,7 +22,7 @@ class VeoIE(InfoExtractor):
'timestamp': 1603847208,
'duration': 1916,
'view_count': int,
- }
+ },
}, {
'url': 'https://app.veo.co/matches/20220313-2022-03-13_u15m-plsjq-vs-csl/',
'only_matching': True,
@@ -32,10 +32,10 @@ class VeoIE(InfoExtractor):
video_id = self._match_id(url)
metadata = self._download_json(
- 'https://app.veo.co/api/app/matches/%s' % video_id, video_id)
+ f'https://app.veo.co/api/app/matches/{video_id}', video_id)
video_data = self._download_json(
- 'https://app.veo.co/api/app/matches/%s/videos' % video_id, video_id, 'Downloading video data')
+ f'https://app.veo.co/api/app/matches/{video_id}/videos', video_id, 'Downloading video data')
formats = []
for fmt in video_data:
diff --git a/yt_dlp/extractor/veoh.py b/yt_dlp/extractor/veoh.py
index 92ff86521..dc1bf96ec 100644
--- a/yt_dlp/extractor/veoh.py
+++ b/yt_dlp/extractor/veoh.py
@@ -80,7 +80,7 @@ class VeohIE(InfoExtractor):
'age_limit': 18,
'categories': ['technology_and_gaming', 'gaming'],
'tags': ['puzzle', 'of', 'flesh'],
- }
+ },
}]
def _real_extract(self, url):
@@ -138,17 +138,17 @@ class VeohUserIE(VeohIE): # XXX: Do not subclass from concrete IE
'url': 'https://www.veoh.com/users/valentinazoe',
'info_dict': {
'id': 'valentinazoe',
- 'title': 'valentinazoe (Uploads)'
+ 'title': 'valentinazoe (Uploads)',
},
- 'playlist_mincount': 75
+ 'playlist_mincount': 75,
},
{
'url': 'https://www.veoh.com/users/PiensaLibre',
'info_dict': {
'id': 'PiensaLibre',
- 'title': 'PiensaLibre (Uploads)'
+ 'title': 'PiensaLibre (Uploads)',
},
- 'playlist_mincount': 2
+ 'playlist_mincount': 2,
}]
_PAGE_SIZE = 16
@@ -159,14 +159,14 @@ class VeohUserIE(VeohIE): # XXX: Do not subclass from concrete IE
note=f'Downloading videos page {page + 1}',
headers={
'x-csrf-token': self._TOKEN,
- 'content-type': 'application/json;charset=UTF-8'
+ 'content-type': 'application/json;charset=UTF-8',
},
data=json.dumps({
'username': uploader,
'maxResults': self._PAGE_SIZE,
'page': page + 1,
- 'requestName': 'userPage'
- }).encode('utf-8'))
+ 'requestName': 'userPage',
+ }).encode())
if not response.get('success'):
raise ExtractorError(response['message'])
diff --git a/yt_dlp/extractor/vesti.py b/yt_dlp/extractor/vesti.py
index a2e90226a..844041a61 100644
--- a/yt_dlp/extractor/vesti.py
+++ b/yt_dlp/extractor/vesti.py
@@ -94,7 +94,7 @@ class VestiIE(InfoExtractor):
# rtmp download
'skip_download': True,
},
- 'skip': 'Translation has finished'
+ 'skip': 'Translation has finished',
},
]
@@ -109,7 +109,7 @@ class VestiIE(InfoExtractor):
page)
if mobj:
video_id = mobj.group('id')
- page = self._download_webpage('http://www.vesti.ru/only_video.html?vid=%s' % video_id, video_id,
+ page = self._download_webpage(f'http://www.vesti.ru/only_video.html?vid={video_id}', video_id,
'Downloading video page')
rutv_url = RUTVIE._extract_url(page)
diff --git a/yt_dlp/extractor/vevo.py b/yt_dlp/extractor/vevo.py
index 7715d6839..8552a609c 100644
--- a/yt_dlp/extractor/vevo.py
+++ b/yt_dlp/extractor/vevo.py
@@ -2,7 +2,6 @@ import json
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -22,10 +21,10 @@ class VevoBaseIE(InfoExtractor):
class VevoIE(VevoBaseIE):
- '''
+ """
Accepts urls from vevo.com or in the format 'vevo:{id}'
(currently used by MTVIE and MySpaceIE)
- '''
+ """
_VALID_URL = r'''(?x)
(?:https?://(?:www\.)?vevo\.com/watch/(?!playlist|genre)(?:[^/]+/(?:[^/]+/)?)?|
https?://cache\.vevo\.com/m/html/embed\.html\?video=|
@@ -166,14 +165,14 @@ class VevoIE(VevoBaseIE):
data=json.dumps({
'client_id': 'SPupX1tvqFEopQ1YS6SS',
'grant_type': 'urn:vevo:params:oauth:grant-type:anonymous',
- }).encode('utf-8'),
+ }).encode(),
headers={
'Content-Type': 'application/json',
})
if re.search(r'(?i)THIS PAGE IS CURRENTLY UNAVAILABLE IN YOUR REGION', webpage):
self.raise_geo_restricted(
- '%s said: This page is currently unavailable in your region' % self.IE_NAME)
+ f'{self.IE_NAME} said: This page is currently unavailable in your region')
auth_info = self._parse_json(webpage, video_id)
self._api_url_template = self.http_scheme() + '//apiv2.vevo.com/%s?token=' + auth_info['legacy_token']
@@ -185,7 +184,7 @@ class VevoIE(VevoBaseIE):
if isinstance(e.cause, HTTPError):
errors = self._parse_json(e.cause.response.read().decode(), None)['errors']
error_message = ', '.join([error['message'] for error in errors])
- raise ExtractorError('%s said: %s' % (self.IE_NAME, error_message), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error_message}', expected=True)
raise
return data
@@ -195,11 +194,11 @@ class VevoIE(VevoBaseIE):
self._initialize_api(video_id)
video_info = self._call_api(
- 'video/%s' % video_id, video_id, 'Downloading api video info',
+ f'video/{video_id}', video_id, 'Downloading api video info',
'Failed to download video info')
video_versions = self._call_api(
- 'video/%s/streams' % video_id, video_id,
+ f'video/{video_id}/streams', video_id,
'Downloading video versions info',
'Failed to download video versions info',
fatal=False)
@@ -215,7 +214,7 @@ class VevoIE(VevoBaseIE):
video_versions = [
value
for key, value in json_data['apollo']['data'].items()
- if key.startswith('%s.streams' % video_id)]
+ if key.startswith(f'{video_id}.streams')]
uploader = None
artist = None
@@ -238,16 +237,16 @@ class VevoIE(VevoBaseIE):
continue
elif '.mpd' in version_url:
formats.extend(self._extract_mpd_formats(
- version_url, video_id, mpd_id='dash-%s' % version,
- note='Downloading %s MPD information' % version,
- errnote='Failed to download %s MPD information' % version,
+ version_url, video_id, mpd_id=f'dash-{version}',
+ note=f'Downloading {version} MPD information',
+ errnote=f'Failed to download {version} MPD information',
fatal=False))
elif '.m3u8' in version_url:
formats.extend(self._extract_m3u8_formats(
version_url, video_id, 'mp4', 'm3u8_native',
- m3u8_id='hls-%s' % version,
- note='Downloading %s m3u8 information' % version,
- errnote='Failed to download %s m3u8 information' % version,
+ m3u8_id=f'hls-{version}',
+ note=f'Downloading {version} m3u8 information',
+ errnote=f'Failed to download {version} m3u8 information',
fatal=False))
else:
m = re.search(r'''(?xi)
@@ -275,13 +274,13 @@ class VevoIE(VevoBaseIE):
track = video_info['title']
if featured_artist:
- artist = '%s ft. %s' % (artist, featured_artist)
- title = '%s - %s' % (artist, track) if artist else track
+ artist = f'{artist} ft. {featured_artist}'
+ title = f'{artist} - {track}' if artist else track
genres = video_info.get('genres')
genre = (
genres[0] if genres and isinstance(genres, list)
- and isinstance(genres[0], compat_str) else None)
+ and isinstance(genres[0], str) else None)
is_explicit = video_info.get('isExplicit')
if is_explicit is True:
@@ -337,15 +336,15 @@ class VevoPlaylistIE(VevoBaseIE):
r'<meta[^>]+content=(["\'])vevo://video/(?P<id>.+?)\1[^>]*>',
webpage, 'video id', default=None, group='id')
if video_id:
- return self.url_result('vevo:%s' % video_id, VevoIE.ie_key())
+ return self.url_result(f'vevo:{video_id}', VevoIE.ie_key())
- playlists = self._extract_json(webpage, playlist_id)['default']['%ss' % playlist_kind]
+ playlists = self._extract_json(webpage, playlist_id)['default'][f'{playlist_kind}s']
- playlist = (list(playlists.values())[0]
+ playlist = (next(iter(playlists.values()))
if playlist_kind == 'playlist' else playlists[playlist_id])
entries = [
- self.url_result('vevo:%s' % src, VevoIE.ie_key())
+ self.url_result(f'vevo:{src}', VevoIE.ie_key())
for src in playlist['isrcs']]
return self.playlist_result(
diff --git a/yt_dlp/extractor/vgtv.py b/yt_dlp/extractor/vgtv.py
index db338fa10..1eb25530f 100644
--- a/yt_dlp/extractor/vgtv.py
+++ b/yt_dlp/extractor/vgtv.py
@@ -39,7 +39,7 @@ class VGTVIE(XstreamIE): # XXX: Do not subclass from concrete IE
_VALID_URL = r'''(?x)
(?:https?://(?:www\.)?
(?P<host>
- %s
+ {}
)
/?
(?:
@@ -48,10 +48,10 @@ class VGTVIE(XstreamIE): # XXX: Do not subclass from concrete IE
a(?:rticles)?/
)|
(?P<appname>
- %s
+ {}
):)
(?P<id>\d+)
- ''' % ('|'.join(_HOST_TO_APPNAME.keys()), '|'.join(_APP_NAME_TO_VENDOR.keys()))
+ '''.format('|'.join(_HOST_TO_APPNAME.keys()), '|'.join(_APP_NAME_TO_VENDOR.keys()))
_TESTS = [
{
@@ -174,13 +174,12 @@ class VGTVIE(XstreamIE): # XXX: Do not subclass from concrete IE
vendor = self._APP_NAME_TO_VENDOR[appname]
data = self._download_json(
- 'http://svp.vg.no/svp/api/v1/%s/assets/%s?appName=%s-website'
- % (vendor, video_id, appname),
+ f'http://svp.vg.no/svp/api/v1/{vendor}/assets/{video_id}?appName={appname}-website',
video_id, 'Downloading media JSON')
if data.get('status') == 'inactive':
raise ExtractorError(
- 'Video %s is no longer available' % video_id, expected=True)
+ f'Video {video_id} is no longer available', expected=True)
info = {
'formats': [],
@@ -203,7 +202,7 @@ class VGTVIE(XstreamIE): # XXX: Do not subclass from concrete IE
if hds_url:
hdcore_sign = 'hdcore=3.7.0'
f4m_formats = self._extract_f4m_formats(
- hds_url + '?%s' % hdcore_sign, video_id, f4m_id='hds', fatal=False)
+ hds_url + f'?{hdcore_sign}', video_id, f4m_id='hds', fatal=False)
if f4m_formats:
for entry in f4m_formats:
# URLs without the extra param induce an 404 error
@@ -225,7 +224,7 @@ class VGTVIE(XstreamIE): # XXX: Do not subclass from concrete IE
'width': int(mobj.group(1)),
'height': int(mobj.group(2)),
'tbr': tbr,
- 'format_id': 'mp4-%s' % tbr,
+ 'format_id': f'mp4-{tbr}',
})
formats.append(format_info)
@@ -275,7 +274,7 @@ class BTArticleIE(InfoExtractor):
webpage = self._download_webpage(url, self._match_id(url))
video_id = self._search_regex(
r'<video[^>]+data-id="(\d+)"', webpage, 'video id')
- return self.url_result('bttv:%s' % video_id, 'VGTV')
+ return self.url_result(f'bttv:{video_id}', 'VGTV')
class BTVestlendingenIE(InfoExtractor):
@@ -308,4 +307,4 @@ class BTVestlendingenIE(InfoExtractor):
}]
def _real_extract(self, url):
- return self.url_result('bttv:%s' % self._match_id(url), 'VGTV')
+ return self.url_result(f'bttv:{self._match_id(url)}', 'VGTV')
diff --git a/yt_dlp/extractor/vh1.py b/yt_dlp/extractor/vh1.py
index 41b8a4607..53d5a7108 100644
--- a/yt_dlp/extractor/vh1.py
+++ b/yt_dlp/extractor/vh1.py
@@ -1,6 +1,6 @@
from .mtv import MTVServicesInfoExtractor
-# TODO Remove - Reason: Outdated Site
+# TODO: Remove - Reason: Outdated Site
class VH1IE(MTVServicesInfoExtractor):
diff --git a/yt_dlp/extractor/vice.py b/yt_dlp/extractor/vice.py
index b072d9d73..3739a37e4 100644
--- a/yt_dlp/extractor/vice.py
+++ b/yt_dlp/extractor/vice.py
@@ -7,7 +7,6 @@ import time
from .adobepass import AdobePassIE
from .common import InfoExtractor
from .youtube import YoutubeIE
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -28,7 +27,7 @@ class ViceBaseIE(InfoExtractor):
%s(locale: "%s", %s: "%s"%s) {
%s
}
-}''' % (resource, locale, resource_key, resource_id, args, fields),
+}''' % (resource, locale, resource_key, resource_id, args, fields), # noqa: UP031
})['data'][resource]
@@ -127,7 +126,7 @@ class ViceIE(ViceBaseIE, AdobePassIE):
query.update({
'exp': exp,
- 'sign': hashlib.sha512(('%s:GET:%d' % (video_id, exp)).encode()).hexdigest(),
+ 'sign': hashlib.sha512(f'{video_id}:GET:{exp}'.encode()).hexdigest(),
'skipadstitching': 1,
'platform': 'desktop',
'rn': random.randint(10000, 100000),
@@ -135,14 +134,13 @@ class ViceIE(ViceBaseIE, AdobePassIE):
try:
preplay = self._download_json(
- 'https://vms.vice.com/%s/video/preplay/%s' % (locale, video_id),
+ f'https://vms.vice.com/{locale}/video/preplay/{video_id}',
video_id, query=query)
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status in (400, 401):
error = json.loads(e.cause.response.read().decode())
error_message = error.get('error_description') or error['details']
- raise ExtractorError('%s said: %s' % (
- self.IE_NAME, error_message), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {error_message}', expected=True)
raise
video_data = preplay['video']
@@ -157,7 +155,7 @@ class ViceIE(ViceBaseIE, AdobePassIE):
cc_url = subtitle.get('url')
if not cc_url:
continue
- language_code = try_get(subtitle, lambda x: x['languages'][0]['language_code'], compat_str) or 'en'
+ language_code = try_get(subtitle, lambda x: x['languages'][0]['language_code'], str) or 'en'
subtitles.setdefault(language_code, []).append({
'url': cc_url,
})
@@ -171,7 +169,7 @@ class ViceIE(ViceBaseIE, AdobePassIE):
'duration': int_or_none(video_data.get('video_duration')),
'timestamp': int_or_none(video_data.get('created_at'), 1000),
'age_limit': parse_age_limit(video_data.get('video_rating') or rating),
- 'series': try_get(video_data, lambda x: x['show']['base']['display_title'], compat_str),
+ 'series': try_get(video_data, lambda x: x['show']['base']['display_title'], str),
'episode_number': int_or_none(episode.get('episode_number')),
'episode_id': str_or_none(episode.get('id') or video_data.get('episode_id')),
'season_number': int_or_none(season.get('season_number')),
@@ -202,7 +200,7 @@ class ViceShowIE(ViceBaseIE):
def _fetch_page(self, locale, show_id, page):
videos = self._call_api('videos', 'show_id', show_id, locale, '''body
id
- url''', ', page: %d, per_page: %d' % (page + 1, self._PAGE_SIZE))
+ url''', f', page: {page + 1}, per_page: {self._PAGE_SIZE}')
for video in videos:
yield self.url_result(
video['url'], ViceIE.ie_key(), video.get('id'))
diff --git a/yt_dlp/extractor/viddler.py b/yt_dlp/extractor/viddler.py
index 40914774a..60ebfbd12 100644
--- a/yt_dlp/extractor/viddler.py
+++ b/yt_dlp/extractor/viddler.py
@@ -25,7 +25,7 @@ class ViddlerIE(InfoExtractor):
'view_count': int,
'comment_count': int,
'categories': ['video content', 'high quality video', 'video made easy', 'how to produce video with limited resources', 'viddler'],
- }
+ },
}, {
'url': 'http://www.viddler.com/v/4d03aad9/',
'md5': 'f12c5a7fa839c47a79363bfdf69404fb',
@@ -38,7 +38,7 @@ class ViddlerIE(InfoExtractor):
'timestamp': 1422285291,
'view_count': int,
'comment_count': int,
- }
+ },
}, {
'url': 'http://www.viddler.com/player/221ebbbd/0/',
'md5': '740511f61d3d1bb71dc14a0fe01a1c10',
@@ -52,7 +52,7 @@ class ViddlerIE(InfoExtractor):
'timestamp': 1411997190,
'view_count': int,
'comment_count': int,
- }
+ },
}, {
# secret protected
'url': 'http://www.viddler.com/v/890c0985?secret=34051570',
diff --git a/yt_dlp/extractor/videa.py b/yt_dlp/extractor/videa.py
index 634d2edea..8dc7ebd15 100644
--- a/yt_dlp/extractor/videa.py
+++ b/yt_dlp/extractor/videa.py
@@ -1,9 +1,10 @@
+import base64
import random
import string
import struct
from .common import InfoExtractor
-from ..compat import compat_b64decode, compat_ord
+from ..compat import compat_ord
from ..utils import (
ExtractorError,
int_or_none,
@@ -118,7 +119,7 @@ class VideaIE(InfoExtractor):
l = nonce[:32]
s = nonce[32:]
result = ''
- for i in range(0, 32):
+ for i in range(32):
result += s[i - (self._STATIC_SECRET.index(l[i]) - 31)]
query = parse_qs(player_url)
@@ -133,7 +134,7 @@ class VideaIE(InfoExtractor):
else:
key = result[16:] + random_seed + handle.headers['x-videa-xs']
info = self._parse_xml(self.rc4(
- compat_b64decode(b64_info), key), video_id)
+ base64.b64decode(b64_info), key), video_id)
video = xpath_element(info, './video', 'video')
if video is None:
diff --git a/yt_dlp/extractor/videocampus_sachsen.py b/yt_dlp/extractor/videocampus_sachsen.py
index 37bc7d718..6f98c685c 100644
--- a/yt_dlp/extractor/videocampus_sachsen.py
+++ b/yt_dlp/extractor/videocampus_sachsen.py
@@ -70,11 +70,11 @@ class VideocampusSachsenIE(InfoExtractor):
'www.wenglor-media.com',
'www2.univ-sba.dz',
)
- _VALID_URL = r'''(?x)https?://(?P<host>%s)/(?:
+ _VALID_URL = r'''(?x)https?://(?P<host>{})/(?:
m/(?P<tmp_id>[0-9a-f]+)|
- (?:category/)?video/(?P<display_id>[\w-]+)/(?P<id>[0-9a-f]{32})|
- media/embed.*(?:\?|&)key=(?P<embed_id>[0-9a-f]{32}&?)
- )''' % ('|'.join(map(re.escape, _INSTANCES)))
+ (?:category/)?video/(?P<display_id>[\w-]+)/(?P<id>[0-9a-f]{{32}})|
+ media/embed.*(?:\?|&)key=(?P<embed_id>[0-9a-f]{{32}}&?)
+ )'''.format('|'.join(map(re.escape, _INSTANCES)))
_TESTS = [
{
@@ -119,7 +119,7 @@ class VideocampusSachsenIE(InfoExtractor):
'thumbnail': 'https://www2.univ-sba.dz/cache/4d5d4a0b4189271a8cc6cb5328e14769.jpg',
'display_id': 'Presentation-de-la-Faculte-de-droit-et-des-sciences-politiques-Journee-portes-ouvertes-202122',
'ext': 'mp4',
- }
+ },
},
{
'url': 'https://vimp.weka-fachmedien.de/video/Preisverleihung-Produkte-des-Jahres-2022/c8816f1cc942c12b6cce57c835cffd7c',
@@ -187,10 +187,10 @@ class VideocampusSachsenIE(InfoExtractor):
class ViMPPlaylistIE(InfoExtractor):
IE_NAME = 'ViMP:Playlist'
- _VALID_URL = r'''(?x)(?P<host>https?://(?:%s))/(?:
+ _VALID_URL = r'''(?x)(?P<host>https?://(?:{}))/(?:
album/view/aid/(?P<album_id>[0-9]+)|
(?P<mode>category|channel)/(?P<name>[\w-]+)/(?P<id>[0-9]+)
- )''' % '|'.join(map(re.escape, VideocampusSachsenIE._INSTANCES))
+ )'''.format('|'.join(map(re.escape, VideocampusSachsenIE._INSTANCES)))
_TESTS = [{
'url': 'https://vimp.oth-regensburg.de/channel/Designtheorie-1-SoSe-2020/3',
@@ -216,9 +216,9 @@ class ViMPPlaylistIE(InfoExtractor):
}]
_PAGE_SIZE = 10
- def _fetch_page(self, host, url_part, id, data, page):
+ def _fetch_page(self, host, url_part, playlist_id, data, page):
webpage = self._download_webpage(
- f'{host}/media/ajax/component/boxList/{url_part}', id,
+ f'{host}/media/ajax/component/boxList/{url_part}', playlist_id,
query={'page': page, 'page_only': 1}, data=urlencode_postdata(data))
urls = re.findall(r'"([^"]+/video/[^"]+)"', webpage)
@@ -226,28 +226,28 @@ class ViMPPlaylistIE(InfoExtractor):
yield self.url_result(host + url, VideocampusSachsenIE)
def _real_extract(self, url):
- host, album_id, mode, name, id = self._match_valid_url(url).group(
+ host, album_id, mode, name, playlist_id = self._match_valid_url(url).group(
'host', 'album_id', 'mode', 'name', 'id')
- webpage = self._download_webpage(url, album_id or id, fatal=False) or ''
+ webpage = self._download_webpage(url, album_id or playlist_id, fatal=False) or ''
title = (self._html_search_meta('title', webpage, fatal=False)
or self._html_extract_title(webpage))
url_part = (f'aid/{album_id}' if album_id
- else f'category/{name}/category_id/{id}' if mode == 'category'
- else f'title/{name}/channel/{id}')
+ else f'category/{name}/category_id/{playlist_id}' if mode == 'category'
+ else f'title/{name}/channel/{playlist_id}')
mode = mode or 'album'
data = {
'vars[mode]': mode,
- f'vars[{mode}]': album_id or id,
+ f'vars[{mode}]': album_id or playlist_id,
'vars[context]': '4' if album_id else '1' if mode == 'category' else '3',
- 'vars[context_id]': album_id or id,
+ 'vars[context_id]': album_id or playlist_id,
'vars[layout]': 'thumb',
'vars[per_page][thumb]': str(self._PAGE_SIZE),
}
return self.playlist_result(
OnDemandPagedList(functools.partial(
- self._fetch_page, host, url_part, album_id or id, data), self._PAGE_SIZE),
- playlist_title=title, id=f'{mode}-{album_id or id}')
+ self._fetch_page, host, url_part, album_id or playlist_id, data), self._PAGE_SIZE),
+ playlist_title=title, id=f'{mode}-{album_id or playlist_id}')
diff --git a/yt_dlp/extractor/videofyme.py b/yt_dlp/extractor/videofyme.py
index f1f88c499..6cdda75b2 100644
--- a/yt_dlp/extractor/videofyme.py
+++ b/yt_dlp/extractor/videofyme.py
@@ -31,7 +31,7 @@ class VideofyMeIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- config = self._download_json('http://vf-player-info-loader.herokuapp.com/%s.json' % video_id, video_id)['videoinfo']
+ config = self._download_json(f'http://vf-player-info-loader.herokuapp.com/{video_id}.json', video_id)['videoinfo']
video = config.get('video')
blog = config.get('blog', {})
diff --git a/yt_dlp/extractor/videoken.py b/yt_dlp/extractor/videoken.py
index eaf0cc8ae..dc1dcf12b 100644
--- a/yt_dlp/extractor/videoken.py
+++ b/yt_dlp/extractor/videoken.py
@@ -293,7 +293,7 @@ class VideoKenTopicIE(VideoKenBaseIE):
'playlist_mincount': 77,
'info_dict': {
'id': 'gravitational_waves',
- 'title': 'gravitational waves'
+ 'title': 'gravitational waves',
},
}, {
'url': 'https://videos.cncf.io/topic/prometheus/',
diff --git a/yt_dlp/extractor/videomore.py b/yt_dlp/extractor/videomore.py
index ddc33f7d7..c41d3d984 100644
--- a/yt_dlp/extractor/videomore.py
+++ b/yt_dlp/extractor/videomore.py
@@ -1,7 +1,4 @@
from .common import InfoExtractor
-from ..compat import (
- compat_str,
-)
from ..utils import (
int_or_none,
parse_qs,
@@ -253,7 +250,7 @@ class VideomoreVideoIE(VideomoreBaseIE):
'params': {
'skip_download': True,
},
- 'skip': 'redirects to https://more.tv/'
+ 'skip': 'redirects to https://more.tv/',
}, {
'url': 'https://videomore.ru/molodezhka/6_sezon/29_seriya?utm_so',
'only_matching': True,
@@ -264,7 +261,7 @@ class VideomoreVideoIE(VideomoreBaseIE):
@classmethod
def suitable(cls, url):
- return False if VideomoreIE.suitable(url) else super(VideomoreVideoIE, cls).suitable(url)
+ return False if VideomoreIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
@@ -292,14 +289,14 @@ class VideomoreSeasonIE(VideomoreBaseIE):
@classmethod
def suitable(cls, url):
return (False if (VideomoreIE.suitable(url) or VideomoreVideoIE.suitable(url))
- else super(VideomoreSeasonIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
display_id = self._match_id(url)
season = self._download_page_data(display_id)
- season_id = compat_str(season['id'])
+ season_id = str(season['id'])
tracks = self._download_json(
- self._API_BASE_URL + 'seasons/%s/tracks' % season_id,
+ self._API_BASE_URL + f'seasons/{season_id}/tracks',
season_id)['data']
entries = []
for track in tracks:
diff --git a/yt_dlp/extractor/videopress.py b/yt_dlp/extractor/videopress.py
index 0734aee9c..d3c9c8e22 100644
--- a/yt_dlp/extractor/videopress.py
+++ b/yt_dlp/extractor/videopress.py
@@ -14,7 +14,7 @@ from ..utils import (
class VideoPressIE(InfoExtractor):
_ID_REGEX = r'[\da-zA-Z]{8}'
_PATH_REGEX = r'video(?:\.word)?press\.com/embed/'
- _VALID_URL = r'https?://%s(?P<id>%s)' % (_PATH_REGEX, _ID_REGEX)
+ _VALID_URL = rf'https?://{_PATH_REGEX}(?P<id>{_ID_REGEX})'
_EMBED_REGEX = [rf'<iframe[^>]+src=["\'](?P<url>(?:https?://)?{_PATH_REGEX}{_ID_REGEX})']
_TESTS = [{
'url': 'https://videopress.com/embed/kUJmAcSf',
@@ -44,7 +44,7 @@ class VideoPressIE(InfoExtractor):
query = random_birthday('birth_year', 'birth_month', 'birth_day')
query['fields'] = 'description,duration,file_url_base,files,height,original,poster,rating,title,upload_date,width'
video = self._download_json(
- 'https://public-api.wordpress.com/rest/v1.1/videos/%s' % video_id,
+ f'https://public-api.wordpress.com/rest/v1.1/videos/{video_id}',
video_id, query=query)
title = video['title']
@@ -63,7 +63,7 @@ class VideoPressIE(InfoExtractor):
if ext in ('mp4', 'ogg'):
formats.append({
'url': urljoin(base_url, path),
- 'format_id': '%s-%s' % (format_id, ext),
+ 'format_id': f'{format_id}-{ext}',
'ext': determine_ext(path, ext),
'quality': quality(format_id),
})
diff --git a/yt_dlp/extractor/vidio.py b/yt_dlp/extractor/vidio.py
index 6322bb04b..955a11647 100644
--- a/yt_dlp/extractor/vidio.py
+++ b/yt_dlp/extractor/vidio.py
@@ -31,7 +31,7 @@ class VidioBaseIE(InfoExtractor):
login_page = self._download_webpage(
self._LOGIN_URL, None, 'Downloading log in page')
- login_form = self._form_hidden_inputs("login-form", login_page)
+ login_form = self._form_hidden_inputs('login-form', login_page)
login_form.update({
'user[login]': username,
'user[password]': password,
@@ -52,7 +52,7 @@ class VidioBaseIE(InfoExtractor):
elif reason:
subreason = get_element_by_class('onboarding-modal__description-text', login_post) or ''
raise ExtractorError(
- 'Unable to log in: %s. %s' % (reason, clean_html(subreason)), expected=True)
+ f'Unable to log in: {reason}. {clean_html(subreason)}', expected=True)
raise ExtractorError('Unable to log in')
def _initialize_pre_login(self):
@@ -98,7 +98,7 @@ class VidioIE(VidioBaseIE):
}, {
# Premier-exclusive video
'url': 'https://www.vidio.com/watch/1550718-stand-by-me-doraemon',
- 'only_matching': True
+ 'only_matching': True,
}, {
# embed url from https://enamplus.liputan6.com/read/5033648/video-fakta-temuan-suspek-cacar-monyet-di-jawa-tengah
'url': 'https://www.vidio.com/embed/7115874-fakta-temuan-suspek-cacar-monyet-di-jawa-tengah',
@@ -135,7 +135,7 @@ class VidioIE(VidioBaseIE):
if is_premium:
sources = self._download_json(
- 'https://www.vidio.com/interactions_stream.json?video_id=%s&type=videos' % video_id,
+ f'https://www.vidio.com/interactions_stream.json?video_id={video_id}&type=videos',
display_id, note='Downloading premier API JSON')
if not (sources.get('source') or sources.get('source_dash')):
self.raise_login_required('This video is only available for registered users with the appropriate subscription')
@@ -199,7 +199,7 @@ class VidioPremierIE(VidioBaseIE):
def _playlist_entries(self, playlist_url, display_id):
index = 1
while playlist_url:
- playlist_json = self._call_api(playlist_url, display_id, 'Downloading API JSON page %s' % index)
+ playlist_json = self._call_api(playlist_url, display_id, f'Downloading API JSON page {index}')
for video_json in playlist_json.get('data', []):
link = video_json['links']['watchpage']
yield self.url_result(link, 'Vidio', video_json['id'])
@@ -217,14 +217,14 @@ class VidioPremierIE(VidioBaseIE):
self._playlist_entries(playlist_url, playlist_id),
playlist_id=playlist_id, playlist_title=idata.get('title'))
- playlist_data = self._call_api('https://api.vidio.com/content_profiles/%s/playlists' % playlist_id, display_id)
+ playlist_data = self._call_api(f'https://api.vidio.com/content_profiles/{playlist_id}/playlists', display_id)
return self.playlist_from_matches(
playlist_data.get('data', []), playlist_id=playlist_id, ie=self.ie_key(),
getter=lambda data: smuggle_url(url, {
'url': data['relationships']['videos']['links']['related'],
'id': data['id'],
- 'title': try_get(data, lambda x: x['attributes']['name'])
+ 'title': try_get(data, lambda x: x['attributes']['name']),
}))
@@ -252,7 +252,7 @@ class VidioLiveIE(VidioBaseIE):
def _real_extract(self, url):
video_id, display_id = self._match_valid_url(url).groups()
stream_data = self._call_api(
- 'https://www.vidio.com/api/livestreamings/%s/detail' % video_id, display_id)
+ f'https://www.vidio.com/api/livestreamings/{video_id}/detail', display_id)
stream_meta = stream_data['livestreamings'][0]
user = stream_data.get('users', [{}])[0]
@@ -265,14 +265,14 @@ class VidioLiveIE(VidioBaseIE):
self.report_drm(video_id)
if stream_meta.get('is_premium'):
sources = self._download_json(
- 'https://www.vidio.com/interactions_stream.json?video_id=%s&type=livestreamings' % video_id,
+ f'https://www.vidio.com/interactions_stream.json?video_id={video_id}&type=livestreamings',
display_id, note='Downloading premier API JSON')
if not (sources.get('source') or sources.get('source_dash')):
self.raise_login_required('This video is only available for registered users with the appropriate subscription')
if str_or_none(sources.get('source')):
token_json = self._download_json(
- 'https://www.vidio.com/live/%s/tokens' % video_id,
+ f'https://www.vidio.com/live/{video_id}/tokens',
display_id, note='Downloading HLS token JSON', data=b'')
formats.extend(self._extract_m3u8_formats(
sources['source'] + '?' + token_json.get('token', ''), display_id, 'mp4', 'm3u8_native'))
@@ -281,7 +281,7 @@ class VidioLiveIE(VidioBaseIE):
else:
if stream_meta.get('stream_token_url'):
token_json = self._download_json(
- 'https://www.vidio.com/live/%s/tokens' % video_id,
+ f'https://www.vidio.com/live/{video_id}/tokens',
display_id, note='Downloading HLS token JSON', data=b'')
formats.extend(self._extract_m3u8_formats(
stream_meta['stream_token_url'] + '?' + token_json.get('token', ''),
diff --git a/yt_dlp/extractor/vidlii.py b/yt_dlp/extractor/vidlii.py
index e1219a8a0..d9e33ca9e 100644
--- a/yt_dlp/extractor/vidlii.py
+++ b/yt_dlp/extractor/vidlii.py
@@ -34,7 +34,7 @@ class VidLiiIE(InfoExtractor):
'average_rating': float,
'categories': ['News & Politics'],
'tags': ['Vidlii', 'Jan', 'Videogames'],
- }
+ },
}, {
'url': 'https://www.vidlii.com/watch?v=zTAtaAgOLKt',
'md5': '5778f7366aa4c569b77002f8bf6b614f',
@@ -63,7 +63,7 @@ class VidLiiIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'https://www.vidlii.com/watch?v=%s' % video_id, video_id)
+ f'https://www.vidlii.com/watch?v={video_id}', video_id)
formats = []
sources = [source[1] for source in re.findall(
diff --git a/yt_dlp/extractor/vidly.py b/yt_dlp/extractor/vidly.py
index 49a196041..60698f0e1 100644
--- a/yt_dlp/extractor/vidly.py
+++ b/yt_dlp/extractor/vidly.py
@@ -45,7 +45,7 @@ class VidlyIE(InfoExtractor):
'ext': 'mp4',
'title': 'w8p5b0',
'thumbnail': r're:https://\w+\.cloudfront\.net/',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/viewlift.py b/yt_dlp/extractor/viewlift.py
index c5d65cdd6..7ac094f2f 100644
--- a/yt_dlp/extractor/viewlift.py
+++ b/yt_dlp/extractor/viewlift.py
@@ -62,8 +62,8 @@ class ViewLiftBaseIE(InfoExtractor):
class ViewLiftEmbedIE(ViewLiftBaseIE):
IE_NAME = 'viewlift:embed'
- _VALID_URL = r'https?://(?:(?:www|embed)\.)?(?P<domain>%s)/embed/player\?.*\bfilmId=(?P<id>[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12})' % ViewLiftBaseIE._DOMAINS_REGEX
- _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:embed\.)?(?:%s)/embed/player.+?)\1' % ViewLiftBaseIE._DOMAINS_REGEX]
+ _VALID_URL = rf'https?://(?:(?:www|embed)\.)?(?P<domain>{ViewLiftBaseIE._DOMAINS_REGEX})/embed/player\?.*\bfilmId=(?P<id>[\da-f]{{8}}-(?:[\da-f]{{4}}-){{3}}[\da-f]{{12}})'
+ _EMBED_REGEX = [rf'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:embed\.)?(?:{ViewLiftBaseIE._DOMAINS_REGEX})/embed/player.+?)\1']
_TESTS = [{
'url': 'http://embed.snagfilms.com/embed/player?filmId=74849a00-85a9-11e1-9660-123139220831&w=500',
'md5': '2924e9215c6eff7a55ed35b72276bd93',
@@ -74,7 +74,7 @@ class ViewLiftEmbedIE(ViewLiftBaseIE):
'description': 'md5:b542bef32a6f657dadd0df06e26fb0c8',
'timestamp': 1334350096,
'upload_date': '20120413',
- }
+ },
}, {
# invalid labels, 360p is better that 480p
'url': 'http://www.snagfilms.com/embed/player?filmId=17ca0950-a74a-11e0-a92a-0026bb61d036',
@@ -98,7 +98,7 @@ class ViewLiftEmbedIE(ViewLiftBaseIE):
content_data = self._call_api(
site, 'entitlement/video/status', film_id, url, {
- 'id': film_id
+ 'id': film_id,
})['video']
gist = content_data['gist']
title = gist['title']
@@ -120,7 +120,7 @@ class ViewLiftEmbedIE(ViewLiftBaseIE):
'height', default=None))
formats.append({
'url': video_asset_url,
- 'format_id': 'http%s' % ('-%d' % bitrate if bitrate else ''),
+ 'format_id': 'http{}'.format(f'-{bitrate}' if bitrate else ''),
'tbr': bitrate,
'height': height,
'vcodec': video_asset.get('codec'),
@@ -153,7 +153,7 @@ class ViewLiftEmbedIE(ViewLiftBaseIE):
class ViewLiftIE(ViewLiftBaseIE):
IE_NAME = 'viewlift'
_API_BASE = 'https://prod-api-cached-2.viewlift.com/'
- _VALID_URL = r'https?://(?:www\.)?(?P<domain>%s)(?P<path>(?:/(?:films/title|show|(?:news/)?videos?|watch))?/(?P<id>[^?#]+))' % ViewLiftBaseIE._DOMAINS_REGEX
+ _VALID_URL = rf'https?://(?:www\.)?(?P<domain>{ViewLiftBaseIE._DOMAINS_REGEX})(?P<path>(?:/(?:films/title|show|(?:news/)?videos?|watch))?/(?P<id>[^?#]+))'
_TESTS = [{
'url': 'http://www.snagfilms.com/films/title/lost_for_life',
'md5': '19844f897b35af219773fd63bdec2942',
@@ -169,7 +169,7 @@ class ViewLiftIE(ViewLiftBaseIE):
'age_limit': 14,
'upload_date': '20150421',
'timestamp': 1429656820,
- }
+ },
}, {
'url': 'http://www.snagfilms.com/show/the_world_cut_project/india',
'md5': 'e6292e5b837642bbda82d7f8bf3fbdfd',
@@ -183,7 +183,7 @@ class ViewLiftIE(ViewLiftBaseIE):
'duration': 979,
'timestamp': 1399478279,
'upload_date': '20140507',
- }
+ },
}, {
'url': 'http://main.snagfilms.com/augie_alone/s_2_ep_12_love',
'info_dict': {
@@ -253,7 +253,7 @@ class ViewLiftIE(ViewLiftBaseIE):
'description': 'md5:ca30a682b4528d02a3eb6d0427dd0f87',
'thumbnail': r're:^https?://.*\.jpg$',
'upload_date': '20210830',
- 'series': 'Case Jaundice'
+ 'series': 'Case Jaundice',
},
'params': {'skip_download': True},
}, { # Free video
@@ -265,7 +265,7 @@ class ViewLiftIE(ViewLiftBaseIE):
'description': 'md5:9d21edc1827d32f8633eb67c2054fc31',
'thumbnail': r're:^https?://.*\.jpg$',
'upload_date': '20211006',
- 'series': 'Six (Hindi)'
+ 'series': 'Six (Hindi)',
},
'params': {'skip_download': True},
}, { # Free episode
@@ -277,7 +277,7 @@ class ViewLiftIE(ViewLiftBaseIE):
'description': 'md5:ef6ffae01a3d83438597367400f824ed',
'thumbnail': r're:^https?://.*\.jpg$',
'upload_date': '20211004',
- 'series': 'Asian Paints Moner Thikana'
+ 'series': 'Asian Paints Moner Thikana',
},
'params': {'skip_download': True},
}, { # Free series
@@ -294,7 +294,7 @@ class ViewLiftIE(ViewLiftBaseIE):
},
}, { # Premium movie
'url': 'https://www.hoichoi.tv/movies/detective-2020',
- 'only_matching': True
+ 'only_matching': True,
}, { # Chorki Premium series
'url': 'https://www.chorki.com/bn/series/sinpaat',
'playlist_mincount': 7,
@@ -326,7 +326,7 @@ class ViewLiftIE(ViewLiftBaseIE):
@classmethod
def suitable(cls, url):
- return False if ViewLiftEmbedIE.suitable(url) else super(ViewLiftIE, cls).suitable(url)
+ return False if ViewLiftEmbedIE.suitable(url) else super().suitable(url)
def _show_entries(self, domain, seasons):
for season in seasons:
@@ -355,7 +355,7 @@ class ViewLiftIE(ViewLiftBaseIE):
film_id = next(m['contentData'][0]['gist']['id'] for m in modules if m.get('moduleType') == 'VideoDetailModule')
return {
'_type': 'url_transparent',
- 'url': 'http://%s/embed/player?filmId=%s' % (domain, film_id),
+ 'url': f'http://{domain}/embed/player?filmId={film_id}',
'id': film_id,
'display_id': display_id,
'ie_key': 'ViewLiftEmbed',
diff --git a/yt_dlp/extractor/viidea.py b/yt_dlp/extractor/viidea.py
index 649ffe395..2f04e880f 100644
--- a/yt_dlp/extractor/viidea.py
+++ b/yt_dlp/extractor/viidea.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -124,13 +121,13 @@ class ViideaIE(InfoExtractor):
r'cfg\s*:\s*({[^}]+})'],
webpage, 'cfg'), lecture_slug, js_to_json)
- lecture_id = compat_str(cfg['obj_id'])
+ lecture_id = str(cfg['obj_id'])
base_url = self._proto_relative_url(cfg['livepipe'], 'http:')
try:
lecture_data = self._download_json(
- '%s/site/api/lecture/%s?format=json' % (base_url, lecture_id),
+ f'{base_url}/site/api/lecture/{lecture_id}?format=json',
lecture_id)['lecture'][0]
except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
@@ -150,18 +147,18 @@ class ViideaIE(InfoExtractor):
playlist_entries = []
lecture_type = lecture_data.get('type')
- parts = [compat_str(video) for video in cfg.get('videos', [])]
+ parts = [str(video) for video in cfg.get('videos', [])]
if parts:
multipart = len(parts) > 1
def extract_part(part_id):
- smil_url = '%s/%s/video/%s/smil.xml' % (base_url, lecture_slug, part_id)
+ smil_url = f'{base_url}/{lecture_slug}/video/{part_id}/smil.xml'
smil = self._download_smil(smil_url, lecture_id)
info = self._parse_smil(smil, smil_url, lecture_id)
- info['id'] = lecture_id if not multipart else '%s_part%s' % (lecture_id, part_id)
- info['display_id'] = lecture_slug if not multipart else '%s_part%s' % (lecture_slug, part_id)
+ info['id'] = lecture_id if not multipart else f'{lecture_id}_part{part_id}'
+ info['display_id'] = lecture_slug if not multipart else f'{lecture_slug}_part{part_id}'
if multipart:
- info['title'] += ' (Part %s)' % part_id
+ info['title'] += f' (Part {part_id})'
switch = smil.find('.//switch')
if switch is not None:
info['duration'] = parse_duration(switch.attrib.get('dur'))
@@ -187,9 +184,9 @@ class ViideaIE(InfoExtractor):
# It's probably a playlist
if not parts or lecture_type == 'evt':
playlist_webpage = self._download_webpage(
- '%s/site/ajax/drilldown/?id=%s' % (base_url, lecture_id), lecture_id)
+ f'{base_url}/site/ajax/drilldown/?id={lecture_id}', lecture_id)
entries = [
- self.url_result(compat_urlparse.urljoin(url, video_url), 'Viidea')
+ self.url_result(urllib.parse.urljoin(url, video_url), 'Viidea')
for _, video_url in re.findall(
r'<a[^>]+href=(["\'])(.+?)\1[^>]+id=["\']lec=\d+', playlist_webpage)]
playlist_entries.extend(entries)
diff --git a/yt_dlp/extractor/viki.py b/yt_dlp/extractor/viki.py
index 3246dab52..75f9cdf2f 100644
--- a/yt_dlp/extractor/viki.py
+++ b/yt_dlp/extractor/viki.py
@@ -43,14 +43,14 @@ class VikiBaseIE(InfoExtractor):
'X-Viki-as-id': '100005a-1625321982-3932',
'timestamp': str(timestamp),
'signature': str(sig),
- 'x-viki-app-ver': self._APP_VERSION
+ 'x-viki-app-ver': self._APP_VERSION,
}
def _api_query(self, path, version=4, **kwargs):
path += '?' if '?' not in path else '&'
query = f'/v{version}/{path}app={self._APP}'
if self._token:
- query += '&token=%s' % self._token
+ query += f'&token={self._token}'
return query + ''.join(f'&{name}={val}' for name, val in kwargs.items())
def _sign_query(self, path):
@@ -68,7 +68,7 @@ class VikiBaseIE(InfoExtractor):
url = self._API_URL_TEMPLATE % self._api_query(path, version=4)
resp = self._download_json(
url, video_id, note, fatal=fatal, query=query,
- data=json.dumps(data).encode('utf-8') if data else None,
+ data=json.dumps(data).encode() if data else None,
headers=({'x-viki-app-ver': self._APP_VERSION} if data
else self._stream_headers(timestamp, sig) if query is None
else None), expected_status=400) or {}
@@ -79,7 +79,7 @@ class VikiBaseIE(InfoExtractor):
def _raise_error(self, error, fatal=True):
if error is None:
return
- msg = '%s said: %s' % (self.IE_NAME, error)
+ msg = f'{self.IE_NAME} said: {error}'
if fatal:
raise ExtractorError(msg, expected=True)
else:
@@ -113,7 +113,7 @@ class VikiBaseIE(InfoExtractor):
class VikiIE(VikiBaseIE):
IE_NAME = 'viki'
- _VALID_URL = r'%s(?:videos|player)/(?P<id>[0-9]+v)' % VikiBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{VikiBaseIE._VALID_URL_BASE}(?:videos|player)/(?P<id>[0-9]+v)'
_TESTS = [{
'note': 'Free non-DRM video with storyboards in MPD',
'url': 'https://www.viki.com/videos/1175236v-choosing-spouse-by-lottery-episode-1',
@@ -233,10 +233,10 @@ class VikiIE(VikiBaseIE):
title = try_get(video, lambda x: x['titles']['en'], str)
episode_number = int_or_none(video.get('number'))
if not title:
- title = 'Episode %d' % episode_number if video.get('type') == 'episode' else video.get('id') or video_id
+ title = f'Episode {episode_number}' if video.get('type') == 'episode' else video.get('id') or video_id
container_titles = try_get(video, lambda x: x['container']['titles'], dict) or {}
container_title = self.dict_selection(container_titles, 'en')
- title = '%s - %s' % (container_title, title)
+ title = f'{container_title} - {title}'
thumbnails = [{
'id': thumbnail_id,
@@ -244,15 +244,15 @@ class VikiIE(VikiBaseIE):
} for thumbnail_id, thumbnail in (video.get('images') or {}).items() if thumbnail.get('url')]
resp = self._call_api(
- 'playback_streams/%s.json?drms=dt3&device_id=%s' % (video_id, self._DEVICE_ID),
+ f'playback_streams/{video_id}.json?drms=dt3&device_id={self._DEVICE_ID}',
video_id, 'Downloading video streams JSON')['main'][0]
stream_id = try_get(resp, lambda x: x['properties']['track']['stream_id'])
subtitles = dict((lang, [{
'ext': ext,
'url': self._API_URL_TEMPLATE % self._api_query(
- f'videos/{video_id}/auth_subtitles/{lang}.{ext}', stream_id=stream_id)
- } for ext in ('srt', 'vtt')]) for lang in (video.get('subtitle_completions') or {}).keys())
+ f'videos/{video_id}/auth_subtitles/{lang}.{ext}', stream_id=stream_id),
+ } for ext in ('srt', 'vtt')]) for lang in (video.get('subtitle_completions') or {}))
mpd_url = resp['url']
# 720p is hidden in another MPD which can be found in the current manifest content
@@ -283,7 +283,7 @@ class VikiIE(VikiBaseIE):
class VikiChannelIE(VikiBaseIE):
IE_NAME = 'viki:channel'
- _VALID_URL = r'%s(?:tv|news|movies|artists)/(?P<id>[0-9]+c)' % VikiBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{VikiBaseIE._VALID_URL_BASE}(?:tv|news|movies|artists)/(?P<id>[0-9]+c)'
_TESTS = [{
'url': 'http://www.viki.com/tv/50c-boys-over-flowers',
'info_dict': {
@@ -317,7 +317,7 @@ class VikiChannelIE(VikiBaseIE):
def _entries(self, channel_id):
params = {
'app': self._APP, 'token': self._token, 'only_ids': 'true',
- 'direction': 'asc', 'sort': 'number', 'per_page': 30
+ 'direction': 'asc', 'sort': 'number', 'per_page': 30,
}
video_types = self._configuration_arg('video_types') or self._video_types
for video_type in video_types:
@@ -329,7 +329,7 @@ class VikiChannelIE(VikiBaseIE):
params['page'] = page_num
res = self._call_api(
f'containers/{channel_id}/{video_type}.json', channel_id, query=params, fatal=False,
- note='Downloading %s JSON page %d' % (video_type.title(), page_num))
+ note=f'Downloading {video_type.title()} JSON page {page_num}')
for video_id in res.get('response') or []:
yield self.url_result(f'https://www.viki.com/videos/{video_id}', VikiIE.ie_key(), video_id)
@@ -338,7 +338,7 @@ class VikiChannelIE(VikiBaseIE):
def _real_extract(self, url):
channel_id = self._match_id(url)
- channel = self._call_api('containers/%s.json' % channel_id, channel_id, 'Downloading channel JSON')
+ channel = self._call_api(f'containers/{channel_id}.json', channel_id, 'Downloading channel JSON')
self._check_errors(channel)
return self.playlist_result(
self._entries(channel_id), channel_id,
diff --git a/yt_dlp/extractor/vimeo.py b/yt_dlp/extractor/vimeo.py
index ac96ade18..7e79032f2 100644
--- a/yt_dlp/extractor/vimeo.py
+++ b/yt_dlp/extractor/vimeo.py
@@ -2,9 +2,9 @@ import base64
import functools
import itertools
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_str, compat_urlparse
from ..networking import HEADRequest, Request
from ..networking.exceptions import HTTPError
from ..utils import (
@@ -141,7 +141,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
continue
formats.append({
'url': video_url,
- 'format_id': 'http-%s' % f.get('quality'),
+ 'format_id': 'http-{}'.format(f.get('quality')),
'source_preference': 10,
'width': int_or_none(f.get('width')),
'height': int_or_none(f.get('height')),
@@ -156,19 +156,19 @@ class VimeoBaseInfoExtractor(InfoExtractor):
manifest_url = cdn_data.get('url')
if not manifest_url:
continue
- format_id = '%s-%s' % (files_type, cdn_name)
+ format_id = f'{files_type}-{cdn_name}'
sep_manifest_urls = []
if re.search(sep_pattern, manifest_url):
for suffix, repl in (('', 'video'), ('_sep', 'sep/video')):
sep_manifest_urls.append((format_id + suffix, re.sub(
- sep_pattern, '/%s/' % repl, manifest_url)))
+ sep_pattern, f'/{repl}/', manifest_url)))
else:
sep_manifest_urls = [(format_id, manifest_url)]
for f_id, m_url in sep_manifest_urls:
if files_type == 'hls':
fmts, subs = self._extract_m3u8_formats_and_subtitles(
m_url, video_id, 'mp4', live=is_live, m3u8_id=f_id,
- note='Downloading %s m3u8 information' % cdn_name,
+ note=f'Downloading {cdn_name} m3u8 information',
fatal=False)
formats.extend(fmts)
self._merge_subtitles(subs, target=subtitles)
@@ -179,7 +179,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
m_url = real_m_url
fmts, subs = self._extract_mpd_formats_and_subtitles(
m_url.replace('/master.json', '/master.mpd'), video_id, f_id,
- 'Downloading %s MPD information' % cdn_name,
+ f'Downloading {cdn_name} MPD information',
fatal=False)
formats.extend(fmts)
self._merge_subtitles(subs, target=subtitles)
@@ -256,10 +256,10 @@ class VimeoBaseInfoExtractor(InfoExtractor):
download_url = try_get(source_file, lambda x: x['download_url'])
if download_url and not source_file.get('is_cold') and not source_file.get('is_defrosting'):
source_name = source_file.get('public_name', 'Original')
- if self._is_valid_url(download_url, video_id, '%s video' % source_name):
+ if self._is_valid_url(download_url, video_id, f'{source_name} video'):
ext = (try_get(
source_file, lambda x: x['extension'],
- compat_str) or determine_ext(
+ str) or determine_ext(
download_url, None) or 'mp4').lower()
return {
'url': download_url,
@@ -275,7 +275,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
'https://vimeo.com/_rv/viewer', video_id, note='Downloading jwt token', fatal=False) or {}
if not jwt_response.get('jwt'):
return
- headers = {'Authorization': 'jwt %s' % jwt_response['jwt'], 'Accept': 'application/json'}
+ headers = {'Authorization': 'jwt {}'.format(jwt_response['jwt']), 'Accept': 'application/json'}
original_response = self._download_json(
f'https://api.vimeo.com/videos/{video_id}', video_id,
headers=headers, fatal=False, expected_status=(403, 404)) or {}
@@ -361,7 +361,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
'params': {
'format': 'best[protocol=https]',
},
- 'skip': 'No longer available'
+ 'skip': 'No longer available',
},
{
'url': 'http://player.vimeo.com/video/54469442',
@@ -739,7 +739,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
'Content-Type': 'application/x-www-form-urlencoded',
})
checked = self._download_json(
- f'{compat_urlparse.urlsplit(url)._replace(query=None).geturl()}/check-password',
+ f'{urllib.parse.urlsplit(url)._replace(query=None).geturl()}/check-password',
video_id, 'Verifying the password', data=data, headers=headers)
if checked is False:
raise ExtractorError('Wrong video password', expected=True)
@@ -748,7 +748,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
def _extract_from_api(self, video_id, unlisted_hash=None):
token = self._download_json(
'https://vimeo.com/_rv/jwt', video_id, headers={
- 'X-Requested-With': 'XMLHttpRequest'
+ 'X-Requested-With': 'XMLHttpRequest',
})['token']
api_url = 'https://api.vimeo.com/videos/' + video_id
if unlisted_hash:
@@ -802,7 +802,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
self._set_vimeo_cookie('vuid', viewer['vuid'])
try:
self._download_json(
- 'https://vimeo.com/showcase/%s/auth' % album_id,
+ f'https://vimeo.com/showcase/{album_id}/auth',
album_id, 'Verifying the password', data=urlencode_postdata({
'password': password,
'token': viewer['xsrft'],
@@ -864,7 +864,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
seed_status = vimeo_config.get('seed_status') or {}
if seed_status.get('state') == 'failed':
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, seed_status['title']),
+ '{} said: {}'.format(self.IE_NAME, seed_status['title']),
expected=True)
cc_license = None
@@ -916,7 +916,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
feature_id = vod.get('feature_id')
if feature_id and not data.get('force_feature_id', False):
return self.url_result(smuggle_url(
- 'https://player.vimeo.com/player/%s' % feature_id,
+ f'https://player.vimeo.com/player/{feature_id}',
{'force_feature_id': True}), 'Vimeo')
if not video_description:
@@ -1051,7 +1051,7 @@ class VimeoChannelIE(VimeoBaseInfoExtractor):
_BASE_URL_TEMPL = 'https://vimeo.com/channels/%s'
def _page_url(self, base_url, pagenum):
- return '%s/videos/page:%d/' % (base_url, pagenum)
+ return f'{base_url}/videos/page:{pagenum}/'
def _extract_list_title(self, webpage):
return self._TITLE or self._html_search_regex(
@@ -1062,7 +1062,7 @@ class VimeoChannelIE(VimeoBaseInfoExtractor):
page_url = self._page_url(base_url, pagenum)
webpage = self._download_webpage(
page_url, list_id,
- 'Downloading page %s' % pagenum)
+ f'Downloading page {pagenum}')
if pagenum == 1:
yield self._extract_list_title(webpage)
@@ -1074,13 +1074,13 @@ class VimeoChannelIE(VimeoBaseInfoExtractor):
if clips:
for video_id, video_url, video_title in clips:
yield self.url_result(
- compat_urlparse.urljoin(base_url, video_url),
+ urllib.parse.urljoin(base_url, video_url),
VimeoIE.ie_key(), video_id=video_id, video_title=video_title)
# More relaxed fallback
else:
for video_id in re.findall(r'id=["\']clip_(\d+)', webpage):
yield self.url_result(
- 'https://vimeo.com/%s' % video_id,
+ f'https://vimeo.com/{video_id}',
VimeoIE.ie_key(), video_id=video_id)
if re.search(self._MORE_PAGES_INDICATOR, webpage, re.DOTALL) is None:
@@ -1135,7 +1135,7 @@ class VimeoAlbumIE(VimeoBaseInfoExtractor):
'playlist_count': 1,
'params': {
'videopassword': 'youtube-dl',
- }
+ },
}]
_PAGE_SIZE = 100
@@ -1150,8 +1150,8 @@ class VimeoAlbumIE(VimeoBaseInfoExtractor):
query['_hashed_pass'] = hashed_pass
try:
videos = self._download_json(
- 'https://api.vimeo.com/albums/%s/videos' % album_id,
- album_id, 'Downloading page %d' % api_page, query=query, headers={
+ f'https://api.vimeo.com/albums/{album_id}/videos',
+ album_id, f'Downloading page {api_page}', query=query, headers={
'Authorization': 'jwt ' + authorization,
'Accept': 'application/json',
})['data']
@@ -1191,7 +1191,7 @@ class VimeoAlbumIE(VimeoBaseInfoExtractor):
self._set_vimeo_cookie('vuid', viewer['vuid'])
try:
hashed_pass = self._download_json(
- 'https://vimeo.com/showcase/%s/auth' % album_id,
+ f'https://vimeo.com/showcase/{album_id}/auth',
album_id, 'Verifying the password', data=urlencode_postdata({
'password': password,
'token': viewer['xsrft'],
@@ -1311,7 +1311,7 @@ class VimeoWatchLaterIE(VimeoChannelIE): # XXX: Do not subclass from concrete I
}]
def _page_url(self, base_url, pagenum):
- url = '%s/page:%d/' % (base_url, pagenum)
+ url = f'{base_url}/page:{pagenum}/'
request = Request(url)
# Set the header to get a partial html page with the ids,
# the normal page doesn't contain them.
@@ -1339,11 +1339,11 @@ class VimeoLikesIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE
}]
def _page_url(self, base_url, pagenum):
- return '%s/page:%d/' % (base_url, pagenum)
+ return f'{base_url}/page:{pagenum}/'
def _real_extract(self, url):
user_id = self._match_id(url)
- return self._extract_videos(user_id, 'https://vimeo.com/%s/likes' % user_id)
+ return self._extract_videos(user_id, f'https://vimeo.com/{user_id}/likes')
class VHXEmbedIE(VimeoBaseInfoExtractor):
diff --git a/yt_dlp/extractor/vine.py b/yt_dlp/extractor/vine.py
index 1909980f2..eed4bfeeb 100644
--- a/yt_dlp/extractor/vine.py
+++ b/yt_dlp/extractor/vine.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
determine_ext,
format_field,
@@ -62,11 +61,11 @@ class VineIE(InfoExtractor):
video_id = self._match_id(url)
data = self._download_json(
- 'https://archive.vine.co/posts/%s.json' % video_id, video_id)
+ f'https://archive.vine.co/posts/{video_id}.json', video_id)
def video_url(kind):
for url_suffix in ('Url', 'URL'):
- format_url = data.get('video%s%s' % (kind, url_suffix))
+ format_url = data.get(f'video{kind}{url_suffix}')
if format_url:
return format_url
@@ -126,14 +125,14 @@ class VineUserIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if VineIE.suitable(url) else super(VineUserIE, cls).suitable(url)
+ return False if VineIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
mobj = self._match_valid_url(url)
user = mobj.group('user')
u = mobj.group('u')
- profile_url = '%sapi/users/profiles/%s%s' % (
+ profile_url = '{}api/users/profiles/{}{}'.format(
self._VINE_BASE_URL, 'vanity/' if not u else '', user)
profile_data = self._download_json(
profile_url, user, note='Downloading user profile data')
@@ -141,11 +140,11 @@ class VineUserIE(InfoExtractor):
data = profile_data['data']
user_id = data.get('userId') or data['userIdStr']
profile = self._download_json(
- 'https://archive.vine.co/profiles/%s.json' % user_id, user_id)
+ f'https://archive.vine.co/profiles/{user_id}.json', user_id)
entries = [
self.url_result(
- 'https://vine.co/v/%s' % post_id, ie='Vine', video_id=post_id)
+ f'https://vine.co/v/{post_id}', ie='Vine', video_id=post_id)
for post_id in profile['posts']
- if post_id and isinstance(post_id, compat_str)]
+ if post_id and isinstance(post_id, str)]
return self.playlist_result(
entries, user, profile.get('username'), profile.get('description'))
diff --git a/yt_dlp/extractor/viously.py b/yt_dlp/extractor/viously.py
index 9ec7ed35f..3e8519c3e 100644
--- a/yt_dlp/extractor/viously.py
+++ b/yt_dlp/extractor/viously.py
@@ -25,7 +25,7 @@ class ViouslyIE(InfoExtractor):
'timestamp': 1680037507,
'duration': 3716,
'categories': ['motors'],
- }
+ },
}]
def _extract_from_webpage(self, url, webpage):
diff --git a/yt_dlp/extractor/viqeo.py b/yt_dlp/extractor/viqeo.py
index f0a7b5e44..433fdc7a3 100644
--- a/yt_dlp/extractor/viqeo.py
+++ b/yt_dlp/extractor/viqeo.py
@@ -39,7 +39,7 @@ class ViqeoIE(InfoExtractor):
video_id = self._match_id(url)
webpage = self._download_webpage(
- 'https://cdn.viqeo.tv/embed/?vid=%s' % video_id, video_id)
+ f'https://cdn.viqeo.tv/embed/?vid={video_id}', video_id)
data = self._parse_json(
self._search_regex(
diff --git a/yt_dlp/extractor/viu.py b/yt_dlp/extractor/viu.py
index 480f49b7b..01e59352b 100644
--- a/yt_dlp/extractor/viu.py
+++ b/yt_dlp/extractor/viu.py
@@ -5,7 +5,6 @@ import urllib.parse
import uuid
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -68,7 +67,7 @@ class ViuIE(ViuBaseIE):
'clip/load', video_id, 'Downloading video data', query={
'appid': 'viu_desktop',
'fmt': 'json',
- 'id': video_id
+ 'id': video_id,
})['item'][0]
title = video_data['title']
@@ -82,7 +81,7 @@ class ViuIE(ViuBaseIE):
# hls_file = video_data.get('hlsfile')
hls_file = video_data.get('jwhlsfile')
if url_path and tdirforwhole and hls_file:
- m3u8_url = '%s/%s/%s' % (url_path, tdirforwhole, hls_file)
+ m3u8_url = f'{url_path}/{tdirforwhole}/{hls_file}'
else:
# m3u8_url = re.sub(
# r'(/hlsc_)[a-z]+(\d+\.m3u8)',
@@ -96,7 +95,7 @@ class ViuIE(ViuBaseIE):
continue
subtitles.setdefault(mobj.group('lang'), []).append({
'url': value,
- 'ext': mobj.group('ext')
+ 'ext': mobj.group('ext'),
})
return {
@@ -132,7 +131,7 @@ class ViuPlaylistIE(ViuBaseIE):
'Downloading playlist info', query={
'appid': 'viu_desktop',
'fmt': 'json',
- 'id': 'playlist-' + playlist_id
+ 'id': 'playlist-' + playlist_id,
})['container']
entries = []
@@ -140,7 +139,7 @@ class ViuPlaylistIE(ViuBaseIE):
item_id = item.get('id')
if not item_id:
continue
- item_id = compat_str(item_id)
+ item_id = str(item_id)
entries.append(self.url_result(
'viu:' + item_id, 'Viu', item_id))
@@ -227,14 +226,14 @@ class ViuOTTIE(InfoExtractor):
return
headers = {
'Authorization': f'Bearer {self._auth_codes[country_code]}',
- 'Content-Type': 'application/json'
+ 'Content-Type': 'application/json',
}
data = self._download_json(
'https://api-gateway-global.viu.com/api/account/validate',
video_id, 'Validating email address', headers=headers,
data=json.dumps({
'principal': username,
- 'provider': 'email'
+ 'provider': 'email',
}).encode())
if not data.get('exists'):
raise ExtractorError('Invalid email address')
@@ -264,8 +263,8 @@ class ViuOTTIE(InfoExtractor):
'platformFlagLabel': 'web',
'language': 'en',
'uuid': str(uuid.uuid4()),
- 'carrierId': '0'
- }).encode('utf-8'))['token']
+ 'carrierId': '0',
+ }).encode())['token']
def _real_extract(self, url):
url, idata = unsmuggle_url(url, {})
@@ -319,7 +318,7 @@ class ViuOTTIE(InfoExtractor):
headers={
'Authorization': f'Bearer {self._auth_codes[country_code]}',
'Referer': url,
- 'Origin': url
+ 'Origin': url,
})
return self._detect_error(stream_data).get('stream')
@@ -365,7 +364,7 @@ class ViuOTTIE(InfoExtractor):
'url': stream_url,
'height': height,
'ext': 'mp4',
- 'filesize': try_get(stream_data, lambda x: x['size'][vid_format], int)
+ 'filesize': try_get(stream_data, lambda x: x['size'][vid_format], int),
})
subtitles = {}
@@ -415,12 +414,12 @@ class ViuOTTIndonesiaBaseIE(InfoExtractor):
_HEADERS = {
'x-session-id': _SESSION_ID,
- 'x-client': 'browser'
+ 'x-client': 'browser',
}
_AGE_RATINGS_MAPPER = {
'ADULTS': 18,
- 'teens': 13
+ 'teens': 13,
}
def _real_initialize(self):
@@ -447,7 +446,7 @@ class ViuOTTIndonesiaIE(ViuOTTIndonesiaBaseIE):
'thumbnail': 'https://vuclipi-a.akamaihd.net/p/cloudinary/h_171,w_304,dpr_1.5,f_auto,c_thumb,q_auto:low/1165863189/d-1',
'upload_date': '20210101',
'timestamp': 1609459200,
- }
+ },
}, {
'url': 'https://www.viu.com/ott/id/id/all/video-korean-reality-tv_shows-entertainment_weekly_episode_1622-1118617054',
'info_dict': {
@@ -461,8 +460,8 @@ class ViuOTTIndonesiaIE(ViuOTTIndonesiaBaseIE):
'thumbnail': 'https://vuclipi-a.akamaihd.net/p/cloudinary/h_171,w_304,dpr_1.5,f_auto,c_thumb,q_auto:low/1120187848/d-1',
'timestamp': 1420070400,
'upload_date': '20150101',
- 'cast': ['Shin Hyun-joon', 'Lee Da-Hee']
- }
+ 'cast': ['Shin Hyun-joon', 'Lee Da-Hee'],
+ },
}, {
# age-limit test
'url': 'https://www.viu.com/ott/id/id/all/video-japanese-trailer-tv_shows-trailer_jujutsu_kaisen_ver_01-1166044219?containerId=playlist-26273140',
@@ -477,7 +476,7 @@ class ViuOTTIndonesiaIE(ViuOTTIndonesiaBaseIE):
'description': 'Trailer \'Jujutsu Kaisen\' Ver.01',
'cast': ['Junya Enoki', ' Yûichi Nakamura', ' Yuma Uchida', 'Asami Seto'],
'age_limit': 13,
- }
+ },
}, {
# json ld metadata type equal to Movie instead of TVEpisodes
'url': 'https://www.viu.com/ott/id/id/all/video-japanese-animation-movies-demon_slayer_kimetsu_no_yaiba_the_movie_mugen_train-1165892707?containerId=1675060691786',
@@ -492,7 +491,7 @@ class ViuOTTIndonesiaIE(ViuOTTIndonesiaBaseIE):
'thumbnail': 'https://vuclipi-a.akamaihd.net/p/cloudinary/h_171,w_304,dpr_1.5,f_auto,c_thumb,q_auto:low/1165895279/d-1',
'description': 'md5:1ce9c35a3aeab384085533f746c87469',
'duration': 7021,
- }
+ },
}]
def _real_extract(self, url):
@@ -538,5 +537,5 @@ class ViuOTTIndonesiaIE(ViuOTTIndonesiaBaseIE):
'episode_number': (traverse_obj(initial_state, 'episode_no', 'episodeno', expected_type=int_or_none)
or int_or_none(episode.get('episodeNumber'))),
'cast': traverse_obj(episode, ('actor', ..., 'name'), default=None),
- 'age_limit': self._AGE_RATINGS_MAPPER.get(initial_state.get('internal_age_rating'))
+ 'age_limit': self._AGE_RATINGS_MAPPER.get(initial_state.get('internal_age_rating')),
}
diff --git a/yt_dlp/extractor/vk.py b/yt_dlp/extractor/vk.py
index 9a3c75b62..6ccc701a2 100644
--- a/yt_dlp/extractor/vk.py
+++ b/yt_dlp/extractor/vk.py
@@ -140,7 +140,7 @@ class VKIE(VKBaseIE):
'comment_count': int,
'like_count': int,
'thumbnail': r're:https?://.+(?:\.jpg|getVideoPreview.*)$',
- }
+ },
},
{
'note': 'Embedded video',
@@ -220,7 +220,7 @@ class VKIE(VKBaseIE):
'like_count': int,
'view_count': int,
'thumbnail': r're:https?://.+x1080$',
- 'tags': list
+ 'tags': list,
},
},
{
@@ -335,7 +335,7 @@ class VKIE(VKBaseIE):
mv_data = opts.get('mvData') or {}
player = opts.get('player') or {}
else:
- video_id = '%s_%s' % (mobj.group('oid'), mobj.group('id'))
+ video_id = '{}_{}'.format(mobj.group('oid'), mobj.group('id'))
info_page = self._download_webpage(
'http://vk.com/video_ext.php?' + mobj.group('embed_query'), video_id)
@@ -530,7 +530,7 @@ class VKUserVideosIE(VKBaseIE):
'url': 'https://vk.com/video/playlist/-174476437_2',
'info_dict': {
'id': '-174476437_playlist_2',
- 'title': 'Анонсы'
+ 'title': 'Анонсы',
},
'playlist_mincount': 108,
}]
@@ -580,7 +580,7 @@ class VKUserVideosIE(VKBaseIE):
section = 'all'
playlist_title = clean_html(get_element_by_class('VideoInfoPanel__title', webpage))
- return self.playlist_result(self._entries(page_id, section), '%s_%s' % (page_id, section), playlist_title)
+ return self.playlist_result(self._entries(page_id, section), f'{page_id}_{section}', playlist_title)
class VKWallPostIE(VKBaseIE):
diff --git a/yt_dlp/extractor/vodplatform.py b/yt_dlp/extractor/vodplatform.py
index 5ff05004b..703854bc8 100644
--- a/yt_dlp/extractor/vodplatform.py
+++ b/yt_dlp/extractor/vodplatform.py
@@ -13,7 +13,7 @@ class VODPlatformIE(InfoExtractor):
'id': 'RufMcytHDolTH1MuKHY9Fw',
'ext': 'mp4',
'title': 'LBCi News_ النصرة في ضيافة الـ "سي.أن.أن"',
- }
+ },
}, {
'url': 'http://embed.kwikmotion.com/embed/RufMcytHDolTH1MuKHY9Fw',
'only_matching': True,
diff --git a/yt_dlp/extractor/voicy.py b/yt_dlp/extractor/voicy.py
index 9ab97688a..f83c3f941 100644
--- a/yt_dlp/extractor/voicy.py
+++ b/yt_dlp/extractor/voicy.py
@@ -1,7 +1,6 @@
import itertools
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
smuggle_url,
@@ -14,14 +13,14 @@ from ..utils import (
class VoicyBaseIE(InfoExtractor):
def _extract_from_playlist_data(self, value):
- voice_id = compat_str(value.get('PlaylistId'))
+ voice_id = str(value.get('PlaylistId'))
upload_date = unified_strdate(value.get('Published'), False)
items = [self._extract_single_article(voice_data) for voice_data in value['VoiceData']]
return {
'_type': 'multi_video',
'entries': items,
'id': voice_id,
- 'title': compat_str(value.get('PlaylistName')),
+ 'title': str(value.get('PlaylistName')),
'uploader': value.get('SpeakerName'),
'uploader_id': str_or_none(value.get('SpeakerId')),
'channel': value.get('ChannelName'),
@@ -45,7 +44,7 @@ class VoicyBaseIE(InfoExtractor):
'vcodec': 'none',
}]
return {
- 'id': compat_str(entry.get('ArticleId')),
+ 'id': str(entry.get('ArticleId')),
'title': entry.get('ArticleTitle'),
'description': entry.get('MediaName'),
'formats': formats,
@@ -54,7 +53,7 @@ class VoicyBaseIE(InfoExtractor):
def _call_api(self, url, video_id, **kwargs):
response = self._download_json(url, video_id, **kwargs)
if response.get('Status') != 0:
- message = traverse_obj(response, ('Value', 'Error', 'Message'), expected_type=compat_str)
+ message = traverse_obj(response, ('Value', 'Error', 'Message'), expected_type=str)
if not message:
message = 'There was a error in the response: %d' % response.get('Status')
raise ExtractorError(message, expected=False)
@@ -111,7 +110,7 @@ class VoicyChannelIE(VoicyBaseIE):
def _entries(self, channel_id):
pager = ''
for count in itertools.count(1):
- article_list = self._call_api(self.PROGRAM_LIST_API_URL % (channel_id, pager), channel_id, note='Paging #%d' % count)
+ article_list = self._call_api(self.PROGRAM_LIST_API_URL % (channel_id, pager), channel_id, note=f'Paging #{count}')
playlist_data = article_list.get('PlaylistData')
if not playlist_data:
break
@@ -124,12 +123,12 @@ class VoicyChannelIE(VoicyBaseIE):
articles = self._entries(channel_id)
first_article = next(articles, None)
- title = traverse_obj(first_article, ('ChannelName', ), expected_type=compat_str)
- speaker_name = traverse_obj(first_article, ('SpeakerName', ), expected_type=compat_str)
+ title = traverse_obj(first_article, ('ChannelName', ), expected_type=str)
+ speaker_name = traverse_obj(first_article, ('SpeakerName', ), expected_type=str)
if not title and speaker_name:
- title = 'Uploads from %s' % speaker_name
+ title = f'Uploads from {speaker_name}'
if not title:
- title = 'Uploads from channel ID %s' % channel_id
+ title = f'Uploads from channel ID {channel_id}'
articles = itertools.chain([first_article], articles) if first_article else articles
diff --git a/yt_dlp/extractor/volejtv.py b/yt_dlp/extractor/volejtv.py
index 622d841f1..42ef9b128 100644
--- a/yt_dlp/extractor/volejtv.py
+++ b/yt_dlp/extractor/volejtv.py
@@ -11,7 +11,7 @@ class VolejTVIE(InfoExtractor):
'description': 'Zápas VK Královo Pole vs VK Prostějov 10.12.2022 v 19:00 na Volej.TV',
'thumbnail': 'https://volej.tv/images/og/16/17186/og.png',
'title': 'VK Královo Pole vs VK Prostějov',
- }
+ },
}, {
'url': 'https://volej.tv/video/725605/',
'info_dict': {
@@ -20,7 +20,7 @@ class VolejTVIE(InfoExtractor):
'thumbnail': 'https://volej.tv/images/og/15/17185/og.png',
'title': 'VK Lvi Praha vs VK Euro Sitex Příbram',
'description': 'Zápas VK Lvi Praha vs VK Euro Sitex Příbram 11.12.2022 v 19:00 na Volej.TV',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/voxmedia.py b/yt_dlp/extractor/voxmedia.py
index f36908754..e9b0047a4 100644
--- a/yt_dlp/extractor/voxmedia.py
+++ b/yt_dlp/extractor/voxmedia.py
@@ -1,6 +1,7 @@
+import urllib.parse
+
from .common import InfoExtractor
from .once import OnceIE
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
ExtractorError,
int_or_none,
@@ -52,7 +53,7 @@ class VoxMediaVolumeIE(OnceIE):
return info
for provider_video_type in ('youtube', 'brightcove'):
- provider_video_id = video_data.get('%s_id' % provider_video_type)
+ provider_video_id = video_data.get(f'{provider_video_type}_id')
if not provider_video_id:
continue
if provider_video_type == 'brightcove':
@@ -60,7 +61,7 @@ class VoxMediaVolumeIE(OnceIE):
else:
info.update({
'_type': 'url_transparent',
- 'url': provider_video_id if provider_video_type == 'youtube' else '%s:%s' % (provider_video_type, provider_video_id),
+ 'url': provider_video_id if provider_video_type == 'youtube' else f'{provider_video_type}:{provider_video_id}',
'ie_key': provider_video_type.capitalize(),
})
return info
@@ -172,7 +173,7 @@ class VoxMediaIE(InfoExtractor):
def _real_extract(self, url):
display_id = self._match_id(url)
- webpage = compat_urllib_parse_unquote(self._download_webpage(url, display_id))
+ webpage = urllib.parse.unquote(self._download_webpage(url, display_id))
def create_entry(provider_video_id, provider_video_type, title=None, description=None):
video_url = {
diff --git a/yt_dlp/extractor/vrt.py b/yt_dlp/extractor/vrt.py
index 3d26549a4..33ff57475 100644
--- a/yt_dlp/extractor/vrt.py
+++ b/yt_dlp/extractor/vrt.py
@@ -38,12 +38,12 @@ class VRTBaseIE(GigyaBaseIE):
'device': 'undefined (undefined)',
'os': {
'name': 'Windows',
- 'version': 'x86_64'
+ 'version': 'x86_64',
},
'player': {
'name': 'VRT web player',
- 'version': '2.7.4-prod-2023-04-19T06:05:45'
- }
+ 'version': '2.7.4-prod-2023-04-19T06:05:45',
+ },
}
# From https://player.vrt.be/vrtnws/js/main.js & https://player.vrt.be/ketnet/js/main.8cdb11341bcb79e4cd44.js
_JWT_KEY_ID = '0-0Fp51UZykfaiCJrfTE3+oMI8zvDteYfPtR+2n1R+z8w='
@@ -98,8 +98,8 @@ class VRTBaseIE(GigyaBaseIE):
}, data=json.dumps({
'identityToken': id_token or {},
'playerInfo': jwt_encode_hs256(player_info, self._JWT_SIGNING_KEY, headers={
- 'kid': self._JWT_KEY_ID
- }).decode()
+ 'kid': self._JWT_KEY_ID,
+ }).decode(),
}, separators=(',', ':')).encode())['vrtPlayerToken']
return self._download_json(
@@ -365,7 +365,7 @@ class KetnetIE(VRTBaseIE):
subtitleVideodetail
titleVideodetail
}
-}''' % display_id,
+}''' % display_id, # noqa: UP031
})['data']['video']
video_id = urllib.parse.unquote(video['mediaReference'])
@@ -437,9 +437,9 @@ class Radio1BeIE(VRTBaseIE):
'title': 'Komt N-VA volgend jaar op in Wallonië?',
'display_id': 'de-ochtend/komt-n-va-volgend-jaar-op-in-wallonie',
'description': 'md5:b374ea1c9302f38362df9dea1931468e',
- 'thumbnail': r're:https?://cds\.vrt\.radio/[^/#\?&]+'
+ 'thumbnail': r're:https?://cds\.vrt\.radio/[^/#\?&]+',
},
- 'playlist_mincount': 1
+ 'playlist_mincount': 1,
}, {
'url': 'https://radio1.be/lees/europese-unie-wil-onmiddellijke-humanitaire-pauze-en-duurzaam-staakt-het-vuren-in-gaza?view=web',
'info_dict': {
@@ -447,9 +447,9 @@ class Radio1BeIE(VRTBaseIE):
'title': 'Europese Unie wil "onmiddellijke humanitaire pauze" en "duurzaam staakt-het-vuren" in Gaza',
'description': 'md5:1aad1fae7d39edeffde5d3e67d276b64',
'thumbnail': r're:https?://cds\.vrt\.radio/[^/#\?&]+',
- 'display_id': 'europese-unie-wil-onmiddellijke-humanitaire-pauze-en-duurzaam-staakt-het-vuren-in-gaza'
+ 'display_id': 'europese-unie-wil-onmiddellijke-humanitaire-pauze-en-duurzaam-staakt-het-vuren-in-gaza',
},
- 'playlist_mincount': 1
+ 'playlist_mincount': 1,
}]
def _extract_video_entries(self, next_js_data, display_id):
@@ -466,7 +466,7 @@ class Radio1BeIE(VRTBaseIE):
'subtitles': subtitles,
**traverse_obj(data, {
'title': ('title', {str}),
- 'description': ('body', {clean_html})
+ 'description': ('body', {clean_html}),
}),
}
diff --git a/yt_dlp/extractor/vtm.py b/yt_dlp/extractor/vtm.py
index 6db49c5b6..41b41ec17 100644
--- a/yt_dlp/extractor/vtm.py
+++ b/yt_dlp/extractor/vtm.py
@@ -21,7 +21,7 @@ class VTMIE(InfoExtractor):
'duration': 74,
# TODO: fix url _type result processing
# 'series': 'Op Interventie',
- }
+ },
}
def _real_extract(self, url):
@@ -42,7 +42,7 @@ class VTMIE(InfoExtractor):
title
}
}
-}''' % uuid,
+}''' % uuid, # noqa: UP031
}, headers={
'x-api-key': 'da2-lz2cab4tfnah3mve6wiye4n77e',
})['data']['getComponent']
diff --git a/yt_dlp/extractor/vuclip.py b/yt_dlp/extractor/vuclip.py
index 0e562983d..ad7eab351 100644
--- a/yt_dlp/extractor/vuclip.py
+++ b/yt_dlp/extractor/vuclip.py
@@ -1,9 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_urllib_parse_urlparse,
-)
from ..utils import (
ExtractorError,
parse_duration,
@@ -21,7 +19,7 @@ class VuClipIE(InfoExtractor):
'ext': '3gp',
'title': 'Top 10 TV Convicts',
'duration': 733,
- }
+ },
}
def _real_extract(self, url):
@@ -31,7 +29,7 @@ class VuClipIE(InfoExtractor):
ad_m = re.search(
r'''value="No.*?" onClick="location.href='([^"']+)'"''', webpage)
if ad_m:
- urlr = compat_urllib_parse_urlparse(url)
+ urlr = urllib.parse.urlparse(url)
adfree_url = urlr.scheme + '://' + urlr.netloc + ad_m.group(1)
webpage = self._download_webpage(
adfree_url, video_id, note='Download post-ad page')
@@ -41,7 +39,7 @@ class VuClipIE(InfoExtractor):
default=None)
if error_msg:
raise ExtractorError(
- '%s said: %s' % (self.IE_NAME, error_msg), expected=True)
+ f'{self.IE_NAME} said: {error_msg}', expected=True)
# These clowns alternate between two page types
video_url = self._search_regex(
diff --git a/yt_dlp/extractor/vvvvid.py b/yt_dlp/extractor/vvvvid.py
index b96112360..4671bcd1f 100644
--- a/yt_dlp/extractor/vvvvid.py
+++ b/yt_dlp/extractor/vvvvid.py
@@ -12,7 +12,7 @@ from ..utils import (
class VVVVIDIE(InfoExtractor):
_VALID_URL_BASE = r'https?://(?:www\.)?vvvvid\.it/(?:#!)?(?:show|anime|film|series)/'
- _VALID_URL = r'%s(?P<show_id>\d+)/[^/]+/(?P<season_id>\d+)/(?P<id>[0-9]+)' % _VALID_URL_BASE
+ _VALID_URL = rf'{_VALID_URL_BASE}(?P<show_id>\d+)/[^/]+/(?P<season_id>\d+)/(?P<id>[0-9]+)'
_TESTS = [{
# video_type == 'video/vvvvid'
'url': 'https://www.vvvvid.it/show/498/the-power-of-computing/518/505692/playstation-vr-cambiera-il-nostro-modo-di-giocare',
@@ -109,7 +109,7 @@ class VVVVIDIE(InfoExtractor):
},
}, {
'url': 'https://www.vvvvid.it/show/434/perche-dovrei-guardarlo-di-dario-moccia/437/489048',
- 'only_matching': True
+ 'only_matching': True,
}]
_conn_id = None
@@ -132,12 +132,12 @@ class VVVVIDIE(InfoExtractor):
if query:
q.update(query)
response = self._download_json(
- 'https://www.vvvvid.it/vvvvid/ondemand/%s/%s' % (show_id, path),
+ f'https://www.vvvvid.it/vvvvid/ondemand/{show_id}/{path}',
video_id, headers=self._headers, query=q, fatal=fatal)
if not (response or fatal):
return
if response.get('result') == 'error':
- raise ExtractorError('%s said: %s' % (
+ raise ExtractorError('{} said: {}'.format(
self.IE_NAME, response['message']), expected=True)
return response['data']
@@ -151,18 +151,18 @@ class VVVVIDIE(InfoExtractor):
show_id, season_id, video_id = self._match_valid_url(url).groups()
response = self._download_info(
- show_id, 'season/%s' % season_id,
+ show_id, f'season/{season_id}',
video_id, query={'video_id': video_id})
vid = int(video_id)
- video_data = list(filter(
- lambda episode: episode.get('video_id') == vid, response))[0]
+ video_data = next(filter(
+ lambda episode: episode.get('video_id') == vid, response))
title = video_data['title']
formats = []
# vvvvid embed_info decryption algorithm is reverse engineered from function $ds(h) at vvvvid.js
def ds(h):
- g = "MNOPIJKL89+/4567UVWXQRSTEFGHABCDcdefYZabstuvopqr0123wxyzklmnghij"
+ g = 'MNOPIJKL89+/4567UVWXQRSTEFGHABCDcdefYZabstuvopqr0123wxyzklmnghij'
def f(m):
l = []
@@ -260,7 +260,7 @@ class VVVVIDIE(InfoExtractor):
embed_code, video_id, 'mp4', m3u8_id='hls', fatal=False))
else:
formats.extend(self._extract_wowza_formats(
- 'http://sb.top-ix.org/videomg/_definst_/mp4:%s/playlist.m3u8' % embed_code, video_id, skip_protocols=['f4m']))
+ f'http://sb.top-ix.org/videomg/_definst_/mp4:{embed_code}/playlist.m3u8', video_id, skip_protocols=['f4m']))
metadata_from_url(embed_code)
if not is_youtube:
@@ -283,7 +283,7 @@ class VVVVIDIE(InfoExtractor):
class VVVVIDShowIE(VVVVIDIE): # XXX: Do not subclass from concrete IE
- _VALID_URL = r'(?P<base_url>%s(?P<id>\d+)(?:/(?P<show_title>[^/?&#]+))?)/?(?:[?#&]|$)' % VVVVIDIE._VALID_URL_BASE
+ _VALID_URL = rf'(?P<base_url>{VVVVIDIE._VALID_URL_BASE}(?P<id>\d+)(?:/(?P<show_title>[^/?&#]+))?)/?(?:[?#&]|$)'
_TESTS = [{
'url': 'https://www.vvvvid.it/show/156/psyco-pass',
'info_dict': {
@@ -307,7 +307,7 @@ class VVVVIDShowIE(VVVVIDIE): # XXX: Do not subclass from concrete IE
show_id, 'info/', show_title, fatal=False)
if not show_title:
- base_url += "/title"
+ base_url += '/title'
entries = []
for season in (seasons or []):
diff --git a/yt_dlp/extractor/walla.py b/yt_dlp/extractor/walla.py
index 3ac0f8387..442a9bcf9 100644
--- a/yt_dlp/extractor/walla.py
+++ b/yt_dlp/extractor/walla.py
@@ -23,7 +23,7 @@ class WallaIE(InfoExtractor):
'params': {
# rtmp download
'skip_download': True,
- }
+ },
}
_SUBTITLE_LANGS = {
@@ -36,7 +36,7 @@ class WallaIE(InfoExtractor):
display_id = mobj.group('display_id')
video = self._download_xml(
- 'http://video2.walla.co.il/?w=null/null/%s/@@/video/flv_pl' % video_id,
+ f'http://video2.walla.co.il/?w=null/null/{video_id}/@@/video/flv_pl',
display_id)
item = video.find('./items/item')
diff --git a/yt_dlp/extractor/washingtonpost.py b/yt_dlp/extractor/washingtonpost.py
index 1cfed2da5..36e1f30e5 100644
--- a/yt_dlp/extractor/washingtonpost.py
+++ b/yt_dlp/extractor/washingtonpost.py
@@ -82,7 +82,7 @@ class WashingtonPostArticleIE(InfoExtractor):
'upload_date': '20141230',
'timestamp': 1419972442,
'title': 'Why black boxes don’t transmit data in real time',
- }
+ },
}],
'skip': 'Doesnt have a video anymore',
}, {
@@ -92,7 +92,7 @@ class WashingtonPostArticleIE(InfoExtractor):
@classmethod
def suitable(cls, url):
- return False if WashingtonPostIE.suitable(url) else super(WashingtonPostArticleIE, cls).suitable(url)
+ return False if WashingtonPostIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
page_id = self._match_id(url)
@@ -112,7 +112,7 @@ class WashingtonPostArticleIE(InfoExtractor):
if content_element.get('type') == 'video':
uuids.append(content_element.get('_id'))
- entries = [self.url_result('washingtonpost:%s' % uuid, 'WashingtonPost', uuid) for uuid in uuids]
+ entries = [self.url_result(f'washingtonpost:{uuid}', 'WashingtonPost', uuid) for uuid in uuids]
return {
'_type': 'playlist',
diff --git a/yt_dlp/extractor/wat.py b/yt_dlp/extractor/wat.py
index 9ea3fddd6..03bac66ac 100644
--- a/yt_dlp/extractor/wat.py
+++ b/yt_dlp/extractor/wat.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -52,13 +51,13 @@ class WatIE(InfoExtractor):
'ext': 'mp4',
},
'params': {'skip_download': 'm3u8'},
- }
+ },
]
_GEO_BYPASS = False
def _real_extract(self, url):
video_id = self._match_id(url)
- video_id = video_id if video_id.isdigit() and len(video_id) > 6 else compat_str(int(video_id, 36))
+ video_id = video_id if video_id.isdigit() and len(video_id) > 6 else str(int(video_id, 36))
# 'contentv4' is used in the website, but it also returns the related
# videos, we don't need them
diff --git a/yt_dlp/extractor/wdr.py b/yt_dlp/extractor/wdr.py
index 0b7ddd239..1c1f0ed05 100644
--- a/yt_dlp/extractor/wdr.py
+++ b/yt_dlp/extractor/wdr.py
@@ -1,10 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import (
- compat_str,
- compat_urlparse,
-)
from ..utils import (
ExtractorError,
determine_ext,
@@ -39,7 +36,7 @@ class WDRIE(InfoExtractor):
def _asset_url(self, wdr_id):
id_len = max(len(wdr_id), 5)
- return ''.join(('https:', self.__API_URL_TPL % (wdr_id[:id_len - 4], wdr_id, ), '.js'))
+ return ''.join(('https:', self.__API_URL_TPL % (wdr_id[:id_len - 4], wdr_id), '.js'))
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -94,7 +91,7 @@ class WDRIE(InfoExtractor):
medium_url, 'stream', fatal=False))
else:
a_format = {
- 'url': medium_url
+ 'url': medium_url,
}
if ext == 'unknown_video':
urlh = self._request_webpage(
@@ -168,7 +165,7 @@ class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE
'upload_date': '20160312',
'description': 'md5:e127d320bc2b1f149be697ce044a3dd7',
'is_live': False,
- 'subtitles': {}
+ 'subtitles': {},
},
'skip': 'HTTP Error 404: Not Found',
},
@@ -202,7 +199,7 @@ class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE
'upload_date': 're:^[0-9]{8}$',
'title': 're:^Die Sendung (?:mit der Maus )?vom [0-9.]{10}$',
},
- 'skip': 'The id changes from week to week because of the new episode'
+ 'skip': 'The id changes from week to week because of the new episode',
},
{
'url': 'http://www.wdrmaus.de/filme/sachgeschichten/achterbahn.php5',
@@ -228,7 +225,7 @@ class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE
},
'params': {
'skip_download': True, # m3u8 download
- }
+ },
},
{
'url': 'http://www.sportschau.de/handballem2018/handball-nationalmannschaft-em-stolperstein-vorrunde-100.html',
@@ -260,7 +257,7 @@ class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE
'alt_title': 'Rockpalast',
'upload_date': '20220725',
},
- }
+ },
]
def _real_extract(self, url):
@@ -289,14 +286,14 @@ class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE
if not media_link_obj:
continue
jsonp_url = try_get(
- media_link_obj, lambda x: x['mediaObj']['url'], compat_str)
+ media_link_obj, lambda x: x['mediaObj']['url'], str)
if jsonp_url:
# metadata, or player JS with ['ref'] giving WDR id, or just media, perhaps
clip_id = media_link_obj['mediaObj'].get('ref')
if jsonp_url.endswith('.assetjsonp'):
asset = self._download_json(
jsonp_url, display_id, fatal=False, transform_source=strip_jsonp)
- clip_id = try_get(asset, lambda x: x['trackerData']['trackerClipId'], compat_str)
+ clip_id = try_get(asset, lambda x: x['trackerData']['trackerClipId'], str)
if clip_id:
jsonp_url = self._asset_url(clip_id[4:])
entries.append(self.url_result(jsonp_url, ie=WDRIE.ie_key()))
@@ -305,7 +302,7 @@ class WDRPageIE(WDRIE): # XXX: Do not subclass from concrete IE
if not entries:
entries = [
self.url_result(
- compat_urlparse.urljoin(url, mobj.group('href')),
+ urllib.parse.urljoin(url, mobj.group('href')),
ie=WDRPageIE.ie_key())
for mobj in re.finditer(
r'<a[^>]+\bhref=(["\'])(?P<href>(?:(?!\1).)+)\1[^>]+\bdata-extension(?:-ard)?=',
@@ -324,7 +321,7 @@ class WDRElefantIE(InfoExtractor):
'title': 'Wippe',
'id': 'mdb-1198320',
'ext': 'mp4',
- 'upload_date': '20071003'
+ 'upload_date': '20071003',
},
}
@@ -348,7 +345,7 @@ class WDRElefantIE(InfoExtractor):
zmdb_url_element = xml_metadata.find('./movie/zmdb_url')
if zmdb_url_element is None:
raise ExtractorError(
- '%s is not a video' % display_id, expected=True)
+ f'{display_id} is not a video', expected=True)
return self.url_result(zmdb_url_element.text, ie=WDRIE.ie_key())
@@ -368,7 +365,7 @@ class WDRMobileIE(InfoExtractor):
'ext': 'mp4',
'age_limit': 0,
},
- 'skip': 'Problems with loading data.'
+ 'skip': 'Problems with loading data.',
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/webcamerapl.py b/yt_dlp/extractor/webcamerapl.py
index a02d9519c..e0ee17c23 100644
--- a/yt_dlp/extractor/webcamerapl.py
+++ b/yt_dlp/extractor/webcamerapl.py
@@ -12,7 +12,7 @@ class WebcameraplIE(InfoExtractor):
'ext': 'mp4',
'title': r're:WIDOK NA PLAC ZAMKOWY W WARSZAWIE \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
'live_status': 'is_live',
- }
+ },
}, {
'url': 'https://gdansk-stare-miasto.webcamera.pl/',
'info_dict': {
@@ -20,7 +20,7 @@ class WebcameraplIE(InfoExtractor):
'ext': 'mp4',
'title': r're:GDAŃSK - widok na Stare Miasto \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
'live_status': 'is_live',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/webcaster.py b/yt_dlp/extractor/webcaster.py
index 43eeca017..b0865e3e6 100644
--- a/yt_dlp/extractor/webcaster.py
+++ b/yt_dlp/extractor/webcaster.py
@@ -35,7 +35,7 @@ class WebcasterIE(InfoExtractor):
formats = []
for format_id in (None, 'noise'):
track_tag = join_nonempty('track', format_id, delim='_')
- for track in video.findall('.//iphone/%s' % track_tag):
+ for track in video.findall(f'.//iphone/{track_tag}'):
track_url = track.text
if not track_url:
continue
diff --git a/yt_dlp/extractor/webofstories.py b/yt_dlp/extractor/webofstories.py
index 65f48f3b1..24befe7cd 100644
--- a/yt_dlp/extractor/webofstories.py
+++ b/yt_dlp/extractor/webofstories.py
@@ -22,7 +22,7 @@ class WebOfStoriesIE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'description': 'Hans Bethe talks about calculating the temperature of the sun',
'duration': 238,
- }
+ },
}, {
'url': 'http://www.webofstories.com/play/55908',
'md5': '2985a698e1fe3211022422c4b5ed962c',
@@ -79,19 +79,15 @@ class WebOfStoriesIE(InfoExtractor):
ms_prefix = 'mini_sites/'
if is_great_life_series:
- mp4_url = '{0:}lives/{1:}/{2:}.mp4'.format(
- self._VIDEO_DOMAIN, speaker_id, story_filename)
+ mp4_url = f'{self._VIDEO_DOMAIN}lives/{speaker_id}/{story_filename}.mp4'
rtmp_ext = 'flv'
streamer = self._GREAT_LIFE_STREAMER
- play_path = 'stories/{0:}/{1:}'.format(
- speaker_id, story_filename)
+ play_path = f'stories/{speaker_id}/{story_filename}'
else:
- mp4_url = '{0:}{1:}{2:}/{3:}.mp4'.format(
- self._VIDEO_DOMAIN, ms_prefix, speaker_id, story_filename)
+ mp4_url = f'{self._VIDEO_DOMAIN}{ms_prefix}{speaker_id}/{story_filename}.mp4'
rtmp_ext = 'mp4'
streamer = self._USER_STREAMER
- play_path = 'mp4:{0:}{1:}/{2}.mp4'.format(
- ms_prefix, speaker_id, story_filename)
+ play_path = f'mp4:{ms_prefix}{speaker_id}/{story_filename}.mp4'
formats = [{
'format_id': 'mp4_sd',
@@ -132,7 +128,7 @@ class WebOfStoriesPlaylistIE(InfoExtractor):
entries = [
self.url_result(
- 'http://www.webofstories.com/play/%s' % video_id,
+ f'http://www.webofstories.com/play/{video_id}',
'WebOfStories', video_id=video_id)
for video_id in orderedSet(re.findall(r'\bid=["\']td_(\d+)', webpage))
]
@@ -145,7 +141,7 @@ class WebOfStoriesPlaylistIE(InfoExtractor):
r'<span id="primaryField">([^<]+)</span>',
webpage, 'field', default=None)
if field:
- title += ' (%s)' % field
+ title += f' ({field})'
if not title:
title = self._search_regex(
diff --git a/yt_dlp/extractor/weibo.py b/yt_dlp/extractor/weibo.py
index b6a659385..3e8290982 100644
--- a/yt_dlp/extractor/weibo.py
+++ b/yt_dlp/extractor/weibo.py
@@ -34,7 +34,7 @@ class WeiboBaseIE(InfoExtractor):
'browser': f'Chrome{chrome_ver},0,0,0',
'fonts': 'undefined',
'screenInfo': '1920*1080*24',
- 'plugins': ''
+ 'plugins': '',
}, separators=(',', ':'))}))['data']
self._download_webpage(
@@ -90,7 +90,7 @@ class WeiboBaseIE(InfoExtractor):
'video_details', lambda _, v: v['label'].startswith(format_id), {
'size': ('size', {int_or_none}),
'tbr': ('bitrate', {int_or_none}),
- }
+ },
), get_all=False),
})
return formats
@@ -162,7 +162,7 @@ class WeiboIE(WeiboBaseIE):
'view_count': int,
'like_count': int,
'repost_count': int,
- }
+ },
}, {
'url': 'https://weibo.com/0/4224132150961381',
'note': 'no playback_list example',
@@ -185,7 +185,7 @@ class WeiboVideoIE(WeiboBaseIE):
'ext': 'mp4',
'display_id': 'LEZDodaiW',
'title': '呃,稍微了解了一下靡烟miya,感觉这东西也太二了',
- 'description': '呃,稍微了解了一下靡烟miya,感觉这东西也太二了 http://t.cn/A6aerGsM ​​​',
+ 'description': '呃,稍微了解了一下靡烟miya,感觉这东西也太二了 http://t.cn/A6aerGsM \u200b\u200b\u200b',
'duration': 76,
'timestamp': 1659344278,
'upload_date': '20220801',
@@ -196,7 +196,7 @@ class WeiboVideoIE(WeiboBaseIE):
'view_count': int,
'like_count': int,
'repost_count': int,
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/wevidi.py b/yt_dlp/extractor/wevidi.py
index 3b6d03238..0db52af43 100644
--- a/yt_dlp/extractor/wevidi.py
+++ b/yt_dlp/extractor/wevidi.py
@@ -15,7 +15,7 @@ class WeVidiIE(InfoExtractor):
'description': 'md5:73a27d0a87d49fbcc5584566326ebeed',
'uploader': 'eclecRC',
'duration': 932.098,
- }
+ },
}, {
'url': 'https://wevidi.net/watch/ievRuuQHbPS',
'md5': 'ce8a94989a959bff9003fa27ee572935',
@@ -27,7 +27,7 @@ class WeVidiIE(InfoExtractor):
'description': 'md5:32cdfca272687390d9bd9b0c9c6153ee',
'uploader': 'WeVidi',
'duration': 36.1999,
- }
+ },
}, {
'url': 'https://wevidi.net/watch/PcMzDWaQSWb',
'md5': '55ee0d3434be5d9e5cc76b83f2bb57ec',
@@ -39,7 +39,7 @@ class WeVidiIE(InfoExtractor):
'description': 'md5:e2c9e2b54b8bb424cc64937c8fdc068f',
'uploader': 'WeVidi',
'duration': 41.972,
- }
+ },
}, {
'url': 'https://wevidi.net/watch/wJnRqDHNe_u',
'md5': 'c8f263dd47e66cc17546b3abf47b5a77',
@@ -51,7 +51,7 @@ class WeVidiIE(InfoExtractor):
'description': 'md5:e65036f0d4af80e0af191bd11af5195e',
'uploader': 'GissyEva',
'duration': 630.451,
- }
+ },
}, {
'url': 'https://wevidi.net/watch/4m1c4yJR_yc',
'md5': 'c63ce5ca6990dce86855fc02ca5bc1ed',
@@ -63,7 +63,7 @@ class WeVidiIE(InfoExtractor):
'description': 'md5:96af99dd63468b2dfab3020560e3e9b2',
'uploader': 'eclecRC',
'duration': 6.804,
- }
+ },
}]
def _extract_formats(self, wvplayer_props):
@@ -74,7 +74,7 @@ class WeVidiIE(InfoExtractor):
3: 360,
4: 480,
5: 720,
- 6: 1080
+ 6: 1080,
}
src_path = f'{wvplayer_props["srcVID"]}/{wvplayer_props["srcUID"]}/{wvplayer_props["srcNAME"]}'
diff --git a/yt_dlp/extractor/whowatch.py b/yt_dlp/extractor/whowatch.py
index 492891d78..9ca5c3b1a 100644
--- a/yt_dlp/extractor/whowatch.py
+++ b/yt_dlp/extractor/whowatch.py
@@ -1,5 +1,4 @@
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -21,8 +20,8 @@ class WhoWatchIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
self._download_webpage(url, video_id)
- metadata = self._download_json('https://api.whowatch.tv/lives/%s' % video_id, video_id)
- live_data = self._download_json('https://api.whowatch.tv/lives/%s/play' % video_id, video_id)
+ metadata = self._download_json(f'https://api.whowatch.tv/lives/{video_id}', video_id)
+ live_data = self._download_json(f'https://api.whowatch.tv/lives/{video_id}/play', video_id)
title = try_call(
lambda: live_data['share_info']['live_title'][1:-1],
@@ -37,7 +36,7 @@ class WhoWatchIE(InfoExtractor):
formats = []
for i, fmt in enumerate(live_data.get('streams') or []):
- name = fmt.get('quality') or fmt.get('name') or compat_str(i)
+ name = fmt.get('quality') or fmt.get('name') or str(i)
hls_url = fmt.get('hls_url')
rtmp_url = fmt.get('rtmp_url')
audio_only = fmt.get('audio_only')
@@ -45,7 +44,7 @@ class WhoWatchIE(InfoExtractor):
if hls_url:
hls_fmts = self._extract_m3u8_formats(
- hls_url, video_id, ext='mp4', m3u8_id='hls-%s' % name, quality=quality)
+ hls_url, video_id, ext='mp4', m3u8_id=f'hls-{name}', quality=quality)
formats.extend(hls_fmts)
else:
hls_fmts = []
@@ -54,7 +53,7 @@ class WhoWatchIE(InfoExtractor):
if rtmp_url and not audio_only:
formats.append({
'url': rtmp_url,
- 'format_id': 'rtmp-%s' % name,
+ 'format_id': f'rtmp-{name}',
'ext': 'mp4',
'protocol': 'rtmp_ffmpeg', # ffmpeg can, while rtmpdump can't
'vcodec': 'h264',
@@ -71,12 +70,12 @@ class WhoWatchIE(InfoExtractor):
hls_url, video_id, ext='mp4', m3u8_id='hls'))
self._remove_duplicate_formats(formats)
- uploader_url = try_get(metadata, lambda x: x['live']['user']['user_path'], compat_str)
+ uploader_url = try_get(metadata, lambda x: x['live']['user']['user_path'], str)
if uploader_url:
- uploader_url = 'https://whowatch.tv/profile/%s' % uploader_url
- uploader_id = compat_str(try_get(metadata, lambda x: x['live']['user']['id'], int))
- uploader = try_get(metadata, lambda x: x['live']['user']['name'], compat_str)
- thumbnail = try_get(metadata, lambda x: x['live']['latest_thumbnail_url'], compat_str)
+ uploader_url = f'https://whowatch.tv/profile/{uploader_url}'
+ uploader_id = str(try_get(metadata, lambda x: x['live']['user']['id'], int))
+ uploader = try_get(metadata, lambda x: x['live']['user']['name'], str)
+ thumbnail = try_get(metadata, lambda x: x['live']['latest_thumbnail_url'], str)
timestamp = int_or_none(try_get(metadata, lambda x: x['live']['started_at'], int), scale=1000)
view_count = try_get(metadata, lambda x: x['live']['total_view_count'], int)
comment_count = try_get(metadata, lambda x: x['live']['comment_count'], int)
diff --git a/yt_dlp/extractor/wikimedia.py b/yt_dlp/extractor/wikimedia.py
index 11c801f0c..632693068 100644
--- a/yt_dlp/extractor/wikimedia.py
+++ b/yt_dlp/extractor/wikimedia.py
@@ -24,8 +24,8 @@ class WikimediaIE(InfoExtractor):
'description': 'md5:7cd84f76e7081f1be033d0b155b4a460',
'license': 'Creative Commons Attribution 4.0 International',
'uploader': 'ZDF/Terra X/Gruppe 5/Luise Wagner, Jonas Sichert, Andreas Hougardy',
- 'subtitles': 'count:4'
- }
+ 'subtitles': 'count:4',
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/wimtv.py b/yt_dlp/extractor/wimtv.py
index d7d77c0db..000d1437f 100644
--- a/yt_dlp/extractor/wimtv.py
+++ b/yt_dlp/extractor/wimtv.py
@@ -10,14 +10,14 @@ from ..utils import (
class WimTVIE(InfoExtractor):
_player = None
_UUID_RE = r'[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}'
- _VALID_URL = r'''(?x:
+ _VALID_URL = rf'''(?x:
https?://platform\.wim\.tv/
(?:
(?:embed/)?\?
|\#/webtv/.+?/
)
(?P<type>vod|live|cast)[=/]
- (?P<id>%s).*?)''' % _UUID_RE
+ (?P<id>{_UUID_RE}).*?)'''
_EMBED_REGEX = [rf'<iframe[^>]+src=["\'](?P<url>{_VALID_URL})']
_TESTS = [{
# vod stream
@@ -28,7 +28,7 @@ class WimTVIE(InfoExtractor):
'ext': 'mp4',
'title': 'AMA SUPERCROSS 2020 - R2 ST. LOUIS',
'duration': 6481,
- 'thumbnail': r're:https?://.+?/thumbnail/.+?/720$'
+ 'thumbnail': r're:https?://.+?/thumbnail/.+?/720$',
},
'params': {
'skip_download': True,
@@ -66,7 +66,7 @@ class WimTVIE(InfoExtractor):
'vars': [{
'regex': r'appAuth = "(.+?)"',
'variable': 'app_auth',
- }]
+ }],
}, {
'url': 'https://platform.wim.tv/common/config/endpointconfig.js',
'vars': [{
@@ -75,7 +75,7 @@ class WimTVIE(InfoExtractor):
}, {
'regex': r'PRODUCTION_HOSTNAME_THUMB\s*\+\s*"(.+?)"',
'variable': 'thumb_server_path',
- }]
+ }],
}]
for data in datas:
@@ -83,13 +83,13 @@ class WimTVIE(InfoExtractor):
for var in data['vars']:
val = self._search_regex(var['regex'], temp, msg_id)
if not val:
- raise ExtractorError('%s not found' % var['variable'])
+ raise ExtractorError('{} not found'.format(var['variable']))
self._player[var['variable']] = val
def _generate_token(self):
json = self._download_json(
'https://platform.wim.tv/wimtv-server/oauth/token', 'Token generation',
- headers={'Authorization': 'Basic %s' % self._player['app_auth']},
+ headers={'Authorization': 'Basic {}'.format(self._player['app_auth'])},
data=urlencode_postdata({'grant_type': 'client_credentials'}))
token = json.get('access_token')
if not token:
@@ -101,7 +101,7 @@ class WimTVIE(InfoExtractor):
return None
if not self._player.get('thumb_server_path'):
self._player['thumb_server_path'] = ''
- return '%s%s/asset/thumbnail/%s/%s' % (
+ return '{}{}/asset/thumbnail/{}/{}'.format(
self._player['thumb_server'],
self._player['thumb_server_path'],
thumb_id, width)
@@ -118,11 +118,11 @@ class WimTVIE(InfoExtractor):
is_live = False
token = self._generate_token()
json = self._download_json(
- 'https://platform.wim.tv/wimtv-server/api/public/%s/%s/play' % (
- stream_type, video_id), video_id,
- headers={'Authorization': 'Bearer %s' % token,
- 'Content-Type': 'application/json'},
- data=bytes('{}', 'utf-8'))
+ f'https://platform.wim.tv/wimtv-server/api/public/{stream_type}/{video_id}/play',
+ video_id, headers={
+ 'Authorization': f'Bearer {token}',
+ 'Content-Type': 'application/json',
+ }, data=b'{}')
formats = []
for src in json.get('srcs') or []:
diff --git a/yt_dlp/extractor/wistia.py b/yt_dlp/extractor/wistia.py
index f2256fdc6..fb2a8648f 100644
--- a/yt_dlp/extractor/wistia.py
+++ b/yt_dlp/extractor/wistia.py
@@ -24,7 +24,7 @@ class WistiaBaseIE(InfoExtractor):
_EMBED_BASE_URL = 'http://fast.wistia.net/embed/'
def _download_embed_config(self, config_type, config_id, referer):
- base_url = self._EMBED_BASE_URL + '%s/%s' % (config_type, config_id)
+ base_url = self._EMBED_BASE_URL + f'{config_type}/{config_id}'
embed_config = self._download_json(
base_url + '.json', config_id, headers={
'Referer': referer if referer.startswith('http') else base_url, # Some videos require this.
@@ -74,7 +74,7 @@ class WistiaBaseIE(InfoExtractor):
display_name = a.get('display_name')
format_id = atype
if atype and atype.endswith('_video') and display_name:
- format_id = '%s-%s' % (atype[:-6], display_name)
+ format_id = f'{atype[:-6]}-{display_name}'
f = {
'format_id': format_id,
'url': aurl,
@@ -157,7 +157,7 @@ class WistiaBaseIE(InfoExtractor):
class WistiaIE(WistiaBaseIE):
- _VALID_URL = r'(?:wistia:|%s(?:iframe|medias)/)%s' % (WistiaBaseIE._VALID_URL_BASE, WistiaBaseIE._VALID_ID_REGEX)
+ _VALID_URL = rf'(?:wistia:|{WistiaBaseIE._VALID_URL_BASE}(?:iframe|medias)/){WistiaBaseIE._VALID_ID_REGEX}'
_EMBED_REGEX = [
r'''(?x)
<(?:meta[^>]+?content|(?:iframe|script)[^>]+?src)=["\']
@@ -189,7 +189,7 @@ class WistiaIE(WistiaBaseIE):
'duration': 966.0,
'timestamp': 1616614369,
'thumbnail': 'https://embed-ssl.wistia.com/deliveries/53dc60239348dc9b9fba3755173ea4c2.png',
- }
+ },
}, {
'url': 'wistia:5vd7p4bct5',
'md5': 'b9676d24bf30945d97060638fbfe77f0',
@@ -228,7 +228,7 @@ class WistiaIE(WistiaBaseIE):
'description': 'md5:27abc99a758573560be72600ef95cece',
'upload_date': '20210421',
'thumbnail': 'https://embed-ssl.wistia.com/deliveries/6c551820ae950cdee2306d6cbe9ef742.jpg',
- }
+ },
}, {
'url': 'https://study.com/academy/lesson/north-american-exploration-failed-colonies-of-spain-france-england.html#lesson',
'md5': 'b9676d24bf30945d97060638fbfe77f0',
@@ -254,19 +254,19 @@ class WistiaIE(WistiaBaseIE):
urls = list(super()._extract_embed_urls(url, webpage))
for match in cls._extract_wistia_async_embed(webpage):
if match.group('type') != 'wistia_channel':
- urls.append('wistia:%s' % match.group('id'))
+ urls.append('wistia:{}'.format(match.group('id')))
for match in re.finditer(r'(?:data-wistia-?id=["\']|Wistia\.embed\(["\']|id=["\']wistia_)(?P<id>[a-z0-9]{10})',
webpage):
- urls.append('wistia:%s' % match.group('id'))
+ urls.append('wistia:{}'.format(match.group('id')))
if not WistiaChannelIE._extract_embed_urls(url, webpage): # Fallback
media_id = cls._extract_url_media_id(url)
if media_id:
- urls.append('wistia:%s' % match.group('id'))
+ urls.append('wistia:{}'.format(match.group('id')))
return urls
class WistiaPlaylistIE(WistiaBaseIE):
- _VALID_URL = r'%splaylists/%s' % (WistiaBaseIE._VALID_URL_BASE, WistiaBaseIE._VALID_ID_REGEX)
+ _VALID_URL = rf'{WistiaBaseIE._VALID_URL_BASE}playlists/{WistiaBaseIE._VALID_ID_REGEX}'
_TEST = {
'url': 'https://fast.wistia.net/embed/playlists/aodt9etokc',
@@ -291,7 +291,7 @@ class WistiaPlaylistIE(WistiaBaseIE):
class WistiaChannelIE(WistiaBaseIE):
- _VALID_URL = r'(?:wistiachannel:|%schannel/)%s' % (WistiaBaseIE._VALID_URL_BASE, WistiaBaseIE._VALID_ID_REGEX)
+ _VALID_URL = rf'(?:wistiachannel:|{WistiaBaseIE._VALID_URL_BASE}channel/){WistiaBaseIE._VALID_ID_REGEX}'
_TESTS = [{
# JSON Embed API returns 403, should fall back to webpage
@@ -299,7 +299,7 @@ class WistiaChannelIE(WistiaBaseIE):
'info_dict': {
'id': 'yvyvu7wjbg',
'title': 'Copysmith Tutorials and Education!',
- 'description': 'Learn all things Copysmith via short and informative videos!'
+ 'description': 'Learn all things Copysmith via short and informative videos!',
},
'playlist_mincount': 7,
'expected_warnings': ['falling back to webpage'],
@@ -370,7 +370,7 @@ class WistiaChannelIE(WistiaBaseIE):
self.report_warning('Failed to download channel data from API, falling back to webpage.')
webpage = self._download_webpage(f'https://fast.wistia.net/embed/channel/{channel_id}', channel_id)
data = self._parse_json(
- self._search_regex(r'wchanneljsonp-%s\'\]\s*=[^\"]*\"([A-Za-z0-9=/]*)' % channel_id, webpage, 'jsonp', channel_id),
+ self._search_regex(rf'wchanneljsonp-{channel_id}\'\]\s*=[^\"]*\"([A-Za-z0-9=/]*)', webpage, 'jsonp', channel_id),
channel_id, transform_source=lambda x: urllib.parse.unquote_plus(base64.b64decode(x).decode('utf-8')))
# XXX: can there be more than one series?
diff --git a/yt_dlp/extractor/wordpress.py b/yt_dlp/extractor/wordpress.py
index 378d99dbc..a0a319405 100644
--- a/yt_dlp/extractor/wordpress.py
+++ b/yt_dlp/extractor/wordpress.py
@@ -44,10 +44,10 @@ class WordpressPlaylistEmbedIE(InfoExtractor):
'duration': 49.0,
'artist': 'Nancy and Randall Faber',
'description': 'md5:a9f8e9aeabbd2912bc13cc0fab1a4ce8',
- }
+ },
}],
'playlist_count': 6,
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}]
def _extract_from_webpage(self, url, webpage):
@@ -94,11 +94,11 @@ class WordpressMiniAudioPlayerEmbedIE(InfoExtractor):
'id': 'over_the_horizon_2013',
'ext': 'mp3',
'title': 'Over the Horizon 2013',
- 'url': 'http://news.samsung.com/global/wp-content/uploads/ringtones/over_the_horizon_2013.mp3'
- }
+ 'url': 'http://news.samsung.com/global/wp-content/uploads/ringtones/over_the_horizon_2013.mp3',
+ },
}],
'playlist_count': 6,
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
# Version 1.9.3: https://plugins.trac.wordpress.org/browser/wp-miniaudioplayer/tags/1.9.3
'url': 'https://www.booksontape.com/collections/audiobooks-with-teacher-guides/',
@@ -108,7 +108,7 @@ class WordpressMiniAudioPlayerEmbedIE(InfoExtractor):
'age_limit': 0,
'thumbnail': 'https://www.booksontape.com/wp-content/uploads/2016/09/bot-logo-1200x630.jpg',
},
- 'playlist_mincount': 12
+ 'playlist_mincount': 12,
}, {
# Version 1.9.7: https://plugins.trac.wordpress.org/browser/wp-miniaudioplayer/tags/1.9.7
# But has spaces around href filter
@@ -122,7 +122,7 @@ class WordpressMiniAudioPlayerEmbedIE(InfoExtractor):
'thumbnail': 'https://www.estudiords.com.br/wp-content/uploads/2021/03/LOGO-TEMAS.png',
'description': 'md5:ab24d6a7ed0312ad2d466e721679f5a0',
},
- 'playlist_mincount': 30
+ 'playlist_mincount': 30,
}]
def _extract_from_webpage(self, url, webpage):
diff --git a/yt_dlp/extractor/worldstarhiphop.py b/yt_dlp/extractor/worldstarhiphop.py
index c6948a1eb..3af4bd498 100644
--- a/yt_dlp/extractor/worldstarhiphop.py
+++ b/yt_dlp/extractor/worldstarhiphop.py
@@ -9,8 +9,8 @@ class WorldStarHipHopIE(InfoExtractor):
'info_dict': {
'id': 'wshh6a7q1ny0G34ZwuIO',
'ext': 'mp4',
- 'title': 'KO Of The Week: MMA Fighter Gets Knocked Out By Swift Head Kick!'
- }
+ 'title': 'KO Of The Week: MMA Fighter Gets Knocked Out By Swift Head Kick!',
+ },
}, {
'url': 'http://m.worldstarhiphop.com/android/video.php?v=wshh6a7q1ny0G34ZwuIO',
'only_matching': True,
diff --git a/yt_dlp/extractor/wppilot.py b/yt_dlp/extractor/wppilot.py
index 0ef4e8e53..b4cc1abc5 100644
--- a/yt_dlp/extractor/wppilot.py
+++ b/yt_dlp/extractor/wppilot.py
@@ -103,7 +103,7 @@ class WPPilotIE(WPPilotBaseIE):
is_authorized = next((c for c in self.cookiejar if c.name == 'netviapisessid'), None)
# cookies starting with "g:" are assigned to guests
- is_authorized = True if is_authorized is not None and not is_authorized.value.startswith('g:') else False
+ is_authorized = is_authorized is not None and not is_authorized.value.startswith('g:')
video = self._download_json(
(self._VIDEO_URL if is_authorized else self._VIDEO_GUEST_URL) % video_id,
@@ -120,7 +120,7 @@ class WPPilotIE(WPPilotBaseIE):
data=json.dumps({
'channelId': video_id,
't': stream_token,
- }).encode('utf-8'))
+ }).encode())
if try_get(close, lambda x: x['data']['status']) == 'ok':
return self.url_result(url, ie=WPPilotIE.ie_key())
diff --git a/yt_dlp/extractor/wsj.py b/yt_dlp/extractor/wsj.py
index 35fe30362..7b3f6aa2a 100644
--- a/yt_dlp/extractor/wsj.py
+++ b/yt_dlp/extractor/wsj.py
@@ -76,7 +76,7 @@ class WSJIE(InfoExtractor):
tbr = int_or_none(v.get('bitrate'))
formats.append({
'url': mp4_url,
- 'format_id': 'http' + ('-%d' % tbr if tbr else ''),
+ 'format_id': 'http' + (f'-{tbr}' if tbr else ''),
'tbr': tbr,
'width': int_or_none(v.get('width')),
'height': int_or_none(v.get('height')),
@@ -108,7 +108,7 @@ class WSJArticleIE(InfoExtractor):
'upload_date': '20170221',
'uploader_id': 'ralcaraz',
'title': 'Bao Bao the Panda Leaves for China',
- }
+ },
}
def _real_extract(self, url):
@@ -117,4 +117,4 @@ class WSJArticleIE(InfoExtractor):
video_id = self._search_regex(
r'(?:id=["\']video|video-|iframe\.html\?guid=|data-src=["\'])([a-fA-F0-9-]{36})',
webpage, 'video id')
- return self.url_result('wsj:%s' % video_id, WSJIE.ie_key(), video_id)
+ return self.url_result(f'wsj:{video_id}', WSJIE.ie_key(), video_id)
diff --git a/yt_dlp/extractor/wwe.py b/yt_dlp/extractor/wwe.py
index 9bbd477c3..3b8197a0c 100644
--- a/yt_dlp/extractor/wwe.py
+++ b/yt_dlp/extractor/wwe.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
try_get,
unescapeHTML,
@@ -17,7 +16,7 @@ class WWEBaseIE(InfoExtractor):
}
def _extract_entry(self, data, url, video_id=None):
- video_id = compat_str(video_id or data['nid'])
+ video_id = str(video_id or data['nid'])
title = data['title']
formats = self._extract_m3u8_formats(
@@ -69,7 +68,7 @@ class WWEIE(WWEBaseIE):
'title': 'Daniel Bryan vs. Andrade "Cien" Almas: SmackDown LIVE, Sept. 4, 2018',
'description': 'md5:2d7424dbc6755c61a0e649d2a8677f67',
'thumbnail': r're:^https?://.*\.jpg$',
- }
+ },
}, {
'url': 'https://de.wwe.com/videos/gran-metalik-vs-tony-nese-wwe-205-live-sept-4-2018',
'only_matching': True,
@@ -111,7 +110,7 @@ class WWEPlaylistIE(WWEBaseIE):
@classmethod
def suitable(cls, url):
- return False if WWEIE.suitable(url) else super(WWEPlaylistIE, cls).suitable(url)
+ return False if WWEIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
display_id = self._match_id(url)
diff --git a/yt_dlp/extractor/wykop.py b/yt_dlp/extractor/wykop.py
index 1d29cc89b..2ae0a2a5e 100644
--- a/yt_dlp/extractor/wykop.py
+++ b/yt_dlp/extractor/wykop.py
@@ -209,7 +209,7 @@ class WykopPostIE(WykopBaseExtractor):
'playlist_mincount': 15,
'params': {
'flat_playlist': True,
- }
+ },
}]
@classmethod
diff --git a/yt_dlp/extractor/xanimu.py b/yt_dlp/extractor/xanimu.py
index e0b7bf968..b48935877 100644
--- a/yt_dlp/extractor/xanimu.py
+++ b/yt_dlp/extractor/xanimu.py
@@ -16,11 +16,11 @@ class XanimuIE(InfoExtractor):
'thumbnail': 'https://xanimu.com/storage/2020/09/the-princess-and-the-frog-hentai.jpg',
'description': r're:^Enjoy The Princess \+ The Frog Hentai',
'duration': 207.0,
- 'age_limit': 18
- }
+ 'age_limit': 18,
+ },
}, {
'url': 'https://xanimu.com/huge-expansion/',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -28,14 +28,15 @@ class XanimuIE(InfoExtractor):
webpage = self._download_webpage(url, video_id)
formats = []
- for format in ['videoHigh', 'videoLow']:
- format_url = self._search_json(r'var\s+%s\s*=' % re.escape(format), webpage, format,
- video_id, default=None, contains_pattern=r'[\'"]([^\'"]+)[\'"]')
+ for format_id in ['videoHigh', 'videoLow']:
+ format_url = self._search_json(
+ rf'var\s+{re.escape(format_id)}\s*=', webpage, format_id,
+ video_id, default=None, contains_pattern=r'[\'"]([^\'"]+)[\'"]')
if format_url:
formats.append({
'url': format_url,
- 'format_id': format,
- 'quality': -2 if format.endswith('Low') else None,
+ 'format_id': format_id,
+ 'quality': -2 if format_id.endswith('Low') else None,
})
return {
@@ -47,5 +48,5 @@ class XanimuIE(InfoExtractor):
'description': self._html_search_meta('description', webpage, default=None),
'duration': int_or_none(self._search_regex(r'duration:\s*[\'"]([^\'"]+?)[\'"]',
webpage, 'duration', fatal=False)),
- 'age_limit': 18
+ 'age_limit': 18,
}
diff --git a/yt_dlp/extractor/xboxclips.py b/yt_dlp/extractor/xboxclips.py
index 235b567d9..d726e62af 100644
--- a/yt_dlp/extractor/xboxclips.py
+++ b/yt_dlp/extractor/xboxclips.py
@@ -21,7 +21,7 @@ class XboxClipsIE(InfoExtractor):
'filesize_approx': 26800000,
'upload_date': '20140807',
'duration': 56,
- }
+ },
}, {
'url': 'https://gameclips.io/iAbdulElah/074a69a9-5faf-46aa-b93b-9909c1720325',
'only_matching': True,
@@ -32,7 +32,7 @@ class XboxClipsIE(InfoExtractor):
if '/video.php' in url:
qs = parse_qs(url)
- url = 'https://gameclips.io/%s/%s' % (qs['gamertag'][0], qs['vid'][0])
+ url = 'https://gameclips.io/{}/{}'.format(qs['gamertag'][0], qs['vid'][0])
webpage = self._download_webpage(url, video_id)
info = self._parse_html5_media_entries(url, webpage, video_id)[0]
diff --git a/yt_dlp/extractor/xhamster.py b/yt_dlp/extractor/xhamster.py
index 0b3a620ec..c965c3042 100644
--- a/yt_dlp/extractor/xhamster.py
+++ b/yt_dlp/extractor/xhamster.py
@@ -2,7 +2,6 @@ import itertools
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
clean_html,
@@ -22,14 +21,14 @@ from ..utils import (
class XHamsterIE(InfoExtractor):
_DOMAINS = r'(?:xhamster\.(?:com|one|desi)|xhms\.pro|xhamster\d+\.com|xhday\.com|xhvid\.com)'
- _VALID_URL = r'''(?x)
+ _VALID_URL = rf'''(?x)
https?://
- (?:[^/?#]+\.)?%s/
+ (?:[^/?#]+\.)?{_DOMAINS}/
(?:
movies/(?P<id>[\dA-Za-z]+)/(?P<display_id>[^/]*)\.html|
videos/(?P<display_id_2>[^/]*)-(?P<id_2>[\dA-Za-z]+)
)
- ''' % _DOMAINS
+ '''
_TESTS = [{
'url': 'https://xhamster.com/videos/femaleagent-shy-beauty-takes-the-bait-1509445',
'md5': '34e1ab926db5dc2750fed9e1f34304bb',
@@ -177,7 +176,7 @@ class XHamsterIE(InfoExtractor):
continue
format_urls.add(format_url)
formats.append({
- 'format_id': '%s-%s' % (format_id, quality),
+ 'format_id': f'{format_id}-{quality}',
'url': format_url,
'ext': determine_ext(format_url, 'mp4'),
'height': get_height(quality),
@@ -228,7 +227,7 @@ class XHamsterIE(InfoExtractor):
or str_or_none(standard_format.get('label'))
or '')
formats.append({
- 'format_id': '%s-%s' % (format_id, quality),
+ 'format_id': f'{format_id}-{quality}',
'url': standard_url,
'ext': ext,
'height': get_height(quality),
@@ -245,7 +244,7 @@ class XHamsterIE(InfoExtractor):
if not isinstance(c, dict):
continue
c_name = c.get('name')
- if isinstance(c_name, compat_str):
+ if isinstance(c_name, str):
categories.append(c_name)
else:
categories = None
@@ -258,7 +257,7 @@ class XHamsterIE(InfoExtractor):
'description': video.get('description'),
'timestamp': int_or_none(video.get('created')),
'uploader': try_get(
- video, lambda x: x['author']['name'], compat_str),
+ video, lambda x: x['author']['name'], str),
'uploader_url': uploader_url,
'uploader_id': uploader_url.split('/')[-1] if uploader_url else None,
'thumbnail': video.get('thumbURL'),
@@ -372,7 +371,7 @@ class XHamsterIE(InfoExtractor):
class XHamsterEmbedIE(InfoExtractor):
- _VALID_URL = r'https?://(?:[^/?#]+\.)?%s/xembed\.php\?video=(?P<id>\d+)' % XHamsterIE._DOMAINS
+ _VALID_URL = rf'https?://(?:[^/?#]+\.)?{XHamsterIE._DOMAINS}/xembed\.php\?video=(?P<id>\d+)'
_EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?xhamster\.com/xembed\.php\?video=\d+)\1']
_TEST = {
'url': 'http://xhamster.com/xembed.php?video=3328539',
@@ -385,7 +384,7 @@ class XHamsterEmbedIE(InfoExtractor):
'uploader': 'ManyakisArt',
'duration': 5,
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
@@ -394,14 +393,14 @@ class XHamsterEmbedIE(InfoExtractor):
webpage = self._download_webpage(url, video_id)
video_url = self._search_regex(
- r'href="(https?://xhamster\.com/(?:movies/{0}/[^"]*\.html|videos/[^/]*-{0})[^"]*)"'.format(video_id),
+ rf'href="(https?://xhamster\.com/(?:movies/{video_id}/[^"]*\.html|videos/[^/]*-{video_id})[^"]*)"',
webpage, 'xhamster url', default=None)
if not video_url:
- vars = self._parse_json(
+ player_vars = self._parse_json(
self._search_regex(r'vars\s*:\s*({.+?})\s*,\s*\n', webpage, 'vars'),
video_id)
- video_url = dict_get(vars, ('downloadLink', 'homepageLink', 'commentsLink', 'shareUrl'))
+ video_url = dict_get(player_vars, ('downloadLink', 'homepageLink', 'commentsLink', 'shareUrl'))
return self.url_result(video_url, 'XHamster')
@@ -441,7 +440,7 @@ class XHamsterUserIE(InfoExtractor):
next_page_url = f'https://xhamster.com/{prefix}/{user_id}/{suffix}/1'
for pagenum in itertools.count(1):
page = self._download_webpage(
- next_page_url, user_id, 'Downloading page %s' % pagenum)
+ next_page_url, user_id, f'Downloading page {pagenum}')
for video_tag in re.findall(
r'(<a[^>]+class=["\'].*?\bvideo-thumb__image-container[^>]+>)',
page):
diff --git a/yt_dlp/extractor/xiaohongshu.py b/yt_dlp/extractor/xiaohongshu.py
index faad9d923..00c6ed7c5 100644
--- a/yt_dlp/extractor/xiaohongshu.py
+++ b/yt_dlp/extractor/xiaohongshu.py
@@ -25,7 +25,7 @@ class XiaoHongShuIE(InfoExtractor):
'tags': ['今日快乐今日发', '吃货薯看这里', '香妃蛋糕', '小五卷蛋糕', '新手蛋糕卷'],
'duration': 101.726,
'thumbnail': r're:https?://sns-webpic-qc\.xhscdn\.com/\d+/[a-z0-9]+/[\w]+',
- }
+ },
}]
def _real_extract(self, url):
@@ -51,7 +51,7 @@ class XiaoHongShuIE(InfoExtractor):
'tbr': ('avgBitrate', {int_or_none}),
'format': ('qualityType', {str}),
'filesize': ('size', {int_or_none}),
- 'duration': ('duration', {functools.partial(float_or_none, scale=1000)})
+ 'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
})
formats.extend(traverse_obj(info, (('mediaUrl', ('backupUrls', ...)), {
diff --git a/yt_dlp/extractor/ximalaya.py b/yt_dlp/extractor/ximalaya.py
index c98c8a4fc..e900a4ad9 100644
--- a/yt_dlp/extractor/ximalaya.py
+++ b/yt_dlp/extractor/ximalaya.py
@@ -22,7 +22,7 @@ class XimalayaIE(XimalayaBaseIE):
'uploader_id': '61425525',
'uploader_url': 'http://www.ximalaya.com/zhubo/61425525/',
'title': '261.唐诗三百首.卷八.送孟浩然之广陵.李白',
- 'description': "contains:《送孟浩然之广陵》\n作者:李白\n故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,惟见长江天际流。",
+ 'description': 'contains:《送孟浩然之广陵》\n作者:李白\n故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,惟见长江天际流。',
'thumbnail': r're:^https?://.*\.jpg',
'thumbnails': [
{
@@ -33,14 +33,14 @@ class XimalayaIE(XimalayaBaseIE):
'name': 'cover_url_142',
'url': r're:^https?://.*\.jpg',
'width': 180,
- 'height': 180
- }
+ 'height': 180,
+ },
],
'categories': ['其他'],
'duration': 93,
'view_count': int,
'like_count': int,
- }
+ },
},
{
'url': 'http://m.ximalaya.com/61425525/sound/47740352/',
@@ -51,7 +51,7 @@ class XimalayaIE(XimalayaBaseIE):
'uploader_id': '61425525',
'uploader_url': 'http://www.ximalaya.com/zhubo/61425525/',
'title': '261.唐诗三百首.卷八.送孟浩然之广陵.李白',
- 'description': "contains:《送孟浩然之广陵》\n作者:李白\n故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,惟见长江天际流。",
+ 'description': 'contains:《送孟浩然之广陵》\n作者:李白\n故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,惟见长江天际流。',
'thumbnail': r're:^https?://.*\.jpg',
'thumbnails': [
{
@@ -62,35 +62,35 @@ class XimalayaIE(XimalayaBaseIE):
'name': 'cover_url_142',
'url': r're:^https?://.*\.jpg',
'width': 180,
- 'height': 180
- }
+ 'height': 180,
+ },
],
'categories': ['人文'],
'duration': 93,
'view_count': int,
'like_count': int,
- }
- }
+ },
+ },
]
def _real_extract(self, url):
scheme = 'https' if url.startswith('https') else 'http'
audio_id = self._match_id(url)
- audio_info_file = '%s://m.ximalaya.com/tracks/%s.json' % (scheme, audio_id)
- audio_info = self._download_json(audio_info_file, audio_id,
- 'Downloading info json %s' % audio_info_file,
- 'Unable to download info file')
+ audio_info_file = f'{scheme}://m.ximalaya.com/tracks/{audio_id}.json'
+ audio_info = self._download_json(
+ audio_info_file, audio_id,
+ f'Downloading info json {audio_info_file}', 'Unable to download info file')
formats = [{
'format_id': f'{bps}k',
'url': audio_info[k],
'abr': bps,
- 'vcodec': 'none'
+ 'vcodec': 'none',
} for bps, k in ((24, 'play_path_32'), (64, 'play_path_64')) if audio_info.get(k)]
thumbnails = []
- for k in audio_info.keys():
+ for k in audio_info:
# cover pics kyes like: cover_url', 'cover_url_142'
if k.startswith('cover_url'):
thumbnail = {'name': k, 'url': audio_info[k]}
diff --git a/yt_dlp/extractor/xinpianchang.py b/yt_dlp/extractor/xinpianchang.py
index bd67e8b29..10849916b 100644
--- a/yt_dlp/extractor/xinpianchang.py
+++ b/yt_dlp/extractor/xinpianchang.py
@@ -25,7 +25,7 @@ class XinpianchangIE(InfoExtractor):
'uploader': '正时文创',
'uploader_id': '10357277',
'categories': ['宣传片', '国家城市', '广告', '其他'],
- 'tags': ['北京冬奥会', '冰墩墩', '再见', '告别', '冰墩墩哭了', '感动', '闭幕式', '熄火']
+ 'tags': ['北京冬奥会', '冰墩墩', '再见', '告别', '冰墩墩哭了', '感动', '闭幕式', '熄火'],
},
}, {
'url': 'https://www.xinpianchang.com/a11762904',
@@ -39,7 +39,7 @@ class XinpianchangIE(InfoExtractor):
'uploader': '精品动画',
'uploader_id': '10858927',
'categories': ['动画', '三维CG'],
- 'tags': ['France Télévisions', '法国3台', '蠢萌', '冬奥会']
+ 'tags': ['France Télévisions', '法国3台', '蠢萌', '冬奥会'],
},
}, {
'url': 'https://www.xinpianchang.com/a11779743?from=IndexPick&part=%E7%BC%96%E8%BE%91%E7%B2%BE%E9%80%89&index=2',
diff --git a/yt_dlp/extractor/xminus.py b/yt_dlp/extractor/xminus.py
index 37e31045c..af9cf40ab 100644
--- a/yt_dlp/extractor/xminus.py
+++ b/yt_dlp/extractor/xminus.py
@@ -26,7 +26,7 @@ class XMinusIE(InfoExtractor):
'filesize_approx': 5900000,
'view_count': int,
'description': 'md5:03238c5b663810bc79cf42ef3c03e371',
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/xnxx.py b/yt_dlp/extractor/xnxx.py
index 74d4f0419..a1b7e75ed 100644
--- a/yt_dlp/extractor/xnxx.py
+++ b/yt_dlp/extractor/xnxx.py
@@ -41,7 +41,7 @@ class XNXXIE(InfoExtractor):
def get(meta, default=NO_DEFAULT, fatal=True):
return self._search_regex(
- r'set%s\s*\(\s*(["\'])(?P<value>(?:(?!\1).)+)\1' % meta,
+ rf'set{meta}\s*\(\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
webpage, meta, default=default, fatal=fatal, group='value')
title = self._og_search_title(
diff --git a/yt_dlp/extractor/xstream.py b/yt_dlp/extractor/xstream.py
index 322e86570..f7b48322c 100644
--- a/yt_dlp/extractor/xstream.py
+++ b/yt_dlp/extractor/xstream.py
@@ -41,8 +41,7 @@ class XstreamIE(InfoExtractor):
def _extract_video_info(self, partner_id, video_id):
data = self._download_xml(
- 'http://frontend.xstream.dk/%s/feed/video/?platform=web&id=%s'
- % (partner_id, video_id),
+ f'http://frontend.xstream.dk/{partner_id}/feed/video/?platform=web&id={video_id}',
video_id)
NS_MAP = {
@@ -71,7 +70,7 @@ class XstreamIE(InfoExtractor):
if mobj:
formats.append({
'url': mobj.group('url'),
- 'play_path': 'mp4:%s' % mobj.group('playpath'),
+ 'play_path': 'mp4:{}'.format(mobj.group('playpath')),
'app': mobj.group('app'),
'ext': 'flv',
'tbr': tbr,
diff --git a/yt_dlp/extractor/xvideos.py b/yt_dlp/extractor/xvideos.py
index 6b16ac291..e7d43ba9d 100644
--- a/yt_dlp/extractor/xvideos.py
+++ b/yt_dlp/extractor/xvideos.py
@@ -1,7 +1,7 @@
import re
+import urllib.parse
from .common import InfoExtractor
-from ..compat import compat_urllib_parse_unquote
from ..utils import (
ExtractorError,
clean_html,
@@ -32,7 +32,7 @@ class XVideosIE(InfoExtractor):
'duration': 1238,
'age_limit': 18,
'thumbnail': r're:^https://cdn\d+-pic.xvideos-cdn.com/.+\.jpg',
- }
+ },
}, {
# Broken HLS formats
'url': 'https://www.xvideos.com/video65982001/what_s_her_name',
@@ -44,7 +44,7 @@ class XVideosIE(InfoExtractor):
'duration': 120,
'age_limit': 18,
'thumbnail': r're:^https://cdn\d+-pic.xvideos-cdn.com/.+\.jpg',
- }
+ },
}, {
'url': 'https://flashservice.xvideos.com/embedframe/4588838',
'only_matching': True,
@@ -56,40 +56,40 @@ class XVideosIE(InfoExtractor):
'only_matching': True,
}, {
'url': 'http://xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://xvideos.es/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.xvideos.es/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://xvideos.es/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://www.xvideos.es/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://fr.xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://fr.xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://it.xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://it.xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'http://de.xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://de.xvideos.com/video4588838/biker_takes_his_girl',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://flashservice.xvideos.com/embedframe/ucuvbkfda4e',
'only_matching': True,
@@ -101,7 +101,7 @@ class XVideosIE(InfoExtractor):
'only_matching': True,
}, {
'url': 'https://xvideos.es/video.ucuvbkfda4e/a_beautiful_red-haired_stranger_was_refused_but_still_came_to_my_room_for_sex',
- 'only_matching': True
+ 'only_matching': True,
}]
def _real_extract(self, url):
@@ -110,7 +110,7 @@ class XVideosIE(InfoExtractor):
mobj = re.search(r'<h1 class="inlineError">(.+?)</h1>', webpage)
if mobj:
- raise ExtractorError('%s said: %s' % (self.IE_NAME, clean_html(mobj.group(1))), expected=True)
+ raise ExtractorError(f'{self.IE_NAME} said: {clean_html(mobj.group(1))}', expected=True)
title = self._html_search_regex(
(r'<title>(?P<title>.+?)\s+-\s+XVID',
@@ -121,7 +121,7 @@ class XVideosIE(InfoExtractor):
thumbnails = []
for preference, thumbnail in enumerate(('', '169')):
thumbnail_url = self._search_regex(
- r'setThumbUrl%s\(\s*(["\'])(?P<thumbnail>(?:(?!\1).)+)\1' % thumbnail,
+ rf'setThumbUrl{thumbnail}\(\s*(["\'])(?P<thumbnail>(?:(?!\1).)+)\1',
webpage, 'thumbnail', default=None, group='thumbnail')
if thumbnail_url:
thumbnails.append({
@@ -137,7 +137,7 @@ class XVideosIE(InfoExtractor):
formats = []
- video_url = compat_urllib_parse_unquote(self._search_regex(
+ video_url = urllib.parse.unquote(self._search_regex(
r'flv_url=(.+?)&', webpage, 'video URL', default=''))
if video_url:
formats.append({
@@ -157,7 +157,7 @@ class XVideosIE(InfoExtractor):
elif format_id in ('urllow', 'urlhigh'):
formats.append({
'url': format_url,
- 'format_id': '%s-%s' % (determine_ext(format_url, 'mp4'), format_id[3:]),
+ 'format_id': '{}-{}'.format(determine_ext(format_url, 'mp4'), format_id[3:]),
'quality': -2 if format_id.endswith('low') else None,
})
@@ -184,7 +184,7 @@ class XVideosQuickiesIE(InfoExtractor):
'age_limit': 18,
'duration': 81,
'thumbnail': r're:^https://cdn.*-pic.xvideos-cdn.com/.+\.jpg',
- }
+ },
}, {
'url': 'https://www.xvideos.com/profiles/lili_love#quickies/a/ipphaob6fd1',
'md5': '5340938aac6b46e19ebdd1d84535862e',
@@ -195,7 +195,7 @@ class XVideosQuickiesIE(InfoExtractor):
'age_limit': 18,
'duration': 56,
'thumbnail': r're:^https://cdn.*-pic.xvideos-cdn.com/.+\.jpg',
- }
+ },
}, {
'url': 'https://www.xvideos.com/amateur-channels/lili_love#quickies/a/hfmffmd7661',
'md5': '92428518bbabcb4c513e55922e022491',
@@ -206,7 +206,7 @@ class XVideosQuickiesIE(InfoExtractor):
'age_limit': 18,
'duration': 9,
'thumbnail': r're:^https://cdn.*-pic.xvideos-cdn.com/.+\.jpg',
- }
+ },
}, {
'url': 'https://www.xvideos.com/amateur-channels/wifeluna#quickies/a/47258683',
'md5': '16e322a93282667f1963915568f782c1',
@@ -217,7 +217,7 @@ class XVideosQuickiesIE(InfoExtractor):
'age_limit': 18,
'duration': 16,
'thumbnail': r're:^https://cdn.*-pic.xvideos-cdn.com/.+\.jpg',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/xxxymovies.py b/yt_dlp/extractor/xxxymovies.py
index aa6c84d09..003af1de9 100644
--- a/yt_dlp/extractor/xxxymovies.py
+++ b/yt_dlp/extractor/xxxymovies.py
@@ -21,7 +21,7 @@ class XXXYMoviesIE(InfoExtractor):
'like_count': int,
'dislike_count': int,
'age_limit': 18,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/yahoo.py b/yt_dlp/extractor/yahoo.py
index 24148a0bd..f0ba83038 100644
--- a/yt_dlp/extractor/yahoo.py
+++ b/yt_dlp/extractor/yahoo.py
@@ -70,7 +70,7 @@ class YahooIE(InfoExtractor):
'duration': 128,
'timestamp': 1385722202,
'upload_date': '20131129',
- }
+ },
}, {
'url': 'https://www.yahoo.com/movies/v/true-story-trailer-173000497.html',
'md5': '2a9752f74cb898af5d1083ea9f661b58',
@@ -177,7 +177,7 @@ class YahooIE(InfoExtractor):
def _extract_yahoo_video(self, video_id, country):
video = self._download_json(
- 'https://%s.yahoo.com/_td/api/resource/VideoService.videos;view=full;video_ids=["%s"]' % (country, video_id),
+ f'https://{country}.yahoo.com/_td/api/resource/VideoService.videos;view=full;video_ids=["{video_id}"]',
video_id, 'Downloading video JSON metadata')[0]
title = video['title']
@@ -193,7 +193,7 @@ class YahooIE(InfoExtractor):
for fmt in fmts:
media_obj = self._download_json(
'https://video-api.yql.yahoo.com/v1/video/sapi/streams/' + video_id,
- video_id, 'Downloading %s JSON metadata' % fmt,
+ video_id, f'Downloading {fmt} JSON metadata',
headers=self.geo_verification_headers(), query={
'format': fmt,
'region': country.upper(),
@@ -213,7 +213,7 @@ class YahooIE(InfoExtractor):
tbr = int_or_none(s.get('bitrate'))
formats.append({
'url': s_url,
- 'format_id': fmt + ('-%d' % tbr if tbr else ''),
+ 'format_id': fmt + (f'-{tbr}' if tbr else ''),
'width': int_or_none(s.get('width')),
'height': int_or_none(s.get('height')),
'tbr': tbr,
@@ -277,9 +277,9 @@ class YahooIE(InfoExtractor):
country = country.split('-')[0]
items = self._download_json(
- 'https://%s.yahoo.com/caas/content/article' % country, display_id,
+ f'https://{country}.yahoo.com/caas/content/article', display_id,
'Downloading content JSON metadata', query={
- 'url': url
+ 'url': url,
})['items'][0]
item = items['data']['partnerData']
@@ -327,7 +327,7 @@ class YahooSearchIE(SearchInfoExtractor):
def _search_results(self, query):
for pagenum in itertools.count(0):
- result_url = 'http://video.search.yahoo.com/search/?p=%s&fr=screen&o=js&gs=0&b=%d' % (urllib.parse.quote_plus(query), pagenum * 30)
+ result_url = f'http://video.search.yahoo.com/search/?p={urllib.parse.quote_plus(query)}&fr=screen&o=js&gs=0&b={pagenum * 30}'
info = self._download_json(result_url, query,
note='Downloading results page ' + str(pagenum + 1))
yield from (self.url_result(result['rurl']) for result in info['results'])
@@ -354,7 +354,7 @@ class YahooJapanNewsIE(InfoExtractor):
},
}, {
'url': 'https://news.yahoo.co.jp/feature/1356',
- 'only_matching': True
+ 'only_matching': True,
}]
def _extract_formats(self, json_data, content_id):
diff --git a/yt_dlp/extractor/yandexdisk.py b/yt_dlp/extractor/yandexdisk.py
index d5eecbd9c..56aa79292 100644
--- a/yt_dlp/extractor/yandexdisk.py
+++ b/yt_dlp/extractor/yandexdisk.py
@@ -102,7 +102,7 @@ class YandexDiskIE(InfoExtractor):
'format_id': 'source',
'ext': determine_ext(title, meta.get('ext') or mimetype2ext(meta.get('mime_type')) or 'mp4'),
'quality': 1,
- 'filesize': int_or_none(meta.get('size'))
+ 'filesize': int_or_none(meta.get('size')),
})
for video in (video_streams.get('videos') or []):
@@ -118,7 +118,7 @@ class YandexDiskIE(InfoExtractor):
height = int_or_none(size.get('height'))
format_id = 'hls'
if height:
- format_id += '-%dp' % height
+ format_id += f'-{height}p'
formats.append({
'ext': 'mp4',
'format_id': format_id,
diff --git a/yt_dlp/extractor/yandexmusic.py b/yt_dlp/extractor/yandexmusic.py
index acfe69bf4..12cc5ca28 100644
--- a/yt_dlp/extractor/yandexmusic.py
+++ b/yt_dlp/extractor/yandexmusic.py
@@ -2,7 +2,6 @@ import hashlib
import itertools
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
float_or_none,
@@ -35,19 +34,19 @@ class YandexMusicBaseIE(InfoExtractor):
expected=True)
def _download_webpage_handle(self, *args, **kwargs):
- webpage = super(YandexMusicBaseIE, self)._download_webpage_handle(*args, **kwargs)
+ webpage = super()._download_webpage_handle(*args, **kwargs)
if 'Нам очень жаль, но&nbsp;запросы, поступившие с&nbsp;вашего IP-адреса, похожи на&nbsp;автоматические.' in webpage:
self._raise_captcha()
return webpage
def _download_json(self, *args, **kwargs):
- response = super(YandexMusicBaseIE, self)._download_json(*args, **kwargs)
+ response = super()._download_json(*args, **kwargs)
self._handle_error(response)
return response
def _call_api(self, ep, tld, url, item_id, note, query):
return self._download_json(
- 'https://music.yandex.%s/handlers/%s.jsx' % (tld, ep),
+ f'https://music.yandex.{tld}/handlers/{ep}.jsx',
item_id, note,
fatal=False,
headers={
@@ -61,7 +60,7 @@ class YandexMusicBaseIE(InfoExtractor):
class YandexMusicTrackIE(YandexMusicBaseIE):
IE_NAME = 'yandexmusic:track'
IE_DESC = 'Яндекс.Музыка - Трек'
- _VALID_URL = r'%s/album/(?P<album_id>\d+)/track/(?P<id>\d+)' % YandexMusicBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{YandexMusicBaseIE._VALID_URL_BASE}/album/(?P<album_id>\d+)/track/(?P<id>\d+)'
_TESTS = [{
'url': 'http://music.yandex.ru/album/540508/track/4878838',
@@ -110,19 +109,19 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
track = self._call_api(
'track', tld, url, track_id, 'Downloading track JSON',
- {'track': '%s:%s' % (track_id, album_id)})['track']
+ {'track': f'{track_id}:{album_id}'})['track']
track_title = track['title']
download_data = self._download_json(
- 'https://music.yandex.ru/api/v2.1/handlers/track/%s:%s/web-album_track-track-track-main/download/m' % (track_id, album_id),
+ f'https://music.yandex.ru/api/v2.1/handlers/track/{track_id}:{album_id}/web-album_track-track-track-main/download/m',
track_id, 'Downloading track location url JSON', query={'hq': 1}, headers={'X-Retpath-Y': url})
fd_data = self._download_json(
download_data['src'], track_id,
'Downloading track location JSON',
query={'format': 'json'})
- key = hashlib.md5(('XGRlBW9FXlekgbPrRHuSiA' + fd_data['path'][1:] + fd_data['s']).encode('utf-8')).hexdigest()
- f_url = 'http://%s/get-mp3/%s/%s?track-id=%s ' % (fd_data['host'], key, fd_data['ts'] + fd_data['path'], track['id'])
+ key = hashlib.md5(('XGRlBW9FXlekgbPrRHuSiA' + fd_data['path'][1:] + fd_data['s']).encode()).hexdigest()
+ f_url = 'http://{}/get-mp3/{}/{}?track-id={} '.format(fd_data['host'], key, fd_data['ts'] + fd_data['path'], track['id'])
thumbnail = None
cover_uri = track.get('albums', [{}])[0].get('coverUri')
@@ -151,7 +150,7 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
for element in decomposed:
if isinstance(element, dict) and element.get('name'):
parts.append(element['name'])
- elif isinstance(element, compat_str):
+ elif isinstance(element, str):
parts.append(element)
return ''.join(parts)
@@ -183,7 +182,7 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
if track_artist:
track_info.update({
'artist': track_artist,
- 'title': '%s - %s' % (track_artist, track_title),
+ 'title': f'{track_artist} - {track_title}',
})
else:
track_info['title'] = track_title
@@ -194,14 +193,14 @@ class YandexMusicTrackIE(YandexMusicBaseIE):
class YandexMusicPlaylistBaseIE(YandexMusicBaseIE):
def _extract_tracks(self, source, item_id, url, tld):
tracks = source['tracks']
- track_ids = [compat_str(track_id) for track_id in source['trackIds']]
+ track_ids = [str(track_id) for track_id in source['trackIds']]
# tracks dictionary shipped with playlist.jsx API is limited to 150 tracks,
# missing tracks should be retrieved manually.
if len(tracks) < len(track_ids):
- present_track_ids = set([
- compat_str(track['id'])
- for track in tracks if track.get('id')])
+ present_track_ids = {
+ str(track['id'])
+ for track in tracks if track.get('id')}
missing_track_ids = [
track_id for track_id in track_ids
if track_id not in present_track_ids]
@@ -215,10 +214,10 @@ class YandexMusicPlaylistBaseIE(YandexMusicBaseIE):
assert missing_track_ids_req
missing_tracks = self._call_api(
'track-entries', tld, url, item_id,
- 'Downloading missing tracks JSON chunk %d' % (chunk_num + 1), {
+ f'Downloading missing tracks JSON chunk {chunk_num + 1}', {
'entries': ','.join(missing_track_ids_req),
'lang': tld,
- 'external-domain': 'music.yandex.%s' % tld,
+ 'external-domain': f'music.yandex.{tld}',
'overembed': 'false',
'strict': 'true',
})
@@ -245,7 +244,7 @@ class YandexMusicPlaylistBaseIE(YandexMusicBaseIE):
if not album_id:
continue
entries.append(self.url_result(
- 'http://music.yandex.ru/album/%s/track/%s' % (album_id, track_id),
+ f'http://music.yandex.ru/album/{album_id}/track/{track_id}',
ie=YandexMusicTrackIE.ie_key(), video_id=track_id))
return entries
@@ -253,7 +252,7 @@ class YandexMusicPlaylistBaseIE(YandexMusicBaseIE):
class YandexMusicAlbumIE(YandexMusicPlaylistBaseIE):
IE_NAME = 'yandexmusic:album'
IE_DESC = 'Яндекс.Музыка - Альбом'
- _VALID_URL = r'%s/album/(?P<id>\d+)' % YandexMusicBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{YandexMusicBaseIE._VALID_URL_BASE}/album/(?P<id>\d+)'
_TESTS = [{
'url': 'http://music.yandex.ru/album/540508',
@@ -283,7 +282,7 @@ class YandexMusicAlbumIE(YandexMusicPlaylistBaseIE):
@classmethod
def suitable(cls, url):
- return False if YandexMusicTrackIE.suitable(url) else super(YandexMusicAlbumIE, cls).suitable(url)
+ return False if YandexMusicTrackIE.suitable(url) else super().suitable(url)
def _real_extract(self, url):
mobj = self._match_valid_url(url)
@@ -297,20 +296,20 @@ class YandexMusicAlbumIE(YandexMusicPlaylistBaseIE):
entries = self._build_playlist([track for volume in album['volumes'] for track in volume])
title = album['title']
- artist = try_get(album, lambda x: x['artists'][0]['name'], compat_str)
+ artist = try_get(album, lambda x: x['artists'][0]['name'], str)
if artist:
- title = '%s - %s' % (artist, title)
+ title = f'{artist} - {title}'
year = album.get('year')
if year:
- title += ' (%s)' % year
+ title += f' ({year})'
- return self.playlist_result(entries, compat_str(album['id']), title)
+ return self.playlist_result(entries, str(album['id']), title)
class YandexMusicPlaylistIE(YandexMusicPlaylistBaseIE):
IE_NAME = 'yandexmusic:playlist'
IE_DESC = 'Яндекс.Музыка - Плейлист'
- _VALID_URL = r'%s/users/(?P<user>[^/]+)/playlists/(?P<id>\d+)' % YandexMusicBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{YandexMusicBaseIE._VALID_URL_BASE}/users/(?P<user>[^/]+)/playlists/(?P<id>\d+)'
_TESTS = [{
'url': 'http://music.yandex.ru/users/music.partners/playlists/1245',
@@ -348,7 +347,7 @@ class YandexMusicPlaylistIE(YandexMusicPlaylistBaseIE):
'kinds': playlist_id,
'light': 'true',
'lang': tld,
- 'external-domain': 'music.yandex.%s' % tld,
+ 'external-domain': f'music.yandex.{tld}',
'overembed': 'false',
})['playlist']
@@ -356,7 +355,7 @@ class YandexMusicPlaylistIE(YandexMusicPlaylistBaseIE):
return self.playlist_result(
self._build_playlist(tracks),
- compat_str(playlist_id),
+ str(playlist_id),
playlist.get('title'), playlist.get('description'))
@@ -364,14 +363,14 @@ class YandexMusicArtistBaseIE(YandexMusicPlaylistBaseIE):
def _call_artist(self, tld, url, artist_id):
return self._call_api(
'artist', tld, url, artist_id,
- 'Downloading artist %s JSON' % self._ARTIST_WHAT, {
+ f'Downloading artist {self._ARTIST_WHAT} JSON', {
'artist': artist_id,
'what': self._ARTIST_WHAT,
'sort': self._ARTIST_SORT or '',
'dir': '',
'period': '',
'lang': tld,
- 'external-domain': 'music.yandex.%s' % tld,
+ 'external-domain': f'music.yandex.{tld}',
'overembed': 'false',
})
@@ -381,7 +380,7 @@ class YandexMusicArtistBaseIE(YandexMusicPlaylistBaseIE):
artist_id = mobj.group('id')
data = self._call_artist(tld, url, artist_id)
tracks = self._extract_tracks(data, artist_id, url, tld)
- title = try_get(data, lambda x: x['artist']['name'], compat_str)
+ title = try_get(data, lambda x: x['artist']['name'], str)
return self.playlist_result(
self._build_playlist(tracks), artist_id, title)
@@ -389,7 +388,7 @@ class YandexMusicArtistBaseIE(YandexMusicPlaylistBaseIE):
class YandexMusicArtistTracksIE(YandexMusicArtistBaseIE):
IE_NAME = 'yandexmusic:artist:tracks'
IE_DESC = 'Яндекс.Музыка - Артист - Треки'
- _VALID_URL = r'%s/artist/(?P<id>\d+)/tracks' % YandexMusicBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{YandexMusicBaseIE._VALID_URL_BASE}/artist/(?P<id>\d+)/tracks'
_TESTS = [{
'url': 'https://music.yandex.ru/artist/617526/tracks',
@@ -410,8 +409,8 @@ class YandexMusicArtistTracksIE(YandexMusicArtistBaseIE):
artist_id = mobj.group('id')
data = self._call_artist(tld, url, artist_id)
tracks = self._extract_tracks(data, artist_id, url, tld)
- artist = try_get(data, lambda x: x['artist']['name'], compat_str)
- title = '%s - %s' % (artist or artist_id, 'Треки')
+ artist = try_get(data, lambda x: x['artist']['name'], str)
+ title = '{} - {}'.format(artist or artist_id, 'Треки')
return self.playlist_result(
self._build_playlist(tracks), artist_id, title)
@@ -419,7 +418,7 @@ class YandexMusicArtistTracksIE(YandexMusicArtistBaseIE):
class YandexMusicArtistAlbumsIE(YandexMusicArtistBaseIE):
IE_NAME = 'yandexmusic:artist:albums'
IE_DESC = 'Яндекс.Музыка - Артист - Альбомы'
- _VALID_URL = r'%s/artist/(?P<id>\d+)/albums' % YandexMusicBaseIE._VALID_URL_BASE
+ _VALID_URL = rf'{YandexMusicBaseIE._VALID_URL_BASE}/artist/(?P<id>\d+)/albums'
_TESTS = [{
'url': 'https://music.yandex.ru/artist/617526/albums',
@@ -447,8 +446,8 @@ class YandexMusicArtistAlbumsIE(YandexMusicArtistBaseIE):
if not album_id:
continue
entries.append(self.url_result(
- 'http://music.yandex.ru/album/%s' % album_id,
+ f'http://music.yandex.ru/album/{album_id}',
ie=YandexMusicAlbumIE.ie_key(), video_id=album_id))
- artist = try_get(data, lambda x: x['artist']['name'], compat_str)
- title = '%s - %s' % (artist or artist_id, 'Альбомы')
+ artist = try_get(data, lambda x: x['artist']['name'], str)
+ title = '{} - {}'.format(artist or artist_id, 'Альбомы')
return self.playlist_result(entries, artist_id, title)
diff --git a/yt_dlp/extractor/yandexvideo.py b/yt_dlp/extractor/yandexvideo.py
index 95a9446e3..cdd32c5e4 100644
--- a/yt_dlp/extractor/yandexvideo.py
+++ b/yt_dlp/extractor/yandexvideo.py
@@ -89,10 +89,10 @@ class YandexVideoIE(InfoExtractor):
title
views_count
}
-}''' % video_id).encode(), fatal=False)), lambda x: x['player']['content'])
+}''' % video_id).encode(), fatal=False)), lambda x: x['player']['content']) # noqa: UP031
if not player or player.get('error'):
player = self._download_json(
- 'https://frontend.vh.yandex.ru/v23/player/%s.json' % video_id,
+ f'https://frontend.vh.yandex.ru/v23/player/{video_id}.json',
video_id, query={
'stream_options': 'hires',
'disable_trackings': 1,
@@ -179,10 +179,10 @@ class YandexVideoPreviewIE(InfoExtractor):
}]
def _real_extract(self, url):
- id = self._match_id(url)
- webpage = self._download_webpage(url, id)
+ video_id = self._match_id(url)
+ webpage = self._download_webpage(url, video_id)
data_raw = self._search_regex(r'window.Ya.__inline_params__\s*=\s*JSON.parse\(\'([^"]+?\\u0022video\\u0022:[^"]+?})\'\);', webpage, 'data_raw')
- data_json = self._parse_json(data_raw, id, transform_source=lowercase_escape)
+ data_json = self._parse_json(data_raw, video_id, transform_source=lowercase_escape)
return self.url_result(data_json['video']['url'])
@@ -196,7 +196,7 @@ class ZenYandexIE(InfoExtractor):
'title': 'ВОТ ЭТО Focus. Деды Морозы на гидроциклах',
'description': 'md5:8684912f6086f298f8078d4af0e8a600',
'thumbnail': 're:^https://avatars.dzeninfra.ru/',
- 'uploader': 'AcademeG DailyStream'
+ 'uploader': 'AcademeG DailyStream',
},
'params': {
'skip_download': 'm3u8',
diff --git a/yt_dlp/extractor/yapfiles.py b/yt_dlp/extractor/yapfiles.py
index d6024d912..8d89d1d10 100644
--- a/yt_dlp/extractor/yapfiles.py
+++ b/yt_dlp/extractor/yapfiles.py
@@ -10,7 +10,7 @@ from ..utils import (
class YapFilesIE(InfoExtractor):
_WORKING = False
_YAPFILES_URL = r'//(?:(?:www|api)\.)?yapfiles\.ru/get_player/*\?.*?\bv=(?P<id>\w+)'
- _VALID_URL = r'https?:%s' % _YAPFILES_URL
+ _VALID_URL = rf'https?:{_YAPFILES_URL}'
_EMBED_REGEX = [rf'<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?{_YAPFILES_URL}.*?)\1']
_TESTS = [{
# with hd
@@ -42,7 +42,7 @@ class YapFilesIE(InfoExtractor):
'player url', default=None, group='url')
if not player_url:
- player_url = 'http://api.yapfiles.ru/load/%s/' % video_id
+ player_url = f'http://api.yapfiles.ru/load/{video_id}/'
query = {
'md5': 'ded5f369be61b8ae5f88e2eeb2f3caff',
'type': 'json',
@@ -58,7 +58,7 @@ class YapFilesIE(InfoExtractor):
if title == 'Ролик удален' or 'deleted.jpg' in (thumbnail or ''):
raise ExtractorError(
- 'Video %s has been removed' % video_id, expected=True)
+ f'Video {video_id} has been removed', expected=True)
playlist = self._download_json(
playlist_url, video_id)['player']['main']
diff --git a/yt_dlp/extractor/yappy.py b/yt_dlp/extractor/yappy.py
index 5ce647eee..171e0f136 100644
--- a/yt_dlp/extractor/yappy.py
+++ b/yt_dlp/extractor/yappy.py
@@ -27,7 +27,7 @@ class YappyIE(InfoExtractor):
'categories': ['Образование и наука', 'Лайфхак', 'Технологии', 'Арт/искусство'],
'repost_count': int,
'uploader': 'YAPPY',
- }
+ },
}, {
'url': 'https://yappy.media/video/3862451954ad4bd58ae2ccefddb0bd33',
'info_dict': {
@@ -43,7 +43,7 @@ class YappyIE(InfoExtractor):
'uploader': 'LENA SHTURMAN',
'upload_date': '20230126',
'thumbnail': 'https://cdn-st.ritm.media/static/pic/user_thumbnails/6e76bb4bbad640b6/9ec84c115b2b1967/1674716171.jpg',
- }
+ },
}]
def _real_extract(self, url):
@@ -64,13 +64,13 @@ class YappyIE(InfoExtractor):
'url': media_url,
'ext': 'mp4',
'format_note': 'Watermarked' if has_watermark else None,
- 'preference': -10 if has_watermark else None
+ 'preference': -10 if has_watermark else None,
}] if media_url else []
if has_watermark:
formats.append({
'url': media_url.replace('-wm.mp4', '.mp4'),
- 'ext': 'mp4'
+ 'ext': 'mp4',
})
audio_link = traverse_obj(media_data, ('audio', 'link'))
@@ -79,7 +79,7 @@ class YappyIE(InfoExtractor):
'url': audio_link,
'ext': 'mp3',
'acodec': 'mp3',
- 'vcodec': 'none'
+ 'vcodec': 'none',
})
return {
@@ -97,7 +97,7 @@ class YappyIE(InfoExtractor):
'uploader': traverse_obj(media_data, ('creator', 'firstName')),
'uploader_id': traverse_obj(media_data, ('creator', ('uuid', 'nickname')), get_all=False),
'categories': traverse_obj(media_data, ('categories', ..., 'name')) or None,
- 'repost_count': int_or_none(media_data.get('sharingCount'))
+ 'repost_count': int_or_none(media_data.get('sharingCount')),
}
diff --git a/yt_dlp/extractor/yle_areena.py b/yt_dlp/extractor/yle_areena.py
index dd0e59901..796f7f316 100644
--- a/yt_dlp/extractor/yle_areena.py
+++ b/yt_dlp/extractor/yle_areena.py
@@ -34,8 +34,8 @@ class YleAreenaIE(InfoExtractor):
'timestamp': 1543916210,
'subtitles': {'fin': [{'url': r're:^https?://', 'ext': 'srt'}]},
'age_limit': 7,
- 'webpage_url': 'https://areena.yle.fi/1-4371942'
- }
+ 'webpage_url': 'https://areena.yle.fi/1-4371942',
+ },
},
{
'url': 'https://areena.yle.fi/1-2158940',
@@ -55,8 +55,8 @@ class YleAreenaIE(InfoExtractor):
'timestamp': 1638448202,
'subtitles': {},
'age_limit': 0,
- 'webpage_url': 'https://areena.yle.fi/1-2158940'
- }
+ 'webpage_url': 'https://areena.yle.fi/1-2158940',
+ },
},
{
'url': 'https://areena.yle.fi/1-64829589',
@@ -83,7 +83,7 @@ class YleAreenaIE(InfoExtractor):
video_id, headers={
'origin': 'https://areena.yle.fi',
'referer': 'https://areena.yle.fi/',
- 'content-type': 'application/json'
+ 'content-type': 'application/json',
})
# Example title: 'K1, J2: Pouchit | Modernit miehet'
diff --git a/yt_dlp/extractor/youjizz.py b/yt_dlp/extractor/youjizz.py
index cd12be500..f7ef222c2 100644
--- a/yt_dlp/extractor/youjizz.py
+++ b/yt_dlp/extractor/youjizz.py
@@ -18,7 +18,7 @@ class YouJizzIE(InfoExtractor):
'title': 'Zeichentrick 1',
'age_limit': 18,
'duration': 2874,
- }
+ },
}, {
'url': 'http://www.youjizz.com/videos/-2189178.html',
'only_matching': True,
diff --git a/yt_dlp/extractor/youku.py b/yt_dlp/extractor/youku.py
index 1f3f98a86..fa6b0539b 100644
--- a/yt_dlp/extractor/youku.py
+++ b/yt_dlp/extractor/youku.py
@@ -104,7 +104,7 @@ class YoukuIE(InfoExtractor):
@staticmethod
def get_ysuid():
- return '%d%s' % (int(time.time()), ''.join(
+ return '{}{}'.format(int(time.time()), ''.join(
random.choices(string.ascii_letters, k=3)))
def get_format_name(self, fm):
@@ -273,7 +273,7 @@ class YoukuShowIE(InfoExtractor):
continue
_, new_entries = self._extract_entries(
'http://list.youku.com/show/episode', show_id,
- note='Downloading playlist data page %d' % (idx + 1),
+ note=f'Downloading playlist data page {idx + 1}',
query={
'id': page_config['showid'],
'stage': reload_id,
diff --git a/yt_dlp/extractor/younow.py b/yt_dlp/extractor/younow.py
index b67cb2e17..409ee758a 100644
--- a/yt_dlp/extractor/younow.py
+++ b/yt_dlp/extractor/younow.py
@@ -1,7 +1,6 @@
import itertools
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
format_field,
@@ -11,7 +10,7 @@ from ..utils import (
)
CDN_API_BASE = 'https://cdn.younow.com/php/api'
-MOMENT_URL_FORMAT = '%s/moment/fetch/id=%%s' % CDN_API_BASE
+MOMENT_URL_FORMAT = f'{CDN_API_BASE}/moment/fetch/id=%s'
class YouNowLiveIE(InfoExtractor):
@@ -38,21 +37,20 @@ class YouNowLiveIE(InfoExtractor):
def suitable(cls, url):
return (False
if YouNowChannelIE.suitable(url) or YouNowMomentIE.suitable(url)
- else super(YouNowLiveIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
username = self._match_id(url)
data = self._download_json(
- 'https://api.younow.com/php/api/broadcast/info/curId=0/user=%s'
- % username, username)
+ f'https://api.younow.com/php/api/broadcast/info/curId=0/user={username}', username)
if data.get('errorCode') != 0:
raise ExtractorError(data['errorMsg'], expected=True)
uploader = try_get(
data, lambda x: x['user']['profileUrlString'],
- compat_str) or username
+ str) or username
return {
'id': uploader,
@@ -63,13 +61,12 @@ class YouNowLiveIE(InfoExtractor):
'categories': data.get('tags'),
'uploader': uploader,
'uploader_id': data.get('userId'),
- 'uploader_url': 'https://www.younow.com/%s' % username,
+ 'uploader_url': f'https://www.younow.com/{username}',
'creator': uploader,
'view_count': int_or_none(data.get('viewers')),
'like_count': int_or_none(data.get('likes')),
'formats': [{
- 'url': '%s/broadcast/videoPath/hls=1/broadcastId=%s/channelId=%s'
- % (CDN_API_BASE, data['broadcastId'], data['userId']),
+ 'url': '{}/broadcast/videoPath/hls=1/broadcastId={}/channelId={}'.format(CDN_API_BASE, data['broadcastId'], data['userId']),
'ext': 'mp4',
'protocol': 'm3u8',
}],
@@ -83,18 +80,18 @@ def _extract_moment(item, fatal=True):
return
raise ExtractorError('Unable to extract moment id')
- moment_id = compat_str(moment_id)
+ moment_id = str(moment_id)
title = item.get('text')
if not title:
title = 'YouNow %s' % (
item.get('momentType') or item.get('titleType') or 'moment')
- uploader = try_get(item, lambda x: x['owner']['name'], compat_str)
+ uploader = try_get(item, lambda x: x['owner']['name'], str)
uploader_id = try_get(item, lambda x: x['owner']['userId'])
uploader_url = format_field(uploader, None, 'https://www.younow.com/%s')
- entry = {
+ return {
'extractor_key': 'YouNowMoment',
'id': moment_id,
'title': title,
@@ -106,15 +103,12 @@ def _extract_moment(item, fatal=True):
'uploader_id': str_or_none(uploader_id),
'uploader_url': uploader_url,
'formats': [{
- 'url': 'https://hls.younow.com/momentsplaylists/live/%s/%s.m3u8'
- % (moment_id, moment_id),
+ 'url': f'https://hls.younow.com/momentsplaylists/live/{moment_id}/{moment_id}.m3u8',
'ext': 'mp4',
'protocol': 'm3u8_native',
}],
}
- return entry
-
class YouNowChannelIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?younow\.com/(?P<id>[^/]+)/channel'
@@ -122,7 +116,7 @@ class YouNowChannelIE(InfoExtractor):
'url': 'https://www.younow.com/its_Kateee_/channel',
'info_dict': {
'id': '14629760',
- 'title': 'its_Kateee_ moments'
+ 'title': 'its_Kateee_ moments',
},
'playlist_mincount': 8,
}
@@ -133,9 +127,8 @@ class YouNowChannelIE(InfoExtractor):
if created_before is None:
break
info = self._download_json(
- '%s/moment/profile/channelId=%s/createdBefore=%d/records=20'
- % (CDN_API_BASE, channel_id, created_before), username,
- note='Downloading moments page %d' % page_num)
+ f'{CDN_API_BASE}/moment/profile/channelId={channel_id}/createdBefore={created_before}/records=20',
+ username, note=f'Downloading moments page {page_num}')
items = info.get('items')
if not items or not isinstance(items, list):
break
@@ -153,7 +146,7 @@ class YouNowChannelIE(InfoExtractor):
for moment_id in moments:
m = self._download_json(
MOMENT_URL_FORMAT % moment_id, username,
- note='Downloading %s moment JSON' % moment_id,
+ note=f'Downloading {moment_id} moment JSON',
fatal=False)
if m and isinstance(m, dict) and m.get('item'):
entry = _extract_moment(m['item'])
@@ -163,12 +156,12 @@ class YouNowChannelIE(InfoExtractor):
def _real_extract(self, url):
username = self._match_id(url)
- channel_id = compat_str(self._download_json(
- 'https://api.younow.com/php/api/broadcast/info/curId=0/user=%s'
- % username, username, note='Downloading user information')['userId'])
+ channel_id = str(self._download_json(
+ f'https://api.younow.com/php/api/broadcast/info/curId=0/user={username}',
+ username, note='Downloading user information')['userId'])
return self.playlist_result(
self._entries(username, channel_id), channel_id,
- '%s moments' % username)
+ f'{username} moments')
class YouNowMomentIE(InfoExtractor):
@@ -193,7 +186,7 @@ class YouNowMomentIE(InfoExtractor):
def suitable(cls, url):
return (False
if YouNowChannelIE.suitable(url)
- else super(YouNowMomentIE, cls).suitable(url))
+ else super().suitable(url))
def _real_extract(self, url):
video_id = self._match_id(url)
diff --git a/yt_dlp/extractor/youporn.py b/yt_dlp/extractor/youporn.py
index 0e047aa16..4a00dfe9c 100644
--- a/yt_dlp/extractor/youporn.py
+++ b/yt_dlp/extractor/youporn.py
@@ -90,7 +90,7 @@ class YouPornIE(InfoExtractor):
'timestamp': 1606147564,
'title': 'Tinder In Real Life',
'view_count': int,
- }
+ },
}]
def _real_extract(self, url):
@@ -126,7 +126,7 @@ class YouPornIE(InfoExtractor):
for definition in get_format_data(definitions, 'mp4'):
f = traverse_obj(definition, {
'url': 'videoUrl',
- 'filesize': ('videoSize', {int_or_none})
+ 'filesize': ('videoSize', {int_or_none}),
})
height = int_or_none(definition.get('quality'))
# Video URL's path looks like this:
@@ -140,7 +140,7 @@ class YouPornIE(InfoExtractor):
height = int(mobj.group('height'))
bitrate = int(mobj.group('bitrate'))
f.update({
- 'format_id': '%dp-%dk' % (height, bitrate),
+ 'format_id': f'{height}p-{bitrate}k',
'tbr': bitrate,
})
f['height'] = height
diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py
index 54da4e362..a227f2425 100644
--- a/yt_dlp/extractor/youtube.py
+++ b/yt_dlp/extractor/youtube.py
@@ -77,9 +77,9 @@ INNERTUBE_CLIENTS = {
'client': {
'clientName': 'WEB',
'clientVersion': '2.20220801.00.00',
- }
+ },
},
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 1
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 1,
},
'web_embedded': {
'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
@@ -89,7 +89,7 @@ INNERTUBE_CLIENTS = {
'clientVersion': '1.20220731.00.00',
},
},
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 56
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 56,
},
'web_music': {
'INNERTUBE_API_KEY': 'AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30',
@@ -98,7 +98,7 @@ INNERTUBE_CLIENTS = {
'client': {
'clientName': 'WEB_REMIX',
'clientVersion': '1.20220727.01.00',
- }
+ },
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 67,
},
@@ -108,7 +108,7 @@ INNERTUBE_CLIENTS = {
'client': {
'clientName': 'WEB_CREATOR',
'clientVersion': '1.20220726.00.00',
- }
+ },
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 62,
},
@@ -119,11 +119,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'ANDROID',
'clientVersion': '19.09.37',
'androidSdkVersion': 30,
- 'userAgent': 'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip'
- }
+ 'userAgent': 'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip',
+ },
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 3,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
'android_embedded': {
'INNERTUBE_API_KEY': 'AIzaSyCjc_pVEDi4qsv5MtC2dMXzpIaDoRFLsxw',
@@ -132,11 +132,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'ANDROID_EMBEDDED_PLAYER',
'clientVersion': '19.09.37',
'androidSdkVersion': 30,
- 'userAgent': 'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip'
+ 'userAgent': 'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip',
},
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 55,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
'android_music': {
'INNERTUBE_API_KEY': 'AIzaSyAOghZGza2MQSZkY_zfZ370N-PUdXEo8AI',
@@ -145,11 +145,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'ANDROID_MUSIC',
'clientVersion': '6.42.52',
'androidSdkVersion': 30,
- 'userAgent': 'com.google.android.apps.youtube.music/6.42.52 (Linux; U; Android 11) gzip'
- }
+ 'userAgent': 'com.google.android.apps.youtube.music/6.42.52 (Linux; U; Android 11) gzip',
+ },
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 21,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
'android_creator': {
'INNERTUBE_API_KEY': 'AIzaSyD_qjV8zaaUMehtLkrKFgVeSX_Iqbtyws8',
@@ -158,11 +158,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'ANDROID_CREATOR',
'clientVersion': '22.30.100',
'androidSdkVersion': 30,
- 'userAgent': 'com.google.android.apps.youtube.creator/22.30.100 (Linux; U; Android 11) gzip'
+ 'userAgent': 'com.google.android.apps.youtube.creator/22.30.100 (Linux; U; Android 11) gzip',
},
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 14,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
# iOS clients have HLS live streams. Setting device model to get 60fps formats.
# See: https://github.com/TeamNewPipe/NewPipeExtractor/issues/680#issuecomment-1002724558
@@ -173,11 +173,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'IOS',
'clientVersion': '19.09.3',
'deviceModel': 'iPhone14,3',
- 'userAgent': 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)'
- }
+ 'userAgent': 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)',
+ },
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 5,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
'ios_embedded': {
'INNERTUBE_CONTEXT': {
@@ -185,11 +185,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'IOS_MESSAGES_EXTENSION',
'clientVersion': '19.09.3',
'deviceModel': 'iPhone14,3',
- 'userAgent': 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)'
+ 'userAgent': 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)',
},
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 66,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
'ios_music': {
'INNERTUBE_API_KEY': 'AIzaSyBAETezhkwP0ZWA02RsqT1zu78Fpt0bC_s',
@@ -198,11 +198,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'IOS_MUSIC',
'clientVersion': '6.33.3',
'deviceModel': 'iPhone14,3',
- 'userAgent': 'com.google.ios.youtubemusic/6.33.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)'
+ 'userAgent': 'com.google.ios.youtubemusic/6.33.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)',
},
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 26,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
'ios_creator': {
'INNERTUBE_CONTEXT': {
@@ -210,11 +210,11 @@ INNERTUBE_CLIENTS = {
'clientName': 'IOS_CREATOR',
'clientVersion': '22.33.101',
'deviceModel': 'iPhone14,3',
- 'userAgent': 'com.google.ios.ytcreator/22.33.101 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)'
+ 'userAgent': 'com.google.ios.ytcreator/22.33.101 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)',
},
},
'INNERTUBE_CONTEXT_CLIENT_NAME': 15,
- 'REQUIRE_JS_PLAYER': False
+ 'REQUIRE_JS_PLAYER': False,
},
# mweb has 'ultralow' formats
# See: https://github.com/yt-dlp/yt-dlp/pull/557
@@ -224,9 +224,9 @@ INNERTUBE_CLIENTS = {
'client': {
'clientName': 'MWEB',
'clientVersion': '2.20220801.00.00',
- }
+ },
},
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 2
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 2,
},
# This client can access age restricted videos (unless the uploader has disabled the 'allow embedding' option)
# See: https://github.com/zerodytrash/YouTube-Internal-Clients
@@ -238,7 +238,7 @@ INNERTUBE_CLIENTS = {
'clientVersion': '2.0',
},
},
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 85
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 85,
},
# This client has pre-merged video+audio 720p/1080p streams
'mediaconnect': {
@@ -248,7 +248,7 @@ INNERTUBE_CLIENTS = {
'clientVersion': '0.1',
},
},
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 95
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 95,
},
}
@@ -465,7 +465,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
'lt', 'hu', 'nl', 'no', 'uz', 'pl', 'pt-PT', 'pt', 'ro', 'sq', 'sk', 'sl', 'sr-Latn', 'fi',
'sv', 'vi', 'tr', 'be', 'bg', 'ky', 'kk', 'mk', 'mn', 'ru', 'sr', 'uk', 'el', 'hy', 'iw',
'ur', 'ar', 'fa', 'ne', 'mr', 'hi', 'as', 'bn', 'pa', 'gu', 'or', 'ta', 'te', 'kn', 'ml',
- 'si', 'th', 'lo', 'my', 'ka', 'am', 'km', 'zh-CN', 'zh-TW', 'zh-HK', 'ja', 'ko'
+ 'si', 'th', 'lo', 'my', 'ka', 'am', 'km', 'zh-CN', 'zh-TW', 'zh-HK', 'ja', 'ko',
]
_IGNORED_WARNINGS = {'Unavailable videos will be hidden during playback'}
@@ -698,7 +698,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
'X-Youtube-Identity-Token': identity_token or self._extract_identity_token(ytcfg),
'X-Goog-PageId': account_syncid or self._extract_account_syncid(ytcfg),
'X-Goog-Visitor-Id': visitor_data or self._extract_visitor_data(ytcfg),
- 'User-Agent': self._ytcfg_get_safe(ytcfg, lambda x: x['INNERTUBE_CONTEXT']['client']['userAgent'], default_client=default_client)
+ 'User-Agent': self._ytcfg_get_safe(ytcfg, lambda x: x['INNERTUBE_CONTEXT']['client']['userAgent'], default_client=default_client),
}
if session_index is None:
session_index = self._extract_session_index(ytcfg)
@@ -715,7 +715,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
url = {
'web': 'https://www.youtube.com',
'web_music': 'https://music.youtube.com',
- 'web_embedded': f'https://www.youtube.com/embed/{video_id}?html5=1'
+ 'web_embedded': f'https://www.youtube.com/embed/{video_id}?html5=1',
}.get(client)
if not url:
return {}
@@ -726,7 +726,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
@staticmethod
def _build_api_continuation_query(continuation, ctp=None):
query = {
- 'continuation': continuation
+ 'continuation': continuation,
}
# TODO: Inconsistency with clickTrackingParams.
# Currently we have a fixed ctp contained within context (from ytcfg)
@@ -766,7 +766,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
return traverse_obj(renderer, (
('contents', 'items', 'rows'), ..., 'continuationItemRenderer',
- ('continuationEndpoint', ('button', 'buttonRenderer', 'command'))
+ ('continuationEndpoint', ('button', 'buttonRenderer', 'command')),
), get_all=False, expected_type=cls._extract_continuation_ep_data)
@classmethod
@@ -793,7 +793,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
for alert_type, alert_message in (warnings + errors[:-1]):
self.report_warning(f'YouTube said: {alert_type} - {alert_message}', only_once=only_once)
if errors:
- raise ExtractorError('YouTube said: %s' % errors[-1][1], expected=expected)
+ raise ExtractorError(f'YouTube said: {errors[-1][1]}', expected=expected)
def _extract_and_report_alerts(self, data, *args, **kwargs):
return self._report_alerts(self._extract_alerts(data), *args, **kwargs)
@@ -927,7 +927,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
if start:
return datetime_from_str(start)
try:
- return datetime_from_str('now-%s%s' % (mobj.group('time'), mobj.group('unit')))
+ return datetime_from_str('now-{}{}'.format(mobj.group('time'), mobj.group('unit')))
except ValueError:
return None
@@ -1114,13 +1114,13 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
is_unlisted=self._has_badge(badges, BadgeType.AVAILABILITY_UNLISTED) or None),
view_count_field: view_count,
'live_status': live_status,
- 'channel_is_verified': True if self._has_badge(owner_badges, BadgeType.VERIFIED) else None
+ 'channel_is_verified': True if self._has_badge(owner_badges, BadgeType.VERIFIED) else None,
}
class YoutubeIE(YoutubeBaseInfoExtractor):
IE_DESC = 'YouTube'
- _VALID_URL = r"""(?x)^
+ _VALID_URL = r'''(?x)^
(
(?:https?://|//) # http(s):// or protocol-independent URL
(?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie|kids)?\.com|
@@ -1129,7 +1129,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
(?:www\.)?hooktube\.com|
(?:www\.)?yourepeat\.com|
tube\.majestyc\.net|
- %(invidious)s|
+ {invidious}|
youtube\.googleapis\.com)/ # the various hostnames, with wildcard subdomains
(?:.*?\#/)? # handle anchor (#/) redirect urls
(?: # the various things that can precede the ID:
@@ -1145,16 +1145,16 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
youtu\.be| # just youtu.be/xxxx
vid\.plus| # or vid.plus/xxxx
zwearz\.com/watch| # or zwearz.com/watch/xxxx
- %(invidious)s
+ {invidious}
)/
|(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId=
)
)? # all until now is optional -> you can pass the naked ID
- (?P<id>[0-9A-Za-z_-]{11}) # here is it! the YouTube video ID
+ (?P<id>[0-9A-Za-z_-]{{11}}) # here is it! the YouTube video ID
(?(1).+)? # if we found the ID, everything can follow
- (?:\#|$)""" % {
- 'invidious': '|'.join(YoutubeBaseInfoExtractor._INVIDIOUS_SITES),
- }
+ (?:\#|$)'''.format(
+ invidious='|'.join(YoutubeBaseInfoExtractor._INVIDIOUS_SITES),
+ )
_EMBED_REGEX = [
r'''(?x)
(?:
@@ -1326,7 +1326,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'uploader_id': '@PhilippHagemeister',
'heatmap': 'count:100',
'timestamp': 1349198244,
- }
+ },
},
{
'url': '//www.YouTube.com/watch?v=yZIXLfi8CZQ',
@@ -1383,7 +1383,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'ext': 'm4a',
'upload_date': '20121002',
'description': '',
- 'title': 'UHDTV TEST 8K VIDEO.mp4'
+ 'title': 'UHDTV TEST 8K VIDEO.mp4',
},
'params': {
'youtube_include_dash_manifest': True,
@@ -1591,7 +1591,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
},
'expected_warnings': [
'DASH manifest missing',
- ]
+ ],
},
# Olympics (https://github.com/ytdl-org/youtube-dl/issues/4431)
{
@@ -1626,7 +1626,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
},
'params': {
'skip_download': 'requires avconv',
- }
+ },
},
# Non-square pixels
{
@@ -1850,7 +1850,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'playable_in_embed': True,
'like_count': int,
'age_limit': 0,
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'params': {
'skip_download': True,
@@ -2111,7 +2111,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'channel_id': 'UC-pWHpBjdGG69N9mM2auIAA',
'tags': 'count:11',
'live_status': 'not_live',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'params': {
'skip_download': True,
@@ -2288,7 +2288,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'comment_count': int,
'channel_is_verified': True,
'timestamp': 1405513526,
- }
+ },
},
{
# restricted location, https://github.com/ytdl-org/youtube-dl/issues/28685
@@ -2323,11 +2323,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
}, {
# Has multiple audio streams
'url': 'WaOKSUlf4TM',
- 'only_matching': True
+ 'only_matching': True,
}, {
# Requires Premium: has format 141 when requested using YTM url
'url': 'https://music.youtube.com/watch?v=XclachpHxis',
- 'only_matching': True
+ 'only_matching': True,
}, {
# multiple subtitles with same lang_code
'url': 'https://www.youtube.com/watch?v=wsQiKKfKxug',
@@ -2412,7 +2412,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'channel_is_verified': True,
'heatmap': 'count:100',
'timestamp': 1395685455,
- }, 'params': {'format': 'mhtml', 'skip_download': True}
+ }, 'params': {'format': 'mhtml', 'skip_download': True},
}, {
# Ensure video upload_date is in UTC timezone (video was uploaded 1641170939)
'url': 'https://www.youtube.com/watch?v=2NUZ8W2llS4',
@@ -2442,7 +2442,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'uploader_id': '@LeonNguyen',
'heatmap': 'count:100',
'timestamp': 1641170939,
- }
+ },
}, {
# date text is premiered video, ensure upload date in UTC (published 1641172509)
'url': 'https://www.youtube.com/watch?v=mzZzzBU6lrM',
@@ -2475,7 +2475,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'channel_is_verified': True,
'heatmap': 'count:100',
'timestamp': 1641172509,
- }
+ },
},
{ # continuous livestream.
# Upload date was 2022-07-12T05:12:29-07:00, while stream start is 2022-07-12T15:59:30+00:00
@@ -2535,7 +2535,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'uploader': 'Lesmiscore',
'uploader_url': 'https://www.youtube.com/@lesmiscore',
'timestamp': 1648005313,
- }
+ },
}, {
# Prefer primary title+description language metadata by default
# Do not prefer translated description if primary is empty
@@ -2564,7 +2564,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'uploader': 'cole-dlp-test-acc',
'timestamp': 1662677394,
},
- 'params': {'skip_download': True}
+ 'params': {'skip_download': True},
}, {
# Extractor argument: prefer translated title+description
'url': 'https://www.youtube.com/watch?v=gHKT4uU8Zng',
@@ -2765,7 +2765,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
},
'params': {
'skip_download': True,
- }
+ },
},
]
@@ -2922,7 +2922,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if not should_continue:
known_idx = idx - 1
raise ExtractorError('breaking out of outer loop')
- last_segment_url = urljoin(fragment_base_url, 'sq/%d' % idx)
+ last_segment_url = urljoin(fragment_base_url, f'sq/{idx}')
yield {
'url': last_segment_url,
'fragment_count': last_seq,
@@ -2971,7 +2971,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if id_m:
break
else:
- raise ExtractorError('Cannot identify player %r' % player_url)
+ raise ExtractorError(f'Cannot identify player {player_url!r}')
return id_m.group('id')
def _load_player(self, video_id, player_url, fatal=True):
@@ -2980,7 +2980,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
code = self._download_webpage(
player_url, video_id, fatal=fatal,
note='Downloading player ' + player_id,
- errnote='Download of %s failed' % player_url)
+ errnote=f'Download of {player_url} failed')
if code:
self._code_cache[player_id] = code
return self._code_cache.get(player_id)
@@ -3041,10 +3041,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
cache_res = func(test_string)
cache_spec = [ord(c) for c in cache_res]
expr_code = ' + '.join(gen_sig_code(cache_spec))
- signature_id_tuple = '(%s)' % (
- ', '.join(str(len(p)) for p in example_sig.split('.')))
- code = ('if tuple(len(p) for p in s.split(\'.\')) == %s:\n'
- ' return %s\n') % (signature_id_tuple, expr_code)
+ signature_id_tuple = '({})'.format(', '.join(str(len(p)) for p in example_sig.split('.')))
+ code = (f'if tuple(len(p) for p in s.split(\'.\')) == {signature_id_tuple}:\n'
+ f' return {expr_code}\n')
self.to_screen('Extracted signature function:\n' + code)
def _parse_sig_js(self, jscode):
@@ -3150,9 +3149,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# For redundancy
func_code = self._search_regex(
- r'''(?xs)%s\s*=\s*function\s*\((?P<var>[\w$]+)\)\s*
+ rf'''(?xs){func_name}\s*=\s*function\s*\((?P<var>[\w$]+)\)\s*
# NB: The end of the regex is intentionally kept strict
- {(?P<code>.+?}\s*return\ [\w$]+.join\(""\))};''' % func_name,
+ {{(?P<code>.+?}}\s*return\ [\w$]+.join\(""\))}};''',
jscode, 'nsig function', group=('var', 'code'), default=None)
if func_code:
func_code = ([func_code[0]], func_code[1])
@@ -3218,7 +3217,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# cpn generation algorithm is reverse engineered from base.js.
# In fact it works even with dummy cpn.
CPN_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'
- cpn = ''.join(CPN_ALPHABET[random.randint(0, 256) & 63] for _ in range(0, 16))
+ cpn = ''.join(CPN_ALPHABET[random.randint(0, 256) & 63] for _ in range(16))
# # more consistent results setting it to right before the end
video_length = [str(float((qs.get('len') or ['1.5'])[0]) - 1)]
@@ -3255,7 +3254,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
webpage)
if mobj:
yield cls.url_result(mobj.group('url'), cls)
- raise cls.StopExtraction()
+ raise cls.StopExtraction
yield from super()._extract_from_webpage(url, webpage)
@@ -3280,7 +3279,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
chapter_list = traverse_obj(
data, (
'playerOverlays', 'playerOverlayRenderer', 'decoratedPlayerBarRenderer',
- 'decoratedPlayerBarRenderer', 'playerBar', 'chapteredPlayerBarRenderer', 'chapters'
+ 'decoratedPlayerBarRenderer', 'playerBar', 'chapteredPlayerBarRenderer', 'chapters',
), expected_type=list)
return self._extract_chapters_helper(
@@ -3334,7 +3333,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'author_is_uploader': ('author', 'isCreator', {bool}),
'author_is_verified': ('author', 'isVerified', {bool}),
'author_url': ('author', 'channelCommand', 'innertubeCommand', (
- ('browseEndpoint', 'canonicalBaseUrl'), ('commandMetadata', 'webCommandMetadata', 'url')
+ ('browseEndpoint', 'canonicalBaseUrl'), ('commandMetadata', 'webCommandMetadata', 'url'),
), {lambda x: urljoin('https://www.youtube.com', x)}),
}, get_all=False),
'is_favorited': (None if toolbar_entity_payload is None else
@@ -3420,7 +3419,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
sort_text = str_or_none(sort_menu_item.get('title'))
if not sort_text:
sort_text = 'top comments' if comment_sort_index == 0 else 'newest first'
- self.to_screen('Sorting comments by %s' % sort_text.lower())
+ self.to_screen(f'Sorting comments by {sort_text.lower()}')
break
return _continuation
@@ -3491,15 +3490,15 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# Keeps track of counts across recursive calls
if not tracker:
- tracker = dict(
- running_total=0,
- est_total=None,
- current_page_thread=0,
- total_parent_comments=0,
- total_reply_comments=0,
- seen_comment_ids=set(),
- pinned_comment_ids=set()
- )
+ tracker = {
+ 'running_total': 0,
+ 'est_total': None,
+ 'current_page_thread': 0,
+ 'total_parent_comments': 0,
+ 'total_reply_comments': 0,
+ 'seen_comment_ids': set(),
+ 'pinned_comment_ids': set(),
+ }
# TODO: Deprecated
# YouTube comments have a max depth of 2
@@ -3510,8 +3509,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if max_depth == 1 and parent:
return
- max_comments, max_parents, max_replies, max_replies_per_thread, *_ = map(
- lambda p: int_or_none(p, default=sys.maxsize), self._configuration_arg('max_comments', ) + [''] * 4)
+ max_comments, max_parents, max_replies, max_replies_per_thread, *_ = (
+ int_or_none(p, default=sys.maxsize) for p in self._configuration_arg('max_comments') + [''] * 4)
continuation = self._extract_continuation(root_continuation_data)
@@ -3540,7 +3539,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
note_prefix = ' Downloading comment API JSON reply thread %d %s' % (
tracker['current_page_thread'], comment_prog_str)
else:
- note_prefix = '%sDownloading comment%s API JSON page %d %s' % (
+ note_prefix = '{}Downloading comment{} API JSON page {} {}'.format(
' ' if parent else '', ' replies' if parent else '',
page_num, comment_prog_str)
@@ -3627,9 +3626,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
context['signatureTimestamp'] = sts
return {
'playbackContext': {
- 'contentPlaybackContext': context
+ 'contentPlaybackContext': context,
},
- **cls._get_checkok_params()
+ **cls._get_checkok_params(),
}
@staticmethod
@@ -3669,7 +3668,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
item_id=video_id, ep='player', query=yt_query,
ytcfg=player_ytcfg, headers=headers, fatal=True,
default_client=client,
- note='Downloading %s player API JSON' % client.replace('_', ' ').strip()
+ note='Downloading {} player API JSON'.format(client.replace('_', ' ').strip()),
) or None
def _get_requested_clients(self, url, smuggled_data):
@@ -3677,7 +3676,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
android_clients = []
default = ['ios', 'web']
allowed_clients = sorted(
- (client for client in INNERTUBE_CLIENTS.keys() if client[:1] != '_'),
+ (client for client in INNERTUBE_CLIENTS if client[:1] != '_'),
key=lambda client: INNERTUBE_CLIENTS[client]['priority'], reverse=True)
for client in self._configuration_arg('player_client'):
if client == 'default':
@@ -3805,7 +3804,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# audio-only formats with unknown quality may get tagged as tiny
'tiny',
'audio_quality_ultralow', 'audio_quality_low', 'audio_quality_medium', 'audio_quality_high', # Audio only formats
- 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'
+ 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres',
])
streaming_formats = traverse_obj(streaming_data, (..., ('formats', 'adaptiveFormats'), ...))
format_types = self._configuration_arg('formats')
@@ -3818,8 +3817,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
def build_fragments(f):
return LazyList({
'url': update_url_query(f['url'], {
- 'range': f'{range_start}-{min(range_start + CHUNK_SIZE - 1, f["filesize"])}'
- })
+ 'range': f'{range_start}-{min(range_start + CHUNK_SIZE - 1, f["filesize"])}',
+ }),
} for range_start in range(0, f['filesize'], CHUNK_SIZE))
for fmt in streaming_formats:
@@ -3860,9 +3859,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if not all((sc, fmt_url, player_url, encrypted_sig)):
continue
try:
- fmt_url += '&%s=%s' % (
+ fmt_url += '&{}={}'.format(
traverse_obj(sc, ('sp', -1)) or 'signature',
- self._decrypt_signature(encrypted_sig, video_id, player_url)
+ self._decrypt_signature(encrypted_sig, video_id, player_url),
)
except ExtractorError as e:
self.report_warning('Signature extraction failed: Some formats may be missing',
@@ -3876,7 +3875,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
try:
decrypt_nsig = self._cached(self._decrypt_nsig, 'nsig', query['n'][0])
fmt_url = update_url_query(fmt_url, {
- 'n': decrypt_nsig(query['n'][0], video_id, player_url)
+ 'n': decrypt_nsig(query['n'][0], video_id, player_url),
})
except ExtractorError as e:
phantomjs_hint = ''
@@ -4182,7 +4181,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
expected_type=str)
if multifeed_metadata_list and not smuggled_data.get('force_singlefeed'):
if self.get_param('noplaylist'):
- self.to_screen('Downloading just video %s because of --no-playlist' % video_id)
+ self.to_screen(f'Downloading just video {video_id} because of --no-playlist')
else:
entries = []
feed_ids = []
@@ -4203,19 +4202,19 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
feed_title = feed_entry('title')
title = video_title
if feed_title:
- title += ' (%s)' % feed_title
+ title += f' ({feed_title})'
entries.append({
'_type': 'url_transparent',
'ie_key': 'Youtube',
'url': smuggle_url(
- '%swatch?v=%s' % (base_url, feed_data['id'][0]),
+ '{}watch?v={}'.format(base_url, feed_data['id'][0]),
{'force_singlefeed': True}),
'title': title,
})
feed_ids.append(feed_id)
self.to_screen(
- 'Downloading multifeed video (%s) - add --no-playlist to just download video %s'
- % (', '.join(feed_ids), video_id))
+ 'Downloading multifeed video ({}) - add --no-playlist to just download video {}'.format(
+ ', '.join(feed_ids), video_id))
return self.playlist_result(
entries, video_id, video_title, video_description)
@@ -4279,7 +4278,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# While the *1,*2,*3 thumbnails are just below their corresponding "*default" variants
# in resolution, these are not the custom thumbnail. So de-prioritize them
'maxresdefault', 'hq720', 'sddefault', 'hqdefault', '0', 'mqdefault', 'default',
- 'sd1', 'sd2', 'sd3', 'hq1', 'hq2', 'hq3', 'mq1', 'mq2', 'mq3', '1', '2', '3'
+ 'sd1', 'sd2', 'sd3', 'hq1', 'hq2', 'hq3', 'mq1', 'mq2', 'mq3', '1', '2', '3',
]
n_thumbnail_names = len(thumbnail_names)
thumbnails.extend({
@@ -4353,7 +4352,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'live_status': live_status,
'release_timestamp': live_start_time,
'_format_sort_fields': ( # source_preference is lower for throttled/potentially damaged formats
- 'quality', 'res', 'fps', 'hdr:12', 'source', 'vcodec:vp9.2', 'channels', 'acodec', 'lang', 'proto')
+ 'quality', 'res', 'fps', 'hdr:12', 'source', 'vcodec:vp9.2', 'channels', 'acodec', 'lang', 'proto'),
}
subtitles = {}
@@ -4431,7 +4430,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
for d_k, s_ks in [('start', ('start', 't')), ('end', ('end',))]:
d_k += '_time'
if d_k not in info and k in s_ks:
- info[d_k] = parse_duration(query[k][0])
+ info[d_k] = parse_duration(v[0])
# Youtube Music Auto-generated description
if (video_description or '').strip().endswith('\nAuto-generated by YouTube.'):
@@ -4483,10 +4482,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
info['comment_count'] = traverse_obj(initial_data, (
'contents', 'twoColumnWatchNextResults', 'results', 'results', 'contents', ..., 'itemSectionRenderer',
- 'contents', ..., 'commentsEntryPointHeaderRenderer', 'commentCount'
+ 'contents', ..., 'commentsEntryPointHeaderRenderer', 'commentCount',
), (
'engagementPanels', lambda _, v: v['engagementPanelSectionListRenderer']['panelIdentifier'] == 'comment-item-section',
- 'engagementPanelSectionListRenderer', 'header', 'engagementPanelTitleHeaderRenderer', 'contextualInfo'
+ 'engagementPanelSectionListRenderer', 'header', 'engagementPanelTitleHeaderRenderer', 'contextualInfo',
), expected_type=self._get_count, get_all=False)
try: # This will error if there is no livechat
@@ -4716,7 +4715,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
def _extract_basic_item_renderer(item):
# Modified from _extract_grid_item_renderer
known_basic_renderers = (
- 'playlistRenderer', 'videoRenderer', 'channelRenderer', 'showRenderer', 'reelItemRenderer'
+ 'playlistRenderer', 'videoRenderer', 'channelRenderer', 'showRenderer', 'reelItemRenderer',
)
for key, renderer in item.items():
if not isinstance(renderer, dict):
@@ -4777,7 +4776,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
playlist_id = renderer.get('playlistId')
if playlist_id:
yield self.url_result(
- 'https://www.youtube.com/playlist?list=%s' % playlist_id,
+ f'https://www.youtube.com/playlist?list={playlist_id}',
ie=YoutubeTabIE.ie_key(), video_id=playlist_id,
video_title=title)
continue
@@ -4835,7 +4834,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
yield from self._grid_entries(renderer)
renderer = content.get('horizontalListRenderer')
if renderer:
- # TODO
+ # TODO: handle case
pass
def _shelf_entries(self, shelf_renderer, skip_channels=False):
@@ -4912,7 +4911,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
post_renderer, lambda x: x['backstageAttachment']['playlistRenderer']['playlistId'], str)
if playlist_id:
yield self.url_result(
- 'https://www.youtube.com/playlist?list=%s' % playlist_id,
+ f'https://www.youtube.com/playlist?list={playlist_id}',
ie=YoutubeTabIE.ie_key(), video_id=playlist_id)
# inline video links
runs = try_get(post_renderer, lambda x: x['contentText']['runs'], list) or []
@@ -5065,12 +5064,12 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
continuation_items = traverse_obj(response, (
('onResponseReceivedActions', 'onResponseReceivedEndpoints'), ...,
- 'appendContinuationItemsAction', 'continuationItems'
+ 'appendContinuationItemsAction', 'continuationItems',
), 'continuationContents', get_all=False)
continuation_item = traverse_obj(continuation_items, 0, None, expected_type=dict, default={})
video_items_renderer = None
- for key in continuation_item.keys():
+ for key in continuation_item:
if key not in known_renderers:
continue
func, parent_key = known_renderers[key]
@@ -5137,7 +5136,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
avatar_thumbnails.append({
'url': uncropped_avatar,
'id': 'avatar_uncropped',
- 'preference': 1
+ 'preference': 1,
})
channel_banners = self._extract_thumbnails(
@@ -5151,7 +5150,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
channel_banners.append({
'url': uncropped_banner,
'id': 'banner_uncropped',
- 'preference': -5
+ 'preference': -5,
})
# Deprecated - remove primary_sidebar_renderer when layout discontinued
@@ -5221,7 +5220,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
info.update({
'channel': self._search_regex(r'^by (.+) and \d+ others?$', owner_text, 'uploader', default=owner_text),
'channel_id': self.ucid_or_none(browse_ep.get('browseId')),
- 'uploader_id': self.handle_from_url(urljoin('https://www.youtube.com', browse_ep.get('canonicalBaseUrl')))
+ 'uploader_id': self.handle_from_url(urljoin('https://www.youtube.com', browse_ep.get('canonicalBaseUrl'))),
})
info.update({
@@ -5253,12 +5252,12 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
'playlistId': playlist_id,
'videoId': watch_endpoint.get('videoId') or last_id,
'index': watch_endpoint.get('index') or len(videos),
- 'params': watch_endpoint.get('params') or 'OAE%3D'
+ 'params': watch_endpoint.get('params') or 'OAE%3D',
}
response = self._extract_response(
- item_id='%s page %d' % (playlist_id, page_num),
+ item_id=f'{playlist_id} page {page_num}',
query=query, ep='next', headers=headers, ytcfg=ytcfg,
- check_get_keys='contents'
+ check_get_keys='contents',
)
playlist = try_get(
response, lambda x: x['contents']['twoColumnWatchNextResults']['playlist']['playlist'], dict)
@@ -5349,7 +5348,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
visitor_data=self._extract_visitor_data(data, ytcfg))
query = {
'params': 'wgYCCAA=',
- 'browseId': f'VL{item_id}'
+ 'browseId': f'VL{item_id}',
}
return self._extract_response(
item_id=item_id, headers=headers, query=query,
@@ -5481,7 +5480,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
(?!consent\.)(?:\w+\.)?
(?:
youtube(?:kids)?\.com|
- %(invidious)s
+ {invidious}
)/
(?:
(?P<channel_type>channel|c|user|browse)/|
@@ -5489,13 +5488,13 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
feed/|hashtag/|
(?:playlist|watch)\?.*?\blist=
)|
- (?!(?:%(reserved_names)s)\b) # Direct URLs
+ (?!(?:{reserved_names})\b) # Direct URLs
)
(?P<id>[^/?\#&]+)
- )''' % {
- 'reserved_names': YoutubeBaseInfoExtractor._RESERVED_NAMES,
- 'invidious': '|'.join(YoutubeBaseInfoExtractor._INVIDIOUS_SITES),
- }
+ )'''.format(
+ reserved_names=YoutubeBaseInfoExtractor._RESERVED_NAMES,
+ invidious='|'.join(YoutubeBaseInfoExtractor._INVIDIOUS_SITES),
+ )
IE_NAME = 'youtube:tab'
_TESTS = [{
@@ -5513,7 +5512,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
'tags': ['критическое мышление', 'наука просто', 'математика', 'анализ данных'],
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
}, {
'note': 'playlists, multipage, different order',
@@ -5530,7 +5529,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
'channel': 'Igor Kleiner Ph.D.',
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
}, {
'note': 'playlists, series',
@@ -5565,8 +5564,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UCAEtajcuhQ6an9WEzY9LEMQ',
'tags': 'count:12',
'channel': 'ThirstForScience',
- 'channel_follower_count': int
- }
+ 'channel_follower_count': int,
+ },
}, {
'url': 'https://www.youtube.com/c/ChristophLaimer/playlists',
'only_matching': True,
@@ -5621,7 +5620,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'uploader_url': 'https://www.youtube.com/@lexwill718',
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'playlist_mincount': 2,
}, {
@@ -5638,7 +5637,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'uploader_url': 'https://www.youtube.com/@lexwill718',
'channel': 'lex will',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'playlist_mincount': 975,
}, {
@@ -5655,7 +5654,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel': 'lex will',
'tags': ['bible', 'history', 'prophesy'],
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'playlist_mincount': 199,
}, {
@@ -5672,7 +5671,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'tags': ['bible', 'history', 'prophesy'],
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'playlist_mincount': 17,
}, {
@@ -5995,11 +5994,11 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
}, {
'note': 'Requires Premium: should request additional YTM-info webpage (and have format 141) for videos in playlist',
'url': 'https://music.youtube.com/playlist?list=PLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
- 'only_matching': True
+ 'only_matching': True,
}, {
'note': '/browse/ should redirect to /channel/',
'url': 'https://music.youtube.com/browse/UC1a8OFewdjuLq6KlF8M_8Ng',
- 'only_matching': True
+ 'only_matching': True,
}, {
'note': 'VLPL, should redirect to playlist?list=PL...',
'url': 'https://music.youtube.com/browse/VLPLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
@@ -6096,7 +6095,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'uploader_id': '@PhilippHagemeister',
'uploader_url': 'https://www.youtube.com/@PhilippHagemeister',
'uploader': 'Philipp Hagemeister',
- }
+ },
}],
'playlist_count': 1,
'params': {'extract_flat': True},
@@ -6111,7 +6110,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 50,
'params': {
'skip_download': True,
- 'extractor_args': {'youtubetab': {'skip': ['webpage']}}
+ 'extractor_args': {'youtubetab': {'skip': ['webpage']}},
},
}, {
'note': 'API Fallback: /videos tab, sorted by oldest first',
@@ -6124,12 +6123,12 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'channel_id': 'UCu6mSoMNzHQiBIOCkHUa2Aw',
'tags': [],
'channel_url': 'https://www.youtube.com/channel/UCu6mSoMNzHQiBIOCkHUa2Aw',
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'playlist_mincount': 650,
'params': {
'skip_download': True,
- 'extractor_args': {'youtubetab': {'skip': ['webpage']}}
+ 'extractor_args': {'youtubetab': {'skip': ['webpage']}},
},
'skip': 'Query for sorting no longer works',
}, {
@@ -6151,13 +6150,13 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'playlist_mincount': 101,
'params': {
'skip_download': True,
- 'extractor_args': {'youtubetab': {'skip': ['webpage']}}
+ 'extractor_args': {'youtubetab': {'skip': ['webpage']}},
},
'expected_warnings': ['YouTube Music is not directly supported', r'[Uu]navailable videos (are|will be) hidden'],
}, {
'note': 'non-standard redirect to regional channel',
'url': 'https://www.youtube.com/channel/UCwVVpHQ2Cs9iGJfpdFngePQ',
- 'only_matching': True
+ 'only_matching': True,
}, {
'note': 'collaborative playlist (uploader name in the form "by <uploader> and x other(s)")',
'url': 'https://www.youtube.com/playlist?list=PLx-_-Kk4c89oOHEDQAojOXzEzemXxoqx6',
@@ -6176,7 +6175,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'uploader_id': '@pukkandan',
'uploader': 'pukkandan',
},
- 'playlist_mincount': 2
+ 'playlist_mincount': 2,
}, {
'note': 'translated tab name',
'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/playlists',
@@ -6317,7 +6316,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
# No uploads and no UCID given. Should fail with no uploads error
# See test_youtube_lists
'url': 'https://www.youtube.com/news',
- 'only_matching': True
+ 'only_matching': True,
}, {
# No videos tab but has a shorts tab
'url': 'https://www.youtube.com/c/TKFShorts',
@@ -6379,7 +6378,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'title': 'SHORT short',
'view_count': int,
'thumbnails': list,
- }
+ },
}],
'params': {'extract_flat': True},
}, {
@@ -6387,8 +6386,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'url': 'https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg/live',
'info_dict': {
'id': 'UCQvWX73GQygcwXOTSf_VDVg',
- 'title': 'UCQvWX73GQygcwXOTSf_VDVg - Live', # TODO, should be Minecraft - Live or Minecraft - Topic - Live
- 'tags': []
+ 'title': 'UCQvWX73GQygcwXOTSf_VDVg - Live', # TODO: should be Minecraft - Live or Minecraft - Topic - Live
+ 'tags': [],
},
'playlist': [{
'info_dict': {
@@ -6406,10 +6405,10 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'uploader_url': str,
'uploader_id': str,
'channel_is_verified': bool, # this will keep changing
- }
+ },
}],
'params': {'extract_flat': True, 'playlist_items': '1'},
- 'playlist_mincount': 1
+ 'playlist_mincount': 1,
}, {
# Channel renderer metadata. Contains number of videos on the channel
'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/channels',
@@ -6442,7 +6441,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
'uploader_url': 'https://www.youtube.com/@PewDiePie',
'uploader_id': '@PewDiePie',
'channel_is_verified': True,
- }
+ },
}],
'params': {'extract_flat': True},
}, {
@@ -6614,7 +6613,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
# Handle both video/playlist URLs
qs = parse_qs(url)
- video_id, playlist_id = [traverse_obj(qs, (key, 0)) for key in ('v', 'list')]
+ video_id, playlist_id = (traverse_obj(qs, (key, 0)) for key in ('v', 'list'))
if not video_id and mobj['not_channel'].startswith('watch'):
if not playlist_id:
# If there is neither video or playlist ids, youtube redirects to home page, which is undesirable
@@ -6746,15 +6745,15 @@ class YoutubePlaylistIE(InfoExtractor):
(?:
(?:
youtube(?:kids)?\.com|
- %(invidious)s
+ {invidious}
)
/.*?\?.*?\blist=
)?
- (?P<id>%(playlist_id)s)
- )''' % {
- 'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE,
- 'invidious': '|'.join(YoutubeBaseInfoExtractor._INVIDIOUS_SITES),
- }
+ (?P<id>{playlist_id})
+ )'''.format(
+ playlist_id=YoutubeBaseInfoExtractor._PLAYLIST_ID_RE,
+ invidious='|'.join(YoutubeBaseInfoExtractor._INVIDIOUS_SITES),
+ )
IE_NAME = 'youtube:playlist'
_TESTS = [{
'note': 'issue #673',
@@ -6854,7 +6853,7 @@ class YoutubePlaylistIE(InfoExtractor):
class YoutubeYtBeIE(InfoExtractor):
IE_DESC = 'youtu.be'
- _VALID_URL = r'https?://youtu\.be/(?P<id>[0-9A-Za-z_-]{11})/*?.*?\blist=(?P<playlist_id>%(playlist_id)s)' % {'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE}
+ _VALID_URL = rf'https?://youtu\.be/(?P<id>[0-9A-Za-z_-]{{11}})/*?.*?\blist=(?P<playlist_id>{YoutubeBaseInfoExtractor._PLAYLIST_ID_RE})'
_TESTS = [{
'url': 'https://youtu.be/yeWKywCrFtk?list=PL2qgrgXsNUG5ig9cat4ohreBjYLAPC0J5',
'info_dict': {
@@ -6880,7 +6879,7 @@ class YoutubeYtBeIE(InfoExtractor):
'availability': 'public',
'duration': 59,
'comment_count': int,
- 'channel_follower_count': int
+ 'channel_follower_count': int,
},
'params': {
'noplaylist': True,
@@ -7054,7 +7053,7 @@ class YoutubeSearchIE(YoutubeTabBaseInfoExtractor, SearchInfoExtractor):
'info_dict': {
'id': 'youtube-dl test video',
'title': 'youtube-dl test video',
- }
+ },
}, {
'note': 'Suicide/self-harm search warning',
'url': 'ytsearch1:i hate myself and i wanna die',
@@ -7062,7 +7061,7 @@ class YoutubeSearchIE(YoutubeTabBaseInfoExtractor, SearchInfoExtractor):
'info_dict': {
'id': 'i hate myself and i wanna die',
'title': 'i hate myself and i wanna die',
- }
+ },
}]
@@ -7077,7 +7076,7 @@ class YoutubeSearchDateIE(YoutubeTabBaseInfoExtractor, SearchInfoExtractor):
'info_dict': {
'id': 'youtube-dl test video',
'title': 'youtube-dl test video',
- }
+ },
}]
@@ -7091,14 +7090,14 @@ class YoutubeSearchURLIE(YoutubeTabBaseInfoExtractor):
'info_dict': {
'id': 'youtube-dl test video',
'title': 'youtube-dl test video',
- }
+ },
}, {
'url': 'https://www.youtube.com/results?search_query=python&sp=EgIQAg%253D%253D',
'playlist_mincount': 5,
'info_dict': {
'id': 'python',
'title': 'python',
- }
+ },
}, {
'url': 'https://www.youtube.com/results?search_query=%23cats',
'playlist_mincount': 1,
@@ -7137,7 +7136,7 @@ class YoutubeSearchURLIE(YoutubeTabBaseInfoExtractor):
'uploader': 'Kurzgesagt – In a Nutshell',
'channel_is_verified': True,
'channel_follower_count': int,
- }
+ },
}],
'params': {'extract_flat': True, 'playlist_items': '1'},
'playlist_mincount': 1,
@@ -7162,7 +7161,7 @@ class YoutubeMusicSearchURLIE(YoutubeTabBaseInfoExtractor):
'info_dict': {
'id': 'royalty free music',
'title': 'royalty free music',
- }
+ },
}, {
'url': 'https://music.youtube.com/search?q=royalty+free+music&sp=EgWKAQIIAWoKEAoQAxAEEAkQBQ%3D%3D',
'playlist_mincount': 30,
@@ -7170,7 +7169,7 @@ class YoutubeMusicSearchURLIE(YoutubeTabBaseInfoExtractor):
'id': 'royalty free music - songs',
'title': 'royalty free music - songs',
},
- 'params': {'extract_flat': 'in_playlist'}
+ 'params': {'extract_flat': 'in_playlist'},
}, {
'url': 'https://music.youtube.com/search?q=royalty+free+music#community+playlists',
'playlist_mincount': 30,
@@ -7178,7 +7177,7 @@ class YoutubeMusicSearchURLIE(YoutubeTabBaseInfoExtractor):
'id': 'royalty free music - community playlists',
'title': 'royalty free music - community playlists',
},
- 'params': {'extract_flat': 'in_playlist'}
+ 'params': {'extract_flat': 'in_playlist'},
}]
_SECTIONS = {
@@ -7197,7 +7196,7 @@ class YoutubeMusicSearchURLIE(YoutubeTabBaseInfoExtractor):
if params:
section = next((k for k, v in self._SECTIONS.items() if v == params), params)
else:
- section = urllib.parse.unquote_plus((url.split('#') + [''])[1]).lower()
+ section = urllib.parse.unquote_plus(([*url.split('#'), ''])[1]).lower()
params = self._SECTIONS.get(section)
if not params:
section = None
@@ -7217,8 +7216,8 @@ class YoutubeFeedsInfoExtractor(InfoExtractor):
YoutubeBaseInfoExtractor._check_login_required(self)
@classproperty
- def IE_NAME(self):
- return f'youtube:{self._FEED_NAME}'
+ def IE_NAME(cls):
+ return f'youtube:{cls._FEED_NAME}'
def _real_extract(self, url):
return self.url_result(
@@ -7386,7 +7385,7 @@ class YoutubeClipIE(YoutubeTabBaseInfoExtractor):
'chapters': 'count:20',
'comment_count': int,
'heatmap': 'count:100',
- }
+ },
}]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/zaiko.py b/yt_dlp/extractor/zaiko.py
index 2b6221da2..c8c4ec0b8 100644
--- a/yt_dlp/extractor/zaiko.py
+++ b/yt_dlp/extractor/zaiko.py
@@ -106,7 +106,7 @@ class ZaikoIE(ZaikoBaseIE):
**traverse_obj(player_meta, ('initial_event_info', {
'alt_title': ('title', {str}),
})),
- 'thumbnails': [{'url': url, 'id': url_basename(url)} for url in thumbnail_urls if url_or_none(url)]
+ 'thumbnails': [{'url': url, 'id': url_basename(url)} for url in thumbnail_urls if url_or_none(url)],
}
diff --git a/yt_dlp/extractor/zapiks.py b/yt_dlp/extractor/zapiks.py
index 2a12aa509..2916367c8 100644
--- a/yt_dlp/extractor/zapiks.py
+++ b/yt_dlp/extractor/zapiks.py
@@ -55,11 +55,11 @@ class ZapiksIE(InfoExtractor):
r'data-media-id="(\d+)"', webpage, 'video id')
playlist = self._download_xml(
- 'http://www.zapiks.fr/view/index.php?action=playlist&media_id=%s&lang=en' % video_id,
+ f'http://www.zapiks.fr/view/index.php?action=playlist&media_id={video_id}&lang=en',
display_id)
NS_MAP = {
- 'jwplayer': 'http://rss.jwpcdn.com/'
+ 'jwplayer': 'http://rss.jwpcdn.com/',
}
def ns(path):
diff --git a/yt_dlp/extractor/zattoo.py b/yt_dlp/extractor/zattoo.py
index 5cc9c5f7a..161804b60 100644
--- a/yt_dlp/extractor/zattoo.py
+++ b/yt_dlp/extractor/zattoo.py
@@ -2,7 +2,6 @@ import re
import uuid
from .common import InfoExtractor
-from ..compat import compat_str
from ..networking.exceptions import HTTPError
from ..utils import (
ExtractorError,
@@ -27,13 +26,13 @@ class ZattooPlatformBaseIE(InfoExtractor):
def _perform_login(self, username, password):
try:
data = self._download_json(
- '%s/zapi/v2/account/login' % self._host_url(), None, 'Logging in',
+ f'{self._host_url()}/zapi/v2/account/login', None, 'Logging in',
data=urlencode_postdata({
'login': username,
'password': password,
'remember': 'true',
}), headers={
- 'Referer': '%s/login' % self._host_url(),
+ 'Referer': f'{self._host_url()}/login',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
})
except ExtractorError as e:
@@ -51,9 +50,9 @@ class ZattooPlatformBaseIE(InfoExtractor):
# Will setup appropriate cookies
self._request_webpage(
- '%s/zapi/v3/session/hello' % self._host_url(), None,
+ f'{self._host_url()}/zapi/v3/session/hello', None,
'Opening session', data=urlencode_postdata({
- 'uuid': compat_str(uuid.uuid4()),
+ 'uuid': str(uuid.uuid4()),
'lang': 'en',
'app_version': '1.8.2',
'format': 'json',
@@ -72,8 +71,7 @@ class ZattooPlatformBaseIE(InfoExtractor):
def _extract_cid(self, video_id, channel_name):
channel_groups = self._download_json(
- '%s/zapi/v2/cached/channels/%s' % (self._host_url(),
- self._power_guide_hash),
+ f'{self._host_url()}/zapi/v2/cached/channels/{self._power_guide_hash}',
video_id, 'Downloading channel list',
query={'details': False})['channel_groups']
channel_list = []
@@ -90,8 +88,7 @@ class ZattooPlatformBaseIE(InfoExtractor):
def _extract_cid_and_video_info(self, video_id):
data = self._download_json(
- '%s/zapi/v2/cached/program/power_details/%s' % (
- self._host_url(), self._power_guide_hash),
+ f'{self._host_url()}/zapi/v2/cached/program/power_details/{self._power_guide_hash}',
video_id,
'Downloading video information',
query={
@@ -113,7 +110,7 @@ class ZattooPlatformBaseIE(InfoExtractor):
'season_number': int_or_none(p.get('s_no')),
'release_year': int_or_none(p.get('year')),
'categories': try_get(p, lambda x: x['c'], list),
- 'tags': try_get(p, lambda x: x['g'], list)
+ 'tags': try_get(p, lambda x: x['g'], list),
}
return cid, info_dict
@@ -123,7 +120,7 @@ class ZattooPlatformBaseIE(InfoExtractor):
@returns (ondemand_token, ondemand_type, info_dict)
"""
data = self._download_json(
- '%s/zapi/vod/movies/%s' % (self._host_url(), ondemand_id),
+ f'{self._host_url()}/zapi/vod/movies/{ondemand_id}',
ondemand_id, 'Downloading ondemand information')
info_dict = {
'id': ondemand_id,
@@ -144,18 +141,18 @@ class ZattooPlatformBaseIE(InfoExtractor):
if is_live:
postdata_common.update({'timeshift': 10800})
- url = '%s/zapi/watch/live/%s' % (self._host_url(), cid)
+ url = f'{self._host_url()}/zapi/watch/live/{cid}'
elif record_id:
- url = '%s/zapi/watch/recording/%s' % (self._host_url(), record_id)
+ url = f'{self._host_url()}/zapi/watch/recording/{record_id}'
elif ondemand_id:
postdata_common.update({
'teasable_id': ondemand_id,
'term_token': ondemand_termtoken,
- 'teasable_type': ondemand_type
+ 'teasable_type': ondemand_type,
})
- url = '%s/zapi/watch/vod/video' % self._host_url()
+ url = f'{self._host_url()}/zapi/watch/vod/video'
else:
- url = '%s/zapi/v3/watch/replay/%s/%s' % (self._host_url(), cid, video_id)
+ url = f'{self._host_url()}/zapi/v3/watch/replay/{cid}/{video_id}'
formats = []
subtitles = {}
for stream_type in ('dash', 'hls7'):
@@ -163,7 +160,7 @@ class ZattooPlatformBaseIE(InfoExtractor):
postdata['stream_type'] = stream_type
data = self._download_json(
- url, video_id, 'Downloading %s formats' % stream_type.upper(),
+ url, video_id, f'Downloading {stream_type.upper()} formats',
data=urlencode_postdata(postdata), fatal=False)
if not data:
continue
@@ -218,7 +215,7 @@ class ZattooPlatformBaseIE(InfoExtractor):
'title': channel_name,
'is_live': True,
'formats': formats,
- 'subtitles': subtitles
+ 'subtitles': subtitles,
}
def _extract_record(self, record_id):
@@ -267,9 +264,9 @@ class ZattooIE(ZattooBaseIE):
'release_year': 2022,
'episode': 'Folge 1655',
'categories': 'count:1',
- 'tags': 'count:2'
+ 'tags': 'count:2',
},
- 'params': {'skip_download': 'm3u8'}
+ 'params': {'skip_download': 'm3u8'},
}, {
'url': 'https://zattoo.com/program/daserste/210177916',
'only_matching': True,
@@ -322,7 +319,7 @@ class ZattooRecordingsIE(ZattooBaseIE):
class NetPlusTVBaseIE(ZattooPlatformBaseIE):
_NETRC_MACHINE = 'netplus'
_HOST = 'netplus.tv'
- _API_HOST = 'www.%s' % _HOST
+ _API_HOST = f'www.{_HOST}'
class NetPlusTVIE(NetPlusTVBaseIE):
@@ -458,7 +455,7 @@ class WalyTVRecordingsIE(WalyTVBaseIE):
class BBVTVBaseIE(ZattooPlatformBaseIE):
_NETRC_MACHINE = 'bbvtv'
_HOST = 'bbv-tv.net'
- _API_HOST = 'www.%s' % _HOST
+ _API_HOST = f'www.{_HOST}'
class BBVTVIE(BBVTVBaseIE):
@@ -504,7 +501,7 @@ class BBVTVRecordingsIE(BBVTVBaseIE):
class VTXTVBaseIE(ZattooPlatformBaseIE):
_NETRC_MACHINE = 'vtxtv'
_HOST = 'vtxtv.ch'
- _API_HOST = 'www.%s' % _HOST
+ _API_HOST = f'www.{_HOST}'
class VTXTVIE(VTXTVBaseIE):
@@ -595,7 +592,7 @@ class GlattvisionTVRecordingsIE(GlattvisionTVBaseIE):
class SAKTVBaseIE(ZattooPlatformBaseIE):
_NETRC_MACHINE = 'saktv'
_HOST = 'saktv.ch'
- _API_HOST = 'www.%s' % _HOST
+ _API_HOST = f'www.{_HOST}'
class SAKTVIE(SAKTVBaseIE):
@@ -686,7 +683,7 @@ class EWETVRecordingsIE(EWETVBaseIE):
class QuantumTVBaseIE(ZattooPlatformBaseIE):
_NETRC_MACHINE = 'quantumtv'
_HOST = 'quantum-tv.com'
- _API_HOST = 'www.%s' % _HOST
+ _API_HOST = f'www.{_HOST}'
class QuantumTVIE(QuantumTVBaseIE):
@@ -777,7 +774,7 @@ class OsnatelTVRecordingsIE(OsnatelTVBaseIE):
class EinsUndEinsTVBaseIE(ZattooPlatformBaseIE):
_NETRC_MACHINE = '1und1tv'
_HOST = '1und1.tv'
- _API_HOST = 'www.%s' % _HOST
+ _API_HOST = f'www.{_HOST}'
class EinsUndEinsTVIE(EinsUndEinsTVBaseIE):
diff --git a/yt_dlp/extractor/zdf.py b/yt_dlp/extractor/zdf.py
index c04d51b7e..a862e25d0 100644
--- a/yt_dlp/extractor/zdf.py
+++ b/yt_dlp/extractor/zdf.py
@@ -1,7 +1,6 @@
import re
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
NO_DEFAULT,
ExtractorError,
@@ -29,11 +28,11 @@ class ZDFBaseIE(InfoExtractor):
def _call_api(self, url, video_id, item, api_token=None, referrer=None):
headers = {}
if api_token:
- headers['Api-Auth'] = 'Bearer %s' % api_token
+ headers['Api-Auth'] = f'Bearer {api_token}'
if referrer:
headers['Referer'] = referrer
return self._download_json(
- url, video_id, 'Downloading JSON %s' % item, headers=headers)
+ url, video_id, f'Downloading JSON {item}', headers=headers)
@staticmethod
def _extract_subtitles(src):
@@ -73,7 +72,7 @@ class ZDFBaseIE(InfoExtractor):
f.update({
'url': format_url,
'format_id': join_nonempty('http', meta.get('type'), meta.get('quality')),
- 'tbr': int_or_none(self._search_regex(r'_(\d+)k_', format_url, 'tbr', default=None))
+ 'tbr': int_or_none(self._search_regex(r'_(\d+)k_', format_url, 'tbr', default=None)),
})
new_formats = [f]
formats.extend(merge_dicts(f, {
@@ -236,7 +235,7 @@ class ZDFIE(ZDFBaseIE):
'timestamp': 1641355200,
'upload_date': '20220105',
},
- 'skip': 'No longer available "Diese Seite wurde leider nicht gefunden"'
+ 'skip': 'No longer available "Diese Seite wurde leider nicht gefunden"',
}, {
'url': 'https://www.zdf.de/serien/soko-stuttgart/das-geld-anderer-leute-100.html',
'info_dict': {
@@ -270,7 +269,7 @@ class ZDFIE(ZDFBaseIE):
t = content['mainVideoContent']['http://zdf.de/rels/target']
ptmd_path = traverse_obj(t, (
(('streams', 'default'), None),
- ('http://zdf.de/rels/streams/ptmd', 'http://zdf.de/rels/streams/ptmd-template')
+ ('http://zdf.de/rels/streams/ptmd', 'http://zdf.de/rels/streams/ptmd-template'),
), get_all=False)
if not ptmd_path:
raise ExtractorError('Could not extract ptmd_path')
@@ -303,7 +302,7 @@ class ZDFIE(ZDFBaseIE):
chapters = [{
'start_time': chap.get('anchorOffset'),
'end_time': next_chap.get('anchorOffset'),
- 'title': chap.get('anchorLabel')
+ 'title': chap.get('anchorLabel'),
} for chap, next_chap in zip(chapter_marks, chapter_marks[1:])]
return merge_dicts(info, {
@@ -312,7 +311,7 @@ class ZDFIE(ZDFBaseIE):
'duration': int_or_none(t.get('duration')),
'timestamp': unified_timestamp(content.get('editorialDate')),
'thumbnails': thumbnails,
- 'chapters': chapters or None
+ 'chapters': chapters or None,
})
def _extract_regular(self, url, player, video_id):
@@ -322,7 +321,7 @@ class ZDFIE(ZDFBaseIE):
def _extract_mobile(self, video_id):
video = self._download_json(
- 'https://zdf-cdn.live.cellular.de/mediathekV2/document/%s' % video_id,
+ f'https://zdf-cdn.live.cellular.de/mediathekV2/document/{video_id}',
video_id)
formats = []
@@ -341,7 +340,7 @@ class ZDFIE(ZDFBaseIE):
if isinstance(teaser_bild, dict):
for thumbnail_key, thumbnail in teaser_bild.items():
thumbnail_url = try_get(
- thumbnail, lambda x: x['url'], compat_str)
+ thumbnail, lambda x: x['url'], str)
if thumbnail_url:
thumbnails.append({
'url': thumbnail_url,
@@ -356,7 +355,7 @@ class ZDFIE(ZDFBaseIE):
'description': document.get('beschreibung'),
'duration': int_or_none(document.get('length')),
'timestamp': unified_timestamp(document.get('date')) or unified_timestamp(
- try_get(video, lambda x: x['meta']['editorialDate'], compat_str)),
+ try_get(video, lambda x: x['meta']['editorialDate'], str)),
'thumbnails': thumbnails,
'subtitles': self._extract_subtitles(document),
'formats': formats,
@@ -405,10 +404,10 @@ class ZDFChannelIE(ZDFBaseIE):
@classmethod
def suitable(cls, url):
- return False if ZDFIE.suitable(url) else super(ZDFChannelIE, cls).suitable(url)
+ return False if ZDFIE.suitable(url) else super().suitable(url)
def _og_search_title(self, webpage, fatal=False):
- title = super(ZDFChannelIE, self)._og_search_title(webpage, fatal=fatal)
+ title = super()._og_search_title(webpage, fatal=fatal)
return re.split(r'\s+[-|]\s+ZDF(?:mediathek)?$', title or '')[0] or None
def _real_extract(self, url):
@@ -417,7 +416,7 @@ class ZDFChannelIE(ZDFBaseIE):
webpage = self._download_webpage(url, channel_id)
matches = re.finditer(
- r'''<div\b[^>]*?\sdata-plusbar-id\s*=\s*(["'])(?P<p_id>[\w-]+)\1[^>]*?\sdata-plusbar-url=\1(?P<url>%s)\1''' % ZDFIE._VALID_URL,
+ rf'''<div\b[^>]*?\sdata-plusbar-id\s*=\s*(["'])(?P<p_id>[\w-]+)\1[^>]*?\sdata-plusbar-url=\1(?P<url>{ZDFIE._VALID_URL})\1''',
webpage)
if self._downloader.params.get('noplaylist', False):
@@ -428,11 +427,11 @@ class ZDFChannelIE(ZDFBaseIE):
if entry:
return entry
else:
- self.to_screen('Downloading playlist %s - add --no-playlist to download just the main video' % (channel_id, ))
+ self.to_screen(f'Downloading playlist {channel_id} - add --no-playlist to download just the main video')
def check_video(m):
v_ref = self._search_regex(
- r'''(<a\b[^>]*?\shref\s*=[^>]+?\sdata-target-id\s*=\s*(["'])%s\2[^>]*>)''' % (m.group('p_id'), ),
+ r'''(<a\b[^>]*?\shref\s*=[^>]+?\sdata-target-id\s*=\s*(["']){}\2[^>]*>)'''.format(m.group('p_id')),
webpage, 'check id', default='')
v_ref = extract_attributes(v_ref)
return v_ref.get('data-target-video-type') != 'novideo'
diff --git a/yt_dlp/extractor/zee5.py b/yt_dlp/extractor/zee5.py
index ca79cf0a7..fb523de03 100644
--- a/yt_dlp/extractor/zee5.py
+++ b/yt_dlp/extractor/zee5.py
@@ -3,7 +3,6 @@ import time
import uuid
from .common import InfoExtractor
-from ..compat import compat_str
from ..utils import (
ExtractorError,
int_or_none,
@@ -38,7 +37,7 @@ class Zee5IE(InfoExtractor):
'display_id': 'adavari-matalaku-ardhale-verule',
'title': 'Adavari Matalaku Ardhale Verule',
'duration': 9360,
- 'description': compat_str,
+ 'description': str,
'alt_title': 'Adavari Matalaku Ardhale Verule',
'uploader': 'Zee Entertainment Enterprises Ltd',
'release_date': '20070427',
@@ -47,7 +46,7 @@ class Zee5IE(InfoExtractor):
'thumbnail': r're:^https?://.*\.jpg$',
'episode_number': 0,
'episode': 'Episode 0',
- 'tags': list
+ 'tags': list,
},
'params': {
'format': 'bv',
@@ -60,7 +59,7 @@ class Zee5IE(InfoExtractor):
'display_id': 'yoga-se-hoga-bandbudh-aur-budbak',
'title': 'Yoga Se Hoga-Bandbudh aur Budbak',
'duration': 659,
- 'description': compat_str,
+ 'description': str,
'alt_title': 'Yoga Se Hoga-Bandbudh aur Budbak',
'uploader': 'Zee Entertainment Enterprises Ltd',
'release_date': '20150101',
@@ -79,22 +78,22 @@ class Zee5IE(InfoExtractor):
},
}, {
'url': 'https://www.zee5.com/hi/tv-shows/details/kundali-bhagya/0-6-366/kundali-bhagya-march-08-2021/0-1-manual_7g9jv1os7730?country=IN',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.zee5.com/global/hi/tv-shows/details/kundali-bhagya/0-6-366/kundali-bhagya-march-08-2021/0-1-manual_7g9jv1os7730',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.zee5.com/web-series/details/mithya/0-6-4z587408/maine-dekhi-hai-uski-mrityu/0-1-6z587412',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.zee5.com/kids/kids-movies/maya-bommalu/0-0-movie_1040370005',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.zee5.com/news/details/jana-sena-chief-pawan-kalyan-shows-slippers-to-ysrcp-leaders/0-0-newsauto_6ettj4242oo0',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://www.zee5.com/music-videos/details/adhento-gaani-vunnapaatuga-jersey-nani-shraddha-srinath/0-0-56973',
- 'only_matching': True
+ 'only_matching': True,
}]
_DEVICE_ID = str(uuid.uuid4())
_USER_TOKEN = None
@@ -136,10 +135,10 @@ class Zee5IE(InfoExtractor):
'https://launchapi.zee5.com/launch?platform_name=web_app',
video_id, note='Downloading access token')['platform_token']
data = {
- 'x-access-token': access_token_request['token']
+ 'x-access-token': access_token_request['token'],
}
if self._USER_TOKEN:
- data['Authorization'] = 'bearer %s' % self._USER_TOKEN
+ data['Authorization'] = f'bearer {self._USER_TOKEN}'
else:
data['X-Z5-Guest-Token'] = self._DEVICE_ID
@@ -150,7 +149,7 @@ class Zee5IE(InfoExtractor):
'platform_name': 'desktop_web',
'country': self._USER_COUNTRY or self.get_param('geo_bypass_country') or 'IN',
'check_parental_control': False,
- }, headers={'content-type': 'application/json'}, data=json.dumps(data).encode('utf-8'))
+ }, headers={'content-type': 'application/json'}, data=json.dumps(data).encode())
asset_data = json_data['assetDetails']
show_data = json_data.get('showDetails', {})
if 'premium' in asset_data['business_type']:
@@ -186,7 +185,7 @@ class Zee5IE(InfoExtractor):
'season': try_get(show_data, lambda x: x['seasons']['title'], str),
'season_number': int_or_none(try_get(show_data, lambda x: x['seasons'][0]['orderid'])),
'episode_number': int_or_none(try_get(asset_data, lambda x: x['orderid'])),
- 'tags': try_get(asset_data, lambda x: x['tags'], list)
+ 'tags': try_get(asset_data, lambda x: x['tags'], list),
}
@@ -251,17 +250,17 @@ class Zee5SeriesIE(InfoExtractor):
page_num = 0
show_json = self._download_json(show_url, video_id=show_id, headers=headers)
for season in show_json.get('seasons') or []:
- season_id = try_get(season, lambda x: x['id'], compat_str)
+ season_id = try_get(season, lambda x: x['id'], str)
next_url = f'https://gwapi.zee5.com/content/tvshow/?season_id={season_id}&type=episode&translation=en&country=IN&on_air=false&asset_subtype=tvshow&page=1&limit=100'
while next_url:
page_num += 1
episodes_json = self._download_json(
next_url, video_id=show_id, headers=headers,
- note='Downloading JSON metadata page %d' % page_num)
+ note=f'Downloading JSON metadata page {page_num}')
for episode in try_get(episodes_json, lambda x: x['episode'], list) or []:
video_id = episode.get('id')
yield self.url_result(
- 'zee5:%s' % video_id,
+ f'zee5:{video_id}',
ie=Zee5IE.ie_key(), video_id=video_id)
next_url = url_or_none(episodes_json.get('next_episode_api'))
diff --git a/yt_dlp/extractor/zeenews.py b/yt_dlp/extractor/zeenews.py
index e2cb1e7d6..cbe0031cc 100644
--- a/yt_dlp/extractor/zeenews.py
+++ b/yt_dlp/extractor/zeenews.py
@@ -20,7 +20,7 @@ class ZeeNewsIE(InfoExtractor):
'view_count': int,
'duration': 97,
'description': 'ग्रेटर नोएडा जारचा थाना क्षेत्र के प्याबली में दिनदहाड़े दुकान में घुसकर अज्ञात हमलावरों ने हमला कर',
- }
+ },
},
{
'url': 'https://zeenews.india.com/hindi/india/video/videsh-superfast-queen-elizabeth-iis-funeral-today/1357710',
@@ -35,8 +35,8 @@ class ZeeNewsIE(InfoExtractor):
'view_count': int,
'duration': 133,
'description': 'सेगमेंट विदेश सुपराफास्ट में देखिए देश और दुनिया की सभी बड़ी खबरें, वो भी हर खबर फटाफट अंदाज में.',
- }
- }
+ },
+ },
]
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/zenporn.py b/yt_dlp/extractor/zenporn.py
index 8faa0e3f4..084c1fc4f 100644
--- a/yt_dlp/extractor/zenporn.py
+++ b/yt_dlp/extractor/zenporn.py
@@ -21,7 +21,7 @@ class ZenPornIE(InfoExtractor):
'upload_date': '20230925',
'uploader': 'md5:9fae59847f1f58d1da8f2772016c12f3',
'age_limit': 18,
- }
+ },
}, {
'url': 'https://zenporn.com/video/15570701',
'md5': 'acba0d080d692664fcc8c4e5502b1a67',
@@ -35,7 +35,7 @@ class ZenPornIE(InfoExtractor):
'upload_date': '20230921',
'uploader': 'Lois Clarke',
'age_limit': 18,
- }
+ },
}, {
'url': 'https://zenporn.com/video/8531117/amateur-students-having-a-fuck-fest-at-club/',
'md5': '67411256aa9451449e4d29f3be525541',
@@ -49,7 +49,7 @@ class ZenPornIE(InfoExtractor):
'upload_date': '20191005',
'uploader': 'Jackopenass',
'age_limit': 18,
- }
+ },
}, {
'url': 'https://zenporn.com/video/15872038/glad-you-came/',
'md5': '296ccab437f5bac6099433768449d8e1',
@@ -63,7 +63,7 @@ class ZenPornIE(InfoExtractor):
'upload_date': '20231024',
'uploader': 'Martin Rudenko',
'age_limit': 18,
- }
+ },
}]
def _gen_info_url(self, ext_domain, extr_id, lifetime=86400):
diff --git a/yt_dlp/extractor/zetland.py b/yt_dlp/extractor/zetland.py
index 055a643b3..2b9df0018 100644
--- a/yt_dlp/extractor/zetland.py
+++ b/yt_dlp/extractor/zetland.py
@@ -23,7 +23,7 @@ class ZetlandDKArticleIE(InfoExtractor):
'description': 'md5:9619d426772c133f5abb26db27f26a01',
'timestamp': 1705377592,
'series_id': '62d54630-e87b-4ab1-a255-8de58dbe1b14',
- }
+ },
}]
@@ -44,7 +44,7 @@ class ZetlandDKArticleIE(InfoExtractor):
return merge_dicts({
'id': display_id,
'formats': formats,
- 'uploader_id': uploader_id
+ 'uploader_id': uploader_id,
}, traverse_obj(story_data, {
'title': ((('story_content', 'content', 'title'), 'title'), {str}),
'uploader': ('sharer', 'name'),
diff --git a/yt_dlp/extractor/zhihu.py b/yt_dlp/extractor/zhihu.py
index 18b22a5c7..c1a5bf6f4 100644
--- a/yt_dlp/extractor/zhihu.py
+++ b/yt_dlp/extractor/zhihu.py
@@ -20,7 +20,7 @@ class ZhihuIE(InfoExtractor):
'view_count': int,
'like_count': int,
'comment_count': int,
- }
+ },
}
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/zingmp3.py b/yt_dlp/extractor/zingmp3.py
index 909a7a3ae..1685edb92 100644
--- a/yt_dlp/extractor/zingmp3.py
+++ b/yt_dlp/extractor/zingmp3.py
@@ -112,7 +112,7 @@ class ZingMp3IE(ZingMp3BaseIE):
'subtitles': {
'origin': [{
'ext': 'lrc',
- }]
+ }],
},
'duration': 255,
'track': 'Xa Mãi Xa',
@@ -383,7 +383,7 @@ class ZingMp3ChartMusicVideoIE(ZingMp3BaseIE):
'id': song_id,
'type': 'genre',
'page': page,
- 'count': self._PER_PAGE
+ 'count': self._PER_PAGE,
})
def _real_extract(self, url):
@@ -446,7 +446,7 @@ class ZingMp3UserIE(ZingMp3BaseIE):
'id': user_id,
'type': 'artist',
'page': page,
- 'count': self._PER_PAGE
+ 'count': self._PER_PAGE,
})
def _real_extract(self, url):
@@ -569,14 +569,14 @@ class ZingMp3PodcastEpisodeIE(ZingMp3BaseIE):
'info_dict': {
'id': '68Z9W66B',
'title': 'Nhạc Mới Mỗi Ngày',
- 'description': 'md5:2875dfa951f8e5356742f1610cf20691'
+ 'description': 'md5:2875dfa951f8e5356742f1610cf20691',
},
'playlist_mincount': 20,
}, {
'url': 'https://zingmp3.vn/cgr/Am-nhac/IWZ980AO.html',
'info_dict': {
'id': 'IWZ980AO',
- 'title': 'Âm nhạc'
+ 'title': 'Âm nhạc',
},
'playlist_mincount': 2,
}]
@@ -585,7 +585,7 @@ class ZingMp3PodcastEpisodeIE(ZingMp3BaseIE):
return self._call_api(url_type, {
'id': eps_id,
'page': page,
- 'count': self._PER_PAGE
+ 'count': self._PER_PAGE,
})
def _real_extract(self, url):
diff --git a/yt_dlp/extractor/zoom.py b/yt_dlp/extractor/zoom.py
index e2bf81729..fe2db846a 100644
--- a/yt_dlp/extractor/zoom.py
+++ b/yt_dlp/extractor/zoom.py
@@ -109,9 +109,9 @@ class ZoomIE(InfoExtractor):
subtitles = {}
for _type in ('transcript', 'cc', 'chapter'):
- if data.get('%sUrl' % _type):
+ if data.get(f'{_type}Url'):
subtitles[_type] = [{
- 'url': urljoin(base_url, data['%sUrl' % _type]),
+ 'url': urljoin(base_url, data[f'{_type}Url']),
'ext': 'vtt',
}]
@@ -126,7 +126,7 @@ class ZoomIE(InfoExtractor):
'format_id': 'view',
'ext': 'mp4',
'filesize_approx': parse_filesize(str_or_none(traverse_obj(data, ('recording', 'fileSizeInMB')))),
- 'preference': 0
+ 'preference': 0,
})
if data.get('shareMp4Url'):
@@ -137,7 +137,7 @@ class ZoomIE(InfoExtractor):
'height': int_or_none(traverse_obj(data, ('shareResolvtions', 1))),
'format_id': 'share',
'ext': 'mp4',
- 'preference': -1
+ 'preference': -1,
})
view_with_share_url = data.get('viewMp4WithshareUrl')
@@ -149,7 +149,7 @@ class ZoomIE(InfoExtractor):
'url': view_with_share_url,
'format_id': 'view_with_share',
'ext': 'mp4',
- 'preference': 1
+ 'preference': 1,
})
return {
diff --git a/yt_dlp/extractor/zype.py b/yt_dlp/extractor/zype.py
index 8d3156d64..bfa3fc4c5 100644
--- a/yt_dlp/extractor/zype.py
+++ b/yt_dlp/extractor/zype.py
@@ -14,7 +14,7 @@ from ..utils import (
class ZypeIE(InfoExtractor):
_ID_RE = r'[\da-fA-F]+'
_COMMON_RE = r'//player\.zype\.com/embed/%s\.(?:js|json|html)\?.*?(?:access_token|(?:ap[ip]|player)_key)='
- _VALID_URL = r'https?:%s[^&]+' % (_COMMON_RE % ('(?P<id>%s)' % _ID_RE))
+ _VALID_URL = r'https?:%s[^&]+' % (_COMMON_RE % (f'(?P<id>{_ID_RE})'))
_EMBED_REGEX = [fr'<script[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?{_COMMON_RE % _ID_RE}.+?)\1']
_TEST = {
'url': 'https://player.zype.com/embed/5b400b834b32992a310622b9.js?api_key=jZ9GUhRmxcPvX7M3SlfejB6Hle9jyHTdk2jVxG7wOHPLODgncEKVdPYBhuz9iWXQ&autoplay=false&controls=true&da=false',
@@ -84,11 +84,11 @@ class ZypeIE(InfoExtractor):
def get_attr(key):
return self._search_regex(
- r'\b%s\s*:\s*([\'"])(?P<val>(?:(?!\1).)+)\1' % key,
+ rf'\b{key}\s*:\s*([\'"])(?P<val>(?:(?!\1).)+)\1',
source, key, group='val')
if get_attr('integration') == 'verizon-media':
- m3u8_url = 'https://content.uplynk.com/%s.m3u8' % get_attr('id')
+ m3u8_url = 'https://content.uplynk.com/{}.m3u8'.format(get_attr('id'))
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
m3u8_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls')
text_tracks = self._search_regex(
diff --git a/yt_dlp/jsinterp.py b/yt_dlp/jsinterp.py
index bda3fb459..5c82de19e 100644
--- a/yt_dlp/jsinterp.py
+++ b/yt_dlp/jsinterp.py
@@ -190,7 +190,7 @@ class Debugger:
cls.write('=> Raises:', e, '<-|', stmt, level=allow_recursion)
raise
if cls.ENABLED and stmt.strip():
- if should_ret or not repr(ret) == stmt:
+ if should_ret or repr(ret) != stmt:
cls.write(['->', '=>'][should_ret], repr(ret), '<-|', stmt, level=allow_recursion)
return ret, should_ret
return interpret_statement
@@ -216,7 +216,7 @@ class JSInterpreter:
self.code, self._functions = code, {}
self._objects = {} if objects is None else objects
- class Exception(ExtractorError):
+ class Exception(ExtractorError): # noqa: A001
def __init__(self, msg, expr=None, *args, **kwargs):
if expr is not None:
msg = f'{msg.rstrip()} in: {truncate_string(expr, 50, 50)}'
@@ -235,7 +235,7 @@ class JSInterpreter:
flags = 0
if not expr:
return flags, expr
- for idx, ch in enumerate(expr):
+ for idx, ch in enumerate(expr): # noqa: B007
if ch not in cls._RE_FLAGS:
break
flags |= cls._RE_FLAGS[ch]
@@ -474,7 +474,7 @@ class JSInterpreter:
if remaining.startswith('{'):
body, expr = self._separate_at_paren(remaining)
else:
- switch_m = re.match(r'switch\s*\(', remaining) # FIXME
+ switch_m = re.match(r'switch\s*\(', remaining) # FIXME: ?
if switch_m:
switch_val, remaining = self._separate_at_paren(remaining[switch_m.end() - 1:])
body, expr = self._separate_at_paren(remaining, '}')
@@ -585,9 +585,9 @@ class JSInterpreter:
return int(expr), should_return
elif expr == 'break':
- raise JS_Break()
+ raise JS_Break
elif expr == 'continue':
- raise JS_Continue()
+ raise JS_Continue
elif expr == 'undefined':
return JS_Undefined, should_return
elif expr == 'NaN':
@@ -697,12 +697,12 @@ class JSInterpreter:
elif member == 'splice':
assertion(isinstance(obj, list), 'must be applied on a list')
assertion(argvals, 'takes one or more arguments')
- index, howMany = map(int, (argvals + [len(obj)])[:2])
+ index, how_many = map(int, ([*argvals, len(obj)])[:2])
if index < 0:
index += len(obj)
add_items = argvals[2:]
res = []
- for i in range(index, min(index + howMany, len(obj))):
+ for _ in range(index, min(index + how_many, len(obj))):
res.append(obj.pop(index))
for i, item in enumerate(add_items):
obj.insert(index + i, item)
@@ -726,12 +726,12 @@ class JSInterpreter:
elif member == 'forEach':
assertion(argvals, 'takes one or more arguments')
assertion(len(argvals) <= 2, 'takes at-most 2 arguments')
- f, this = (argvals + [''])[:2]
+ f, this = ([*argvals, ''])[:2]
return [f((item, idx, obj), {'this': this}, allow_recursion) for idx, item in enumerate(obj)]
elif member == 'indexOf':
assertion(argvals, 'takes one or more arguments')
assertion(len(argvals) <= 2, 'takes at-most 2 arguments')
- idx, start = (argvals + [0])[:2]
+ idx, start = ([*argvals, 0])[:2]
try:
return obj.index(idx, start)
except ValueError:
diff --git a/yt_dlp/networking/__init__.py b/yt_dlp/networking/__init__.py
index 356712c76..1eaa0ee5f 100644
--- a/yt_dlp/networking/__init__.py
+++ b/yt_dlp/networking/__init__.py
@@ -30,7 +30,7 @@ except Exception as e:
warnings.warn(f'Failed to import "websockets" request handler: {e}' + bug_reports_message())
try:
- from . import _curlcffi # noqa: F401
+ from . import _curlcffi
except ImportError:
pass
except Exception as e:
diff --git a/yt_dlp/networking/_curlcffi.py b/yt_dlp/networking/_curlcffi.py
index f2df399e3..b1f0fb82e 100644
--- a/yt_dlp/networking/_curlcffi.py
+++ b/yt_dlp/networking/_curlcffi.py
@@ -207,7 +207,7 @@ class CurlCFFIRH(ImpersonateRequestHandler, InstanceStoreMixin):
impersonate=self._SUPPORTED_IMPERSONATE_TARGET_MAP.get(
self._get_request_target(request)),
interface=self.source_address,
- stream=True
+ stream=True,
)
except curl_cffi.requests.errors.RequestsError as e:
if e.code == CurlECode.PEER_FAILED_VERIFICATION:
diff --git a/yt_dlp/networking/_helper.py b/yt_dlp/networking/_helper.py
index 8e678b26a..fe3354ea2 100644
--- a/yt_dlp/networking/_helper.py
+++ b/yt_dlp/networking/_helper.py
@@ -235,7 +235,7 @@ def create_socks_proxy_socket(dest_addr, proxy_args, proxy_ip_addr, timeout, sou
connect_proxy_args = proxy_args.copy()
connect_proxy_args.update({'addr': sa[0], 'port': sa[1]})
sock.setproxy(**connect_proxy_args)
- if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: # noqa: E721
+ if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
@@ -251,7 +251,7 @@ def create_connection(
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
source_address=None,
*,
- _create_socket_func=_socket_connect
+ _create_socket_func=_socket_connect,
):
# Work around socket.create_connection() which tries all addresses from getaddrinfo() including IPv6.
# This filters the addresses based on the given source_address.
diff --git a/yt_dlp/networking/_requests.py b/yt_dlp/networking/_requests.py
index bf6fa634d..c69c54b3a 100644
--- a/yt_dlp/networking/_requests.py
+++ b/yt_dlp/networking/_requests.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import contextlib
import functools
import http.client
@@ -58,13 +60,13 @@ from .exceptions import (
from ..socks import ProxyError as SocksProxyError
SUPPORTED_ENCODINGS = [
- 'gzip', 'deflate'
+ 'gzip', 'deflate',
]
if brotli is not None:
SUPPORTED_ENCODINGS.append('br')
-"""
+'''
Override urllib3's behavior to not convert lower-case percent-encoded characters
to upper-case during url normalization process.
@@ -79,7 +81,7 @@ is best to avoid it in requests too for compatability reasons.
1: https://tools.ietf.org/html/rfc3986#section-2.1
2: https://github.com/streamlink/streamlink/pull/4003
-"""
+'''
class Urllib3PercentREOverride:
@@ -96,7 +98,7 @@ class Urllib3PercentREOverride:
# urllib3 >= 1.25.8 uses subn:
# https://github.com/urllib3/urllib3/commit/a2697e7c6b275f05879b60f593c5854a816489f0
-import urllib3.util.url # noqa: E305
+import urllib3.util.url
if hasattr(urllib3.util.url, 'PERCENT_RE'):
urllib3.util.url.PERCENT_RE = Urllib3PercentREOverride(urllib3.util.url.PERCENT_RE)
@@ -105,7 +107,7 @@ elif hasattr(urllib3.util.url, '_PERCENT_RE'): # urllib3 >= 2.0.0
else:
warnings.warn('Failed to patch PERCENT_RE in urllib3 (does the attribute exist?)' + bug_reports_message())
-"""
+'''
Workaround for issue in urllib.util.ssl_.py: ssl_wrap_context does not pass
server_hostname to SSLContext.wrap_socket if server_hostname is an IP,
however this is an issue because we set check_hostname to True in our SSLContext.
@@ -114,7 +116,7 @@ Monkey-patching IS_SECURETRANSPORT forces ssl_wrap_context to pass server_hostna
This has been fixed in urllib3 2.0+.
See: https://github.com/urllib3/urllib3/issues/517
-"""
+'''
if urllib3_version < (2, 0, 0):
with contextlib.suppress(Exception):
@@ -135,7 +137,7 @@ class RequestsResponseAdapter(Response):
self._requests_response = res
- def read(self, amt: int = None):
+ def read(self, amt: int | None = None):
try:
# Interact with urllib3 response directly.
return self.fp.read(amt, decode_content=True)
@@ -329,7 +331,7 @@ class RequestsRH(RequestHandler, InstanceStoreMixin):
timeout=self._calculate_timeout(request),
proxies=self._get_proxies(request),
allow_redirects=True,
- stream=True
+ stream=True,
)
except requests.exceptions.TooManyRedirects as e:
@@ -411,7 +413,7 @@ class SocksProxyManager(urllib3.PoolManager):
super().__init__(num_pools, headers, **connection_pool_kw)
self.pool_classes_by_scheme = {
'http': SocksHTTPConnectionPool,
- 'https': SocksHTTPSConnectionPool
+ 'https': SocksHTTPSConnectionPool,
}
diff --git a/yt_dlp/networking/_urllib.py b/yt_dlp/networking/_urllib.py
index ff110dc29..62995823b 100644
--- a/yt_dlp/networking/_urllib.py
+++ b/yt_dlp/networking/_urllib.py
@@ -246,8 +246,8 @@ class ProxyHandler(urllib.request.BaseHandler):
def __init__(self, proxies=None):
self.proxies = proxies
# Set default handlers
- for type in ('http', 'https', 'ftp'):
- setattr(self, '%s_open' % type, lambda r, meth=self.proxy_open: meth(r))
+ for scheme in ('http', 'https', 'ftp'):
+ setattr(self, f'{scheme}_open', lambda r, meth=self.proxy_open: meth(r))
def proxy_open(self, req):
proxy = select_proxy(req.get_full_url(), self.proxies)
@@ -385,12 +385,12 @@ class UrllibRH(RequestHandler, InstanceStoreMixin):
url=request.url,
data=request.data,
headers=dict(headers),
- method=request.method
+ method=request.method,
)
opener = self._get_instance(
proxies=self._get_proxies(request),
- cookiejar=self._get_cookiejar(request)
+ cookiejar=self._get_cookiejar(request),
)
try:
res = opener.open(urllib_req, timeout=self._calculate_timeout(request))
diff --git a/yt_dlp/networking/_websockets.py b/yt_dlp/networking/_websockets.py
index 6e235b0c6..2153080a3 100644
--- a/yt_dlp/networking/_websockets.py
+++ b/yt_dlp/networking/_websockets.py
@@ -137,7 +137,7 @@ class WebsocketsRH(WebSocketRequestHandler):
wsuri = parse_uri(request.url)
create_conn_kwargs = {
'source_address': (self.source_address, 0) if self.source_address else None,
- 'timeout': timeout
+ 'timeout': timeout,
}
proxy = select_proxy(request.url, self._get_proxies(request))
try:
@@ -147,12 +147,12 @@ class WebsocketsRH(WebSocketRequestHandler):
address=(socks_proxy_options['addr'], socks_proxy_options['port']),
_create_socket_func=functools.partial(
create_socks_proxy_socket, (wsuri.host, wsuri.port), socks_proxy_options),
- **create_conn_kwargs
+ **create_conn_kwargs,
)
else:
sock = create_connection(
address=(wsuri.host, wsuri.port),
- **create_conn_kwargs
+ **create_conn_kwargs,
)
conn = websockets.sync.client.connect(
sock=sock,
diff --git a/yt_dlp/networking/common.py b/yt_dlp/networking/common.py
index d473e16c5..a6db16715 100644
--- a/yt_dlp/networking/common.py
+++ b/yt_dlp/networking/common.py
@@ -83,8 +83,8 @@ class RequestDirector:
rh: sum(pref(rh, request) for pref in self.preferences)
for rh in self.handlers.values()
}
- self._print_verbose('Handler preferences for this request: %s' % ', '.join(
- f'{rh.RH_NAME}={pref}' for rh, pref in preferences.items()))
+ self._print_verbose('Handler preferences for this request: {}'.format(', '.join(
+ f'{rh.RH_NAME}={pref}' for rh, pref in preferences.items())))
return sorted(self.handlers.values(), key=preferences.get, reverse=True)
def _print_verbose(self, msg):
@@ -224,11 +224,11 @@ class RequestHandler(abc.ABC):
headers: HTTPHeaderDict = None,
cookiejar: YoutubeDLCookieJar = None,
timeout: float | int | None = None,
- proxies: dict = None,
- source_address: str = None,
+ proxies: dict | None = None,
+ source_address: str | None = None,
verbose: bool = False,
prefer_system_certs: bool = False,
- client_cert: dict[str, str | None] = None,
+ client_cert: dict[str, str | None] | None = None,
verify: bool = True,
legacy_ssl_support: bool = False,
**_,
@@ -341,7 +341,7 @@ class RequestHandler(abc.ABC):
"""Handle a request from start to finish. Redefine in subclasses."""
pass
- def close(self):
+ def close(self): # noqa: B027
pass
@classproperty
@@ -378,11 +378,11 @@ class Request:
self,
url: str,
data: RequestData = None,
- headers: typing.Mapping = None,
- proxies: dict = None,
- query: dict = None,
- method: str = None,
- extensions: dict = None
+ headers: typing.Mapping | None = None,
+ proxies: dict | None = None,
+ query: dict | None = None,
+ method: str | None = None,
+ extensions: dict | None = None,
):
self._headers = HTTPHeaderDict()
@@ -508,8 +508,8 @@ class Response(io.IOBase):
url: str,
headers: Mapping[str, str],
status: int = 200,
- reason: str = None,
- extensions: dict = None
+ reason: str | None = None,
+ extensions: dict | None = None,
):
self.fp = fp
@@ -527,7 +527,7 @@ class Response(io.IOBase):
def readable(self):
return self.fp.readable()
- def read(self, amt: int = None) -> bytes:
+ def read(self, amt: int | None = None) -> bytes:
# Expected errors raised here should be of type RequestError or subclasses.
# Subclasses should redefine this method with more precise error handling.
try:
diff --git a/yt_dlp/networking/exceptions.py b/yt_dlp/networking/exceptions.py
index 9037f18e2..daa9fb541 100644
--- a/yt_dlp/networking/exceptions.py
+++ b/yt_dlp/networking/exceptions.py
@@ -13,7 +13,7 @@ class RequestError(YoutubeDLError):
self,
msg: str | None = None,
cause: Exception | str | None = None,
- handler: RequestHandler = None
+ handler: RequestHandler = None,
):
self.handler = handler
self.cause = cause
diff --git a/yt_dlp/networking/impersonate.py b/yt_dlp/networking/impersonate.py
index ca66180c7..0626b3b49 100644
--- a/yt_dlp/networking/impersonate.py
+++ b/yt_dlp/networking/impersonate.py
@@ -112,8 +112,8 @@ class ImpersonateRequestHandler(RequestHandler, ABC):
return supported_target
@classproperty
- def supported_targets(self) -> tuple[ImpersonateTarget, ...]:
- return tuple(self._SUPPORTED_IMPERSONATE_TARGET_MAP.keys())
+ def supported_targets(cls) -> tuple[ImpersonateTarget, ...]:
+ return tuple(cls._SUPPORTED_IMPERSONATE_TARGET_MAP.keys())
def is_supported_target(self, target: ImpersonateTarget):
assert isinstance(target, ImpersonateTarget)
@@ -127,7 +127,7 @@ class ImpersonateRequestHandler(RequestHandler, ABC):
headers = self._merge_headers(request.headers)
if self._get_request_target(request) is not None:
# remove all headers present in std_headers
- # todo: change this to not depend on std_headers
+ # TODO: change this to not depend on std_headers
for k, v in std_headers.items():
if headers.get(k) == v:
headers.pop(k)
diff --git a/yt_dlp/options.py b/yt_dlp/options.py
index 9615bfbaa..3d4c07661 100644
--- a/yt_dlp/options.py
+++ b/yt_dlp/options.py
@@ -40,7 +40,7 @@ from .utils import (
from .version import CHANNEL, __version__
-def parseOpts(overrideArguments=None, ignore_config_files='if_override'):
+def parseOpts(overrideArguments=None, ignore_config_files='if_override'): # noqa: N803
PACKAGE_NAME = 'yt-dlp'
root = Config(create_parser())
@@ -264,7 +264,7 @@ def create_parser():
except Exception as err:
raise optparse.OptionValueError(f'wrong {opt_str} formatting; {err}')
for key in keys:
- out_dict[key] = out_dict.get(key, []) + [val] if append else val
+ out_dict[key] = [*out_dict.get(key, []), val] if append else val
setattr(parser.values, option.dest, out_dict)
def when_prefix(default):
@@ -474,14 +474,14 @@ def create_parser():
'no-attach-info-json', 'embed-thumbnail-atomicparsley', 'no-external-downloader-progress',
'embed-metadata', 'seperate-video-versions', 'no-clean-infojson', 'no-keep-subs', 'no-certifi',
'no-youtube-channel-redirect', 'no-youtube-unavailable-videos', 'no-youtube-prefer-utc-upload-date',
- 'prefer-legacy-http-handler', 'manifest-filesize-approx'
+ 'prefer-legacy-http-handler', 'manifest-filesize-approx',
}, 'aliases': {
'youtube-dl': ['all', '-multistreams', '-playlist-match-filter', '-manifest-filesize-approx'],
'youtube-dlc': ['all', '-no-youtube-channel-redirect', '-no-live-chat', '-playlist-match-filter', '-manifest-filesize-approx'],
'2021': ['2022', 'no-certifi', 'filename-sanitization'],
'2022': ['2023', 'no-external-downloader-progress', 'playlist-match-filter', 'prefer-legacy-http-handler', 'manifest-filesize-approx'],
'2023': [],
- }
+ },
}, help=(
'Options that can help keep compatibility with youtube-dl or youtube-dlc '
'configurations by reverting some of the changes made in yt-dlp. '
@@ -541,7 +541,7 @@ def create_parser():
network.add_option(
'--enable-file-urls', action='store_true',
dest='enable_file_urls', default=False,
- help='Enable file:// URLs. This is disabled by default for security reasons.'
+ help='Enable file:// URLs. This is disabled by default for security reasons.',
)
geo = optparse.OptionGroup(parser, 'Geo-restriction')
@@ -682,7 +682,7 @@ def create_parser():
help='Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it')
selection.add_option(
'--no-download-archive',
- dest='download_archive', action="store_const", const=None,
+ dest='download_archive', action='store_const', const=None,
help='Do not use archive file (default)')
selection.add_option(
'--max-downloads',
@@ -1046,7 +1046,7 @@ def create_parser():
callback_kwargs={
'allowed_keys': 'http|ftp|m3u8|dash|rtsp|rtmp|mms',
'default_key': 'default',
- 'process': str.strip
+ 'process': str.strip,
}, help=(
'Name or path of the external downloader to use (optionally) prefixed by '
'the protocols (http, ftp, m3u8, dash, rstp, rtmp, mms) to use it for. '
@@ -1060,9 +1060,9 @@ def create_parser():
metavar='NAME:ARGS', dest='external_downloader_args', default={}, type='str',
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
- 'allowed_keys': r'ffmpeg_[io]\d*|%s' % '|'.join(map(re.escape, list_external_downloaders())),
+ 'allowed_keys': r'ffmpeg_[io]\d*|{}'.format('|'.join(map(re.escape, list_external_downloaders()))),
'default_key': 'default',
- 'process': shlex.split
+ 'process': shlex.split,
}, help=(
'Give these arguments to the external downloader. '
'Specify the downloader name and the arguments separated by a colon ":". '
@@ -1251,7 +1251,7 @@ def create_parser():
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': '(download|postprocess)(-title)?',
- 'default_key': 'download'
+ 'default_key': 'download',
}, help=(
'Template for progress outputs, optionally prefixed with one of "download:" (default), '
'"download-title:" (the console title), "postprocess:", or "postprocess-title:". '
@@ -1317,8 +1317,8 @@ def create_parser():
metavar='[TYPES:]PATH', dest='paths', default={}, type='str',
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
- 'allowed_keys': 'home|temp|%s' % '|'.join(map(re.escape, OUTTMPL_TYPES.keys())),
- 'default_key': 'home'
+ 'allowed_keys': 'home|temp|{}'.format('|'.join(map(re.escape, OUTTMPL_TYPES.keys()))),
+ 'default_key': 'home',
}, help=(
'The paths where the files should be downloaded. '
'Specify the type of file and the path separated by a colon ":". '
@@ -1333,7 +1333,7 @@ def create_parser():
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': '|'.join(map(re.escape, OUTTMPL_TYPES.keys())),
- 'default_key': 'default'
+ 'default_key': 'default',
}, help='Output filename template; see "OUTPUT TEMPLATE" for details')
filesystem.add_option(
'--output-na-placeholder',
@@ -1575,7 +1575,7 @@ def create_parser():
'allowed_keys': r'\w+(?:\+\w+)?',
'default_key': 'default-compat',
'process': shlex.split,
- 'multiple_keys': False
+ 'multiple_keys': False,
}, help=(
'Give these arguments to the postprocessors. '
'Specify the postprocessor/executable name and the arguments separated by a colon ":" '
@@ -1724,8 +1724,8 @@ def create_parser():
'--convert-subs', '--convert-sub', '--convert-subtitles',
metavar='FORMAT', dest='convertsubtitles', default=None,
help=(
- 'Convert the subtitles to another format (currently supported: %s) '
- '(Alias: --convert-subtitles)' % ', '.join(sorted(FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS))))
+ 'Convert the subtitles to another format (currently supported: {}) '
+ '(Alias: --convert-subtitles)'.format(', '.join(sorted(FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)))))
postproc.add_option(
'--convert-thumbnails',
metavar='FORMAT', dest='convertthumbnails', default=None,
@@ -1772,7 +1772,7 @@ def create_parser():
action='callback', callback=_list_from_options_callback,
callback_kwargs={
'delim': None,
- 'process': lambda val: dict(_postprocessor_opts_parser(*val.split(':', 1)))
+ 'process': lambda val: dict(_postprocessor_opts_parser(*val.split(':', 1))),
}, help=(
'The (case sensitive) name of plugin postprocessors to be enabled, '
'and (optionally) arguments to be passed to it, separated by a colon ":". '
@@ -1794,7 +1794,7 @@ def create_parser():
dest='sponsorblock_mark', default=set(), action='callback', type='str',
callback=_set_from_options_callback, callback_kwargs={
'allowed_values': SponsorBlockPP.CATEGORIES.keys(),
- 'aliases': {'default': ['all']}
+ 'aliases': {'default': ['all']},
}, help=(
'SponsorBlock categories to create chapters for, separated by commas. '
f'Available categories are {", ".join(SponsorBlockPP.CATEGORIES.keys())}, all and default (=all). '
@@ -1808,7 +1808,7 @@ def create_parser():
# Note: From https://wiki.sponsor.ajay.app/w/Types:
# The filler category is very aggressive.
# It is strongly recommended to not use this in a client by default.
- 'aliases': {'default': ['all', '-filler']}
+ 'aliases': {'default': ['all', '-filler']},
}, help=(
'SponsorBlock categories to be removed from the video file, separated by commas. '
'If a category is present in both mark and remove, remove takes precedence. '
@@ -1879,7 +1879,7 @@ def create_parser():
extractor.add_option(
'--hls-split-discontinuity',
dest='hls_split_discontinuity', action='store_true', default=False,
- help='Split HLS playlists to different formats at discontinuities such as ad breaks'
+ help='Split HLS playlists to different formats at discontinuities such as ad breaks',
)
extractor.add_option(
'--no-hls-split-discontinuity',
@@ -1894,7 +1894,7 @@ def create_parser():
callback_kwargs={
'multiple_keys': False,
'process': lambda val: dict(
- _extractor_arg_parser(*arg.split('=', 1)) for arg in val.split(';'))
+ _extractor_arg_parser(*arg.split('=', 1)) for arg in val.split(';')),
}, help=(
'Pass ARGS arguments to the IE_KEY extractor. See "EXTRACTOR ARGUMENTS" for details. '
'You can use this option multiple times to give arguments for different extractors'))
diff --git a/yt_dlp/postprocessor/__init__.py b/yt_dlp/postprocessor/__init__.py
index bfe9df733..164540b5d 100644
--- a/yt_dlp/postprocessor/__init__.py
+++ b/yt_dlp/postprocessor/__init__.py
@@ -43,5 +43,5 @@ def get_postprocessor(key):
globals().update(_PLUGIN_CLASSES)
-__all__ = [name for name in globals().keys() if name.endswith('PP')]
+__all__ = [name for name in globals() if name.endswith('PP')]
__all__.extend(('PostProcessor', 'FFmpegPostProcessor'))
diff --git a/yt_dlp/postprocessor/common.py b/yt_dlp/postprocessor/common.py
index 8cef86c43..eeeece82c 100644
--- a/yt_dlp/postprocessor/common.py
+++ b/yt_dlp/postprocessor/common.py
@@ -65,7 +65,7 @@ class PostProcessor(metaclass=PostProcessorMetaClass):
def to_screen(self, text, prefix=True, *args, **kwargs):
if self._downloader:
- tag = '[%s] ' % self.PP_NAME if prefix else ''
+ tag = f'[{self.PP_NAME}] ' if prefix else ''
return self._downloader.to_screen(f'{tag}{text}', *args, **kwargs)
def report_warning(self, text, *args, **kwargs):
@@ -127,7 +127,7 @@ class PostProcessor(metaclass=PostProcessorMetaClass):
if allowed[format_type]:
return func(self, info)
else:
- self.to_screen('Skipping %s' % format_type)
+ self.to_screen(f'Skipping {format_type}')
return [], info
return wrapper
return decorator
@@ -174,7 +174,7 @@ class PostProcessor(metaclass=PostProcessorMetaClass):
self._progress_hooks.append(ph)
def report_progress(self, s):
- s['_default_template'] = '%(postprocessor)s %(status)s' % s
+ s['_default_template'] = '%(postprocessor)s %(status)s' % s # noqa: UP031
if not self._downloader:
return
diff --git a/yt_dlp/postprocessor/embedthumbnail.py b/yt_dlp/postprocessor/embedthumbnail.py
index 9c5372956..673a92468 100644
--- a/yt_dlp/postprocessor/embedthumbnail.py
+++ b/yt_dlp/postprocessor/embedthumbnail.py
@@ -13,7 +13,6 @@ from ..utils import (
check_executable,
encodeArgument,
encodeFilename,
- error_to_compat_str,
prepend_extension,
shell_quote,
)
@@ -48,7 +47,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
if mobj is None:
return guess()
except PostProcessingError as err:
- self.report_warning('unable to find the thumbnail resolution; %s' % error_to_compat_str(err))
+ self.report_warning(f'unable to find the thumbnail resolution; {err}')
return guess()
return int(mobj.group('w')), int(mobj.group('h'))
@@ -104,12 +103,12 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
old_stream, new_stream = self.get_stream_number(
filename, ('tags', 'mimetype'), mimetype)
if old_stream is not None:
- options.extend(['-map', '-0:%d' % old_stream])
+ options.extend(['-map', f'-0:{old_stream}'])
new_stream -= 1
options.extend([
'-attach', self._ffmpeg_filename_argument(thumbnail_filename),
- '-metadata:s:%d' % new_stream, 'mimetype=%s' % mimetype,
- '-metadata:s:%d' % new_stream, 'filename=cover.%s' % thumbnail_ext])
+ f'-metadata:s:{new_stream}', f'mimetype={mimetype}',
+ f'-metadata:s:{new_stream}', f'filename=cover.{thumbnail_ext}'])
self._report_run('ffmpeg', filename)
self.run_ffmpeg(filename, temp_filename, options)
@@ -132,7 +131,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
meta.save()
temp_filename = filename
except Exception as err:
- self.report_warning('unable to embed using mutagen; %s' % error_to_compat_str(err))
+ self.report_warning(f'unable to embed using mutagen; {err}')
success = False
# Method 2: Use AtomicParsley
@@ -157,7 +156,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
cmd += [encodeArgument(o) for o in self._configuration_args('AtomicParsley')]
self._report_run('atomicparsley', filename)
- self.write_debug('AtomicParsley command line: %s' % shell_quote(cmd))
+ self.write_debug(f'AtomicParsley command line: {shell_quote(cmd)}')
stdout, stderr, returncode = Popen.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if returncode:
self.report_warning(f'Unable to embed thumbnails using AtomicParsley; {stderr.strip()}')
@@ -178,9 +177,9 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
old_stream, new_stream = self.get_stream_number(
filename, ('disposition', 'attached_pic'), 1)
if old_stream is not None:
- options.extend(['-map', '-0:%d' % old_stream])
+ options.extend(['-map', f'-0:{old_stream}'])
new_stream -= 1
- options.extend(['-disposition:%s' % new_stream, 'attached_pic'])
+ options.extend([f'-disposition:{new_stream}', 'attached_pic'])
self._report_run('ffmpeg', filename)
self.run_ffmpeg_multiple_files([filename, thumbnail_filename], temp_filename, options)
@@ -196,7 +195,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
f = {'opus': OggOpus, 'flac': FLAC, 'ogg': OggVorbis}[info['ext']](filename)
pic = Picture()
- pic.mime = 'image/%s' % imghdr.what(thumbnail_filename)
+ pic.mime = f'image/{imghdr.what(thumbnail_filename)}'
with open(thumbnail_filename, 'rb') as thumbfile:
pic.data = thumbfile.read()
pic.type = 3 # front cover
diff --git a/yt_dlp/postprocessor/exec.py b/yt_dlp/postprocessor/exec.py
index c2e73fbab..1f0a0015e 100644
--- a/yt_dlp/postprocessor/exec.py
+++ b/yt_dlp/postprocessor/exec.py
@@ -1,6 +1,5 @@
from .common import PostProcessor
-from ..compat import compat_shlex_quote
-from ..utils import Popen, PostProcessingError, variadic
+from ..utils import Popen, PostProcessingError, shell_quote, variadic
class ExecPP(PostProcessor):
@@ -19,7 +18,7 @@ class ExecPP(PostProcessor):
if filepath:
if '{}' not in cmd:
cmd += ' {}'
- cmd = cmd.replace('{}', compat_shlex_quote(filepath))
+ cmd = cmd.replace('{}', shell_quote(filepath))
return cmd
def run(self, info):
diff --git a/yt_dlp/postprocessor/ffmpeg.py b/yt_dlp/postprocessor/ffmpeg.py
index 7d7f3f0eb..1ed37af51 100644
--- a/yt_dlp/postprocessor/ffmpeg.py
+++ b/yt_dlp/postprocessor/ffmpeg.py
@@ -61,7 +61,7 @@ ACODECS = {
def create_mapping_re(supported):
- return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:%s)\s*' % '|'.join(supported)))
+ return re.compile(r'{0}(?:/{0})*$'.format(r'(?:\s*\w+\s*>)?\s*(?:{})\s*'.format('|'.join(supported))))
def resolve_mapping(source, mapping):
@@ -119,7 +119,7 @@ class FFmpegPostProcessor(PostProcessor):
filename = os.path.basename(location)
basename = next((p for p in programs if p in filename), 'ffmpeg')
dirname = os.path.dirname(os.path.abspath(location))
- if basename in self._ffmpeg_to_avconv.keys():
+ if basename in self._ffmpeg_to_avconv:
self._prefer_ffmpeg = True
paths = {p: os.path.join(dirname, p) for p in programs}
@@ -169,12 +169,12 @@ class FFmpegPostProcessor(PostProcessor):
@functools.cached_property
def basename(self):
- self._version # run property
+ _ = self._version # run property
return self.basename
@functools.cached_property
def probe_basename(self):
- self._probe_version # run property
+ _ = self._probe_version # run property
return self.probe_basename
def _get_version(self, kind):
@@ -342,7 +342,7 @@ class FFmpegPostProcessor(PostProcessor):
cmd += [encodeArgument('-loglevel'), encodeArgument('repeat+info')]
def make_args(file, args, name, number):
- keys = ['_%s%d' % (name, number), '_%s' % name]
+ keys = [f'_{name}{number}', f'_{name}']
if name == 'o':
args += ['-movflags', '+faststart']
if number == 1:
@@ -359,7 +359,7 @@ class FFmpegPostProcessor(PostProcessor):
make_args(path, list(opts), arg_type, i + 1)
for i, (path, opts) in enumerate(path_opts) if path)
- self.write_debug('ffmpeg command line: %s' % shell_quote(cmd))
+ self.write_debug(f'ffmpeg command line: {shell_quote(cmd)}')
_, stderr, returncode = Popen.run(
cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
if returncode not in variadic(expected_retcodes):
@@ -437,7 +437,7 @@ class FFmpegPostProcessor(PostProcessor):
class FFmpegExtractAudioPP(FFmpegPostProcessor):
- COMMON_AUDIO_EXTS = MEDIA_EXTENSIONS.common_audio + ('wma', )
+ COMMON_AUDIO_EXTS = (*MEDIA_EXTENSIONS.common_audio, 'wma')
SUPPORTED_EXTS = tuple(ACODECS.keys())
FORMAT_RE = create_mapping_re(('best', *SUPPORTED_EXTS))
@@ -474,7 +474,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
acodec_opts = []
else:
acodec_opts = ['-acodec', codec]
- opts = ['-vn'] + acodec_opts + more_opts
+ opts = ['-vn', *acodec_opts, *more_opts]
try:
FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts)
except FFmpegPostProcessorError as err:
@@ -523,7 +523,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
temp_path = prepend_extension(path, 'temp')
if (self._nopostoverwrites and os.path.exists(encodeFilename(new_path))
and os.path.exists(encodeFilename(orig_path))):
- self.to_screen('Post-process file %s exists, skipping' % new_path)
+ self.to_screen(f'Post-process file {new_path} exists, skipping')
return [], information
self.to_screen(f'Destination: {new_path}')
@@ -641,7 +641,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
if not sub_langs:
return [], info
- input_files = [filename] + sub_filenames
+ input_files = [filename, *sub_filenames]
opts = [
*self.stream_copy_opts(ext=info['ext']),
@@ -650,15 +650,15 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
'-map', '-0:s',
]
for i, (lang, name) in enumerate(zip(sub_langs, sub_names)):
- opts.extend(['-map', '%d:0' % (i + 1)])
+ opts.extend(['-map', f'{i + 1}:0'])
lang_code = ISO639Utils.short2long(lang) or lang
- opts.extend(['-metadata:s:s:%d' % i, 'language=%s' % lang_code])
+ opts.extend([f'-metadata:s:s:{i}', f'language={lang_code}'])
if name:
- opts.extend(['-metadata:s:s:%d' % i, 'handler_name=%s' % name,
- '-metadata:s:s:%d' % i, 'title=%s' % name])
+ opts.extend([f'-metadata:s:s:{i}', f'handler_name={name}',
+ f'-metadata:s:s:{i}', f'title={name}'])
temp_filename = prepend_extension(filename, 'temp')
- self.to_screen('Embedding subtitles in "%s"' % filename)
+ self.to_screen(f'Embedding subtitles in "{filename}"')
self.run_ffmpeg_multiple_files(input_files, temp_filename, opts)
os.replace(temp_filename, filename)
@@ -707,7 +707,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
return [], info
temp_filename = prepend_extension(filename, 'temp')
- self.to_screen('Adding metadata to "%s"' % filename)
+ self.to_screen(f'Adding metadata to "{filename}"')
self.run_ffmpeg_multiple_files(
(filename, metadata_filename), temp_filename,
itertools.chain(self._options(info['ext']), *options))
@@ -728,7 +728,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
metadata_file_content += 'END=%d\n' % (chapter['end_time'] * 1000)
chapter_title = chapter.get('title')
if chapter_title:
- metadata_file_content += 'title=%s\n' % ffmpeg_escape(chapter_title)
+ metadata_file_content += f'title={ffmpeg_escape(chapter_title)}\n'
f.write(metadata_file_content)
yield ('-map_metadata', '1')
@@ -738,7 +738,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
def add(meta_list, info_list=None):
value = next((
- info[key] for key in [f'{meta_prefix}_'] + list(variadic(info_list or meta_list))
+ info[key] for key in [f'{meta_prefix}_', *variadic(info_list or meta_list)]
if info.get(key) is not None), None)
if value not in ('', None):
value = ', '.join(map(str, variadic(value)))
@@ -807,7 +807,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
old_stream, new_stream = self.get_stream_number(info['filepath'], ('tags', 'mimetype'), 'application/json')
if old_stream is not None:
- yield ('-map', '-0:%d' % old_stream)
+ yield ('-map', f'-0:{old_stream}')
new_stream -= 1
yield (
@@ -834,8 +834,8 @@ class FFmpegMergerPP(FFmpegPostProcessor):
args.extend([f'-bsf:a:{audio_streams}', 'aac_adtstoasc'])
audio_streams += 1
if fmt.get('vcodec') != 'none':
- args.extend(['-map', '%u:v:0' % (i)])
- self.to_screen('Merging formats into "%s"' % filename)
+ args.extend(['-map', f'{i}:v:0'])
+ self.to_screen(f'Merging formats into "{filename}"')
self.run_ffmpeg_multiple_files(info['__files_to_merge'], temp_filename, args)
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
return info['__files_to_merge'], info
@@ -848,10 +848,9 @@ class FFmpegMergerPP(FFmpegPostProcessor):
required_version = '10-0'
if is_outdated_version(
self._versions[self.basename], required_version):
- warning = ('Your copy of %s is outdated and unable to properly mux separate video and audio files, '
+ warning = (f'Your copy of {self.basename} is outdated and unable to properly mux separate video and audio files, '
'yt-dlp will download single file media. '
- 'Update %s to version %s or newer to fix this.') % (
- self.basename, self.basename, required_version)
+ f'Update {self.basename} to version {required_version} or newer to fix this.')
self.report_warning(warning)
return False
return True
@@ -873,7 +872,7 @@ class FFmpegFixupStretchedPP(FFmpegFixupPostProcessor):
stretched_ratio = info.get('stretched_ratio')
if stretched_ratio not in (None, 1):
self._fixup('Fixing aspect ratio', info['filepath'], [
- *self.stream_copy_opts(), '-aspect', '%f' % stretched_ratio])
+ *self.stream_copy_opts(), '-aspect', f'{stretched_ratio:f}'])
return [], info
@@ -925,7 +924,7 @@ class FFmpegFixupTimestampPP(FFmpegFixupPostProcessor):
opts = ['-vf', 'setpts=PTS-STARTPTS']
else:
opts = ['-c', 'copy', '-bsf', 'setts=ts=TS-STARTPTS']
- self._fixup('Fixing frame timestamp', info['filepath'], opts + [*self.stream_copy_opts(False), '-ss', self.trim])
+ self._fixup('Fixing frame timestamp', info['filepath'], [*opts, *self.stream_copy_opts(False), '-ss', self.trim])
return [], info
@@ -970,7 +969,7 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
continue
ext = sub['ext']
if ext == new_ext:
- self.to_screen('Subtitle file for %s is already in the requested format' % new_ext)
+ self.to_screen(f'Subtitle file for {new_ext} is already in the requested format')
continue
elif ext == 'json':
self.to_screen(
@@ -1060,7 +1059,7 @@ class FFmpegSplitChaptersPP(FFmpegPostProcessor):
in_file = info['filepath']
if self._force_keyframes and len(chapters) > 1:
in_file = self.force_keyframes(in_file, (c['start_time'] for c in chapters))
- self.to_screen('Splitting video by chapters; %d chapters found' % len(chapters))
+ self.to_screen(f'Splitting video by chapters; {len(chapters)} chapters found')
for idx, chapter in enumerate(chapters):
destination, opts = self._ffmpeg_args_for_chapter(idx + 1, chapter, info)
self.real_run_ffmpeg([(in_file, opts)], [(destination, self.stream_copy_opts())])
@@ -1087,7 +1086,7 @@ class FFmpegThumbnailsConvertorPP(FFmpegPostProcessor):
_, thumbnail_ext = os.path.splitext(thumbnail_filename)
if thumbnail_ext:
if thumbnail_ext.lower() != '.webp' and imghdr.what(thumbnail_filename) == 'webp':
- self.to_screen('Correcting thumbnail "%s" extension to webp' % thumbnail_filename)
+ self.to_screen(f'Correcting thumbnail "{thumbnail_filename}" extension to webp')
webp_filename = replace_extension(thumbnail_filename, 'webp')
os.replace(thumbnail_filename, webp_filename)
info['thumbnails'][idx]['filepath'] = webp_filename
diff --git a/yt_dlp/postprocessor/modify_chapters.py b/yt_dlp/postprocessor/modify_chapters.py
index f5219868c..d82685ed8 100644
--- a/yt_dlp/postprocessor/modify_chapters.py
+++ b/yt_dlp/postprocessor/modify_chapters.py
@@ -54,7 +54,7 @@ class ModifyChaptersPP(FFmpegPostProcessor):
self.write_debug('Expected and actual durations mismatch')
concat_opts = self._make_concat_opts(cuts, real_duration)
- self.write_debug('Concat spec = %s' % ', '.join(f'{c.get("inpoint", 0.0)}-{c.get("outpoint", "inf")}' for c in concat_opts))
+ self.write_debug('Concat spec = {}'.format(', '.join(f'{c.get("inpoint", 0.0)}-{c.get("outpoint", "inf")}' for c in concat_opts)))
def remove_chapters(file, is_sub):
return file, self.remove_chapters(file, cuts, concat_opts, self._force_keyframes and not is_sub)
diff --git a/yt_dlp/postprocessor/movefilesafterdownload.py b/yt_dlp/postprocessor/movefilesafterdownload.py
index 23b09248c..35e87051b 100644
--- a/yt_dlp/postprocessor/movefilesafterdownload.py
+++ b/yt_dlp/postprocessor/movefilesafterdownload.py
@@ -34,16 +34,15 @@ class MoveFilesAfterDownloadPP(PostProcessor):
if os.path.abspath(encodeFilename(oldfile)) == os.path.abspath(encodeFilename(newfile)):
continue
if not os.path.exists(encodeFilename(oldfile)):
- self.report_warning('File "%s" cannot be found' % oldfile)
+ self.report_warning(f'File "{oldfile}" cannot be found')
continue
if os.path.exists(encodeFilename(newfile)):
if self.get_param('overwrites', True):
- self.report_warning('Replacing existing file "%s"' % newfile)
+ self.report_warning(f'Replacing existing file "{newfile}"')
os.remove(encodeFilename(newfile))
else:
self.report_warning(
- 'Cannot move file "%s" out of temporary directory since "%s" already exists. '
- % (oldfile, newfile))
+ f'Cannot move file "{oldfile}" out of temporary directory since "{newfile}" already exists. ')
continue
make_dir(newfile, PostProcessingError)
self.to_screen(f'Moving file "{oldfile}" to "{newfile}"')
diff --git a/yt_dlp/postprocessor/sponskrub.py b/yt_dlp/postprocessor/sponskrub.py
index ff50d5b4f..525b6392a 100644
--- a/yt_dlp/postprocessor/sponskrub.py
+++ b/yt_dlp/postprocessor/sponskrub.py
@@ -35,7 +35,7 @@ class SponSkrubPP(PostProcessor):
if not ignoreerror and self.path is None:
if path:
- raise PostProcessingError('sponskrub not found in "%s"' % path)
+ raise PostProcessingError(f'sponskrub not found in "{path}"')
else:
raise PostProcessingError('sponskrub not found. Please install or provide the path using --sponskrub-path')
@@ -83,7 +83,7 @@ class SponSkrubPP(PostProcessor):
cmd += ['--', information['id'], filename, temp_filename]
cmd = [encodeArgument(i) for i in cmd]
- self.write_debug('sponskrub command line: %s' % shell_quote(cmd))
+ self.write_debug(f'sponskrub command line: {shell_quote(cmd)}')
stdout, _, returncode = Popen.run(cmd, text=True, stdout=None if self.get_param('verbose') else subprocess.PIPE)
if not returncode:
diff --git a/yt_dlp/postprocessor/sponsorblock.py b/yt_dlp/postprocessor/sponsorblock.py
index 6ba87cd67..6cf9ab62e 100644
--- a/yt_dlp/postprocessor/sponsorblock.py
+++ b/yt_dlp/postprocessor/sponsorblock.py
@@ -27,7 +27,7 @@ class SponsorBlockPP(FFmpegPostProcessor):
'filler': 'Filler Tangent',
'interaction': 'Interaction Reminder',
'music_offtopic': 'Non-Music Section',
- **NON_SKIPPABLE_CATEGORIES
+ **NON_SKIPPABLE_CATEGORIES,
}
def __init__(self, downloader, categories=None, api='https://sponsor.ajay.app'):
@@ -57,7 +57,7 @@ class SponsorBlockPP(FFmpegPostProcessor):
if start_end[0] <= 1:
start_end[0] = 0
# Make POI chapters 1 sec so that we can properly mark them
- if s['category'] in self.POI_CATEGORIES.keys():
+ if s['category'] in self.POI_CATEGORIES:
start_end[1] += 1
# Ignore milliseconds difference at the end.
# Never allow the segment to exceed the video.
@@ -91,12 +91,12 @@ class SponsorBlockPP(FFmpegPostProcessor):
return sponsor_chapters
def _get_sponsor_segments(self, video_id, service):
- hash = hashlib.sha256(video_id.encode('ascii')).hexdigest()
+ video_hash = hashlib.sha256(video_id.encode('ascii')).hexdigest()
# SponsorBlock API recommends using first 4 hash characters.
- url = f'{self._API_URL}/api/skipSegments/{hash[:4]}?' + urllib.parse.urlencode({
+ url = f'{self._API_URL}/api/skipSegments/{video_hash[:4]}?' + urllib.parse.urlencode({
'service': service,
'categories': json.dumps(self._categories),
- 'actionTypes': json.dumps(['skip', 'poi', 'chapter'])
+ 'actionTypes': json.dumps(['skip', 'poi', 'chapter']),
})
for d in self._download_json(url) or []:
if d['videoID'] == video_id:
diff --git a/yt_dlp/socks.py b/yt_dlp/socks.py
index b4957ac2e..e553a5f1f 100644
--- a/yt_dlp/socks.py
+++ b/yt_dlp/socks.py
@@ -60,8 +60,8 @@ class ProxyError(OSError):
class InvalidVersionError(ProxyError):
def __init__(self, expected_version, got_version):
- msg = ('Invalid response version from server. Expected {:02x} got '
- '{:02x}'.format(expected_version, got_version))
+ msg = (f'Invalid response version from server. Expected {expected_version:02x} got '
+ f'{got_version:02x}')
super().__init__(0, msg)
@@ -71,7 +71,7 @@ class Socks4Error(ProxyError):
CODES = {
91: 'request rejected or failed',
92: 'request rejected because SOCKS server cannot connect to identd on the client',
- 93: 'request rejected because the client program and identd report different user-ids'
+ 93: 'request rejected because the client program and identd report different user-ids',
}
@@ -88,7 +88,7 @@ class Socks5Error(ProxyError):
0x07: 'Command not supported',
0x08: 'Address type not supported',
0xFE: 'unknown username or invalid password',
- 0xFF: 'all offered authentication methods were rejected'
+ 0xFF: 'all offered authentication methods were rejected',
}
diff --git a/yt_dlp/update.py b/yt_dlp/update.py
index ca70f69a7..8c6790d61 100644
--- a/yt_dlp/update.py
+++ b/yt_dlp/update.py
@@ -13,7 +13,7 @@ from dataclasses import dataclass
from zipimport import zipimporter
from .compat import functools # isort: split
-from .compat import compat_realpath, compat_shlex_quote
+from .compat import compat_realpath
from .networking import Request
from .networking.exceptions import HTTPError, network_exceptions
from .utils import (
@@ -200,7 +200,7 @@ class UpdateInfo:
requested_version: str | None = None
commit: str | None = None
- binary_name: str | None = _get_binary_name()
+ binary_name: str | None = _get_binary_name() # noqa: RUF009: Always returns the same value
checksum: str | None = None
_has_update = True
@@ -381,7 +381,7 @@ class Updater:
has_update = False
resolved_tag = requested_version if self.requested_tag == 'latest' else self.requested_tag
- current_label = _make_label(self._origin, self._channel.partition("@")[2] or self.current_version, self.current_version)
+ current_label = _make_label(self._origin, self._channel.partition('@')[2] or self.current_version, self.current_version)
requested_label = _make_label(self.requested_repo, resolved_tag, requested_version)
latest_or_requested = f'{"Latest" if self.requested_tag == "latest" else "Requested"} version: {requested_label}'
if not has_update:
@@ -515,7 +515,7 @@ class Updater:
os.chmod(self.filename, mask)
except OSError:
return self._report_error(
- f'Unable to set permissions. Run: sudo chmod a+rx {compat_shlex_quote(self.filename)}')
+ f'Unable to set permissions. Run: sudo chmod a+rx {shell_quote(self.filename)}')
self.ydl.to_screen(f'Updated yt-dlp to {update_label}')
return True
@@ -559,7 +559,7 @@ class Updater:
tag = self.requested_tag
self._report_error(
f'Unable to {action}{delim} visit https://github.com/{self.requested_repo}/releases/'
- + tag if tag == "latest" else f"tag/{tag}", True)
+ + tag if tag == 'latest' else f'tag/{tag}', True)
# XXX: Everything below this line in this class is deprecated / for compat only
@property
diff --git a/yt_dlp/utils/_legacy.py b/yt_dlp/utils/_legacy.py
index a23248bbe..356e58022 100644
--- a/yt_dlp/utils/_legacy.py
+++ b/yt_dlp/utils/_legacy.py
@@ -17,7 +17,7 @@ from ..networking._urllib import HTTPHandler
# isort: split
from .networking import escape_rfc3986 # noqa: F401
-from .networking import normalize_url as escape_url # noqa: F401
+from .networking import normalize_url as escape_url
from .networking import random_user_agent, std_headers # noqa: F401
from ..cookies import YoutubeDLCookieJar # noqa: F401
from ..networking._urllib import PUTRequest # noqa: F401
@@ -167,7 +167,7 @@ def decode_png(png_data):
chunks.append({
'type': chunk_type,
'length': length,
- 'data': chunk_data
+ 'data': chunk_data,
})
ihdr = chunks[0]['data']
@@ -195,15 +195,15 @@ def decode_png(png_data):
return pixels[y][x]
for y in range(height):
- basePos = y * (1 + stride)
- filter_type = decompressed_data[basePos]
+ base_pos = y * (1 + stride)
+ filter_type = decompressed_data[base_pos]
current_row = []
pixels.append(current_row)
for x in range(stride):
- color = decompressed_data[1 + basePos + x]
+ color = decompressed_data[1 + base_pos + x]
basex = y * stride + x
left = 0
up = 0
diff --git a/yt_dlp/utils/_utils.py b/yt_dlp/utils/_utils.py
index 42803bb6d..664675a09 100644
--- a/yt_dlp/utils/_utils.py
+++ b/yt_dlp/utils/_utils.py
@@ -53,7 +53,7 @@ from ..compat import (
)
from ..dependencies import xattr
-__name__ = __name__.rsplit('.', 1)[0] # Pretend to be the parent module
+__name__ = __name__.rsplit('.', 1)[0] # noqa: A001: Pretend to be the parent module
# This is not clearly defined otherwise
compiled_regex_type = type(re.compile(''))
@@ -90,7 +90,7 @@ TIMEZONE_NAMES = {
'EST': -5, 'EDT': -4, # Eastern
'CST': -6, 'CDT': -5, # Central
'MST': -7, 'MDT': -6, # Mountain
- 'PST': -8, 'PDT': -7 # Pacific
+ 'PST': -8, 'PDT': -7, # Pacific
}
# needed for sanitizing filenames in restricted mode
@@ -215,7 +215,7 @@ def write_json_file(obj, fn):
def find_xpath_attr(node, xpath, key, val=None):
""" Find the xpath xpath[@key=val] """
assert re.match(r'^[a-zA-Z_-]+$', key)
- expr = xpath + ('[@%s]' % key if val is None else f"[@{key}='{val}']")
+ expr = xpath + (f'[@{key}]' if val is None else f"[@{key}='{val}']")
return node.find(expr)
# On python2.6 the xml.etree.ElementTree.Element methods don't support
@@ -230,7 +230,7 @@ def xpath_with_ns(path, ns_map):
replaced.append(c[0])
else:
ns, tag = c
- replaced.append('{%s}%s' % (ns_map[ns], tag))
+ replaced.append(f'{{{ns_map[ns]}}}{tag}')
return '/'.join(replaced)
@@ -251,7 +251,7 @@ def xpath_element(node, xpath, name=None, fatal=False, default=NO_DEFAULT):
return default
elif fatal:
name = xpath if name is None else name
- raise ExtractorError('Could not find XML element %s' % name)
+ raise ExtractorError(f'Could not find XML element {name}')
else:
return None
return n
@@ -266,7 +266,7 @@ def xpath_text(node, xpath, name=None, fatal=False, default=NO_DEFAULT):
return default
elif fatal:
name = xpath if name is None else name
- raise ExtractorError('Could not find XML element\'s text %s' % name)
+ raise ExtractorError(f'Could not find XML element\'s text {name}')
else:
return None
return n.text
@@ -279,7 +279,7 @@ def xpath_attr(node, xpath, key, name=None, fatal=False, default=NO_DEFAULT):
return default
elif fatal:
name = f'{xpath}[@{key}]' if name is None else name
- raise ExtractorError('Could not find XML attribute %s' % name)
+ raise ExtractorError(f'Could not find XML attribute {name}')
else:
return None
return n.attrib[key]
@@ -320,14 +320,14 @@ def get_element_html_by_attribute(attribute, value, html, **kargs):
def get_elements_by_class(class_name, html, **kargs):
"""Return the content of all tags with the specified class in the passed HTML document as a list"""
return get_elements_by_attribute(
- 'class', r'[^\'"]*(?<=[\'"\s])%s(?=[\'"\s])[^\'"]*' % re.escape(class_name),
+ 'class', rf'[^\'"]*(?<=[\'"\s]){re.escape(class_name)}(?=[\'"\s])[^\'"]*',
html, escape_value=False)
def get_elements_html_by_class(class_name, html):
"""Return the html of all tags with the specified class in the passed HTML document as a list"""
return get_elements_html_by_attribute(
- 'class', r'[^\'"]*(?<=[\'"\s])%s(?=[\'"\s])[^\'"]*' % re.escape(class_name),
+ 'class', rf'[^\'"]*(?<=[\'"\s]){re.escape(class_name)}(?=[\'"\s])[^\'"]*',
html, escape_value=False)
@@ -364,7 +364,7 @@ def get_elements_text_and_html_by_attribute(attribute, value, html, *, tag=r'[\w
yield (
unescapeHTML(re.sub(r'^(?P<q>["\'])(?P<content>.*)(?P=q)$', r'\g<content>', content, flags=re.DOTALL)),
- whole
+ whole,
)
@@ -407,7 +407,7 @@ class HTMLBreakOnClosingTagParser(html.parser.HTMLParser):
else:
raise compat_HTMLParseError(f'matching opening tag for closing {tag} tag not found')
if not self.tagstack:
- raise self.HTMLBreakOnClosingTagException()
+ raise self.HTMLBreakOnClosingTagException
# XXX: This should be far less strict
@@ -587,7 +587,7 @@ def sanitize_open(filename, open_mode):
# FIXME: An exclusive lock also locks the file from being read.
# Since windows locks are mandatory, don't lock the file on windows (for now).
# Ref: https://github.com/yt-dlp/yt-dlp/issues/3124
- raise LockingUnsupportedError()
+ raise LockingUnsupportedError
stream = locked_file(filename, open_mode, block=False).__enter__()
except OSError:
stream = open(filename, open_mode)
@@ -717,9 +717,9 @@ def extract_basic_auth(url):
return url, None
url = urllib.parse.urlunsplit(parts._replace(netloc=(
parts.hostname if parts.port is None
- else '%s:%d' % (parts.hostname, parts.port))))
+ else f'{parts.hostname}:{parts.port}')))
auth_payload = base64.b64encode(
- ('%s:%s' % (parts.username, parts.password or '')).encode())
+ ('{}:{}'.format(parts.username, parts.password or '')).encode())
return url, f'Basic {auth_payload.decode()}'
@@ -758,7 +758,7 @@ def _htmlentity_transform(entity_with_semicolon):
numstr = mobj.group(1)
if numstr.startswith('x'):
base = 16
- numstr = '0%s' % numstr
+ numstr = f'0{numstr}'
else:
base = 10
# See https://github.com/ytdl-org/youtube-dl/issues/7518
@@ -766,7 +766,7 @@ def _htmlentity_transform(entity_with_semicolon):
return chr(int(numstr, base))
# Unknown entity in name, return its literal representation
- return '&%s;' % entity
+ return f'&{entity};'
def unescapeHTML(s):
@@ -970,7 +970,7 @@ class ExtractorError(YoutubeDLError):
class UnsupportedError(ExtractorError):
def __init__(self, url):
super().__init__(
- 'Unsupported URL: %s' % url, expected=True)
+ f'Unsupported URL: {url}', expected=True)
self.url = url
@@ -1367,7 +1367,7 @@ class DateRange:
else:
self.end = dt.datetime.max.date()
if self.start > self.end:
- raise ValueError('Date range: "%s" , the start date must be before the end date' % self)
+ raise ValueError(f'Date range: "{self}" , the start date must be before the end date')
@classmethod
def day(cls, day):
@@ -1400,7 +1400,7 @@ def system_identifier():
with contextlib.suppress(OSError): # We may not have access to the executable
libc_ver = platform.libc_ver()
- return 'Python %s (%s %s %s) - %s (%s%s)' % (
+ return 'Python {} ({} {} {}) - {} ({}{})'.format(
platform.python_version(),
python_implementation,
platform.machine(),
@@ -1413,7 +1413,7 @@ def system_identifier():
@functools.cache
def get_windows_version():
- ''' Get Windows version. returns () if it's not running on Windows '''
+ """ Get Windows version. returns () if it's not running on Windows """
if compat_os_name == 'nt':
return version_tuple(platform.win32_ver()[1])
else:
@@ -1505,7 +1505,7 @@ if sys.platform == 'win32':
ctypes.wintypes.DWORD, # dwReserved
ctypes.wintypes.DWORD, # nNumberOfBytesToLockLow
ctypes.wintypes.DWORD, # nNumberOfBytesToLockHigh
- ctypes.POINTER(OVERLAPPED) # Overlapped
+ ctypes.POINTER(OVERLAPPED), # Overlapped
]
LockFileEx.restype = ctypes.wintypes.BOOL
UnlockFileEx = kernel32.UnlockFileEx
@@ -1514,7 +1514,7 @@ if sys.platform == 'win32':
ctypes.wintypes.DWORD, # dwReserved
ctypes.wintypes.DWORD, # nNumberOfBytesToLockLow
ctypes.wintypes.DWORD, # nNumberOfBytesToLockHigh
- ctypes.POINTER(OVERLAPPED) # Overlapped
+ ctypes.POINTER(OVERLAPPED), # Overlapped
]
UnlockFileEx.restype = ctypes.wintypes.BOOL
whole_low = 0xffffffff
@@ -1537,7 +1537,7 @@ if sys.platform == 'win32':
assert f._lock_file_overlapped_p
handle = msvcrt.get_osfhandle(f.fileno())
if not UnlockFileEx(handle, 0, whole_low, whole_high, f._lock_file_overlapped_p):
- raise OSError('Unlocking file failed: %r' % ctypes.FormatError())
+ raise OSError(f'Unlocking file failed: {ctypes.FormatError()!r}')
else:
try:
@@ -1564,10 +1564,10 @@ else:
except ImportError:
def _lock_file(f, exclusive, block):
- raise LockingUnsupportedError()
+ raise LockingUnsupportedError
def _unlock_file(f):
- raise LockingUnsupportedError()
+ raise LockingUnsupportedError
class locked_file:
@@ -1926,7 +1926,7 @@ def remove_end(s, end):
def remove_quotes(s):
if s is None or len(s) < 2:
return s
- for quote in ('"', "'", ):
+ for quote in ('"', "'"):
if s[0] == quote and s[-1] == quote:
return s[1:-1]
return s
@@ -2095,16 +2095,14 @@ def prepend_extension(filename, ext, expected_real_ext=None):
def replace_extension(filename, ext, expected_real_ext=None):
name, real_ext = os.path.splitext(filename)
- return '{}.{}'.format(
- name if not expected_real_ext or real_ext[1:] == expected_real_ext else filename,
- ext)
+ return f'{name if not expected_real_ext or real_ext[1:] == expected_real_ext else filename}.{ext}'
def check_executable(exe, args=[]):
""" Checks if the given binary is installed somewhere in PATH, and returns its name.
args can be a list of arguments for a short output (like -version) """
try:
- Popen.run([exe] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ Popen.run([exe, *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError:
return False
return exe
@@ -2115,7 +2113,7 @@ def _get_exe_version_output(exe, args):
# STDIN should be redirected too. On UNIX-like systems, ffmpeg triggers
# SIGTTOU if yt-dlp is run in the background.
# See https://github.com/ytdl-org/youtube-dl/issues/955#issuecomment-209789656
- stdout, _, ret = Popen.run([encodeArgument(exe)] + args, text=True,
+ stdout, _, ret = Popen.run([encodeArgument(exe), *args], text=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if ret:
return None
@@ -2161,7 +2159,7 @@ class LazyList(collections.abc.Sequence):
"""Lazy immutable list from an iterable
Note that slices of a LazyList are lists and not LazyList"""
- class IndexError(IndexError):
+ class IndexError(IndexError): # noqa: A001
pass
def __init__(self, iterable, *, reverse=False, _cache=None):
@@ -2248,7 +2246,7 @@ class LazyList(collections.abc.Sequence):
class PagedList:
- class IndexError(IndexError):
+ class IndexError(IndexError): # noqa: A001
pass
def __len__(self):
@@ -2282,7 +2280,7 @@ class PagedList:
raise TypeError('indices must be non-negative integers')
entries = self.getslice(idx, idx + 1)
if not entries:
- raise self.IndexError()
+ raise self.IndexError
return entries[0]
def __bool__(self):
@@ -2443,7 +2441,7 @@ class PlaylistEntries:
except IndexError:
entry = self.MissingEntry
if not self.is_incomplete:
- raise self.IndexError()
+ raise self.IndexError
if entry is self.MissingEntry:
raise EntryNotInPlaylist(f'Entry {i + 1} cannot be found')
return entry
@@ -2452,7 +2450,7 @@ class PlaylistEntries:
try:
return type(self.ydl)._handle_extraction_exceptions(lambda _, i: self._entries[i])(self.ydl, i)
except (LazyList.IndexError, PagedList.IndexError):
- raise self.IndexError()
+ raise self.IndexError
return get_entry
def __getitem__(self, idx):
@@ -2488,7 +2486,7 @@ class PlaylistEntries:
def __len__(self):
return len(tuple(self[:]))
- class IndexError(IndexError):
+ class IndexError(IndexError): # noqa: A001
pass
@@ -2550,7 +2548,7 @@ def update_url(url, *, query_update=None, **kwargs):
assert 'query' not in kwargs, 'query_update and query cannot be specified at the same time'
kwargs['query'] = urllib.parse.urlencode({
**urllib.parse.parse_qs(url.query),
- **query_update
+ **query_update,
}, True)
return urllib.parse.urlunparse(url._replace(**kwargs))
@@ -2560,7 +2558,7 @@ def update_url_query(url, query):
def _multipart_encode_impl(data, boundary):
- content_type = 'multipart/form-data; boundary=%s' % boundary
+ content_type = f'multipart/form-data; boundary={boundary}'
out = b''
for k, v in data.items():
@@ -2582,7 +2580,7 @@ def _multipart_encode_impl(data, boundary):
def multipart_encode(data, boundary=None):
- '''
+ """
Encode a dict to RFC 7578-compliant form-data
data:
@@ -2593,7 +2591,7 @@ def multipart_encode(data, boundary=None):
a random boundary is generated.
Reference: https://tools.ietf.org/html/rfc7578
- '''
+ """
has_specified_boundary = boundary is not None
while True:
@@ -2688,7 +2686,7 @@ def parse_age_limit(s):
s = s.upper()
if s in US_RATINGS:
return US_RATINGS[s]
- m = re.match(r'^TV[_-]?(%s)$' % '|'.join(k[3:] for k in TV_PARENTAL_GUIDELINES), s)
+ m = re.match(r'^TV[_-]?({})$'.format('|'.join(k[3:] for k in TV_PARENTAL_GUIDELINES)), s)
if m:
return TV_PARENTAL_GUIDELINES['TV-' + m.group(1)]
return None
@@ -2736,7 +2734,7 @@ def js_to_json(code, vars={}, *, strict=False):
return v
elif v in ('undefined', 'void 0'):
return 'null'
- elif v.startswith('/*') or v.startswith('//') or v.startswith('!') or v == ',':
+ elif v.startswith(('/*', '//', '!')) or v == ',':
return ''
if v[0] in STRING_QUOTES:
@@ -3079,7 +3077,7 @@ def urlhandle_detect_ext(url_handle, default=NO_DEFAULT):
def encode_data_uri(data, mime_type):
- return 'data:%s;base64,%s' % (mime_type, base64.b64encode(data).decode('ascii'))
+ return 'data:{};base64,{}'.format(mime_type, base64.b64encode(data).decode('ascii'))
def age_restricted(content_limit, age_limit):
@@ -3144,18 +3142,18 @@ def render_table(header_row, data, delim=False, extra_gap=0, hide_empty=False):
def get_max_lens(table):
return [max(width(str(v)) for v in col) for col in zip(*table)]
- def filter_using_list(row, filterArray):
- return [col for take, col in itertools.zip_longest(filterArray, row, fillvalue=True) if take]
+ def filter_using_list(row, filter_array):
+ return [col for take, col in itertools.zip_longest(filter_array, row, fillvalue=True) if take]
max_lens = get_max_lens(data) if hide_empty else []
header_row = filter_using_list(header_row, max_lens)
data = [filter_using_list(row, max_lens) for row in data]
- table = [header_row] + data
+ table = [header_row, *data]
max_lens = get_max_lens(table)
extra_gap += 1
if delim:
- table = [header_row, [delim * (ml + extra_gap) for ml in max_lens]] + data
+ table = [header_row, [delim * (ml + extra_gap) for ml in max_lens], *data]
table[1][-1] = table[1][-1][:-extra_gap * len(delim)] # Remove extra_gap from end of delimiter
for row in table:
for pos, text in enumerate(map(str, row)):
@@ -3163,8 +3161,7 @@ def render_table(header_row, data, delim=False, extra_gap=0, hide_empty=False):
row[pos] = text.replace('\t', ' ' * (max_lens[pos] - width(text))) + ' ' * extra_gap
else:
row[pos] = text + ' ' * (max_lens[pos] - width(text) + extra_gap)
- ret = '\n'.join(''.join(row).rstrip() for row in table)
- return ret
+ return '\n'.join(''.join(row).rstrip() for row in table)
def _match_one(filter_part, dct, incomplete):
@@ -3191,12 +3188,12 @@ def _match_one(filter_part, dct, incomplete):
operator_rex = re.compile(r'''(?x)
(?P<key>[a-z_]+)
- \s*(?P<negation>!\s*)?(?P<op>%s)(?P<none_inclusive>\s*\?)?\s*
+ \s*(?P<negation>!\s*)?(?P<op>{})(?P<none_inclusive>\s*\?)?\s*
(?:
(?P<quote>["\'])(?P<quotedstrval>.+?)(?P=quote)|
(?P<strval>.+?)
)
- ''' % '|'.join(map(re.escape, COMPARISON_OPERATORS.keys())))
+ '''.format('|'.join(map(re.escape, COMPARISON_OPERATORS.keys()))))
m = operator_rex.fullmatch(filter_part.strip())
if m:
m = m.groupdict()
@@ -3207,7 +3204,7 @@ def _match_one(filter_part, dct, incomplete):
op = unnegated_op
comparison_value = m['quotedstrval'] or m['strval'] or m['intval']
if m['quote']:
- comparison_value = comparison_value.replace(r'\%s' % m['quote'], m['quote'])
+ comparison_value = comparison_value.replace(r'\{}'.format(m['quote']), m['quote'])
actual_value = dct.get(m['key'])
numeric_comparison = None
if isinstance(actual_value, (int, float)):
@@ -3224,7 +3221,7 @@ def _match_one(filter_part, dct, incomplete):
if numeric_comparison is None:
numeric_comparison = parse_duration(comparison_value)
if numeric_comparison is not None and m['op'] in STRING_OPERATORS:
- raise ValueError('Operator %s only supports string values!' % m['op'])
+ raise ValueError('Operator {} only supports string values!'.format(m['op']))
if actual_value is None:
return is_incomplete(m['key']) or m['none_inclusive']
return op(actual_value, comparison_value if numeric_comparison is None else numeric_comparison)
@@ -3234,8 +3231,8 @@ def _match_one(filter_part, dct, incomplete):
'!': lambda v: (v is False) if isinstance(v, bool) else (v is None),
}
operator_rex = re.compile(r'''(?x)
- (?P<op>%s)\s*(?P<key>[a-z_]+)
- ''' % '|'.join(map(re.escape, UNARY_OPERATORS.keys())))
+ (?P<op>{})\s*(?P<key>[a-z_]+)
+ '''.format('|'.join(map(re.escape, UNARY_OPERATORS.keys()))))
m = operator_rex.fullmatch(filter_part.strip())
if m:
op = UNARY_OPERATORS[m.group('op')]
@@ -3244,7 +3241,7 @@ def _match_one(filter_part, dct, incomplete):
return True
return op(actual_value)
- raise ValueError('Invalid filter part %r' % filter_part)
+ raise ValueError(f'Invalid filter part {filter_part!r}')
def match_str(filter_str, dct, incomplete=False):
@@ -3351,10 +3348,10 @@ def ass_subtitles_timecode(seconds):
def dfxp2srt(dfxp_data):
- '''
+ """
@param dfxp_data A bytes-like object containing DFXP data
@returns A unicode object containing converted SRT data
- '''
+ """
LEGACY_NAMESPACES = (
(b'http://www.w3.org/ns/ttml', [
b'http://www.w3.org/2004/11/ttaf1',
@@ -3372,7 +3369,7 @@ def dfxp2srt(dfxp_data):
'fontSize',
'fontStyle',
'fontWeight',
- 'textDecoration'
+ 'textDecoration',
]
_x = functools.partial(xpath_with_ns, ns_map={
@@ -3410,11 +3407,11 @@ def dfxp2srt(dfxp_data):
if self._applied_styles and self._applied_styles[-1].get(k) == v:
continue
if k == 'color':
- font += ' color="%s"' % v
+ font += f' color="{v}"'
elif k == 'fontSize':
- font += ' size="%s"' % v
+ font += f' size="{v}"'
elif k == 'fontFamily':
- font += ' face="%s"' % v
+ font += f' face="{v}"'
elif k == 'fontWeight' and v == 'bold':
self._out += '<b>'
unclosed_elements.append('b')
@@ -3438,7 +3435,7 @@ def dfxp2srt(dfxp_data):
if tag not in (_x('ttml:br'), 'br'):
unclosed_elements = self._unclosed_elements.pop()
for element in reversed(unclosed_elements):
- self._out += '</%s>' % element
+ self._out += f'</{element}>'
if unclosed_elements and self._applied_styles:
self._applied_styles.pop()
@@ -4349,7 +4346,7 @@ def bytes_to_long(s):
def ohdave_rsa_encrypt(data, exponent, modulus):
- '''
+ """
Implement OHDave's RSA algorithm. See http://www.ohdave.com/rsa/
Input:
@@ -4358,11 +4355,11 @@ def ohdave_rsa_encrypt(data, exponent, modulus):
Output: hex string of encrypted data
Limitation: supports one block encryption only
- '''
+ """
payload = int(binascii.hexlify(data[::-1]), 16)
encrypted = pow(payload, exponent, modulus)
- return '%x' % encrypted
+ return f'{encrypted:x}'
def pkcs1pad(data, length):
@@ -4377,7 +4374,7 @@ def pkcs1pad(data, length):
raise ValueError('Input data too long for PKCS#1 padding')
pseudo_random = [random.randint(0, 254) for _ in range(length - len(data) - 3)]
- return [0, 2] + pseudo_random + [0] + data
+ return [0, 2, *pseudo_random, 0, *data]
def _base_n_table(n, table):
@@ -4710,16 +4707,14 @@ def jwt_encode_hs256(payload_data, key, headers={}):
payload_b64 = base64.b64encode(json.dumps(payload_data).encode())
h = hmac.new(key.encode(), header_b64 + b'.' + payload_b64, hashlib.sha256)
signature_b64 = base64.b64encode(h.digest())
- token = header_b64 + b'.' + payload_b64 + b'.' + signature_b64
- return token
+ return header_b64 + b'.' + payload_b64 + b'.' + signature_b64
# can be extended in future to verify the signature and parse header and return the algorithm used if it's not HS256
def jwt_decode_hs256(jwt):
header_b64, payload_b64, signature_b64 = jwt.split('.')
# add trailing ='s that may have been stripped, superfluous ='s are ignored
- payload_data = json.loads(base64.urlsafe_b64decode(f'{payload_b64}==='))
- return payload_data
+ return json.loads(base64.urlsafe_b64decode(f'{payload_b64}==='))
WINDOWS_VT_MODE = False if compat_os_name == 'nt' else None
@@ -4797,7 +4792,7 @@ def scale_thumbnails_to_max_format_width(formats, thumbnails, url_width_re):
"""
_keys = ('width', 'height')
max_dimensions = max(
- (tuple(format.get(k) or 0 for k in _keys) for format in formats),
+ (tuple(fmt.get(k) or 0 for k in _keys) for fmt in formats),
default=(0, 0))
if not max_dimensions[0]:
return thumbnails
@@ -5193,7 +5188,7 @@ class FormatSorter:
'function': lambda it: next(filter(None, it), None)},
'ext': {'type': 'combined', 'field': ('vext', 'aext')},
'res': {'type': 'multiple', 'field': ('height', 'width'),
- 'function': lambda it: (lambda l: min(l) if l else 0)(tuple(filter(None, it)))},
+ 'function': lambda it: min(filter(None, it), default=0)},
# Actual field names
'format_id': {'type': 'alias', 'field': 'id'},
@@ -5241,21 +5236,21 @@ class FormatSorter:
self.ydl.deprecated_feature(f'Using arbitrary fields ({field}) for format sorting is '
'deprecated and may be removed in a future version')
self.settings[field] = {}
- propObj = self.settings[field]
- if key not in propObj:
- type = propObj.get('type')
+ prop_obj = self.settings[field]
+ if key not in prop_obj:
+ type_ = prop_obj.get('type')
if key == 'field':
- default = 'preference' if type == 'extractor' else (field,) if type in ('combined', 'multiple') else field
+ default = 'preference' if type_ == 'extractor' else (field,) if type_ in ('combined', 'multiple') else field
elif key == 'convert':
- default = 'order' if type == 'ordered' else 'float_string' if field else 'ignore'
+ default = 'order' if type_ == 'ordered' else 'float_string' if field else 'ignore'
else:
- default = {'type': 'field', 'visible': True, 'order': [], 'not_in_list': (None,)}.get(key, None)
- propObj[key] = default
- return propObj[key]
+ default = {'type': 'field', 'visible': True, 'order': [], 'not_in_list': (None,)}.get(key)
+ prop_obj[key] = default
+ return prop_obj[key]
- def _resolve_field_value(self, field, value, convertNone=False):
+ def _resolve_field_value(self, field, value, convert_none=False):
if value is None:
- if not convertNone:
+ if not convert_none:
return None
else:
value = value.lower()
@@ -5317,7 +5312,7 @@ class FormatSorter:
for item in sort_list:
match = re.match(self.regex, item)
if match is None:
- raise ExtractorError('Invalid format sort string "%s" given by extractor' % item)
+ raise ExtractorError(f'Invalid format sort string "{item}" given by extractor')
field = match.group('field')
if field is None:
continue
@@ -5345,31 +5340,31 @@ class FormatSorter:
def print_verbose_info(self, write_debug):
if self._sort_user:
- write_debug('Sort order given by user: %s' % ', '.join(self._sort_user))
+ write_debug('Sort order given by user: {}'.format(', '.join(self._sort_user)))
if self._sort_extractor:
- write_debug('Sort order given by extractor: %s' % ', '.join(self._sort_extractor))
- write_debug('Formats sorted by: %s' % ', '.join(['%s%s%s' % (
+ write_debug('Sort order given by extractor: {}'.format(', '.join(self._sort_extractor)))
+ write_debug('Formats sorted by: {}'.format(', '.join(['{}{}{}'.format(
'+' if self._get_field_setting(field, 'reverse') else '', field,
- '%s%s(%s)' % ('~' if self._get_field_setting(field, 'closest') else ':',
- self._get_field_setting(field, 'limit_text'),
- self._get_field_setting(field, 'limit'))
+ '{}{}({})'.format('~' if self._get_field_setting(field, 'closest') else ':',
+ self._get_field_setting(field, 'limit_text'),
+ self._get_field_setting(field, 'limit'))
if self._get_field_setting(field, 'limit_text') is not None else '')
- for field in self._order if self._get_field_setting(field, 'visible')]))
+ for field in self._order if self._get_field_setting(field, 'visible')])))
- def _calculate_field_preference_from_value(self, format, field, type, value):
+ def _calculate_field_preference_from_value(self, format_, field, type_, value):
reverse = self._get_field_setting(field, 'reverse')
closest = self._get_field_setting(field, 'closest')
limit = self._get_field_setting(field, 'limit')
- if type == 'extractor':
+ if type_ == 'extractor':
maximum = self._get_field_setting(field, 'max')
if value is None or (maximum is not None and value >= maximum):
value = -1
- elif type == 'boolean':
+ elif type_ == 'boolean':
in_list = self._get_field_setting(field, 'in_list')
not_in_list = self._get_field_setting(field, 'not_in_list')
value = 0 if ((in_list is None or value in in_list) and (not_in_list is None or value not in not_in_list)) else -1
- elif type == 'ordered':
+ elif type_ == 'ordered':
value = self._resolve_field_value(field, value, True)
# try to convert to number
@@ -5385,17 +5380,17 @@ class FormatSorter:
else (0, -value, 0) if limit is None or (reverse and value == limit) or value > limit
else (-1, value, 0))
- def _calculate_field_preference(self, format, field):
- type = self._get_field_setting(field, 'type') # extractor, boolean, ordered, field, multiple
- get_value = lambda f: format.get(self._get_field_setting(f, 'field'))
- if type == 'multiple':
- type = 'field' # Only 'field' is allowed in multiple for now
+ def _calculate_field_preference(self, format_, field):
+ type_ = self._get_field_setting(field, 'type') # extractor, boolean, ordered, field, multiple
+ get_value = lambda f: format_.get(self._get_field_setting(f, 'field'))
+ if type_ == 'multiple':
+ type_ = 'field' # Only 'field' is allowed in multiple for now
actual_fields = self._get_field_setting(field, 'field')
value = self._get_field_setting(field, 'function')(get_value(f) for f in actual_fields)
else:
value = get_value(field)
- return self._calculate_field_preference_from_value(format, field, type, value)
+ return self._calculate_field_preference_from_value(format_, field, type_, value)
def calculate_preference(self, format):
# Determine missing protocol
diff --git a/yt_dlp/utils/networking.py b/yt_dlp/utils/networking.py
index 4b73252cb..933b164be 100644
--- a/yt_dlp/utils/networking.py
+++ b/yt_dlp/utils/networking.py
@@ -112,7 +112,7 @@ def clean_proxies(proxies: dict, headers: HTTPHeaderDict):
replace_scheme = {
'socks5': 'socks5h', # compat: socks5 was treated as socks5h
- 'socks': 'socks4' # compat: non-standard
+ 'socks': 'socks4', # compat: non-standard
}
if proxy_scheme in replace_scheme:
proxies[proxy_key] = urllib.parse.urlunparse(
@@ -160,5 +160,5 @@ def normalize_url(url):
path=escape_rfc3986(remove_dot_segments(url_parsed.path)),
params=escape_rfc3986(url_parsed.params),
query=escape_rfc3986(url_parsed.query),
- fragment=escape_rfc3986(url_parsed.fragment)
+ fragment=escape_rfc3986(url_parsed.fragment),
).geturl()
diff --git a/yt_dlp/webvtt.py b/yt_dlp/webvtt.py
index 7683bfb0f..9f1a5086b 100644
--- a/yt_dlp/webvtt.py
+++ b/yt_dlp/webvtt.py
@@ -77,9 +77,8 @@ class _MatchChildParser(_MatchParser):
class ParseError(Exception):
def __init__(self, parser):
- super().__init__("Parse error at position %u (near %r)" % (
- parser._pos, parser._data[parser._pos:parser._pos + 100]
- ))
+ data = parser._data[parser._pos:parser._pos + 100]
+ super().__init__(f'Parse error at position {parser._pos} (near {data!r})')
# While the specification <https://www.w3.org/TR/webvtt1/#webvtt-timestamp>
@@ -149,7 +148,7 @@ class Magic(HeaderBlock):
# XXX: The X-TIMESTAMP-MAP extension is described in RFC 8216 §3.5
# <https://tools.ietf.org/html/rfc8216#section-3.5>, but the RFC
- # doesn’t specify the exact grammar nor where in the WebVTT
+ # doesn't specify the exact grammar nor where in the WebVTT
# syntax it should be placed; the below has been devised based
# on usage in the wild
#
@@ -273,10 +272,10 @@ class CueBlock(Block):
def parse(cls, parser):
parser = parser.child()
- id = None
+ id_ = None
m = parser.consume(cls._REGEX_ID)
if m:
- id = m.group(1)
+ id_ = m.group(1)
m0 = parser.consume(_REGEX_TS)
if not m0:
@@ -304,9 +303,9 @@ class CueBlock(Block):
parser.commit()
return cls(
- id=id,
+ id=id_,
start=start, end=end, settings=settings,
- text=text.getvalue()
+ text=text.getvalue(),
)
def write_into(self, stream):
@@ -343,7 +342,7 @@ class CueBlock(Block):
start=json['start'],
end=json['end'],
text=json['text'],
- settings=json['settings']
+ settings=json['settings'],
)
def hinges(self, other):