Merge lp:~supagu/openlp/planningcenter-branch into lp:openlp

Proposed by Fabian Mathews
Status: Needs review
Proposed branch: lp:~supagu/openlp/planningcenter-branch
Merge into: lp:openlp
Diff against target: 74722 lines (+71855/-0)
570 files modified
openlp/plugins/planningcenter/__init__.py (+24/-0)
openlp/plugins/planningcenter/forms/__init__.py (+48/-0)
openlp/plugins/planningcenter/forms/logindialog.py (+84/-0)
openlp/plugins/planningcenter/forms/maindialog.py (+121/-0)
openlp/plugins/planningcenter/forms/planimportdialog.py (+685/-0)
openlp/plugins/planningcenter/forms/youtubedownloaddialog.py (+66/-0)
openlp/plugins/planningcenter/lib/__init__.py (+25/-0)
openlp/plugins/planningcenter/lib/db.py (+55/-0)
openlp/plugins/planningcenter/lib/planningcentertab.py (+95/-0)
openlp/plugins/planningcenter/lib/session.py (+119/-0)
openlp/plugins/planningcenter/lib/youtube-dl/LICENSE (+24/-0)
openlp/plugins/planningcenter/lib/youtube-dl/README.txt (+1035/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/YoutubeDL.py (+1889/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__init__.py (+418/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__main__.py (+19/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/aes.py (+331/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/cache.py (+93/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/compat.py (+466/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/__init__.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/common.py (+372/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/external.py (+136/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/f4m.py (+444/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/hls.py (+104/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/http.py (+234/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/rtmp.py (+203/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/downloader/rtsp.py (+45/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/__init__.py (+818/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/abc.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/abc7news.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/academicearth.py (+41/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/addanime.py (+94/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/adobetv.py (+131/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/adultswim.py (+191/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/aftenposten.py (+23/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/aftonbladet.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/airmozilla.py (+74/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/aljazeera.py (+35/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/allocine.py (+89/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/alphaporno.py (+77/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/anitube.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/anysex.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/aol.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/aparat.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/appletrailers.py (+146/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/archiveorg.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ard.py (+191/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/arte.py (+254/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/atresplayer.py (+163/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/atttechchannel.py (+55/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/audiomack.py (+144/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/azubu.py (+93/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/baidu.py (+69/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bambuser.py (+144/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bandcamp.py (+181/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bbccouk.py (+379/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/beatportpro.py (+103/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/beeg.py (+65/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/behindkink.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bet.py (+108/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bild.py (+42/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bilibili.py (+135/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/blinkx.py (+86/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bliptv.py (+293/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bloomberg.py (+44/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/bpb.py (+37/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/br.py (+143/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/breakcom.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/brightcove.py (+348/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/buzzfeed.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/byutv.py (+48/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/c56.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/camdemy.py (+153/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/canal13cl.py (+48/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/canalc2.py (+41/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/canalplus.py (+138/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cbs.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cbsnews.py (+87/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cbssports.py (+30/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ccc.py (+99/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ceskatelevize.py (+156/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/channel9.py (+277/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/chilloutzone.py (+97/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/chirbit.py (+84/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cinchcast.py (+50/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cinemassacre.py (+110/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/clipfish.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cliphunter.py (+82/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/clipsyndicate.py (+54/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cloudy.py (+111/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/clubic.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cmt.py (+19/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cnet.py (+85/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cnn.py (+170/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/collegehumor.py (+101/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/collegerama.py (+92/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/comcarcoff.py (+57/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/comedycentral.py (+274/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/common.py (+1157/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/commonmistakes.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/condenast.py (+120/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cracked.py (+91/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/criterion.py (+43/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/crooksandliars.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/crunchyroll.py (+338/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/cspan.py (+123/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ctsnews.py (+94/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dailymotion.py (+305/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/daum.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dbtv.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dctp.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/deezer.py (+89/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/defense.py (+39/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dfb.py (+49/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dhm.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/discovery.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/divxstage.py (+27/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dotsub.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/douyutv.py (+113/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dramafever.py (+216/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/drbonanza.py (+135/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dreisat.py (+87/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dropbox.py (+40/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/drtuber.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/drtv.py (+115/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dump.py (+39/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dumpert.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/dvtv.py (+125/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/eagleplatform.py (+99/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ebaumsworld.py (+33/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/echomsk.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ehow.py (+38/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/eighttracks.py (+164/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/einthusan.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/eitb.py (+39/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ellentv.py (+84/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/elpais.py (+54/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/embedly.py (+16/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/engadget.py (+41/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/eporner.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/eroprofile.py (+96/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/escapist.py (+107/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/espn.py (+55/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/everyonesmixtape.py (+79/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/exfm.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/expotv.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/extremetube.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/facebook.py (+171/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/faz.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/fc2.py (+114/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/firstpost.py (+50/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/firsttv.py (+79/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/fivemin.py (+84/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/fivetv.py (+88/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/fktv.py (+82/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/flickr.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/folketinget.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/footyroom.py (+49/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/fourtube.py (+107/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/foxgay.py (+48/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/foxnews.py (+94/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/foxsports.py (+32/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/franceculture.py (+69/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/franceinter.py (+52/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/francetv.py (+303/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/freesound.py (+39/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/freespeech.py (+37/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/freevideo.py (+38/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/funnyordie.py (+79/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gamekings.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gameone.py (+134/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gamersyde.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gamespot.py (+82/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gamestar.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gametrailers.py (+19/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gazeta.py (+38/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gdcvault.py (+184/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/generic.py (+1721/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gfycat.py (+110/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/giantbomb.py (+81/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/giga.py (+102/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/glide.py (+40/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/globo.py (+418/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/godtube.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/goldenmoustache.py (+48/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/golem.py (+71/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/googleplus.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/googlesearch.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/gorillavid.py (+116/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/goshgay.py (+51/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/groupon.py (+50/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hark.py (+33/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hearthisat.py (+117/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/heise.py (+79/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hellporno.py (+71/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/helsinki.py (+43/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hentaistigma.py (+39/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/historicfilms.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/history.py (+31/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hitbox.py (+203/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hornbunny.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hostingbulk.py (+80/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hotnewhiphop.py (+69/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/howcast.py (+42/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/howstuffworks.py (+107/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/huffpost.py (+78/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/hypem.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/iconosquare.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ign.py (+145/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/imdb.py (+84/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/imgur.py (+99/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ina.py (+36/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/infoq.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/instagram.py (+134/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/internetvideoarchive.py (+93/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/iprima.py (+113/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/iqiyi.py (+297/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ivi.py (+182/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/izlesene.py (+122/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/jadorecettepub.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/jeuxvideo.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/jove.py (+80/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/jpopsukitv.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/jukebox.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/kaltura.py (+138/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/kanalplay.py (+97/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/kankan.py (+48/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/karaoketv.py (+40/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/karrierevideos.py (+96/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/keek.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/keezmovies.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/khanacademy.py (+82/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/kickstarter.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/kontrtube.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/krasview.py (+57/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ku6.py (+32/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/kuwo.py (+314/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/la7.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/laola1tv.py (+86/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/letv.py (+206/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/libsyn.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/lifenews.py (+168/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/liveleak.py (+119/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/livestream.py (+277/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/lnkgo.py (+113/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/lrt.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/lynda.py (+235/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/m6.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/macgamestore.py (+42/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mailru.py (+89/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/malemotion.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mdr.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/megavideoz.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/metacafe.py (+253/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/metacritic.py (+55/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mgoon.py (+87/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/minhateca.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ministrygrid.py (+57/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/miomio.py (+106/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mit.py (+156/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mitele.py (+74/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mixcloud.py (+105/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mlb.py (+173/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/moevideo.py (+115/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mofosex.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mojvideo.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/moniker.py (+79/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mooshare.py (+112/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/morningstar.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/motherless.py (+105/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/motorsport.py (+49/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/movieclips.py (+80/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/moviezine.py (+45/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/movshare.py (+27/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mpora.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/mtv.py (+290/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/muenchentv.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/musicplayon.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/musicvault.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/muzu.py (+65/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/myspace.py (+180/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/myspass.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/myvi.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/myvideo.py (+176/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/myvidster.py (+29/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nationalgeographic.py (+41/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/naver.py (+99/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nba.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nbc.py (+234/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ndr.py (+130/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ndtv.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nerdcubed.py (+36/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nerdist.py (+80/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/neteasemusic.py (+459/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/netzkino.py (+89/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/newgrounds.py (+42/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/newstube.py (+92/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nextmedia.py (+162/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nfb.py (+94/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nfl.py (+165/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nhl.py (+222/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/niconico.py (+287/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ninegag.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/noco.py (+212/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/normalboots.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nosvideo.py (+77/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nova.py (+179/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/novamov.py (+71/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nowness.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nowtv.py (+192/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nowvideo.py (+28/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/npo.py (+487/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nrk.py (+296/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ntvde.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ntvru.py (+128/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nuvid.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/nytimes.py (+117/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/odnoklassniki.py (+108/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/oktoberfesttv.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/onionstudios.py (+76/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ooyala.py (+196/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/openfilm.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/orf.py (+293/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/parliamentliveuk.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/patreon.py (+117/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pbs.py (+246/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/philharmoniedeparis.py (+78/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/phoenix.py (+45/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/photobucket.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pinkbike.py (+96/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pladform.py (+90/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/planetaplay.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/played.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/playfm.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/playvid.py (+88/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/playwire.py (+78/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/podomatic.py (+69/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/porn91.py (+71/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pornhd.py (+71/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pornhub.py (+156/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pornotube.py (+94/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pornovoisines.py (+96/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pornoxo.py (+65/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/primesharetv.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/promptfile.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/prosiebensat1.py (+349/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/puls4.py (+88/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/pyvideo.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/qqmusic.py (+317/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/quickvid.py (+54/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/r7.py (+88/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/radiobremen.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/radiode.py (+52/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/radiofrance.py (+59/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/radiojavan.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rai.py (+163/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rbmaradio.py (+55/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rds.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/redtube.py (+45/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/restudy.py (+40/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/reverbnation.py (+44/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ringtv.py (+44/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ro220.py (+43/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rottentomatoes.py (+19/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/roxwel.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rtbf.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rte.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rtl2.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rtlnl.py (+116/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rtp.py (+85/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rts.py (+207/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rtve.py (+224/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ruhd.py (+45/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rutube.py (+168/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/rutv.py (+203/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ruutu.py (+119/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/safari.py (+161/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sandia.py (+117/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sapo.py (+119/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/savefrom.py (+37/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sbs.py (+51/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/scivee.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/screencast.py (+111/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/screencastomatic.py (+49/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/screenwavemedia.py (+127/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/senateisvp.py (+145/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/servingsys.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sexu.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sexykarma.py (+120/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/shared.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sharesix.py (+93/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sina.py (+73/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/slideshare.py (+55/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/slutload.py (+44/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/smotri.py (+418/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/snagfilms.py (+171/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/snotr.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sohu.py (+210/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/soompi.py (+146/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/soundcloud.py (+392/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/soundgasm.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/southpark.py (+70/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/space.py (+38/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/spankbang.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/spankwire.py (+113/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/spiegel.py (+134/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/spiegeltv.py (+110/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/spike.py (+29/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sport5.py (+92/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sportbox.py (+115/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sportdeutschland.py (+98/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/srf.py (+104/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/srmediathek.py (+43/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ssa.py (+58/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/stanfordoc.py (+91/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/steam.py (+123/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/streamcloud.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/streamcz.py (+98/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/streetvoice.py (+51/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sunporno.py (+74/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/svt.py (+117/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/swrmediathek.py (+104/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/syfy.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/sztvhu.py (+41/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tagesschau.py (+120/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tapely.py (+107/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tass.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/teachertube.py (+131/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/teachingchannel.py (+33/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/teamcoco.py (+170/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/techtalks.py (+79/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ted.py (+277/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/telebruxelles.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/telecinco.py (+26/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/telemb.py (+78/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/teletask.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tenplay.py (+90/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/testtube.py (+90/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/testurl.py (+68/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tf1.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/theonion.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/theplatform.py (+210/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/thesixtyone.py (+102/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/thisamericanlife.py (+40/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/thisav.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/thvideo.py (+84/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tinypic.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tlc.py (+71/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tmz.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tnaflix.py (+269/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/toutv.py (+74/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/toypics.py (+85/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/traileraddict.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/trilulilu.py (+80/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/trutube.py (+40/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tube8.py (+95/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tubitv.py (+84/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tudou.py (+87/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tumblr.py (+99/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tunein.py (+106/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/turbo.py (+67/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tutv.py (+35/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tv2.py (+126/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tv4.py (+100/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tvc.py (+109/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tvigle.py (+109/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tvp.py (+139/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tvplay.py (+248/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/tweakers.py (+65/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/twentyfourvideo.py (+109/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/twentytwotracks.py (+86/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/twitch.py (+403/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/twitter.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ubu.py (+57/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/udemy.py (+200/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/udn.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ultimedia.py (+105/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/unistra.py (+66/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/urort.py (+66/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ustream.py (+140/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/varzesh3.py (+45/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vbox7.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/veehd.py (+118/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/veoh.py (+129/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vessel.py (+133/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vesti.py (+121/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vevo.py (+229/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vgtv.py (+203/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vh1.py (+127/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vice.py (+36/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/viddler.py (+118/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videobam.py (+81/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videodetective.py (+27/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videofyme.py (+50/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videolecturesnet.py (+86/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videomega.py (+55/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videopremium.py (+46/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videott.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/videoweed.py (+26/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vidme.py (+66/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vidzi.py (+32/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vier.py (+121/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/viewster.py (+129/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/viki.py (+322/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vimeo.py (+685/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vimple.py (+60/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vine.py (+132/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vk.py (+313/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vodlocker.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/voicerepublic.py (+99/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vporn.py (+117/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vrt.py (+95/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vube.py (+172/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vuclip.py (+83/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/vulture.py (+69/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/walla.py (+86/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/washingtonpost.py (+131/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wat.py (+137/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wayofthemaster.py (+52/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wdr.py (+286/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/webofstories.py (+141/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/weibo.py (+49/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wimp.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wistia.py (+63/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/worldstarhiphop.py (+64/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wrzuta.py (+82/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/wsj.py (+89/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xbef.py (+44/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xboxclips.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xhamster.py (+167/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xminus.py (+76/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xnxx.py (+42/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xstream.py (+115/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xtube.py (+134/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xuite.py (+151/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xvideos.py (+75/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/xxxymovies.py (+81/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/yahoo.py (+297/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/yam.py (+123/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/yandexmusic.py (+127/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/yesjapan.py (+62/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/yinyuetai.py (+56/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/ynet.py (+50/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/youjizz.py (+61/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/youku.py (+236/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/youporn.py (+121/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/yourupload.py (+53/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/youtube.py (+1831/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/zapiks.py (+110/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/zdf.py (+159/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/extractor/zingmp3.py (+113/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/jsinterp.py (+258/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/options.py (+801/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/__init__.py (+38/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/common.py (+72/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/embedthumbnail.py (+92/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/execafterdownload.py (+28/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/ffmpeg.py (+520/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/metadatafromtitle.py (+47/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/postprocessor/xattrpp.py (+176/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/swfinterp.py (+829/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/update.py (+212/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/utils.py (+2369/-0)
openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/version.py (+3/-0)
openlp/plugins/planningcenter/lib/youtube.py (+42/-0)
openlp/plugins/planningcenter/planningcenter.py (+184/-0)
openlp/plugins/planningcenter/readme.txt (+1/-0)
openlp/plugins/planningcenter/resources/logindialog.ui (+142/-0)
openlp/plugins/planningcenter/resources/maindialog.ui (+71/-0)
openlp/plugins/planningcenter/resources/planimportdialog.ui (+112/-0)
openlp/plugins/planningcenter/resources/youtubedownloaddialog.ui (+132/-0)
To merge this branch: bzr merge lp:~supagu/openlp/planningcenter-branch
Reviewer Review Type Date Requested Status
Tomas Groth Needs Fixing
Raoul Snyman Needs Fixing
Review via email: mp+272094@code.launchpad.net

Commit message

Added planningcenter plugin

Description of the change

In the plugins directory I added "planningcenter". This plugin adds support for planning center and also adds support to be able to download videos from youtube via planning center.

NOTE: that in the lib directory I have included "youtube-dl" which is a python module I grabbed off the internet. I included it this way to avoid having to install it. It should probably be changed to be an installed module.

Related forum post: http://forums.openlp.org/discussion/2705/planning-center-and-youtube-download-plugin

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Hi Fabian, welcome to OpenLP!

Before I launch into your merge proposal, I just want to make sure you're joining the proper channels of communication.

1. Have you read the pages on the wiki about development, standards, testing, etc?
2. Have you applied to be part of the team on Launchpad?
3. Have you come to say "hi" in IRC?

We actually rely a LOT on IRC for our day-to-day communication. It would benefit you greatly to be part of this conversation.

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Thanks for your work below. Please remember that whatever we say, we're not criticising you personally, we're trying to ensure that the code which is in OpenLP is of the highest standard. This means that we're going to ask you to correct a lot of stuff, but this is not a bad thing and we don't want to discourage you from contributing.

Everyone has to go through this process, and no one is exempt, even the core developers.

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Right, now for the review.

This is not going to go into OpenLP 2.2, as we have already hit feature-freeze, but this is a great candidate for OpenLP 2.4. So please note, we're not going to merge it until trunk is open for 2.4 development.

We changed our coding standards slightly after OpenLP 2.0.x came out. I'm sorry, we didn't update the wiki, so if you read the document on the wiki (which it looks like you did), then there are a few parts which are going to need to change. You'll be pleased to hear that I've since updated the wiki.

All functions, methods and variables should now use words_separated_by_underscores. We no longer use camelCase in any of our code, not even the Qt/Python hybrid classes. Not all our existing code has been converted yet, but this is the way we're going. Please can you update your code to follow this.

Please remove youtube-dl and the rest of the code associated with it. It is against YouTube's terms and conditions, and as a Christian project we will not participate in actions that contravene conditions laid out by services like YouTube.

We use single quotes for strings, not double quotes.

None of your strings are translatable. OpenLP is used in a number of different countries across the world, and they depend on being able to translate OpenLP into their own language.

What is the monkeypatch for? Why do we need to monkeypatch (HACK ALERT!) instead of extending the existing code?

You have no tests. No code without tests will be accepted. Also, since this is a huge change, I won't accept it without at least 50% code coverage.

review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) wrote :

Hi Fabian,

Thanks for contributing to OpenLP! I think this is your first merge request so I'll just start out by saying that all merge requests goes through this review process, and most of them has to be changed and resubmitted one or several times before getting merged. This means that your code (not you!) most likely will meet criticism and you will need to add/rewrite some of it before it can be merged. This can be a long process when adding new features, but I hope you will get through it! :)

Raoul has already given you a lot of stuff to do, so I'll (try to) keep it short...

Regarding code-style, try running "pep8" in the root of openlp to see if your code is compliant with the standards.

I had a look at planningcenteronline and there seems to be 2 APIs a new and an old one. Which one are you using? Does it require a paid subscription to use the API? Can I just create an account and start using it with OpenLP and this plugin?

To make your string translatable wrap them in a "translate()" call, see other plugins for how it works.

And last but not least: Tests. Nothing new gets added to OpenLP without some tests that proves that the code works as expected, so you'll have to write some tests. Take a look at the existing tests to see how it can be done.

review: Needs Fixing
Revision history for this message
Fabian Mathews (supagu) wrote :

Ah it looks like they have introduced a new API since I have been developing this. I am using the old API. It is just using JSON requests with their website to get any appropriate data after you have logged in.

I am not sure if you can have an account on planning center without being linked to an organization that has no subscription.
The plugin works similar to a web-browser in that once you login it just access the planning center website and does appropriate JSON requests.

Revision history for this message
Fabian Mathews (supagu) wrote :

RE: API version

> Ah it looks like they have introduced a new API since I have been developing
> this. I am using the old API. It is just using JSON requests with their
> website to get any appropriate data after you have logged in.
>
> I am not sure if you can have an account on planning center without being
> linked to an organization that has no subscription.
> The plugin works similar to a web-browser in that once you login it just
> access the planning center website and does appropriate JSON requests.

RE: Monkey patching

I didn't want to change code outside of the plugin as I was developing it as I was not initially thinking of submitting it for possible inclusion, so ideally this code should be moved out to the appropriate classes.

Unmerged revisions

2556. By Fabian Mathews

Added planningcenter plugin

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'openlp/plugins/planningcenter'
2=== added file 'openlp/plugins/planningcenter/__init__.py'
3--- openlp/plugins/planningcenter/__init__.py 1970-01-01 00:00:00 +0000
4+++ openlp/plugins/planningcenter/__init__.py 2015-09-23 12:21:33 +0000
5@@ -0,0 +1,24 @@
6+# -*- coding: utf-8 -*-
7+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
8+
9+###############################################################################
10+# OpenLP - Open Source Lyrics Projection #
11+# --------------------------------------------------------------------------- #
12+# Copyright (c) 2008-2015 OpenLP Developers #
13+# --------------------------------------------------------------------------- #
14+# This program is free software; you can redistribute it and/or modify it #
15+# under the terms of the GNU General Public License as published by the Free #
16+# Software Foundation; version 2 of the License. #
17+# #
18+# This program is distributed in the hope that it will be useful, but WITHOUT #
19+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
20+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
21+# more details. #
22+# #
23+# You should have received a copy of the GNU General Public License along #
24+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
25+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
26+###############################################################################
27+"""
28+The :mod:`alerts` module provides the Alerts plugin for producing impromptu on-screen announcements during a service.
29+"""
30
31=== added directory 'openlp/plugins/planningcenter/forms'
32=== added file 'openlp/plugins/planningcenter/forms/__init__.py'
33--- openlp/plugins/planningcenter/forms/__init__.py 1970-01-01 00:00:00 +0000
34+++ openlp/plugins/planningcenter/forms/__init__.py 2015-09-23 12:21:33 +0000
35@@ -0,0 +1,48 @@
36+# -*- coding: utf-8 -*-
37+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
38+
39+###############################################################################
40+# OpenLP - Open Source Lyrics Projection #
41+# --------------------------------------------------------------------------- #
42+# Copyright (c) 2008-2015 OpenLP Developers #
43+# --------------------------------------------------------------------------- #
44+# This program is free software; you can redistribute it and/or modify it #
45+# under the terms of the GNU General Public License as published by the Free #
46+# Software Foundation; version 2 of the License. #
47+# #
48+# This program is distributed in the hope that it will be useful, but WITHOUT #
49+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
50+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
51+# more details. #
52+# #
53+# You should have received a copy of the GNU General Public License along #
54+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
55+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
56+###############################################################################
57+"""
58+Forms in OpenLP are made up of two classes. One class holds all the graphical elements, like buttons and lists, and the
59+other class holds all the functional code, like slots and loading and saving.
60+
61+The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
62+modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be
63+converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings.
64+
65+The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
66+is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned
67+above, like so::
68+
69+ class AuthorsForm(QtGui.QDialog, Ui_AuthorsDialog):
70+
71+ def __init__(self, parent=None):
72+ super(AuthorsForm, self).__init__(parent)
73+ self.setupUi(self)
74+
75+This allows OpenLP to use ``self.object`` for all the GUI elements while keeping them separate from the functionality,
76+so that it is easier to recreate the GUI from the .ui files later if necessary.
77+"""
78+
79+from .youtubedownloaddialog import YoutubeDownloadDialog
80+from .logindialog import LoginDialog
81+from .planimportdialog import PlanImportDialog
82+from .maindialog import MainDialog
83+
84
85=== added file 'openlp/plugins/planningcenter/forms/logindialog.py'
86--- openlp/plugins/planningcenter/forms/logindialog.py 1970-01-01 00:00:00 +0000
87+++ openlp/plugins/planningcenter/forms/logindialog.py 2015-09-23 12:21:33 +0000
88@@ -0,0 +1,84 @@
89+# -*- coding: utf-8 -*-
90+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
91+
92+###############################################################################
93+# OpenLP - Open Source Lyrics Projection #
94+# --------------------------------------------------------------------------- #
95+# Copyright (c) 2008-2015 OpenLP Developers #
96+# --------------------------------------------------------------------------- #
97+# This program is free software; you can redistribute it and/or modify it #
98+# under the terms of the GNU General Public License as published by the Free #
99+# Software Foundation; version 2 of the License. #
100+# #
101+# This program is distributed in the hope that it will be useful, but WITHOUT #
102+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
103+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
104+# more details. #
105+# #
106+# You should have received a copy of the GNU General Public License along #
107+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
108+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
109+###############################################################################
110+
111+import os
112+
113+from PyQt4 import QtGui, QtCore, uic
114+
115+from openlp.core.common import Settings
116+from openlp.core.common import AppLocation, translate
117+from openlp.plugins.planningcenter.lib import Session
118+
119+class LoginDialog(QtGui.QDialog):
120+ """
121+ Provide UI for displaying plans and schedules
122+ """
123+ def __init__(self, parent):
124+ """
125+ Initialise the alert form
126+ """
127+ extra_settings = {
128+ 'planningcenter/login email': ""
129+ }
130+ Settings.extend_default_settings(extra_settings)
131+ QtGui.QDialog.__init__(self, parent)
132+ self.setupUi()
133+
134+ def setupUi(self):
135+ """
136+ Setup the UI
137+ """
138+ uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
139+ 'planningcenter', 'resources', 'logindialog.ui')
140+ uic.loadUi(uiPath, self)
141+ self.setWindowIcon(self.parent().windowIcon())
142+
143+ settings = Settings()
144+ settings.beginGroup('planningcenter')
145+ self.emailEdit.setText(settings.value('login email'))
146+ settings.endGroup()
147+
148+ # if there is a an email address, then give focus to password field
149+ if len(self.emailEdit.text()) > 0:
150+ self.passwordEdit.setFocus();
151+
152+
153+ def exec_(self):
154+ """
155+ Execute the dialog and return the exit code.
156+ """
157+ return QtGui.QDialog.exec_(self)
158+
159+ def accept(self):
160+ s = Session()
161+ s.login(self.emailEdit.text(), self.passwordEdit.text())
162+ if s.isLoggedIn():
163+ settings = Settings()
164+ settings.beginGroup('planningcenter')
165+ settings.setValue('login email', self.emailEdit.text())
166+ settings.endGroup()
167+ QtGui.QDialog.accept(self)
168+ else:
169+ QtGui.QMessageBox.information(self,
170+ translate('PlanningCenterPlugin.LoginDialog', 'Login Failed'),
171+ translate('PlanningCenterPlugin.LoginDialog',
172+ 'Login failed, please ensure you have entered the correct email and password.'))
173
174=== added file 'openlp/plugins/planningcenter/forms/maindialog.py'
175--- openlp/plugins/planningcenter/forms/maindialog.py 1970-01-01 00:00:00 +0000
176+++ openlp/plugins/planningcenter/forms/maindialog.py 2015-09-23 12:21:33 +0000
177@@ -0,0 +1,121 @@
178+# -*- coding: utf-8 -*-
179+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
180+
181+###############################################################################
182+# OpenLP - Open Source Lyrics Projection #
183+# --------------------------------------------------------------------------- #
184+# Copyright (c) 2008-2015 OpenLP Developers #
185+# --------------------------------------------------------------------------- #
186+# This program is free software; you can redistribute it and/or modify it #
187+# under the terms of the GNU General Public License as published by the Free #
188+# Software Foundation; version 2 of the License. #
189+# #
190+# This program is distributed in the hope that it will be useful, but WITHOUT #
191+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
192+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
193+# more details. #
194+# #
195+# You should have received a copy of the GNU General Public License along #
196+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
197+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
198+###############################################################################
199+
200+import os
201+import re
202+import logging
203+
204+from PyQt4 import QtGui, QtCore, uic
205+
206+from openlp.core.common import Registry, AppLocation, translate
207+from openlp.plugins.planningcenter.forms import LoginDialog, PlanImportDialog
208+from openlp.plugins.planningcenter.lib import Session
209+
210+class MainDialog(QtGui.QDialog):
211+ """
212+ Provide UI for displaying plans and schedules
213+ """
214+ def __init__(self, plugin):
215+ """
216+ Initialise the alert form
217+ """
218+ QtGui.QDialog.__init__(self, plugin.main_window)
219+ self.manager = plugin.manager
220+ self.plugin = plugin
221+ self.item_id = None
222+ self.setupUi()
223+
224+ def setupUi(self):
225+ """
226+ Setup the UI
227+ """
228+ uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
229+ 'planningcenter', 'resources', 'maindialog.ui')
230+ uic.loadUi(uiPath, self)
231+ self.setWindowIcon(QtGui.QIcon(self.plugin.iconPath()))
232+
233+ def exec_(self):
234+ """
235+ Execute the dialog and return the exit code.
236+ """
237+ self.treeWidget.clear()
238+ QtCore.QTimer.singleShot(100, self.updatePlans)
239+ return QtGui.QDialog.exec_(self)
240+
241+ def login(self):
242+ """
243+ Display the login dialog to let the user login
244+ """
245+ dlg = LoginDialog(self)
246+ dlg.exec_()
247+
248+ def updatePlans(self):
249+ """
250+ Grab the plans from planning center website and populate the tree widget
251+ """
252+ s = Session()
253+ if not s.isLoggedIn():
254+ self.login()
255+
256+ # user cancelled, so abort
257+ if not s.isLoggedIn():
258+ self.close()
259+ return
260+
261+ # populate the dashboard widget
262+ organisation = s.organisation()
263+
264+ for serviceType in organisation["service_types"]:
265+ serviceTypePlans = s.serviceTypePlans(serviceType["id"])
266+
267+ serviceTypeItem = QtGui.QTreeWidgetItem()
268+ serviceTypeItem.setText(0, serviceType["name"])
269+
270+ icon = QtGui.QIcon(self.planIconPath())
271+ serviceTypeItem.setIcon(0, icon)
272+
273+ self.treeWidget.invisibleRootItem().addChild(serviceTypeItem)
274+
275+ for plan in serviceTypePlans:
276+ planItem = QtGui.QTreeWidgetItem()
277+ planItem.setText(0, plan["dates"])
278+ planItem.setText(1, str(plan["id"]))
279+ serviceTypeItem.addChild(planItem)
280+
281+ self.treeWidget.expandAll()
282+
283+
284+ def on_treeWidget_itemDoubleClicked(self, item, column):
285+ """
286+ Time to import the plan
287+ """
288+ dlg = PlanImportDialog(self, item.text(1))
289+ dlg.exec_()
290+
291+ def planIconPath(self):
292+ """
293+ Get URL for plugin icon
294+ """
295+ return os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
296+ 'planningcenter', 'resources', 'planicon.png')
297+
298+
299
300=== added file 'openlp/plugins/planningcenter/forms/planimportdialog.py'
301--- openlp/plugins/planningcenter/forms/planimportdialog.py 1970-01-01 00:00:00 +0000
302+++ openlp/plugins/planningcenter/forms/planimportdialog.py 2015-09-23 12:21:33 +0000
303@@ -0,0 +1,685 @@
304+# -*- coding: utf-8 -*-
305+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
306+
307+###############################################################################
308+# OpenLP - Open Source Lyrics Projection #
309+# --------------------------------------------------------------------------- #
310+# Copyright (c) 2008-2015 OpenLP Developers #
311+# --------------------------------------------------------------------------- #
312+# This program is free software; you can redistribute it and/or modify it #
313+# under the terms of the GNU General Public License as published by the Free #
314+# Software Foundation; version 2 of the License. #
315+# #
316+# This program is distributed in the hope that it will be useful, but WITHOUT #
317+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
318+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
319+# more details. #
320+# #
321+# You should have received a copy of the GNU General Public License along #
322+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
323+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
324+###############################################################################
325+
326+import os
327+import types
328+import re
329+
330+from PyQt4 import QtGui, QtCore, uic
331+
332+from openlp.core.common import Settings
333+from openlp.core.common import AppLocation, translate
334+from openlp.plugins.planningcenter.lib import Session
335+from openlp.plugins.planningcenter.lib import Youtube
336+from openlp.core.common import Registry, RegistryProperties
337+from openlp.plugins.songs.lib.db import Author
338+from openlp.plugins.custom.lib import CustomXMLBuilder, CustomXMLParser
339+from openlp.plugins.custom.lib.db import CustomSlide
340+
341+class PlanImportDialog(QtGui.QDialog, RegistryProperties):
342+ """
343+ Provide UI for displaying plans and schedules
344+ """
345+ def __init__(self, parent, planId):
346+ """
347+ Initialise the alert form
348+ """
349+ QtGui.QDialog.__init__(self, parent)
350+ self.planId = planId
351+ self.setupUi()
352+
353+ def setupUi(self):
354+ """
355+ Setup the UI
356+ """
357+ uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
358+ 'planningcenter', 'resources', 'planimportdialog.ui')
359+ uic.loadUi(uiPath, self)
360+ self.setWindowIcon(self.parent().windowIcon())
361+ self.progressBar.setValue(0)
362+ self.progressLabel.setText("Processing plan items...")
363+ self.secondLabel.setText("")
364+ self.textEdit.setText("")
365+ self.openDirButton.setVisible(False)
366+
367+ def exec_(self):
368+ """
369+ Execute the dialog and return the exit code.
370+ """
371+ QtCore.QTimer.singleShot(100, self.importPlan)
372+ return QtGui.QDialog.exec_(self)
373+
374+ def importPlan(self):
375+ """
376+ Download the media and import it
377+ """
378+ # item count sucks, really should go add the media size of all items
379+ # or add a loading bar per item
380+ s = Session()
381+ plan = s.plan(self.planId)
382+
383+ self.progressCanceled = False
384+ self.progressComplete = False
385+
386+ # setup the absolute output directory from settings
387+ settings = Settings()
388+ settings.beginGroup('planningcenter')
389+ self.outputDirectory = settings.value('media directory')
390+ if self.outputDirectory != None and len(self.outputDirectory) > 0:
391+ self.outputDirectory = os.path.abspath(self.outputDirectory)
392+ else:
393+ self.outputDirectory = os.path.abspath(plan["dates"])
394+
395+ settings.endGroup()
396+
397+
398+
399+ self.textEdit.setText("Creating output directory: " + self.outputDirectory + "\n")
400+
401+ # create the directory
402+ if not os.path.exists(self.outputDirectory):
403+ os.makedirs(self.outputDirectory)
404+
405+ self.downloadAllItems(plan)
406+
407+ # clean up downloaded files?
408+ if self.progressCanceled:
409+ self.textEdit.append("\nCanceled by user")
410+ self.textEdit.setText("Canceled by user")
411+ self.close()
412+
413+ # save out the service
414+ self.service_manager._save_lite = False
415+ self.service_manager._file_name = os.path.join(self.outputDirectory, plan["dates"] + ".osz")
416+ self.service_manager.decide_save_method()
417+
418+ self.textEdit.append("\nComplete")
419+ self.progressLabel.setText("Complete")
420+
421+ # this gives the chance for the user to check any log output?
422+ self.button.setText("Ok")
423+ self.progressComplete = True
424+
425+ self.openDirButton.setVisible(True)
426+
427+ def downloadAllItems(self, plan):
428+ s = Session()
429+
430+ processedItems = []
431+
432+ # iterate over each item and gather media and songs for download
433+ i = 0
434+ itemsCount = len(plan["items"])
435+ for item in plan["items"]:
436+ i = i + 1
437+ self.secondLabel.setText("Processing item " + str(i) + " of " + str(itemsCount))
438+
439+ percent = (i / float(itemsCount)) * 100
440+ self.progressBar.setValue(percent)
441+
442+ if item["type"] == "PlanMedia":
443+ itemMedia = item["plan_item_medias"]
444+ # no media
445+ if len(itemMedia) <= 0:
446+ continue
447+
448+ media = s.media(itemMedia[0]["media_id"])
449+
450+ # Youtube url's are "public_url"
451+ mediaPublicUrl = None
452+ mediaUrl = None
453+ if "public_url" in media["attachments"][0]:
454+ mediaPublicUrl = media["attachments"][0]["public_url"]
455+ else:
456+ mediaUrl = media["attachments"][0]["url"]
457+
458+ fileName = media["attachments"][0]["filename"]
459+
460+ processedItems.append({"type": "PlanMedia", "mediaUrl": mediaUrl, "mediaPublicUrl": mediaPublicUrl, "fileName": fileName})
461+
462+ elif item["type"] == "PlanSong":
463+ title = item["title"]
464+ author = item["song"]["author"]
465+ arrangementId = item["arrangement"]["id"]
466+ arrangement = s.arrangement(arrangementId)
467+ lyrics = arrangement["chord_chart"]
468+ sequence = arrangement["sequence_to_s"]
469+ processedItems.append({"type": "PlanSong", "title": title, "author": author, "lyrics": lyrics, "sequence": sequence})
470+
471+ elif item["type"] == "PlanItem" and item["using_custom_slides"] == True:
472+ title = item["title"] + " - " + plan["dates"] # add the date as custom slide are custom... plan specific
473+ customSlides = item["custom_slides"]
474+ processedItems.append({"type": "PlanCustomSlides", "title": title, "customSlides": customSlides})
475+
476+
477+ i = 0
478+ itemsCount = len(processedItems)
479+ for item in processedItems:
480+ i = i + 1
481+ self.secondLabel.setText("Processing item " + str(i) + " of " + str(itemsCount))
482+
483+ if self.progressCanceled:
484+ return
485+
486+ if item["type"] == "PlanMedia":
487+ self.progressLabel.setText("Downloading Media " + item["fileName"])
488+
489+ # when downloading from youtube, we dont know the extension till its downloaded
490+ newFileName = self.downloadMedia(item["mediaUrl"], item["mediaPublicUrl"], item["fileName"])
491+ if self.progressCanceled:
492+ return
493+
494+ okMsg = ""
495+ if newFileName == None:
496+ okMsg = " [FAIL]"
497+
498+ # add media to openLP
499+ item["fileName"] = newFileName
500+ #fullFileName = os.path.join(self.outputDirectory, item["fileName"])
501+ self.addMediaToAppropriateManager(newFileName)
502+
503+ self.textEdit.append("Media Downloaded: " + item["fileName"] + okMsg)
504+
505+ elif item["type"] == "PlanSong":
506+ self.progressLabel.setText("Processing Song " + item["title"])
507+
508+ ok = self.processSong(item["title"], item["author"], item["lyrics"], item["sequence"])
509+
510+ okMsg = ""
511+ if ok != True:
512+ okMsg = " [FAIL]"
513+
514+ self.textEdit.append("Song Processed: " + item["title"] + okMsg)
515+
516+ elif item["type"] == "PlanCustomSlides":
517+ self.progressLabel.setText("Processing Custom Slides " + item["title"])
518+ ok = self.processCustomSlides(item["title"], item["customSlides"])
519+
520+ okMsg = ""
521+ if ok != True:
522+ okMsg = " [FAIL]"
523+
524+ self.textEdit.append("Custom Slides Processed: " + item["title"] + okMsg)
525+
526+ return
527+
528+ def processCustomSlides(self, title, customSlides):
529+ """
530+ add the custom slides... code copied from custom/forms/editcustomform.py
531+ """
532+
533+ # see if slides already exist, if so, delete them to avoid duplicates
534+ item = self.custom.findItemByDisplayName(title)
535+ if item != None:
536+ self.custom.plugin.db_manager.delete_object(CustomSlide, item.data(QtCore.Qt.UserRole))
537+ self.custom.on_search_text_button_clicked()
538+
539+ custom_slide = CustomSlide()
540+ sxml = CustomXMLBuilder()
541+ for count in range(len(customSlides)):
542+ sxml.add_verse_to_lyrics('custom', str(count + 1), customSlides[count]['body'])
543+ custom_slide.title = title
544+ custom_slide.text = str(sxml.extract_xml(), 'utf-8')
545+ custom_slide.credits = ''
546+ custom_slide.theme_name = ''
547+ success = self.custom.plugin.db_manager.save_object(custom_slide)
548+ self.custom.auto_select_id = custom_slide.id
549+
550+ self.custom.on_clear_text_button_click()
551+ self.custom.on_selection_change()
552+
553+ # add the slides to the service
554+ item = self.custom.addMedia(custom_slide.id)
555+ if item == None:
556+ self.textEdit.append("Failed to add " + title)
557+ return False
558+
559+ self.custom.add_to_service(item)
560+
561+ return success
562+
563+ # This searches the song for the lyrics
564+ # for example Chorus or C is the sequence title, we return the text between Chorus and the next Verse for example
565+ def findLyrics(self, sequenceTitle, lyrics):
566+ sequenceSplitExp = re.compile('(\D*)(\d*)')
567+ titleExp = re.compile('\(?(Verse|Chorus|Tag|Outro|Bridge|Misc)\)?\s?(\d?)|\(?(\S)(\d+)\)?')
568+
569+ sequenceTitleMatch = sequenceSplitExp.match(sequenceTitle)
570+ if sequenceTitleMatch == None:
571+ return ""
572+
573+ title = sequenceTitleMatch.group(1)
574+ number = sequenceTitleMatch.group(2)
575+ if number == "":
576+ number = "1"
577+
578+
579+ endCharacter = len(lyrics)
580+ startCharacter = endCharacter
581+
582+ titleFound = False
583+ for match in titleExp.finditer(lyrics):
584+
585+ groupCount = len(match.groups())
586+ titleLong = match.group(1) if (groupCount >= 1) else "Undefined"
587+ titleShort = match.group(3) if (groupCount >= 3) else "Undefined"
588+ if titleLong == None:
589+ titleLong = "Undefined"
590+
591+ numberLong = match.group(2) if (groupCount >= 2) else ""
592+ numberShort = match.group(4) if (groupCount >= 4) else ""
593+
594+ if numberLong == "" and numberShort == None:
595+ numberLong = "1";
596+
597+ # ok, we found the next title in the song
598+ if titleFound:
599+ endCharacter = match.start()
600+ break
601+
602+ # we found a match to the title, so now we need to just set titleFound
603+ # and find the next match
604+ if (title == titleLong or title == titleShort or titleLong.startswith(title)) and (number == numberLong or number == numberShort):
605+ titleFound = True
606+ startCharacter = match.end()
607+
608+ # now we know the lyrics to extract from the song are between
609+ # startCharacter and endCharacter
610+ extractedLyrics = lyrics[startCharacter:endCharacter]
611+ extractedLyrics = extractedLyrics.strip('\n')
612+ return extractedLyrics
613+
614+
615+ def processSong(self, title, authors, lyrics, sequence):
616+ """
617+ add the song
618+ """
619+ # emulate the user entering a new song
620+ self.songs.edit_song_form.new_song()
621+ self.songs.edit_song_form.title_edit.setText(title)
622+
623+ # emulate on_author_add_button_clicked
624+ authorList = authors.split(",")
625+ authorDisplayName = "Unknown Author"
626+ firstName = ""
627+ lastName = ""
628+ if len(authorList) >= 1:
629+ authorDisplayName = authorList[0].strip()
630+ authorNameList = authorDisplayName.split(" ")
631+ firstName = authorNameList[0] if len(authorNameList) > 1 else ""
632+ lastName = authorNameList[1] if len(authorNameList) > 1 else ""
633+ author = Author.populate(first_name=firstName, last_name=lastName, display_name=authorDisplayName)
634+
635+ # find existing song, if found simply use that
636+ displayName = title + " (" + authorDisplayName + ")";
637+ item = self.songs.findItemByDisplayName(displayName)
638+ if item:
639+ self.songs.add_to_service(item)
640+ return True
641+
642+ self.songs.edit_song_form.manager.save_object(author)
643+ self.songs.edit_song_form._add_author_to_list(author, "words")
644+ self.songs.edit_song_form.load_authors()
645+ self.songs.edit_song_form.authors_combo_box.setCurrentIndex(0)
646+
647+ # setup each chrosu/verse into its own slide
648+ # TODO: break it based on number of lines
649+ sequenceList = sequence.split(", ")
650+ compiledSequenceList = []
651+ itemMap = {}
652+ for s in sequenceList:
653+ s = s.strip()
654+ if len(s) == 1:
655+ s += "1"
656+
657+ sLower = s.lower()
658+ sLower = sLower.replace("tag", "ending") # openlp doesnt recognise 'tag'
659+
660+ if sLower not in itemMap:
661+ extractedLyrics = self.findLyrics(s, lyrics)
662+ if extractedLyrics == None or len(extractedLyrics) <= 0:
663+ continue
664+
665+ item = QtGui.QTableWidgetItem(extractedLyrics)
666+ item.setData(QtCore.Qt.UserRole, sLower)
667+ item.setText(extractedLyrics)
668+ itemMap[sLower] = item
669+ self.songs.edit_song_form.verse_list_widget.setRowCount(self.songs.edit_song_form.verse_list_widget.rowCount() + 1)
670+ self.songs.edit_song_form.verse_list_widget.setItem(self.songs.edit_song_form.verse_list_widget.rowCount() - 1, 0, item)
671+
672+ if sLower in itemMap:
673+ compiledSequenceList.append(sLower)
674+
675+ verseOrder = " ".join(compiledSequenceList)
676+ self.songs.edit_song_form.verse_order_edit.setText(verseOrder)
677+
678+ # emulate song form accept method
679+ self.songs.edit_song_form.clear_caches()
680+ if not self.songs.edit_song_form._validate_song():
681+ return False
682+
683+ self.songs.edit_song_form.save_song()
684+ songId = self.songs.edit_song_form.song.id
685+ self.songs.edit_song_form.song = None
686+
687+ # add new song to the list
688+ self.songs.on_clear_text_button_click()
689+
690+ # get the item and add to the service manager
691+ item = self.songs.findItem(songId)
692+ self.songs.add_to_service(item)
693+ return True
694+
695+ def addMediaToAppropriateManager(self, fileName):
696+ """
697+ given the file to the appropriate manager
698+ """
699+ self.addMedia(self.images, fileName)
700+ self.addMedia(self.presentations, fileName)
701+ self.addMedia(self.media, fileName)
702+
703+
704+ def addMedia(self, manager, fileName):
705+ # check file type is supported
706+ if not manager.handlesFile(fileName):
707+ return True
708+
709+ item = manager.addMedia(fileName)
710+ if item == None:
711+ self.textEdit.append("Failed to add " + fileName)
712+ return False
713+
714+ manager.add_to_service(item)
715+ return True
716+
717+
718+ @property
719+ def songs(self):
720+ """
721+ Adds the songs plugin to the class dynamically
722+ """
723+ if not hasattr(self, '_songs') or not self._songs:
724+ self._songs = Registry().get('songs')
725+ monkeyPatchSongs(self._songs)
726+ return self._songs
727+
728+ @property
729+ def presentations(self):
730+ """
731+ Adds the presentations plugin to the class dynamically
732+ """
733+ if not hasattr(self, '_presentations') or not self._presentations:
734+ self._presentations = Registry().get('presentations')
735+ monkeyPatchGeneric(self._presentations)
736+ monkeyPatchPresentations(self._presentations)
737+ return self._presentations
738+
739+ @property
740+ def custom(self):
741+ """
742+ Adds the custom slides plugin to the class dynamically
743+ """
744+ if not hasattr(self, '_custom') or not self._custom:
745+ self._custom = Registry().get('custom')
746+ monkeyPatchGeneric(self._custom)
747+ monkeyPatchCustom(self._custom)
748+ return self._custom
749+
750+ @property
751+ def media(self):
752+ """
753+ Adds the media plugin to the class dynamically
754+ """
755+ if not hasattr(self, '_media') or not self._media:
756+ self._media = Registry().get('media')
757+ monkeyPatchGeneric(self._media)
758+ monkeyPatchMedia(self._media)
759+ return self._media
760+
761+ @property
762+ def images(self):
763+ """
764+ Adds the images plugin to the class dynamically
765+ """
766+ if not hasattr(self, '_images') or not self._images:
767+ self._images = Registry().get('images')
768+ monkeyPatchGeneric(self._images)
769+ monkeyPatchImages(self._images)
770+ return self._images
771+
772+ def downloadMedia(self, url, publicUrl, fileName):
773+ """
774+ download a single media item
775+ """
776+
777+ # testing only! no way to guarantee file has not changed
778+ # this will just speed up development though
779+ # TODO: Can we do some date check?
780+ fullFileName = os.path.join(self.outputDirectory, fileName)
781+ if os.path.exists(fullFileName):
782+ return fullFileName
783+
784+ if publicUrl != None:
785+ return self.downloadPublicMedia(publicUrl, fullFileName)
786+
787+ return self.downloadPrivateMedia(url, fullFileName)
788+
789+ def downloadPublicMedia(self, url, fileName):
790+ """
791+ download a single media item from an external website
792+ """
793+ y = Youtube()
794+ return y.download(url, fileName)
795+
796+ def downloadPrivateMedia(self, url, fileName):
797+ """
798+ download a single media item from planning center website
799+ """
800+ s = Session()
801+ r = s.urlStream(url)
802+ if r.status_code != 200:
803+ return None
804+
805+ bytes = 0
806+ contentLength = r.headers['content-length']
807+ with open(fileName + ".part", 'wb') as f:
808+ for chunk in r.iter_content(chunk_size=10240):
809+ if chunk: # filter out keep-alive new chunks
810+ bytes += len(chunk)
811+ f.write(chunk)
812+ f.flush()
813+
814+ percent = (bytes / float(contentLength)) * 100
815+ self.progressBar.setValue(percent)
816+
817+ if self.progressCanceled:
818+ return None
819+
820+ os.rename(fileName + ".part", fileName)
821+ return fileName
822+
823+ def on_button_clicked(self):
824+ """
825+ close or abort
826+ """
827+ if self.progressComplete:
828+ self.close()
829+ else:
830+ self.progressCanceled = True
831+
832+ def on_openDirButton_clicked(self):
833+ """
834+ open explorer to allow us to copy to usb stick or what ever
835+ """
836+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.outputDirectory))
837+
838+
839+
840+"""
841+Monkey patch for all managers
842+"""
843+def monkeyPatchGeneric(target):
844+ def addMedia(self, fileName):
845+ """
846+ Add the file, if it exists override it incase its changed and return the item handle for the added item
847+ """
848+ existingItem = self.findItem(fileName)
849+ if existingItem != None:
850+ return existingItem
851+
852+ self.validate_and_load([fileName])
853+ return self.findItem(fileName)
854+
855+ def handlesFile(target, fileName):
856+ """
857+ Using drag and drop support, determine if the manager can handle the given file type
858+ """
859+ file_type = fileName.split('.')[-1]
860+ if file_type.lower() not in target.on_new_file_masks:
861+ return False
862+
863+ return True
864+
865+ def findItem(self, fileName):
866+ """
867+ Find item given a filename - must be implemented
868+ """
869+ return None
870+
871+ target.addMedia = types.MethodType(addMedia, target)
872+ target.handlesFile = types.MethodType(handlesFile, target)
873+ target.findItem = types.MethodType(findItem, target)
874+
875+"""
876+Monkey patch for 'image' manager
877+"""
878+def monkeyPatchImages(target):
879+ def addMedia(self, fileName):
880+ """
881+ Add the file, if it exists override it incase its changed and return the item handle for the added item
882+ """
883+ existingItem = self.findItem(fileName)
884+ if existingItem != None:
885+ return existingItem
886+
887+ self.save_new_images_list([fileName])
888+ return self.findItem(fileName)
889+
890+ def findItem(self, fileName):
891+ """
892+ Find item given a filename
893+ """
894+ for count in range(self.list_view.count()):
895+ item = self.list_view.item(count)
896+ itemFileName = item.data(0, QtCore.Qt.UserRole).filename;
897+ if itemFileName == fileName:
898+ return item
899+
900+ target.addMedia = types.MethodType(addMedia, target)
901+ target.findItem = types.MethodType(findItem, target)
902+
903+"""
904+Monkey patch for 'presentations' manager
905+"""
906+def monkeyPatchPresentations(target):
907+ def findItem(self, fileName):
908+ """
909+ Find item given a filename
910+ """
911+ for count in range(self.list_view.count()):
912+ item = self.list_view.item(count)
913+ itemFileName = item.data(QtCore.Qt.UserRole)
914+ if itemFileName == fileName:
915+ return item
916+
917+ target.findItem = types.MethodType(findItem, target)
918+
919+"""
920+Monkey patch for 'custom' slides manager
921+"""
922+def monkeyPatchCustom(target):
923+ def findItem(self, fileName):
924+ """
925+ Find item given a id
926+ """
927+ for count in range(self.list_view.count()):
928+ item = self.list_view.item(count)
929+ itemFileName = item.data(QtCore.Qt.UserRole)
930+ if itemFileName == fileName:
931+ return item
932+
933+ def findItemByDisplayName(self, name):
934+ """
935+ Find item given a song name (author name)
936+ """
937+ for count in range(self.list_view.count()):
938+ item = self.list_view.item(count)
939+ itemName = item.data(QtCore.Qt.DisplayRole)
940+ if name == itemName:
941+ return item
942+
943+ target.findItem = types.MethodType(findItem, target)
944+ target.findItemByDisplayName = types.MethodType(findItemByDisplayName, target)
945+
946+"""
947+Monkey patch for 'media' manager
948+"""
949+def monkeyPatchMedia(target):
950+ def findItem(self, fileName):
951+ """
952+ Find item given a filename
953+ """
954+ for count in range(self.list_view.count()):
955+ item = self.list_view.item(count)
956+ itemFileName = item.data(QtCore.Qt.UserRole)
957+ if itemFileName == fileName:
958+ return item
959+
960+ target.findItem = types.MethodType(findItem, target)
961+
962+"""
963+Monkey patch for 'songs' manager
964+"""
965+def monkeyPatchSongs(target):
966+ def findItem(self, id):
967+ """
968+ Find item given a filename
969+ """
970+ for count in range(self.list_view.count()):
971+ item = self.list_view.item(count)
972+ itemName = item.data(QtCore.Qt.DisplayRole)
973+ itemId = item.data(QtCore.Qt.UserRole)
974+ if id == itemId:
975+ return item
976+
977+ def findItemByDisplayName(self, name):
978+ """
979+ Find item given a song name (author name)
980+ """
981+ for count in range(self.list_view.count()):
982+ item = self.list_view.item(count)
983+ itemName = item.data(QtCore.Qt.DisplayRole)
984+ if name == itemName:
985+ return item
986+
987+ target.findItem = types.MethodType(findItem, target)
988+ target.findItemByDisplayName = types.MethodType(findItemByDisplayName, target)
989\ No newline at end of file
990
991=== added file 'openlp/plugins/planningcenter/forms/youtubedownloaddialog.py'
992--- openlp/plugins/planningcenter/forms/youtubedownloaddialog.py 1970-01-01 00:00:00 +0000
993+++ openlp/plugins/planningcenter/forms/youtubedownloaddialog.py 2015-09-23 12:21:33 +0000
994@@ -0,0 +1,66 @@
995+# -*- coding: utf-8 -*-
996+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
997+
998+###############################################################################
999+# OpenLP - Open Source Lyrics Projection #
1000+# --------------------------------------------------------------------------- #
1001+# Copyright (c) 2008-2015 OpenLP Developers #
1002+# --------------------------------------------------------------------------- #
1003+# This program is free software; you can redistribute it and/or modify it #
1004+# under the terms of the GNU General Public License as published by the Free #
1005+# Software Foundation; version 2 of the License. #
1006+# #
1007+# This program is distributed in the hope that it will be useful, but WITHOUT #
1008+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1009+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1010+# more details. #
1011+# #
1012+# You should have received a copy of the GNU General Public License along #
1013+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1014+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1015+###############################################################################
1016+
1017+import os
1018+
1019+from PyQt4 import QtGui, QtCore, uic
1020+
1021+from openlp.core.common import AppLocation, translate
1022+from openlp.plugins.planningcenter.lib import Youtube
1023+
1024+class YoutubeDownloadDialog(QtGui.QDialog):
1025+ """
1026+ Provide UI for downloading youtube media
1027+ """
1028+ def __init__(self, plugin):
1029+ """
1030+ Initialise the form
1031+ """
1032+ QtGui.QDialog.__init__(self, plugin.main_window)
1033+ self.manager = plugin.manager
1034+ self.plugin = plugin
1035+ self.item_id = None
1036+ self.setupUi()
1037+
1038+ def setupUi(self):
1039+ """
1040+ Setup the UI
1041+ """
1042+ uiPath = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
1043+ 'planningcenter', 'resources', 'youtubedownloaddialog.ui')
1044+ uic.loadUi(uiPath, self)
1045+ self.setWindowIcon(QtGui.QIcon(self.plugin.youtubeIconPath()))
1046+
1047+ def exec_(self):
1048+ """
1049+ Execute the dialog and return the exit code.
1050+ """
1051+ return QtGui.QDialog.exec_(self)
1052+
1053+ def accept(self):
1054+ y = Youtube()
1055+ y.download(self.urlEdit.text(), self.filenameEdit.text())
1056+ QtGui.QMessageBox.information(self,
1057+ translate('PlanningCenterPlugin.YoutubeDownloadDialog', 'Ok'),
1058+ translate('PlanningCenterPlugin.YoutubeDownloadDialog',
1059+ 'Download complete.'))
1060+ QtGui.QDialog.accept(self)
1061
1062=== added directory 'openlp/plugins/planningcenter/lib'
1063=== added file 'openlp/plugins/planningcenter/lib/__init__.py'
1064--- openlp/plugins/planningcenter/lib/__init__.py 1970-01-01 00:00:00 +0000
1065+++ openlp/plugins/planningcenter/lib/__init__.py 2015-09-23 12:21:33 +0000
1066@@ -0,0 +1,25 @@
1067+# -*- coding: utf-8 -*-
1068+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
1069+
1070+###############################################################################
1071+# OpenLP - Open Source Lyrics Projection #
1072+# --------------------------------------------------------------------------- #
1073+# Copyright (c) 2008-2015 OpenLP Developers #
1074+# --------------------------------------------------------------------------- #
1075+# This program is free software; you can redistribute it and/or modify it #
1076+# under the terms of the GNU General Public License as published by the Free #
1077+# Software Foundation; version 2 of the License. #
1078+# #
1079+# This program is distributed in the hope that it will be useful, but WITHOUT #
1080+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1081+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1082+# more details. #
1083+# #
1084+# You should have received a copy of the GNU General Public License along #
1085+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1086+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1087+###############################################################################
1088+
1089+from .planningcentertab import PlanningCenterTab
1090+from .session import Session
1091+from .youtube import Youtube
1092
1093=== added file 'openlp/plugins/planningcenter/lib/db.py'
1094--- openlp/plugins/planningcenter/lib/db.py 1970-01-01 00:00:00 +0000
1095+++ openlp/plugins/planningcenter/lib/db.py 2015-09-23 12:21:33 +0000
1096@@ -0,0 +1,55 @@
1097+# -*- coding: utf-8 -*-
1098+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
1099+
1100+###############################################################################
1101+# OpenLP - Open Source Lyrics Projection #
1102+# --------------------------------------------------------------------------- #
1103+# Copyright (c) 2008-2015 OpenLP Developers #
1104+# --------------------------------------------------------------------------- #
1105+# This program is free software; you can redistribute it and/or modify it #
1106+# under the terms of the GNU General Public License as published by the Free #
1107+# Software Foundation; version 2 of the License. #
1108+# #
1109+# This program is distributed in the hope that it will be useful, but WITHOUT #
1110+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1111+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1112+# more details. #
1113+# #
1114+# You should have received a copy of the GNU General Public License along #
1115+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1116+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1117+###############################################################################
1118+"""
1119+The :mod:`db` module provides the database and schema that is the backend for the Alerts plugin.
1120+"""
1121+
1122+from sqlalchemy import Column, Table, types
1123+from sqlalchemy.orm import mapper
1124+
1125+from openlp.core.lib.db import BaseModel, init_db
1126+
1127+
1128+class AlertItem(BaseModel):
1129+ """
1130+ AlertItem model
1131+ """
1132+ pass
1133+
1134+
1135+def init_schema(url):
1136+ """
1137+ Setup the alerts database connection and initialise the database schema
1138+
1139+ :param url:
1140+ The database to setup
1141+ """
1142+ session, metadata = init_db(url)
1143+
1144+ alerts_table = Table('alerts', metadata,
1145+ Column('id', types.Integer(), primary_key=True),
1146+ Column('text', types.UnicodeText, nullable=False))
1147+
1148+ mapper(AlertItem, alerts_table)
1149+
1150+ metadata.create_all(checkfirst=True)
1151+ return session
1152
1153=== added file 'openlp/plugins/planningcenter/lib/planningcentertab.py'
1154--- openlp/plugins/planningcenter/lib/planningcentertab.py 1970-01-01 00:00:00 +0000
1155+++ openlp/plugins/planningcenter/lib/planningcentertab.py 2015-09-23 12:21:33 +0000
1156@@ -0,0 +1,95 @@
1157+# -*- coding: utf-8 -*-
1158+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
1159+
1160+###############################################################################
1161+# OpenLP - Open Source Lyrics Projection #
1162+# --------------------------------------------------------------------------- #
1163+# Copyright (c) 2008-2015 OpenLP Developers #
1164+# --------------------------------------------------------------------------- #
1165+# This program is free software; you can redistribute it and/or modify it #
1166+# under the terms of the GNU General Public License as published by the Free #
1167+# Software Foundation; version 2 of the License. #
1168+# #
1169+# This program is distributed in the hope that it will be useful, but WITHOUT #
1170+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
1171+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
1172+# more details. #
1173+# #
1174+# You should have received a copy of the GNU General Public License along #
1175+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
1176+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1177+###############################################################################
1178+
1179+from PyQt4 import QtGui
1180+
1181+from openlp.core.common import Settings, UiStrings, translate
1182+from openlp.core.lib import ColorButton, SettingsTab
1183+from openlp.core.lib.ui import create_valign_selection_widgets
1184+
1185+
1186+class PlanningCenterTab(SettingsTab):
1187+ """
1188+ PlanningCenterTab is the settings tab in the settings dialog.
1189+ """
1190+ def __init__(self, parent, name, visible_title, icon_path):
1191+ extra_settings = {
1192+ 'planningcenter/media directory': "Media"
1193+ }
1194+ Settings.extend_default_settings(extra_settings)
1195+
1196+ super(PlanningCenterTab, self).__init__(parent, name, visible_title, icon_path)
1197+
1198+ def setupUi(self):
1199+ self.setObjectName('PlanningCenterTab')
1200+ super(PlanningCenterTab, self).setupUi()
1201+
1202+ self.advanced_group_box = QtGui.QGroupBox(self.left_column)
1203+ self.advanced_group_box.setObjectName('advanced_group_box')
1204+ self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box)
1205+ self.advanced_layout.setObjectName('advanced_layout')
1206+
1207+ self.media_directory_label = QtGui.QLabel(self.advanced_group_box)
1208+ self.media_directory_label.setObjectName('media_directory_label')
1209+ self.advanced_layout.addWidget(self.media_directory_label)
1210+
1211+ self.media_directory_edit = QtGui.QLineEdit(self.advanced_group_box)
1212+ self.media_directory_edit.setObjectName('media_directory_edit')
1213+ self.advanced_layout.addWidget(self.media_directory_edit)
1214+
1215+ self.left_layout.addWidget(self.advanced_group_box)
1216+ self.left_layout.addStretch()
1217+ self.right_layout.addStretch()
1218+
1219+ # Signals and slots
1220+ #self.media_directory_edit.valueChanged.connect(self.on_media_directory_edit_changed)
1221+
1222+ def retranslateUi(self):
1223+ self.advanced_group_box.setTitle(UiStrings().Advanced)
1224+ self.media_directory_label.setText(translate('MediaPlugin.MediaTab', 'Media directory'))
1225+
1226+ #def on_media_directory_edit_changed(self, color):
1227+ # """
1228+ # The background color has been changed.
1229+ # """
1230+ # self.changed = True
1231+
1232+ def load(self):
1233+ """
1234+ Load the settings into the UI.
1235+ """
1236+ settings = Settings()
1237+ settings.beginGroup(self.settings_section)
1238+ self.media_directory = settings.value('media directory')
1239+ settings.endGroup()
1240+ self.media_directory_edit.setText(self.media_directory)
1241+ self.changed = False
1242+
1243+ def save(self):
1244+ """
1245+ Save the changes on exit of the Settings dialog.
1246+ """
1247+ settings = Settings()
1248+ settings.beginGroup(self.settings_section)
1249+ settings.setValue('media directory', self.media_directory)
1250+ settings.endGroup()
1251+ self.changed = False
1252
1253=== added file 'openlp/plugins/planningcenter/lib/session.py'
1254--- openlp/plugins/planningcenter/lib/session.py 1970-01-01 00:00:00 +0000
1255+++ openlp/plugins/planningcenter/lib/session.py 2015-09-23 12:21:33 +0000
1256@@ -0,0 +1,119 @@
1257+import logging
1258+import requests
1259+import sys
1260+import json
1261+
1262+# Start a session so we can have persistant cookies
1263+g_session = requests.session()
1264+g_loggedIn = False
1265+g_loginData = None
1266+
1267+class Session():
1268+ """
1269+ Handle login and session data as website
1270+ http://stackoverflow.com/questions/8316818/login-to-website-using-python
1271+
1272+ And provide access to JSON data for planning centre online
1273+ http://get.planningcenteronline.com/api
1274+ """
1275+ def __init__(self):
1276+ return
1277+
1278+ def isLoggedIn(self):
1279+ global g_session
1280+ return g_loggedIn
1281+
1282+ def login(self, email, password):
1283+ global g_loginData
1284+
1285+ # This is the form data that the page sends when logging in
1286+ g_loginData = {
1287+ 'email': email,
1288+ 'password': password,
1289+ 'submit': 'login',
1290+ }
1291+
1292+ return self.relogin()
1293+
1294+ def relogin(self):
1295+ # Attempt relogin
1296+
1297+ global g_session
1298+ global g_loggedIn
1299+
1300+ g_loggedIn = False
1301+
1302+ try:
1303+ r = g_session.post('https://accounts.planningcenteronline.com/login', data = g_loginData)
1304+ except:
1305+ log.info(sys.exc_info()[0])
1306+
1307+ # Login failed - really we should test this after each URL fetch and auto-relogin?
1308+ if "<title>Login - Accounts</title>" not in r.text:
1309+ g_loggedIn = True
1310+
1311+ return g_loggedIn
1312+
1313+
1314+ def organisation(self):
1315+ """
1316+ Contains organisation data - service types
1317+ """
1318+ return json.loads(self.url('https://services.planningcenteronline.com/organization.json').text)
1319+
1320+ def serviceTypePlans(self, serviceTypeId):
1321+ """
1322+ Contains all plans for a certain service type
1323+ """
1324+ return json.loads(self.url('https://planningcenteronline.com/service_types/' + str(serviceTypeId) + '/plans.json').text)
1325+
1326+ def plan(self, planId):
1327+ """
1328+ Plan data
1329+ """
1330+ return json.loads(self.url('https://planningcenteronline.com/plans/' + str(planId) + '.json?include_slides=true').text)
1331+
1332+ def media(self, mediaId):
1333+ """
1334+ Media data
1335+ """
1336+ return json.loads(self.url('https://services.planningcenteronline.com/medias/' + str(mediaId) + '.json').text)
1337+
1338+ def arrangement(self, arrangementId):
1339+ """
1340+ Arrangement data
1341+ """
1342+ return json.loads(self.url('https://planningcenteronline.com/arrangements/' + str(arrangementId) + '.json').text)
1343+
1344+ def urlStream(self, u):
1345+ global g_session
1346+ r = g_session.get(u, stream = True)
1347+ return r
1348+
1349+ def url(self, u):
1350+ global g_session
1351+ try:
1352+ r = g_session.get(u)
1353+ except:
1354+ # attempt a relogin if we havent used the session for a while
1355+ self.relogin()
1356+ r = g_session.get(u)
1357+
1358+ return r
1359+
1360+# module testing
1361+if __name__ == "__main__":
1362+ s = Session()
1363+ email = "xxx"
1364+ password = "xxx"
1365+ print("Logging in to planningcentreonline.com")
1366+
1367+ f = open("login_result.html", "w")
1368+ f.write(s.login(email, password))
1369+ f.close()
1370+
1371+ f = open("plan_result.html", "w")
1372+ f.write(s.plan("16910481"))
1373+ f.close()
1374+
1375+ print("Test complete")
1376
1377=== added directory 'openlp/plugins/planningcenter/lib/youtube-dl'
1378=== added file 'openlp/plugins/planningcenter/lib/youtube-dl/LICENSE'
1379--- openlp/plugins/planningcenter/lib/youtube-dl/LICENSE 1970-01-01 00:00:00 +0000
1380+++ openlp/plugins/planningcenter/lib/youtube-dl/LICENSE 2015-09-23 12:21:33 +0000
1381@@ -0,0 +1,24 @@
1382+This is free and unencumbered software released into the public domain.
1383+
1384+Anyone is free to copy, modify, publish, use, compile, sell, or
1385+distribute this software, either in source code form or as a compiled
1386+binary, for any purpose, commercial or non-commercial, and by any
1387+means.
1388+
1389+In jurisdictions that recognize copyright laws, the author or authors
1390+of this software dedicate any and all copyright interest in the
1391+software to the public domain. We make this dedication for the benefit
1392+of the public at large and to the detriment of our heirs and
1393+successors. We intend this dedication to be an overt act of
1394+relinquishment in perpetuity of all present and future rights to this
1395+software under copyright law.
1396+
1397+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1398+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1399+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1400+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1401+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1402+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1403+OTHER DEALINGS IN THE SOFTWARE.
1404+
1405+For more information, please refer to <http://unlicense.org/>
1406
1407=== added file 'openlp/plugins/planningcenter/lib/youtube-dl/README.txt'
1408--- openlp/plugins/planningcenter/lib/youtube-dl/README.txt 1970-01-01 00:00:00 +0000
1409+++ openlp/plugins/planningcenter/lib/youtube-dl/README.txt 2015-09-23 12:21:33 +0000
1410@@ -0,0 +1,1035 @@
1411+youtube-dl - download videos from youtube.com or other video platforms
1412+
1413+- INSTALLATION
1414+- DESCRIPTION
1415+- OPTIONS
1416+- CONFIGURATION
1417+- OUTPUT TEMPLATE
1418+- FORMAT SELECTION
1419+- VIDEO SELECTION
1420+- FAQ
1421+- DEVELOPER INSTRUCTIONS
1422+- BUGS
1423+- COPYRIGHT
1424+
1425+
1426+
1427+INSTALLATION
1428+
1429+
1430+To install it right away for all UNIX users (Linux, OS X, etc.), type:
1431+
1432+ sudo curl https://yt-dl.org/latest/youtube-dl -o /usr/local/bin/youtube-dl
1433+ sudo chmod a+rx /usr/local/bin/youtube-dl
1434+
1435+If you do not have curl, you can alternatively use a recent wget:
1436+
1437+ sudo wget https://yt-dl.org/downloads/latest/youtube-dl -O /usr/local/bin/youtube-dl
1438+ sudo chmod a+rx /usr/local/bin/youtube-dl
1439+
1440+Windows users can download a .exe file and place it in their home
1441+directory or any other location on their PATH.
1442+
1443+OS X users can install YOUTUBE-DL with Homebrew.
1444+
1445+ brew install youtube-dl
1446+
1447+You can also use pip:
1448+
1449+ sudo pip install youtube-dl
1450+
1451+Alternatively, refer to the developer instructions below for how to
1452+check out and work with the git repository. For further options,
1453+including PGP signatures, see
1454+https://rg3.github.io/youtube-dl/download.html .
1455+
1456+
1457+
1458+DESCRIPTION
1459+
1460+
1461+YOUTUBE-DL is a small command-line program to download videos from
1462+YouTube.com and a few more sites. It requires the Python interpreter,
1463+version 2.6, 2.7, or 3.2+, and it is not platform specific. It should
1464+work on your Unix box, on Windows or on Mac OS X. It is released to the
1465+public domain, which means you can modify it, redistribute it or use it
1466+however you like.
1467+
1468+ youtube-dl [OPTIONS] URL [URL...]
1469+
1470+
1471+
1472+OPTIONS
1473+
1474+
1475+ -h, --help Print this help text and exit
1476+ --version Print program version and exit
1477+ -U, --update Update this program to latest version. Make sure that you have sufficient permissions (run with sudo if needed)
1478+ -i, --ignore-errors Continue on download errors, for example to skip unavailable videos in a playlist
1479+ --abort-on-error Abort downloading of further videos (in the playlist or the command line) if an error occurs
1480+ --dump-user-agent Display the current browser identification
1481+ --list-extractors List all supported extractors
1482+ --extractor-descriptions Output descriptions of all supported extractors
1483+ --force-generic-extractor Force extraction to use the generic extractor
1484+ --default-search PREFIX Use this prefix for unqualified URLs. For example "gvsearch2:" downloads two videos from google videos for youtube-dl "large apple".
1485+ Use the value "auto" to let youtube-dl guess ("auto_warning" to emit a warning when guessing). "error" just throws an error. The
1486+ default value "fixup_error" repairs broken URLs, but emits an error if this is not possible instead of searching.
1487+ --ignore-config Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: Do not read the user configuration
1488+ in ~/.config/youtube-dl/config (%APPDATA%/youtube-dl/config.txt on Windows)
1489+ --flat-playlist Do not extract the videos of a playlist, only list them.
1490+ --no-color Do not emit color codes in output
1491+
1492+
1493+Network Options:
1494+
1495+ --proxy URL Use the specified HTTP/HTTPS proxy. Pass in an empty string (--proxy "") for direct connection
1496+ --socket-timeout SECONDS Time to wait before giving up, in seconds
1497+ --source-address IP Client-side IP address to bind to (experimental)
1498+ -4, --force-ipv4 Make all connections via IPv4 (experimental)
1499+ -6, --force-ipv6 Make all connections via IPv6 (experimental)
1500+ --cn-verification-proxy URL Use this proxy to verify the IP address for some Chinese sites. The default proxy specified by --proxy (or none, if the options is
1501+ not present) is used for the actual downloading. (experimental)
1502+
1503+
1504+Video Selection:
1505+
1506+ --playlist-start NUMBER Playlist video to start at (default is 1)
1507+ --playlist-end NUMBER Playlist video to end at (default is last)
1508+ --playlist-items ITEM_SPEC Playlist video items to download. Specify indices of the videos in the playlist seperated by commas like: "--playlist-items 1,2,5,8"
1509+ if you want to download videos indexed 1, 2, 5, 8 in the playlist. You can specify range: "--playlist-items 1-3,7,10-13", it will
1510+ download the videos at index 1, 2, 3, 7, 10, 11, 12 and 13.
1511+ --match-title REGEX Download only matching titles (regex or caseless sub-string)
1512+ --reject-title REGEX Skip download for matching titles (regex or caseless sub-string)
1513+ --max-downloads NUMBER Abort after downloading NUMBER files
1514+ --min-filesize SIZE Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)
1515+ --max-filesize SIZE Do not download any videos larger than SIZE (e.g. 50k or 44.6m)
1516+ --date DATE Download only videos uploaded in this date
1517+ --datebefore DATE Download only videos uploaded on or before this date (i.e. inclusive)
1518+ --dateafter DATE Download only videos uploaded on or after this date (i.e. inclusive)
1519+ --min-views COUNT Do not download any videos with less than COUNT views
1520+ --max-views COUNT Do not download any videos with more than COUNT views
1521+ --match-filter FILTER Generic video filter (experimental). Specify any key (see help for -o for a list of available keys) to match if the key is present,
1522+ !key to check if the key is not present,key > NUMBER (like "comment_count > 12", also works with >=, <, <=, !=, =) to compare against
1523+ a number, and & to require multiple matches. Values which are not known are excluded unless you put a question mark (?) after the
1524+ operator.For example, to only match videos that have been liked more than 100 times and disliked less than 50 times (or the dislike
1525+ functionality is not available at the given service), but who also have a description, use --match-filter "like_count > 100 &
1526+ dislike_count <? 50 & description" .
1527+ --no-playlist Download only the video, if the URL refers to a video and a playlist.
1528+ --yes-playlist Download the playlist, if the URL refers to a video and a playlist.
1529+ --age-limit YEARS Download only videos suitable for the given age
1530+ --download-archive FILE Download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.
1531+ --include-ads Download advertisements as well (experimental)
1532+
1533+
1534+Download Options:
1535+
1536+ -r, --rate-limit LIMIT Maximum download rate in bytes per second (e.g. 50K or 4.2M)
1537+ -R, --retries RETRIES Number of retries (default is 10), or "infinite".
1538+ --buffer-size SIZE Size of download buffer (e.g. 1024 or 16K) (default is 1024)
1539+ --no-resize-buffer Do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.
1540+ --playlist-reverse Download playlist videos in reverse order
1541+ --xattr-set-filesize Set file xattribute ytdl.filesize with expected filesize (experimental)
1542+ --hls-prefer-native Use the native HLS downloader instead of ffmpeg (experimental)
1543+ --external-downloader COMMAND Use the specified external downloader. Currently supports aria2c,curl,httpie,wget
1544+ --external-downloader-args ARGS Give these arguments to the external downloader
1545+
1546+
1547+Filesystem Options:
1548+
1549+ -a, --batch-file FILE File containing URLs to download ('-' for stdin)
1550+ --id Use only video ID in file name
1551+ -o, --output TEMPLATE Output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader
1552+ nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(format)s for
1553+ the format description (like "22 - 1280x720" or "HD"), %(format_id)s for the unique id of the format (like YouTube's itags: "137"),
1554+ %(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id,
1555+ %(playlist_title)s, %(playlist_id)s, or %(playlist)s (=title if present, ID otherwise) for the playlist the video is in,
1556+ %(playlist_index)s for the position in the playlist. %(height)s and %(width)s for the width and height of the video format.
1557+ %(resolution)s for a textual description of the resolution of the video format. %% for a literal percent. Use - to output to stdout.
1558+ Can also be used to download to a different directory, for example with -o '/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
1559+ --autonumber-size NUMBER Specify the number of digits in %(autonumber)s when it is present in output filename template or --auto-number option is given
1560+ --restrict-filenames Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
1561+ -A, --auto-number [deprecated; use -o "%(autonumber)s-%(title)s.%(ext)s" ] Number downloaded files starting from 00000
1562+ -t, --title [deprecated] Use title in file name (default)
1563+ -l, --literal [deprecated] Alias of --title
1564+ -w, --no-overwrites Do not overwrite files
1565+ -c, --continue Force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
1566+ --no-continue Do not resume partially downloaded files (restart from beginning)
1567+ --no-part Do not use .part files - write directly into output file
1568+ --no-mtime Do not use the Last-modified header to set the file modification time
1569+ --write-description Write video description to a .description file
1570+ --write-info-json Write video metadata to a .info.json file
1571+ --write-annotations Write video annotations to a .annotations.xml file
1572+ --load-info FILE JSON file containing the video information (created with the "--write-info-json" option)
1573+ --cookies FILE File to read cookies from and dump cookie jar in
1574+ --cache-dir DIR Location in the filesystem where youtube-dl can store some downloaded information permanently. By default $XDG_CACHE_HOME/youtube-dl
1575+ or ~/.cache/youtube-dl . At the moment, only YouTube player files (for videos with obfuscated signatures) are cached, but that may
1576+ change.
1577+ --no-cache-dir Disable filesystem caching
1578+ --rm-cache-dir Delete all filesystem cache files
1579+
1580+
1581+Thumbnail images:
1582+
1583+ --write-thumbnail Write thumbnail image to disk
1584+ --write-all-thumbnails Write all thumbnail image formats to disk
1585+ --list-thumbnails Simulate and list all available thumbnail formats
1586+
1587+
1588+Verbosity / Simulation Options:
1589+
1590+ -q, --quiet Activate quiet mode
1591+ --no-warnings Ignore warnings
1592+ -s, --simulate Do not download the video and do not write anything to disk
1593+ --skip-download Do not download the video
1594+ -g, --get-url Simulate, quiet but print URL
1595+ -e, --get-title Simulate, quiet but print title
1596+ --get-id Simulate, quiet but print id
1597+ --get-thumbnail Simulate, quiet but print thumbnail URL
1598+ --get-description Simulate, quiet but print video description
1599+ --get-duration Simulate, quiet but print video length
1600+ --get-filename Simulate, quiet but print output filename
1601+ --get-format Simulate, quiet but print output format
1602+ -j, --dump-json Simulate, quiet but print JSON information. See --output for a description of available keys.
1603+ -J, --dump-single-json Simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump the whole playlist
1604+ information in a single line.
1605+ --print-json Be quiet and print the video information as JSON (video is still being downloaded).
1606+ --newline Output progress bar as new lines
1607+ --no-progress Do not print progress bar
1608+ --console-title Display progress in console titlebar
1609+ -v, --verbose Print various debugging information
1610+ --dump-pages Print downloaded pages encoded using base64 to debug problems (very verbose)
1611+ --write-pages Write downloaded intermediary pages to files in the current directory to debug problems
1612+ --print-traffic Display sent and read HTTP traffic
1613+ -C, --call-home Contact the youtube-dl server for debugging
1614+ --no-call-home Do NOT contact the youtube-dl server for debugging
1615+
1616+
1617+Workarounds:
1618+
1619+ --encoding ENCODING Force the specified encoding (experimental)
1620+ --no-check-certificate Suppress HTTPS certificate validation
1621+ --prefer-insecure Use an unencrypted connection to retrieve information about the video. (Currently supported only for YouTube)
1622+ --user-agent UA Specify a custom user agent
1623+ --referer URL Specify a custom referer, use if the video access is restricted to one domain
1624+ --add-header FIELD:VALUE Specify a custom HTTP header and its value, separated by a colon ':'. You can use this option multiple times
1625+ --bidi-workaround Work around terminals that lack bidirectional text support. Requires bidiv or fribidi executable in PATH
1626+ --sleep-interval SECONDS Number of seconds to sleep before each download.
1627+
1628+
1629+Video Format Options:
1630+
1631+ -f, --format FORMAT Video format code, see the "FORMAT SELECTION" for all the info
1632+ --all-formats Download all available video formats
1633+ --prefer-free-formats Prefer free video formats unless a specific one is requested
1634+ -F, --list-formats List all available formats
1635+ --youtube-skip-dash-manifest Do not download the DASH manifests and related data on YouTube videos
1636+ --merge-output-format FORMAT If a merge is required (e.g. bestvideo+bestaudio), output to given container format. One of mkv, mp4, ogg, webm, flv. Ignored if no
1637+ merge is required
1638+
1639+
1640+Subtitle Options:
1641+
1642+ --write-sub Write subtitle file
1643+ --write-auto-sub Write automatic subtitle file (YouTube only)
1644+ --all-subs Download all the available subtitles of the video
1645+ --list-subs List all available subtitles for the video
1646+ --sub-format FORMAT Subtitle format, accepts formats preference, for example: "srt" or "ass/srt/best"
1647+ --sub-lang LANGS Languages of the subtitles to download (optional) separated by commas, use IETF language tags like 'en,pt'
1648+
1649+
1650+Authentication Options:
1651+
1652+ -u, --username USERNAME Login with this account ID
1653+ -p, --password PASSWORD Account password. If this option is left out, youtube-dl will ask interactively.
1654+ -2, --twofactor TWOFACTOR Two-factor auth code
1655+ -n, --netrc Use .netrc authentication data
1656+ --video-password PASSWORD Video password (vimeo, smotri)
1657+
1658+
1659+Post-processing Options:
1660+
1661+ -x, --extract-audio Convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)
1662+ --audio-format FORMAT Specify audio format: "best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; "best" by default
1663+ --audio-quality QUALITY Specify ffmpeg/avconv audio quality, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default
1664+ 5)
1665+ --recode-video FORMAT Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)
1666+ --postprocessor-args ARGS Give these arguments to the postprocessor
1667+ -k, --keep-video Keep the video file on disk after the post-processing; the video is erased by default
1668+ --no-post-overwrites Do not overwrite post-processed files; the post-processed files are overwritten by default
1669+ --embed-subs Embed subtitles in the video (only for mkv and mp4 videos)
1670+ --embed-thumbnail Embed thumbnail in the audio as cover art
1671+ --add-metadata Write metadata to the video file
1672+ --metadata-from-title FORMAT Parse additional metadata like song title / artist from the video title. The format syntax is the same as --output, the parsed
1673+ parameters replace existing values. Additional templates: %(album)s, %(artist)s. Example: --metadata-from-title "%(artist)s -
1674+ %(title)s" matches a title like "Coldplay - Paradise"
1675+ --xattrs Write metadata to the video file's xattrs (using dublin core and xdg standards)
1676+ --fixup POLICY Automatically correct known faults of the file. One of never (do nothing), warn (only emit a warning), detect_or_warn (the default;
1677+ fix file if we can, warn otherwise)
1678+ --prefer-avconv Prefer avconv over ffmpeg for running the postprocessors (default)
1679+ --prefer-ffmpeg Prefer ffmpeg over avconv for running the postprocessors
1680+ --ffmpeg-location PATH Location of the ffmpeg/avconv binary; either the path to the binary or its containing directory.
1681+ --exec CMD Execute a command on the file after downloading, similar to find's -exec syntax. Example: --exec 'adb push {} /sdcard/Music/ && rm
1682+ {}'
1683+ --convert-subtitles FORMAT Convert the subtitles to other format (currently supported: srt|ass|vtt)
1684+
1685+
1686+
1687+CONFIGURATION
1688+
1689+
1690+You can configure youtube-dl by placing default arguments (such as
1691+--extract-audio --no-mtime to always extract the audio and not copy the
1692+mtime) into /etc/youtube-dl.conf and/or ~/.config/youtube-dl/config. On
1693+Windows, the configuration file locations are
1694+%APPDATA%\youtube-dl\config.txt and
1695+C:\Users\<user name>\youtube-dl.conf.
1696+
1697+Authentication with .netrc file
1698+
1699+You may also want to configure automatic credentials storage for
1700+extractors that support authentication (by providing login and password
1701+with --username and --password) in order not to pass credentials as
1702+command line arguments on every youtube-dl execution and prevent
1703+tracking plain text passwords in shell command history. You can achieve
1704+this using .netrc file on per extractor basis. For that you will need to
1705+create .netrc file in your $HOME and restrict permissions to read/write
1706+by you only:
1707+
1708+ touch $HOME/.netrc
1709+ chmod a-rwx,u+rw $HOME/.netrc
1710+
1711+After that you can add credentials for extractor in the following
1712+format, where _extractor_ is the name of extractor in lowercase:
1713+
1714+ machine <extractor> login <login> password <password>
1715+
1716+For example:
1717+
1718+ machine youtube login myaccount@gmail.com password my_youtube_password
1719+ machine twitch login my_twitch_account_name password my_twitch_password
1720+
1721+To activate authentication with .netrc file you should pass --netrc to
1722+youtube-dl or to place it in configuration file.
1723+
1724+On Windows you may also need to setup %HOME% environment variable
1725+manually.
1726+
1727+
1728+
1729+OUTPUT TEMPLATE
1730+
1731+
1732+The -o option allows users to indicate a template for the output file
1733+names. The basic usage is not to set any template arguments when
1734+downloading a single file, like in
1735+youtube-dl -o funny_video.flv "http://some/video". However, it may
1736+contain special sequences that will be replaced when downloading each
1737+video. The special sequences have the format %(NAME)s. To clarify, that
1738+is a percent symbol followed by a name in parenthesis, followed by a
1739+lowercase S. Allowed names are:
1740+
1741+- id: The sequence will be replaced by the video identifier.
1742+- url: The sequence will be replaced by the video URL.
1743+- uploader: The sequence will be replaced by the nickname of the
1744+ person who uploaded the video.
1745+- upload_date: The sequence will be replaced by the upload date in
1746+ YYYYMMDD format.
1747+- title: The sequence will be replaced by the video title.
1748+- ext: The sequence will be replaced by the appropriate extension
1749+ (like flv or mp4).
1750+- epoch: The sequence will be replaced by the Unix epoch when creating
1751+ the file.
1752+- autonumber: The sequence will be replaced by a five-digit number
1753+ that will be increased with each download, starting at zero.
1754+- playlist: The name or the id of the playlist that contains the
1755+ video.
1756+- playlist_index: The index of the video in the playlist, a five-digit
1757+ number.
1758+
1759+The current default template is %(title)s-%(id)s.%(ext)s.
1760+
1761+In some cases, you don't want special characters such as 中, spaces, or
1762+&, such as when transferring the downloaded filename to a Windows system
1763+or the filename through an 8bit-unsafe channel. In these cases, add the
1764+--restrict-filenames flag to get a shorter title:
1765+
1766+``` {.bash}
1767+$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc
1768+youtube-dl test video ''_ä↭𝕐.mp4 # All kinds of weird characters
1769+$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
1770+youtube-dl_test_video_.mp4 # A simple file name
1771+```
1772+
1773+
1774+
1775+FORMAT SELECTION
1776+
1777+
1778+By default youtube-dl tries to download the best quality, but sometimes
1779+you may want to download other format. The simplest case is requesting a
1780+specific format, for example -f 22. You can get the list of available
1781+formats using --list-formats, you can also use a file extension
1782+(currently it supports aac, m4a, mp3, mp4, ogg, wav, webm) or the
1783+special names best, bestvideo, bestaudio and worst.
1784+
1785+If you want to download multiple videos and they don't have the same
1786+formats available, you can specify the order of preference using
1787+slashes, as in -f 22/17/18. You can also filter the video results by
1788+putting a condition in brackets, as in -f "best[height=720]" (or
1789+-f "[filesize>10M]"). This works for filesize, height, width, tbr, abr,
1790+vbr, asr, and fps and the comparisons <, <=, >, >=, =, != and for ext,
1791+acodec, vcodec, container, and protocol and the comparisons =, != .
1792+Formats for which the value is not known are excluded unless you put a
1793+question mark (?) after the operator. You can combine format filters, so
1794+-f "[height <=? 720][tbr>500]" selects up to 720p videos (or videos
1795+where the height is not known) with a bitrate of at least 500 KBit/s.
1796+Use commas to download multiple formats, such as
1797+-f 136/137/mp4/bestvideo,140/m4a/bestaudio. You can merge the video and
1798+audio of two formats into a single file using
1799+-f <video-format>+<audio-format> (requires ffmpeg or avconv), for
1800+example -f bestvideo+bestaudio.
1801+
1802+Since the end of April 2015 and version 2015.04.26 youtube-dl uses
1803+-f bestvideo+bestaudio/best as default format selection (see #5447,
1804+#5456). If ffmpeg or avconv are installed this results in downloading
1805+bestvideo and bestaudio separately and muxing them together into a
1806+single file giving the best overall quality available. Otherwise it
1807+falls back to best and results in downloading best available quality
1808+served as a single file. best is also needed for videos that don't come
1809+from YouTube because they don't provide the audio and video in two
1810+different files. If you want to only download some dash formats (for
1811+example if you are not interested in getting videos with a resolution
1812+higher than 1080p), you can add
1813+-f bestvideo[height<=?1080]+bestaudio/best to your configuration file.
1814+Note that if you use youtube-dl to stream to stdout (and most likely to
1815+pipe it to your media player then), i.e. you explicitly specify output
1816+template as -o -, youtube-dl still uses -f best format selection in
1817+order to start content delivery immediately to your player and not to
1818+wait until bestvideo and bestaudio are downloaded and muxed.
1819+
1820+If you want to preserve the old format selection behavior (prior to
1821+youtube-dl 2015.04.26), i.e. you want to download best available quality
1822+media served as a single file, you should explicitly specify your choice
1823+with -f best. You may want to add it to the configuration file in order
1824+not to type it every time you run youtube-dl.
1825+
1826+
1827+
1828+VIDEO SELECTION
1829+
1830+
1831+Videos can be filtered by their upload date using the options --date,
1832+--datebefore or --dateafter, they accept dates in two formats:
1833+
1834+- Absolute dates: Dates in the format YYYYMMDD.
1835+- Relative dates: Dates in the format
1836+ (now|today)[+-][0-9](day|week|month|year)(s)?
1837+
1838+Examples:
1839+
1840+``` {.bash}
1841+# Download only the videos uploaded in the last 6 months
1842+$ youtube-dl --dateafter now-6months
1843+
1844+# Download only the videos uploaded on January 1, 1970
1845+$ youtube-dl --date 19700101
1846+
1847+$ # will only download the videos uploaded in the 200x decade
1848+$ youtube-dl --dateafter 20000101 --datebefore 20091231
1849+```
1850+
1851+
1852+
1853+FAQ
1854+
1855+
1856+How do I update youtube-dl?
1857+
1858+If you've followed our manual installation instructions, you can simply
1859+run youtube-dl -U (or, on Linux, sudo youtube-dl -U).
1860+
1861+If you have used pip, a simple sudo pip install -U youtube-dl is
1862+sufficient to update.
1863+
1864+If you have installed youtube-dl using a package manager like _apt-get_
1865+or _yum_, use the standard system update mechanism to update. Note that
1866+distribution packages are often outdated. As a rule of thumb, youtube-dl
1867+releases at least once a month, and often weekly or even daily. Simply
1868+go to http://yt-dl.org/ to find out the current version. Unfortunately,
1869+there is nothing we youtube-dl developers can do if your distributions
1870+serves a really outdated version. You can (and should) complain to your
1871+distribution in their bugtracker or support forum.
1872+
1873+As a last resort, you can also uninstall the version installed by your
1874+package manager and follow our manual installation instructions. For
1875+that, remove the distribution's package, with a line like
1876+
1877+ sudo apt-get remove -y youtube-dl
1878+
1879+Afterwards, simply follow our manual installation instructions:
1880+
1881+ sudo wget https://yt-dl.org/latest/youtube-dl -O /usr/local/bin/youtube-dl
1882+ sudo chmod a+x /usr/local/bin/youtube-dl
1883+ hash -r
1884+
1885+Again, from then on you'll be able to update with sudo youtube-dl -U.
1886+
1887+I'm getting an error Unable to extract OpenGraph title on YouTube playlists
1888+
1889+YouTube changed their playlist format in March 2014 and later on, so
1890+you'll need at least youtube-dl 2014.07.25 to download all YouTube
1891+videos.
1892+
1893+If you have installed youtube-dl with a package manager, pip, setup.py
1894+or a tarball, please use that to update. Note that Ubuntu packages do
1895+not seem to get updated anymore. Since we are not affiliated with
1896+Ubuntu, there is little we can do. Feel free to report bugs to the
1897+Ubuntu packaging guys - all they have to do is update the package to a
1898+somewhat recent version. See above for a way to update.
1899+
1900+Do I always have to pass -citw?
1901+
1902+By default, youtube-dl intends to have the best options (incidentally,
1903+if you have a convincing case that these should be different, please
1904+file an issue where you explain that). Therefore, it is unnecessary and
1905+sometimes harmful to copy long option strings from webpages. In
1906+particular, the only option out of -citw that is regularly useful is -i.
1907+
1908+Can you please put the -b option back?
1909+
1910+Most people asking this question are not aware that youtube-dl now
1911+defaults to downloading the highest available quality as reported by
1912+YouTube, which will be 1080p or 720p in some cases, so you no longer
1913+need the -b option. For some specific videos, maybe YouTube does not
1914+report them to be available in a specific high quality format you're
1915+interested in. In that case, simply request it with the -f option and
1916+youtube-dl will try to download it.
1917+
1918+I get HTTP error 402 when trying to download a video. What's this?
1919+
1920+Apparently YouTube requires you to pass a CAPTCHA test if you download
1921+too much. We're considering to provide a way to let you solve the
1922+CAPTCHA, but at the moment, your best course of action is pointing a
1923+webbrowser to the youtube URL, solving the CAPTCHA, and restart
1924+youtube-dl.
1925+
1926+I have downloaded a video but how can I play it?
1927+
1928+Once the video is fully downloaded, use any video player, such as vlc or
1929+mplayer.
1930+
1931+I extracted a video URL with -g, but it does not play on another machine / in my webbrowser.
1932+
1933+It depends a lot on the service. In many cases, requests for the video
1934+(to download/play it) must come from the same IP address and with the
1935+same cookies. Use the --cookies option to write the required cookies
1936+into a file, and advise your downloader to read cookies from that file.
1937+Some sites also require a common user agent to be used, use
1938+--dump-user-agent to see the one in use by youtube-dl.
1939+
1940+It may be beneficial to use IPv6; in some cases, the restrictions are
1941+only applied to IPv4. Some services (sometimes only for a subset of
1942+videos) do not restrict the video URL by IP address, cookie, or
1943+user-agent, but these are the exception rather than the rule.
1944+
1945+Please bear in mind that some URL protocols are NOT supported by
1946+browsers out of the box, including RTMP. If you are using -g, your own
1947+downloader must support these as well.
1948+
1949+If you want to play the video on a machine that is not running
1950+youtube-dl, you can relay the video content from the machine that runs
1951+youtube-dl. You can use -o - to let youtube-dl stream a video to stdout,
1952+or simply allow the player to download the files written by youtube-dl
1953+in turn.
1954+
1955+ERROR: no fmt_url_map or conn information found in video info
1956+
1957+YouTube has switched to a new video info format in July 2011 which is
1958+not supported by old versions of youtube-dl. See above for how to update
1959+youtube-dl.
1960+
1961+ERROR: unable to download video
1962+
1963+YouTube requires an additional signature since September 2012 which is
1964+not supported by old versions of youtube-dl. See above for how to update
1965+youtube-dl.
1966+
1967+Video URL contains an ampersand and I'm getting some strange output [1] 2839 or 'v' is not recognized as an internal or external command
1968+
1969+That's actually the output from your shell. Since ampersand is one of
1970+the special shell characters it's interpreted by shell preventing you
1971+from passing the whole URL to youtube-dl. To disable your shell from
1972+interpreting the ampersands (or any other special characters) you have
1973+to either put the whole URL in quotes or escape them with a backslash
1974+(which approach will work depends on your shell).
1975+
1976+For example if your URL is
1977+https://www.youtube.com/watch?t=4&v=BaW_jenozKc you should end up with
1978+following command:
1979+
1980+youtube-dl 'https://www.youtube.com/watch?t=4&v=BaW_jenozKc'
1981+
1982+or
1983+
1984+youtube-dl https://www.youtube.com/watch?t=4\&v=BaW_jenozKc
1985+
1986+For Windows you have to use the double quotes:
1987+
1988+youtube-dl "https://www.youtube.com/watch?t=4&v=BaW_jenozKc"
1989+
1990+ExtractorError: Could not find JS function u'OF'
1991+
1992+In February 2015, the new YouTube player contained a character sequence
1993+in a string that was misinterpreted by old versions of youtube-dl. See
1994+above for how to update youtube-dl.
1995+
1996+HTTP Error 429: Too Many Requests or 402: Payment Required
1997+
1998+These two error codes indicate that the service is blocking your IP
1999+address because of overuse. Contact the service and ask them to unblock
2000+your IP address, or - if you have acquired a whitelisted IP address
2001+already - use the --proxy or --source-address options to select another
2002+IP address.
2003+
2004+SyntaxError: Non-ASCII character
2005+
2006+The error
2007+
2008+ File "youtube-dl", line 2
2009+ SyntaxError: Non-ASCII character '\x93' ...
2010+
2011+means you're using an outdated version of Python. Please update to
2012+Python 2.6 or 2.7.
2013+
2014+What is this binary file? Where has the code gone?
2015+
2016+Since June 2012 (#342) youtube-dl is packed as an executable zipfile,
2017+simply unzip it (might need renaming to youtube-dl.zip first on some
2018+systems) or clone the git repository, as laid out above. If you modify
2019+the code, you can run it by executing the __main__.py file. To recompile
2020+the executable, run make youtube-dl.
2021+
2022+The exe throws a _Runtime error from Visual C++_
2023+
2024+To run the exe you need to install first the Microsoft Visual C++ 2008
2025+Redistributable Package.
2026+
2027+On Windows, how should I set up ffmpeg and youtube-dl? Where should I put the exe files?
2028+
2029+If you put youtube-dl and ffmpeg in the same directory that you're
2030+running the command from, it will work, but that's rather cumbersome.
2031+
2032+To make a different directory work - either for ffmpeg, or for
2033+youtube-dl, or for both - simply create the directory (say, C:\bin, or
2034+C:\Users\<User name>\bin), put all the executables directly in there,
2035+and then set your PATH environment variable to include that directory.
2036+
2037+From then on, after restarting your shell, you will be able to access
2038+both youtube-dl and ffmpeg (and youtube-dl will be able to find ffmpeg)
2039+by simply typing youtube-dl or ffmpeg, no matter what directory you're
2040+in.
2041+
2042+How do I put downloads into a specific folder?
2043+
2044+Use the -o to specify an output template, for example
2045+-o "/home/user/videos/%(title)s-%(id)s.%(ext)s". If you want this for
2046+all of your downloads, put the option into your configuration file.
2047+
2048+How do I download a video starting with a - ?
2049+
2050+Either prepend http://www.youtube.com/watch?v= or separate the ID from
2051+the options with --:
2052+
2053+ youtube-dl -- -wNyEUrxzFU
2054+ youtube-dl "http://www.youtube.com/watch?v=-wNyEUrxzFU"
2055+
2056+Can you add support for this anime video site, or site which shows current movies for free?
2057+
2058+As a matter of policy (as well as legality), youtube-dl does not include
2059+support for services that specialize in infringing copyright. As a rule
2060+of thumb, if you cannot easily find a video that the service is quite
2061+obviously allowed to distribute (i.e. that has been uploaded by the
2062+creator, the creator's distributor, or is published under a free
2063+license), the service is probably unfit for inclusion to youtube-dl.
2064+
2065+A note on the service that they don't host the infringing content, but
2066+just link to those who do, is evidence that the service should NOT be
2067+included into youtube-dl. The same goes for any DMCA note when the whole
2068+front page of the service is filled with videos they are not allowed to
2069+distribute. A "fair use" note is equally unconvincing if the service
2070+shows copyright-protected videos in full without authorization.
2071+
2072+Support requests for services that DO purchase the rights to distribute
2073+their content are perfectly fine though. If in doubt, you can simply
2074+include a source that mentions the legitimate purchase of content.
2075+
2076+How can I speed up work on my issue?
2077+
2078+(Also known as: Help, my important issue not being solved!) The
2079+youtube-dl core developer team is quite small. While we do our best to
2080+solve as many issues as possible, sometimes that can take quite a while.
2081+To speed up your issue, here's what you can do:
2082+
2083+First of all, please do report the issue at our issue tracker. That
2084+allows us to coordinate all efforts by users and developers, and serves
2085+as a unified point. Unfortunately, the youtube-dl project has grown too
2086+large to use personal email as an effective communication channel.
2087+
2088+Please read the bug reporting instructions below. A lot of bugs lack all
2089+the necessary information. If you can, offer proxy, VPN, or shell access
2090+to the youtube-dl developers. If you are able to, test the issue from
2091+multiple computers in multiple countries to exclude local censorship or
2092+misconfiguration issues.
2093+
2094+If nobody is interested in solving your issue, you are welcome to take
2095+matters into your own hands and submit a pull request (or coerce/pay
2096+somebody else to do so).
2097+
2098+Feel free to bump the issue from time to time by writing a small comment
2099+("Issue is still present in youtube-dl version ...from France, but fixed
2100+from Belgium"), but please not more than once a month. Please do not
2101+declare your issue as important or urgent.
2102+
2103+How can I detect whether a given URL is supported by youtube-dl?
2104+
2105+For one, have a look at the list of supported sites. Note that it can
2106+sometimes happen that the site changes its URL scheme (say, from
2107+http://example.com/video/1234567 to http://example.com/v/1234567 ) and
2108+youtube-dl reports an URL of a service in that list as unsupported. In
2109+that case, simply report a bug.
2110+
2111+It is _not_ possible to detect whether a URL is supported or not. That's
2112+because youtube-dl contains a generic extractor which matches ALL URLs.
2113+You may be tempted to disable, exclude, or remove the generic extractor,
2114+but the generic extractor not only allows users to extract videos from
2115+lots of websites that embed a video from another service, but may also
2116+be used to extract video from a service that it's hosting itself.
2117+Therefore, we neither recommend nor support disabling, excluding, or
2118+removing the generic extractor.
2119+
2120+If you want to find out whether a given URL is supported, simply call
2121+youtube-dl with it. If you get no videos back, chances are the URL is
2122+either not referring to a video or unsupported. You can find out which
2123+by examining the output (if you run youtube-dl on the console) or
2124+catching an UnsupportedError exception if you run it from a Python
2125+program.
2126+
2127+
2128+
2129+DEVELOPER INSTRUCTIONS
2130+
2131+
2132+Most users do not need to build youtube-dl and can download the builds
2133+or get them from their distribution.
2134+
2135+To run youtube-dl as a developer, you don't need to build anything
2136+either. Simply execute
2137+
2138+ python -m youtube_dl
2139+
2140+To run the test, simply invoke your favorite test runner, or execute a
2141+test file directly; any of the following work:
2142+
2143+ python -m unittest discover
2144+ python test/test_download.py
2145+ nosetests
2146+
2147+If you want to create a build of youtube-dl yourself, you'll need
2148+
2149+- python
2150+- make
2151+- pandoc
2152+- zip
2153+- nosetests
2154+
2155+Adding support for a new site
2156+
2157+If you want to add support for a new site, you can follow this quick
2158+list (assuming your service is called yourextractor):
2159+
2160+1. Fork this repository
2161+2. Check out the source code with
2162+ git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git
2163+3. Start a new git branch with
2164+ cd youtube-dl; git checkout -b yourextractor
2165+4. Start with this simple template and save it to
2166+ youtube_dl/extractor/yourextractor.py:
2167+
2168+ ``` {.python}
2169+ # coding: utf-8
2170+ from __future__ import unicode_literals
2171+
2172+ from .common import InfoExtractor
2173+
2174+
2175+ class YourExtractorIE(InfoExtractor):
2176+ _VALID_URL = r'https?://(?:www\.)?yourextractor\.com/watch/(?P<id>[0-9]+)'
2177+ _TEST = {
2178+ 'url': 'http://yourextractor.com/watch/42',
2179+ 'md5': 'TODO: md5 sum of the first 10241 bytes of the video file (use --test)',
2180+ 'info_dict': {
2181+ 'id': '42',
2182+ 'ext': 'mp4',
2183+ 'title': 'Video title goes here',
2184+ 'thumbnail': 're:^https?://.*\.jpg$',
2185+ # TODO more properties, either as:
2186+ # * A value
2187+ # * MD5 checksum; start the string with md5:
2188+ # * A regular expression; start the string with re:
2189+ # * Any Python type (for example int or float)
2190+ }
2191+ }
2192+
2193+ def _real_extract(self, url):
2194+ video_id = self._match_id(url)
2195+ webpage = self._download_webpage(url, video_id)
2196+
2197+ # TODO more code goes here, for example ...
2198+ title = self._html_search_regex(r'<h1>(.*?)</h1>', webpage, 'title')
2199+
2200+ return {
2201+ 'id': video_id,
2202+ 'title': title,
2203+ 'description': self._og_search_description(webpage),
2204+ # TODO more properties (see youtube_dl/extractor/common.py)
2205+ }
2206+ ```
2207+
2208+5. Add an import in youtube_dl/extractor/__init__.py.
2209+6. Run python test/test_download.py TestDownload.test_YourExtractor.
2210+ This _should fail_ at first, but you can continually re-run it until
2211+ you're done. If you decide to add more than one test, then rename
2212+ _TEST to _TESTS and make it into a list of dictionaries. The tests
2213+ will be then be named TestDownload.test_YourExtractor,
2214+ TestDownload.test_YourExtractor_1,
2215+ TestDownload.test_YourExtractor_2, etc.
2216+7. Have a look at youtube_dl/common/extractor/common.py for possible
2217+ helper methods and a detailed description of what your extractor
2218+ should return. Add tests and code for as many as you want.
2219+8. If you can, check the code with flake8.
2220+9. When the tests pass, add the new files and commit them and push the
2221+ result, like this:
2222+
2223+ $ git add youtube_dl/extractor/__init__.py
2224+ $ git add youtube_dl/extractor/yourextractor.py
2225+ $ git commit -m '[yourextractor] Add new extractor'
2226+ $ git push origin yourextractor
2227+
2228+10. Finally, create a pull request. We'll then review and merge it.
2229+
2230+In any case, thank you very much for your contributions!
2231+
2232+
2233+
2234+EMBEDDING YOUTUBE-DL
2235+
2236+
2237+youtube-dl makes the best effort to be a good command-line program, and
2238+thus should be callable from any programming language. If you encounter
2239+any problems parsing its output, feel free to create a report.
2240+
2241+From a Python program, you can embed youtube-dl in a more powerful
2242+fashion, like this:
2243+
2244+``` {.python}
2245+from __future__ import unicode_literals
2246+import youtube_dl
2247+
2248+ydl_opts = {}
2249+with youtube_dl.YoutubeDL(ydl_opts) as ydl:
2250+ ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
2251+```
2252+
2253+Most likely, you'll want to use various options. For a list of what can
2254+be done, have a look at youtube_dl/YoutubeDL.py. For a start, if you
2255+want to intercept youtube-dl's output, set a logger object.
2256+
2257+Here's a more complete example of a program that outputs only errors
2258+(and a short message after the download is finished), and
2259+downloads/converts the video to an mp3 file:
2260+
2261+``` {.python}
2262+from __future__ import unicode_literals
2263+import youtube_dl
2264+
2265+
2266+class MyLogger(object):
2267+ def debug(self, msg):
2268+ pass
2269+
2270+ def warning(self, msg):
2271+ pass
2272+
2273+ def error(self, msg):
2274+ print(msg)
2275+
2276+
2277+def my_hook(d):
2278+ if d['status'] == 'finished':
2279+ print('Done downloading, now converting ...')
2280+
2281+
2282+ydl_opts = {
2283+ 'format': 'bestaudio/best',
2284+ 'postprocessors': [{
2285+ 'key': 'FFmpegExtractAudio',
2286+ 'preferredcodec': 'mp3',
2287+ 'preferredquality': '192',
2288+ }],
2289+ 'logger': MyLogger(),
2290+ 'progress_hooks': [my_hook],
2291+}
2292+with youtube_dl.YoutubeDL(ydl_opts) as ydl:
2293+ ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
2294+```
2295+
2296+
2297+
2298+BUGS
2299+
2300+
2301+Bugs and suggestions should be reported at:
2302+https://github.com/rg3/youtube-dl/issues . Unless you were prompted so
2303+or there is another pertinent reason (e.g. GitHub fails to accept the
2304+bug report), please do not send bug reports via personal email. For
2305+discussions, join us in the irc channel #youtube-dl on freenode.
2306+
2307+PLEASE INCLUDE THE FULL OUTPUT OF YOUTUBE-DL WHEN RUN WITH -v.
2308+
2309+The output (including the first lines) contain important debugging
2310+information. Issues without the full output are often not reproducible
2311+and therefore do not get solved in short order, if ever.
2312+
2313+Please re-read your issue once again to avoid a couple of common
2314+mistakes (you can and should use this as a checklist):
2315+
2316+Is the description of the issue itself sufficient?
2317+
2318+We often get issue reports that we cannot really decipher. While in most
2319+cases we eventually get the required information after asking back
2320+multiple times, this poses an unnecessary drain on our resources. Many
2321+contributors, including myself, are also not native speakers, so we may
2322+misread some parts.
2323+
2324+So please elaborate on what feature you are requesting, or what bug you
2325+want to be fixed. Make sure that it's obvious
2326+
2327+- What the problem is
2328+- How it could be fixed
2329+- How your proposed solution would look like
2330+
2331+If your report is shorter than two lines, it is almost certainly missing
2332+some of these, which makes it hard for us to respond to it. We're often
2333+too polite to close the issue outright, but the missing info makes
2334+misinterpretation likely. As a commiter myself, I often get frustrated
2335+by these issues, since the only possible way for me to move forward on
2336+them is to ask for clarification over and over.
2337+
2338+For bug reports, this means that your report should contain the
2339+_complete_ output of youtube-dl when called with the -v flag. The error
2340+message you get for (most) bugs even says so, but you would not believe
2341+how many of our bug reports do not contain this information.
2342+
2343+If your server has multiple IPs or you suspect censorship,
2344+adding --call-home may be a good idea to get more diagnostics. If the
2345+error is ERROR: Unable to extract ... and you cannot reproduce it from
2346+multiple countries, add --dump-pages (warning: this will yield a rather
2347+large output, redirect it to the file log.txt by adding >log.txt 2>&1 to
2348+your command-line) or upload the .dump files you get when you add
2349+--write-pages somewhere.
2350+
2351+SITE SUPPORT REQUESTS MUST CONTAIN AN EXAMPLE URL. An example URL is a
2352+URL you might want to download, like
2353+http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious
2354+video present. Except under very special circumstances, the main page of
2355+a video service (e.g. http://www.youtube.com/ ) is _not_ an example URL.
2356+
2357+Are you using the latest version?
2358+
2359+Before reporting any issue, type youtube-dl -U. This should report that
2360+you're up-to-date. About 20% of the reports we receive are already
2361+fixed, but people are using outdated versions. This goes for feature
2362+requests as well.
2363+
2364+Is the issue already documented?
2365+
2366+Make sure that someone has not already opened the issue you're trying to
2367+open. Search at the top of the window or at
2368+https://github.com/rg3/youtube-dl/search?type=Issues . If there is an
2369+issue, feel free to write something along the lines of "This affects me
2370+as well, with version 2015.01.01. Here is some more information on the
2371+issue: ...". While some issues may be old, a new post into them often
2372+spurs rapid activity.
2373+
2374+Why are existing options not enough?
2375+
2376+Before requesting a new feature, please have a quick peek at the list of
2377+supported options. Many feature requests are for features that actually
2378+exist already! Please, absolutely do show off your work in the issue
2379+report and detail how the existing similar options do _not_ solve your
2380+problem.
2381+
2382+Is there enough context in your bug report?
2383+
2384+People want to solve problems, and often think they do us a favor by
2385+breaking down their larger problems (e.g. wanting to skip already
2386+downloaded files) to a specific request (e.g. requesting us to look
2387+whether the file exists before downloading the info page). However, what
2388+often happens is that they break down the problem into two steps: One
2389+simple, and one impossible (or extremely complicated one).
2390+
2391+We are then presented with a very complicated request when the original
2392+problem could be solved far easier, e.g. by recording the downloaded
2393+video IDs in a separate file. To avoid this, you must include the
2394+greater context where it is non-obvious. In particular, every feature
2395+request that does not consist of adding support for a new site should
2396+contain a use case scenario that explains in what situation the missing
2397+feature would be useful.
2398+
2399+Does the issue involve one problem, and one problem only?
2400+
2401+Some of our users seem to think there is a limit of issues they can or
2402+should open. There is no limit of issues they can or should open. While
2403+it may seem appealing to be able to dump all your issues into one
2404+ticket, that means that someone who solves one of your issues cannot
2405+mark the issue as closed. Typically, reporting a bunch of issues leads
2406+to the ticket lingering since nobody wants to attack that behemoth,
2407+until someone mercifully splits the issue into multiple ones.
2408+
2409+In particular, every site support request issue should only pertain to
2410+services at one site (generally under a common domain, but always using
2411+the same backend technology). Do not request support for vimeo user
2412+videos, Whitehouse podcasts, and Google Plus pages in the same issue.
2413+Also, make sure that you don't post bug reports alongside feature
2414+requests. As a rule of thumb, a feature request does not include outputs
2415+of youtube-dl that are not immediately related to the feature at hand.
2416+Do not post reports of a network error alongside the request for a new
2417+video service.
2418+
2419+Is anyone going to need the feature?
2420+
2421+Only post features that you (or an incapacitated friend you can
2422+personally talk to) require. Do not post features because they seem like
2423+a good idea. If they are really useful, they will be requested by
2424+someone who requires them.
2425+
2426+Is your question about youtube-dl?
2427+
2428+It may sound strange, but some bug reports we receive are completely
2429+unrelated to youtube-dl and relate to a different or even the reporter's
2430+own application. Please make sure that you are actually using
2431+youtube-dl. If you are using a UI for youtube-dl, report the bug to the
2432+maintainer of the actual application providing the UI. On the other
2433+hand, if your UI for youtube-dl fails in some way you believe is related
2434+to youtube-dl, by all means, go ahead and report the bug.
2435+
2436+
2437+
2438+COPYRIGHT
2439+
2440+
2441+youtube-dl is released into the public domain by the copyright holders.
2442+
2443+This README file was originally written by Daniel Bolton
2444+(https://github.com/dbbolton) and is likewise released into the public
2445+domain.
2446
2447=== added directory 'openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl'
2448=== added file 'openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/YoutubeDL.py'
2449--- openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/YoutubeDL.py 1970-01-01 00:00:00 +0000
2450+++ openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/YoutubeDL.py 2015-09-23 12:21:33 +0000
2451@@ -0,0 +1,1889 @@
2452+#!/usr/bin/env python
2453+# -*- coding: utf-8 -*-
2454+
2455+from __future__ import absolute_import, unicode_literals
2456+
2457+import collections
2458+import contextlib
2459+import datetime
2460+import errno
2461+import fileinput
2462+import io
2463+import itertools
2464+import json
2465+import locale
2466+import operator
2467+import os
2468+import platform
2469+import re
2470+import shutil
2471+import subprocess
2472+import socket
2473+import sys
2474+import time
2475+import traceback
2476+
2477+if os.name == 'nt':
2478+ import ctypes
2479+
2480+from .compat import (
2481+ compat_basestring,
2482+ compat_cookiejar,
2483+ compat_expanduser,
2484+ compat_get_terminal_size,
2485+ compat_http_client,
2486+ compat_kwargs,
2487+ compat_str,
2488+ compat_urllib_error,
2489+ compat_urllib_request,
2490+)
2491+from .utils import (
2492+ escape_url,
2493+ ContentTooShortError,
2494+ date_from_str,
2495+ DateRange,
2496+ DEFAULT_OUTTMPL,
2497+ determine_ext,
2498+ DownloadError,
2499+ encodeFilename,
2500+ ExtractorError,
2501+ format_bytes,
2502+ formatSeconds,
2503+ HEADRequest,
2504+ locked_file,
2505+ make_HTTPS_handler,
2506+ MaxDownloadsReached,
2507+ PagedList,
2508+ parse_filesize,
2509+ PerRequestProxyHandler,
2510+ PostProcessingError,
2511+ platform_name,
2512+ preferredencoding,
2513+ render_table,
2514+ SameFileError,
2515+ sanitize_filename,
2516+ sanitize_path,
2517+ std_headers,
2518+ subtitles_filename,
2519+ UnavailableVideoError,
2520+ url_basename,
2521+ version_tuple,
2522+ write_json_file,
2523+ write_string,
2524+ YoutubeDLHandler,
2525+ prepend_extension,
2526+ replace_extension,
2527+ args_to_str,
2528+ age_restricted,
2529+)
2530+from .cache import Cache
2531+from .extractor import get_info_extractor, gen_extractors
2532+from .downloader import get_suitable_downloader
2533+from .downloader.rtmp import rtmpdump_version
2534+from .postprocessor import (
2535+ FFmpegFixupM4aPP,
2536+ FFmpegFixupStretchedPP,
2537+ FFmpegMergerPP,
2538+ FFmpegPostProcessor,
2539+ get_postprocessor,
2540+)
2541+from .version import __version__
2542+
2543+
2544+class YoutubeDL(object):
2545+ """YoutubeDL class.
2546+
2547+ YoutubeDL objects are the ones responsible of downloading the
2548+ actual video file and writing it to disk if the user has requested
2549+ it, among some other tasks. In most cases there should be one per
2550+ program. As, given a video URL, the downloader doesn't know how to
2551+ extract all the needed information, task that InfoExtractors do, it
2552+ has to pass the URL to one of them.
2553+
2554+ For this, YoutubeDL objects have a method that allows
2555+ InfoExtractors to be registered in a given order. When it is passed
2556+ a URL, the YoutubeDL object handles it to the first InfoExtractor it
2557+ finds that reports being able to handle it. The InfoExtractor extracts
2558+ all the information about the video or videos the URL refers to, and
2559+ YoutubeDL process the extracted information, possibly using a File
2560+ Downloader to download the video.
2561+
2562+ YoutubeDL objects accept a lot of parameters. In order not to saturate
2563+ the object constructor with arguments, it receives a dictionary of
2564+ options instead. These options are available through the params
2565+ attribute for the InfoExtractors to use. The YoutubeDL also
2566+ registers itself as the downloader in charge for the InfoExtractors
2567+ that are added to it, so this is a "mutual registration".
2568+
2569+ Available options:
2570+
2571+ username: Username for authentication purposes.
2572+ password: Password for authentication purposes.
2573+ videopassword: Password for accessing a video.
2574+ usenetrc: Use netrc for authentication instead.
2575+ verbose: Print additional info to stdout.
2576+ quiet: Do not print messages to stdout.
2577+ no_warnings: Do not print out anything for warnings.
2578+ forceurl: Force printing final URL.
2579+ forcetitle: Force printing title.
2580+ forceid: Force printing ID.
2581+ forcethumbnail: Force printing thumbnail URL.
2582+ forcedescription: Force printing description.
2583+ forcefilename: Force printing final filename.
2584+ forceduration: Force printing duration.
2585+ forcejson: Force printing info_dict as JSON.
2586+ dump_single_json: Force printing the info_dict of the whole playlist
2587+ (or video) as a single JSON line.
2588+ simulate: Do not download the video files.
2589+ format: Video format code. See options.py for more information.
2590+ outtmpl: Template for output names.
2591+ restrictfilenames: Do not allow "&" and spaces in file names
2592+ ignoreerrors: Do not stop on download errors.
2593+ force_generic_extractor: Force downloader to use the generic extractor
2594+ nooverwrites: Prevent overwriting files.
2595+ playliststart: Playlist item to start at.
2596+ playlistend: Playlist item to end at.
2597+ playlist_items: Specific indices of playlist to download.
2598+ playlistreverse: Download playlist items in reverse order.
2599+ matchtitle: Download only matching titles.
2600+ rejecttitle: Reject downloads for matching titles.
2601+ logger: Log messages to a logging.Logger instance.
2602+ logtostderr: Log messages to stderr instead of stdout.
2603+ writedescription: Write the video description to a .description file
2604+ writeinfojson: Write the video description to a .info.json file
2605+ writeannotations: Write the video annotations to a .annotations.xml file
2606+ writethumbnail: Write the thumbnail image to a file
2607+ write_all_thumbnails: Write all thumbnail formats to files
2608+ writesubtitles: Write the video subtitles to a file
2609+ writeautomaticsub: Write the automatic subtitles to a file
2610+ allsubtitles: Downloads all the subtitles of the video
2611+ (requires writesubtitles or writeautomaticsub)
2612+ listsubtitles: Lists all available subtitles for the video
2613+ subtitlesformat: The format code for subtitles
2614+ subtitleslangs: List of languages of the subtitles to download
2615+ keepvideo: Keep the video file after post-processing
2616+ daterange: A DateRange object, download only if the upload_date is in the range.
2617+ skip_download: Skip the actual download of the video file
2618+ cachedir: Location of the cache files in the filesystem.
2619+ False to disable filesystem cache.
2620+ noplaylist: Download single video instead of a playlist if in doubt.
2621+ age_limit: An integer representing the user's age in years.
2622+ Unsuitable videos for the given age are skipped.
2623+ min_views: An integer representing the minimum view count the video
2624+ must have in order to not be skipped.
2625+ Videos without view count information are always
2626+ downloaded. None for no limit.
2627+ max_views: An integer representing the maximum view count.
2628+ Videos that are more popular than that are not
2629+ downloaded.
2630+ Videos without view count information are always
2631+ downloaded. None for no limit.
2632+ download_archive: File name of a file where all downloads are recorded.
2633+ Videos already present in the file are not downloaded
2634+ again.
2635+ cookiefile: File name where cookies should be read from and dumped to.
2636+ nocheckcertificate:Do not verify SSL certificates
2637+ prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
2638+ At the moment, this is only supported by YouTube.
2639+ proxy: URL of the proxy server to use
2640+ cn_verification_proxy: URL of the proxy to use for IP address verification
2641+ on Chinese sites. (Experimental)
2642+ socket_timeout: Time to wait for unresponsive hosts, in seconds
2643+ bidi_workaround: Work around buggy terminals without bidirectional text
2644+ support, using fridibi
2645+ debug_printtraffic:Print out sent and received HTTP traffic
2646+ include_ads: Download ads as well
2647+ default_search: Prepend this string if an input url is not valid.
2648+ 'auto' for elaborate guessing
2649+ encoding: Use this encoding instead of the system-specified.
2650+ extract_flat: Do not resolve URLs, return the immediate result.
2651+ Pass in 'in_playlist' to only show this behavior for
2652+ playlist items.
2653+ postprocessors: A list of dictionaries, each with an entry
2654+ * key: The name of the postprocessor. See
2655+ youtube_dl/postprocessor/__init__.py for a list.
2656+ as well as any further keyword arguments for the
2657+ postprocessor.
2658+ progress_hooks: A list of functions that get called on download
2659+ progress, with a dictionary with the entries
2660+ * status: One of "downloading", "error", or "finished".
2661+ Check this first and ignore unknown values.
2662+
2663+ If status is one of "downloading", or "finished", the
2664+ following properties may also be present:
2665+ * filename: The final filename (always present)
2666+ * tmpfilename: The filename we're currently writing to
2667+ * downloaded_bytes: Bytes on disk
2668+ * total_bytes: Size of the whole file, None if unknown
2669+ * total_bytes_estimate: Guess of the eventual file size,
2670+ None if unavailable.
2671+ * elapsed: The number of seconds since download started.
2672+ * eta: The estimated time in seconds, None if unknown
2673+ * speed: The download speed in bytes/second, None if
2674+ unknown
2675+ * fragment_index: The counter of the currently
2676+ downloaded video fragment.
2677+ * fragment_count: The number of fragments (= individual
2678+ files that will be merged)
2679+
2680+ Progress hooks are guaranteed to be called at least once
2681+ (with status "finished") if the download is successful.
2682+ merge_output_format: Extension to use when merging formats.
2683+ fixup: Automatically correct known faults of the file.
2684+ One of:
2685+ - "never": do nothing
2686+ - "warn": only emit a warning
2687+ - "detect_or_warn": check whether we can do anything
2688+ about it, warn otherwise (default)
2689+ source_address: (Experimental) Client-side IP address to bind to.
2690+ call_home: Boolean, true iff we are allowed to contact the
2691+ youtube-dl servers for debugging.
2692+ sleep_interval: Number of seconds to sleep before each download.
2693+ listformats: Print an overview of available video formats and exit.
2694+ list_thumbnails: Print a table of all thumbnails and exit.
2695+ match_filter: A function that gets called with the info_dict of
2696+ every video.
2697+ If it returns a message, the video is ignored.
2698+ If it returns None, the video is downloaded.
2699+ match_filter_func in utils.py is one example for this.
2700+ no_color: Do not emit color codes in output.
2701+
2702+ The following options determine which downloader is picked:
2703+ external_downloader: Executable of the external downloader to call.
2704+ None or unset for standard (built-in) downloader.
2705+ hls_prefer_native: Use the native HLS downloader instead of ffmpeg/avconv.
2706+
2707+ The following parameters are not used by YoutubeDL itself, they are used by
2708+ the downloader (see youtube_dl/downloader/common.py):
2709+ nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
2710+ noresizebuffer, retries, continuedl, noprogress, consoletitle,
2711+ xattr_set_filesize, external_downloader_args.
2712+
2713+ The following options are used by the post processors:
2714+ prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
2715+ otherwise prefer avconv.
2716+ postprocessor_args: A list of additional command-line arguments for the
2717+ postprocessor.
2718+ """
2719+
2720+ params = None
2721+ _ies = []
2722+ _pps = []
2723+ _download_retcode = None
2724+ _num_downloads = None
2725+ _screen_file = None
2726+
2727+ def __init__(self, params=None, auto_init=True):
2728+ """Create a FileDownloader object with the given options."""
2729+ if params is None:
2730+ params = {}
2731+ self._ies = []
2732+ self._ies_instances = {}
2733+ self._pps = []
2734+ self._progress_hooks = []
2735+ self._download_retcode = 0
2736+ self._num_downloads = 0
2737+ self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
2738+ self._err_file = sys.stderr
2739+ self.params = params
2740+ self.cache = Cache(self)
2741+
2742+ if params.get('bidi_workaround', False):
2743+ try:
2744+ import pty
2745+ master, slave = pty.openpty()
2746+ width = compat_get_terminal_size().columns
2747+ if width is None:
2748+ width_args = []
2749+ else:
2750+ width_args = ['-w', str(width)]
2751+ sp_kwargs = dict(
2752+ stdin=subprocess.PIPE,
2753+ stdout=slave,
2754+ stderr=self._err_file)
2755+ try:
2756+ self._output_process = subprocess.Popen(
2757+ ['bidiv'] + width_args, **sp_kwargs
2758+ )
2759+ except OSError:
2760+ self._output_process = subprocess.Popen(
2761+ ['fribidi', '-c', 'UTF-8'] + width_args, **sp_kwargs)
2762+ self._output_channel = os.fdopen(master, 'rb')
2763+ except OSError as ose:
2764+ if ose.errno == 2:
2765+ self.report_warning('Could not find fribidi executable, ignoring --bidi-workaround . Make sure that fribidi is an executable file in one of the directories in your $PATH.')
2766+ else:
2767+ raise
2768+
2769+ if (sys.version_info >= (3,) and sys.platform != 'win32' and
2770+ sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968'] and
2771+ not params.get('restrictfilenames', False)):
2772+ # On Python 3, the Unicode filesystem API will throw errors (#1474)
2773+ self.report_warning(
2774+ 'Assuming --restrict-filenames since file system encoding '
2775+ 'cannot encode all characters. '
2776+ 'Set the LC_ALL environment variable to fix this.')
2777+ self.params['restrictfilenames'] = True
2778+
2779+ if isinstance(params.get('outtmpl'), bytes):
2780+ self.report_warning(
2781+ 'Parameter outtmpl is bytes, but should be a unicode string. '
2782+ 'Put from __future__ import unicode_literals at the top of your code file or consider switching to Python 3.x.')
2783+
2784+ self._setup_opener()
2785+
2786+ if auto_init:
2787+ self.print_debug_header()
2788+ self.add_default_info_extractors()
2789+
2790+ for pp_def_raw in self.params.get('postprocessors', []):
2791+ pp_class = get_postprocessor(pp_def_raw['key'])
2792+ pp_def = dict(pp_def_raw)
2793+ del pp_def['key']
2794+ pp = pp_class(self, **compat_kwargs(pp_def))
2795+ self.add_post_processor(pp)
2796+
2797+ for ph in self.params.get('progress_hooks', []):
2798+ self.add_progress_hook(ph)
2799+
2800+ def warn_if_short_id(self, argv):
2801+ # short YouTube ID starting with dash?
2802+ idxs = [
2803+ i for i, a in enumerate(argv)
2804+ if re.match(r'^-[0-9A-Za-z_-]{10}$', a)]
2805+ if idxs:
2806+ correct_argv = (
2807+ ['youtube-dl'] +
2808+ [a for i, a in enumerate(argv) if i not in idxs] +
2809+ ['--'] + [argv[i] for i in idxs]
2810+ )
2811+ self.report_warning(
2812+ 'Long argument string detected. '
2813+ 'Use -- to separate parameters and URLs, like this:\n%s\n' %
2814+ args_to_str(correct_argv))
2815+
2816+ def add_info_extractor(self, ie):
2817+ """Add an InfoExtractor object to the end of the list."""
2818+ self._ies.append(ie)
2819+ self._ies_instances[ie.ie_key()] = ie
2820+ ie.set_downloader(self)
2821+
2822+ def get_info_extractor(self, ie_key):
2823+ """
2824+ Get an instance of an IE with name ie_key, it will try to get one from
2825+ the _ies list, if there's no instance it will create a new one and add
2826+ it to the extractor list.
2827+ """
2828+ ie = self._ies_instances.get(ie_key)
2829+ if ie is None:
2830+ ie = get_info_extractor(ie_key)()
2831+ self.add_info_extractor(ie)
2832+ return ie
2833+
2834+ def add_default_info_extractors(self):
2835+ """
2836+ Add the InfoExtractors returned by gen_extractors to the end of the list
2837+ """
2838+ for ie in gen_extractors():
2839+ self.add_info_extractor(ie)
2840+
2841+ def add_post_processor(self, pp):
2842+ """Add a PostProcessor object to the end of the chain."""
2843+ self._pps.append(pp)
2844+ pp.set_downloader(self)
2845+
2846+ def add_progress_hook(self, ph):
2847+ """Add the progress hook (currently only for the file downloader)"""
2848+ self._progress_hooks.append(ph)
2849+
2850+ def _bidi_workaround(self, message):
2851+ if not hasattr(self, '_output_channel'):
2852+ return message
2853+
2854+ assert hasattr(self, '_output_process')
2855+ assert isinstance(message, compat_str)
2856+ line_count = message.count('\n') + 1
2857+ self._output_process.stdin.write((message + '\n').encode('utf-8'))
2858+ self._output_process.stdin.flush()
2859+ res = ''.join(self._output_channel.readline().decode('utf-8')
2860+ for _ in range(line_count))
2861+ return res[:-len('\n')]
2862+
2863+ def to_screen(self, message, skip_eol=False):
2864+ """Print message to stdout if not in quiet mode."""
2865+ return self.to_stdout(message, skip_eol, check_quiet=True)
2866+
2867+ def _write_string(self, s, out=None):
2868+ write_string(s, out=out, encoding=self.params.get('encoding'))
2869+
2870+ def to_stdout(self, message, skip_eol=False, check_quiet=False):
2871+ """Print message to stdout if not in quiet mode."""
2872+ if self.params.get('logger'):
2873+ self.params['logger'].debug(message)
2874+ elif not check_quiet or not self.params.get('quiet', False):
2875+ message = self._bidi_workaround(message)
2876+ terminator = ['\n', ''][skip_eol]
2877+ output = message + terminator
2878+
2879+ self._write_string(output, self._screen_file)
2880+
2881+ def to_stderr(self, message):
2882+ """Print message to stderr."""
2883+ assert isinstance(message, compat_str)
2884+ if self.params.get('logger'):
2885+ self.params['logger'].error(message)
2886+ else:
2887+ message = self._bidi_workaround(message)
2888+ output = message + '\n'
2889+ self._write_string(output, self._err_file)
2890+
2891+ def to_console_title(self, message):
2892+ if not self.params.get('consoletitle', False):
2893+ return
2894+ if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
2895+ # c_wchar_p() might not be necessary if `message` is
2896+ # already of type unicode()
2897+ ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
2898+ elif 'TERM' in os.environ:
2899+ self._write_string('\033]0;%s\007' % message, self._screen_file)
2900+
2901+ def save_console_title(self):
2902+ if not self.params.get('consoletitle', False):
2903+ return
2904+ if 'TERM' in os.environ:
2905+ # Save the title on stack
2906+ self._write_string('\033[22;0t', self._screen_file)
2907+
2908+ def restore_console_title(self):
2909+ if not self.params.get('consoletitle', False):
2910+ return
2911+ if 'TERM' in os.environ:
2912+ # Restore the title from stack
2913+ self._write_string('\033[23;0t', self._screen_file)
2914+
2915+ def __enter__(self):
2916+ self.save_console_title()
2917+ return self
2918+
2919+ def __exit__(self, *args):
2920+ self.restore_console_title()
2921+
2922+ if self.params.get('cookiefile') is not None:
2923+ self.cookiejar.save()
2924+
2925+ def trouble(self, message=None, tb=None):
2926+ """Determine action to take when a download problem appears.
2927+
2928+ Depending on if the downloader has been configured to ignore
2929+ download errors or not, this method may throw an exception or
2930+ not when errors are found, after printing the message.
2931+
2932+ tb, if given, is additional traceback information.
2933+ """
2934+ if message is not None:
2935+ self.to_stderr(message)
2936+ if self.params.get('verbose'):
2937+ if tb is None:
2938+ if sys.exc_info()[0]: # if .trouble has been called from an except block
2939+ tb = ''
2940+ if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
2941+ tb += ''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
2942+ tb += compat_str(traceback.format_exc())
2943+ else:
2944+ tb_data = traceback.format_list(traceback.extract_stack())
2945+ tb = ''.join(tb_data)
2946+ self.to_stderr(tb)
2947+ if not self.params.get('ignoreerrors', False):
2948+ if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
2949+ exc_info = sys.exc_info()[1].exc_info
2950+ else:
2951+ exc_info = sys.exc_info()
2952+ raise DownloadError(message, exc_info)
2953+ self._download_retcode = 1
2954+
2955+ def report_warning(self, message):
2956+ '''
2957+ Print the message to stderr, it will be prefixed with 'WARNING:'
2958+ If stderr is a tty file the 'WARNING:' will be colored
2959+ '''
2960+ if self.params.get('logger') is not None:
2961+ self.params['logger'].warning(message)
2962+ else:
2963+ if self.params.get('no_warnings'):
2964+ return
2965+ if not self.params.get('no_color') and self._err_file.isatty() and os.name != 'nt':
2966+ _msg_header = '\033[0;33mWARNING:\033[0m'
2967+ else:
2968+ _msg_header = 'WARNING:'
2969+ warning_message = '%s %s' % (_msg_header, message)
2970+ self.to_stderr(warning_message)
2971+
2972+ def report_error(self, message, tb=None):
2973+ '''
2974+ Do the same as trouble, but prefixes the message with 'ERROR:', colored
2975+ in red if stderr is a tty file.
2976+ '''
2977+ if not self.params.get('no_color') and self._err_file.isatty() and os.name != 'nt':
2978+ _msg_header = '\033[0;31mERROR:\033[0m'
2979+ else:
2980+ _msg_header = 'ERROR:'
2981+ error_message = '%s %s' % (_msg_header, message)
2982+ self.trouble(error_message, tb)
2983+
2984+ def report_file_already_downloaded(self, file_name):
2985+ """Report file has already been fully downloaded."""
2986+ try:
2987+ self.to_screen('[download] %s has already been downloaded' % file_name)
2988+ except UnicodeEncodeError:
2989+ self.to_screen('[download] The file has already been downloaded')
2990+
2991+ def prepare_filename(self, info_dict):
2992+ """Generate the output filename."""
2993+ try:
2994+ template_dict = dict(info_dict)
2995+
2996+ template_dict['epoch'] = int(time.time())
2997+ autonumber_size = self.params.get('autonumber_size')
2998+ if autonumber_size is None:
2999+ autonumber_size = 5
3000+ autonumber_templ = '%0' + str(autonumber_size) + 'd'
3001+ template_dict['autonumber'] = autonumber_templ % self._num_downloads
3002+ if template_dict.get('playlist_index') is not None:
3003+ template_dict['playlist_index'] = '%0*d' % (len(str(template_dict['n_entries'])), template_dict['playlist_index'])
3004+ if template_dict.get('resolution') is None:
3005+ if template_dict.get('width') and template_dict.get('height'):
3006+ template_dict['resolution'] = '%dx%d' % (template_dict['width'], template_dict['height'])
3007+ elif template_dict.get('height'):
3008+ template_dict['resolution'] = '%sp' % template_dict['height']
3009+ elif template_dict.get('width'):
3010+ template_dict['resolution'] = '?x%d' % template_dict['width']
3011+
3012+ sanitize = lambda k, v: sanitize_filename(
3013+ compat_str(v),
3014+ restricted=self.params.get('restrictfilenames'),
3015+ is_id=(k == 'id'))
3016+ template_dict = dict((k, sanitize(k, v))
3017+ for k, v in template_dict.items()
3018+ if v is not None)
3019+ template_dict = collections.defaultdict(lambda: 'NA', template_dict)
3020+
3021+ outtmpl = sanitize_path(self.params.get('outtmpl', DEFAULT_OUTTMPL))
3022+ tmpl = compat_expanduser(outtmpl)
3023+ filename = tmpl % template_dict
3024+ # Temporary fix for #4787
3025+ # 'Treat' all problem characters by passing filename through preferredencoding
3026+ # to workaround encoding issues with subprocess on python2 @ Windows
3027+ if sys.version_info < (3, 0) and sys.platform == 'win32':
3028+ filename = encodeFilename(filename, True).decode(preferredencoding())
3029+ return filename
3030+ except ValueError as err:
3031+ self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
3032+ return None
3033+
3034+ def _match_entry(self, info_dict, incomplete):
3035+ """ Returns None iff the file should be downloaded """
3036+
3037+ video_title = info_dict.get('title', info_dict.get('id', 'video'))
3038+ if 'title' in info_dict:
3039+ # This can happen when we're just evaluating the playlist
3040+ title = info_dict['title']
3041+ matchtitle = self.params.get('matchtitle', False)
3042+ if matchtitle:
3043+ if not re.search(matchtitle, title, re.IGNORECASE):
3044+ return '"' + title + '" title did not match pattern "' + matchtitle + '"'
3045+ rejecttitle = self.params.get('rejecttitle', False)
3046+ if rejecttitle:
3047+ if re.search(rejecttitle, title, re.IGNORECASE):
3048+ return '"' + title + '" title matched reject pattern "' + rejecttitle + '"'
3049+ date = info_dict.get('upload_date', None)
3050+ if date is not None:
3051+ dateRange = self.params.get('daterange', DateRange())
3052+ if date not in dateRange:
3053+ return '%s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
3054+ view_count = info_dict.get('view_count', None)
3055+ if view_count is not None:
3056+ min_views = self.params.get('min_views')
3057+ if min_views is not None and view_count < min_views:
3058+ return 'Skipping %s, because it has not reached minimum view count (%d/%d)' % (video_title, view_count, min_views)
3059+ max_views = self.params.get('max_views')
3060+ if max_views is not None and view_count > max_views:
3061+ return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
3062+ if age_restricted(info_dict.get('age_limit'), self.params.get('age_limit')):
3063+ return 'Skipping "%s" because it is age restricted' % video_title
3064+ if self.in_download_archive(info_dict):
3065+ return '%s has already been recorded in archive' % video_title
3066+
3067+ if not incomplete:
3068+ match_filter = self.params.get('match_filter')
3069+ if match_filter is not None:
3070+ ret = match_filter(info_dict)
3071+ if ret is not None:
3072+ return ret
3073+
3074+ return None
3075+
3076+ @staticmethod
3077+ def add_extra_info(info_dict, extra_info):
3078+ '''Set the keys from extra_info in info dict if they are missing'''
3079+ for key, value in extra_info.items():
3080+ info_dict.setdefault(key, value)
3081+
3082+ def extract_info(self, url, download=True, ie_key=None, extra_info={},
3083+ process=True, force_generic_extractor=False):
3084+ '''
3085+ Returns a list with a dictionary for each video we find.
3086+ If 'download', also downloads the videos.
3087+ extra_info is a dict containing the extra values to add to each result
3088+ '''
3089+
3090+ if not ie_key and force_generic_extractor:
3091+ ie_key = 'Generic'
3092+
3093+ if ie_key:
3094+ ies = [self.get_info_extractor(ie_key)]
3095+ else:
3096+ ies = self._ies
3097+
3098+ for ie in ies:
3099+ if not ie.suitable(url):
3100+ continue
3101+
3102+ if not ie.working():
3103+ self.report_warning('The program functionality for this site has been marked as broken, '
3104+ 'and will probably not work.')
3105+
3106+ try:
3107+ ie_result = ie.extract(url)
3108+ if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
3109+ break
3110+ if isinstance(ie_result, list):
3111+ # Backwards compatibility: old IE result format
3112+ ie_result = {
3113+ '_type': 'compat_list',
3114+ 'entries': ie_result,
3115+ }
3116+ self.add_default_extra_info(ie_result, ie, url)
3117+ if process:
3118+ return self.process_ie_result(ie_result, download, extra_info)
3119+ else:
3120+ return ie_result
3121+ except ExtractorError as de: # An error we somewhat expected
3122+ self.report_error(compat_str(de), de.format_traceback())
3123+ break
3124+ except MaxDownloadsReached:
3125+ raise
3126+ except Exception as e:
3127+ if self.params.get('ignoreerrors', False):
3128+ self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
3129+ break
3130+ else:
3131+ raise
3132+ else:
3133+ self.report_error('no suitable InfoExtractor for URL %s' % url)
3134+
3135+ def add_default_extra_info(self, ie_result, ie, url):
3136+ self.add_extra_info(ie_result, {
3137+ 'extractor': ie.IE_NAME,
3138+ 'webpage_url': url,
3139+ 'webpage_url_basename': url_basename(url),
3140+ 'extractor_key': ie.ie_key(),
3141+ })
3142+
3143+ def process_ie_result(self, ie_result, download=True, extra_info={}):
3144+ """
3145+ Take the result of the ie(may be modified) and resolve all unresolved
3146+ references (URLs, playlist items).
3147+
3148+ It will also download the videos if 'download'.
3149+ Returns the resolved ie_result.
3150+ """
3151+
3152+ result_type = ie_result.get('_type', 'video')
3153+
3154+ if result_type in ('url', 'url_transparent'):
3155+ extract_flat = self.params.get('extract_flat', False)
3156+ if ((extract_flat == 'in_playlist' and 'playlist' in extra_info) or
3157+ extract_flat is True):
3158+ if self.params.get('forcejson', False):
3159+ self.to_stdout(json.dumps(ie_result))
3160+ return ie_result
3161+
3162+ if result_type == 'video':
3163+ self.add_extra_info(ie_result, extra_info)
3164+ return self.process_video_result(ie_result, download=download)
3165+ elif result_type == 'url':
3166+ # We have to add extra_info to the results because it may be
3167+ # contained in a playlist
3168+ return self.extract_info(ie_result['url'],
3169+ download,
3170+ ie_key=ie_result.get('ie_key'),
3171+ extra_info=extra_info)
3172+ elif result_type == 'url_transparent':
3173+ # Use the information from the embedding page
3174+ info = self.extract_info(
3175+ ie_result['url'], ie_key=ie_result.get('ie_key'),
3176+ extra_info=extra_info, download=False, process=False)
3177+
3178+ force_properties = dict(
3179+ (k, v) for k, v in ie_result.items() if v is not None)
3180+ for f in ('_type', 'url'):
3181+ if f in force_properties:
3182+ del force_properties[f]
3183+ new_result = info.copy()
3184+ new_result.update(force_properties)
3185+
3186+ assert new_result.get('_type') != 'url_transparent'
3187+
3188+ return self.process_ie_result(
3189+ new_result, download=download, extra_info=extra_info)
3190+ elif result_type == 'playlist' or result_type == 'multi_video':
3191+ # We process each entry in the playlist
3192+ playlist = ie_result.get('title', None) or ie_result.get('id', None)
3193+ self.to_screen('[download] Downloading playlist: %s' % playlist)
3194+
3195+ playlist_results = []
3196+
3197+ playliststart = self.params.get('playliststart', 1) - 1
3198+ playlistend = self.params.get('playlistend', None)
3199+ # For backwards compatibility, interpret -1 as whole list
3200+ if playlistend == -1:
3201+ playlistend = None
3202+
3203+ playlistitems_str = self.params.get('playlist_items', None)
3204+ playlistitems = None
3205+ if playlistitems_str is not None:
3206+ def iter_playlistitems(format):
3207+ for string_segment in format.split(','):
3208+ if '-' in string_segment:
3209+ start, end = string_segment.split('-')
3210+ for item in range(int(start), int(end) + 1):
3211+ yield int(item)
3212+ else:
3213+ yield int(string_segment)
3214+ playlistitems = iter_playlistitems(playlistitems_str)
3215+
3216+ ie_entries = ie_result['entries']
3217+ if isinstance(ie_entries, list):
3218+ n_all_entries = len(ie_entries)
3219+ if playlistitems:
3220+ entries = [
3221+ ie_entries[i - 1] for i in playlistitems
3222+ if -n_all_entries <= i - 1 < n_all_entries]
3223+ else:
3224+ entries = ie_entries[playliststart:playlistend]
3225+ n_entries = len(entries)
3226+ self.to_screen(
3227+ "[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
3228+ (ie_result['extractor'], playlist, n_all_entries, n_entries))
3229+ elif isinstance(ie_entries, PagedList):
3230+ if playlistitems:
3231+ entries = []
3232+ for item in playlistitems:
3233+ entries.extend(ie_entries.getslice(
3234+ item - 1, item
3235+ ))
3236+ else:
3237+ entries = ie_entries.getslice(
3238+ playliststart, playlistend)
3239+ n_entries = len(entries)
3240+ self.to_screen(
3241+ "[%s] playlist %s: Downloading %d videos" %
3242+ (ie_result['extractor'], playlist, n_entries))
3243+ else: # iterable
3244+ if playlistitems:
3245+ entry_list = list(ie_entries)
3246+ entries = [entry_list[i - 1] for i in playlistitems]
3247+ else:
3248+ entries = list(itertools.islice(
3249+ ie_entries, playliststart, playlistend))
3250+ n_entries = len(entries)
3251+ self.to_screen(
3252+ "[%s] playlist %s: Downloading %d videos" %
3253+ (ie_result['extractor'], playlist, n_entries))
3254+
3255+ if self.params.get('playlistreverse', False):
3256+ entries = entries[::-1]
3257+
3258+ for i, entry in enumerate(entries, 1):
3259+ self.to_screen('[download] Downloading video %s of %s' % (i, n_entries))
3260+ extra = {
3261+ 'n_entries': n_entries,
3262+ 'playlist': playlist,
3263+ 'playlist_id': ie_result.get('id'),
3264+ 'playlist_title': ie_result.get('title'),
3265+ 'playlist_index': i + playliststart,
3266+ 'extractor': ie_result['extractor'],
3267+ 'webpage_url': ie_result['webpage_url'],
3268+ 'webpage_url_basename': url_basename(ie_result['webpage_url']),
3269+ 'extractor_key': ie_result['extractor_key'],
3270+ }
3271+
3272+ reason = self._match_entry(entry, incomplete=True)
3273+ if reason is not None:
3274+ self.to_screen('[download] ' + reason)
3275+ continue
3276+
3277+ entry_result = self.process_ie_result(entry,
3278+ download=download,
3279+ extra_info=extra)
3280+ playlist_results.append(entry_result)
3281+ ie_result['entries'] = playlist_results
3282+ return ie_result
3283+ elif result_type == 'compat_list':
3284+ self.report_warning(
3285+ 'Extractor %s returned a compat_list result. '
3286+ 'It needs to be updated.' % ie_result.get('extractor'))
3287+
3288+ def _fixup(r):
3289+ self.add_extra_info(
3290+ r,
3291+ {
3292+ 'extractor': ie_result['extractor'],
3293+ 'webpage_url': ie_result['webpage_url'],
3294+ 'webpage_url_basename': url_basename(ie_result['webpage_url']),
3295+ 'extractor_key': ie_result['extractor_key'],
3296+ }
3297+ )
3298+ return r
3299+ ie_result['entries'] = [
3300+ self.process_ie_result(_fixup(r), download, extra_info)
3301+ for r in ie_result['entries']
3302+ ]
3303+ return ie_result
3304+ else:
3305+ raise Exception('Invalid result type: %s' % result_type)
3306+
3307+ def _apply_format_filter(self, format_spec, available_formats):
3308+ " Returns a tuple of the remaining format_spec and filtered formats "
3309+
3310+ OPERATORS = {
3311+ '<': operator.lt,
3312+ '<=': operator.le,
3313+ '>': operator.gt,
3314+ '>=': operator.ge,
3315+ '=': operator.eq,
3316+ '!=': operator.ne,
3317+ }
3318+ operator_rex = re.compile(r'''(?x)\s*\[
3319+ (?P<key>width|height|tbr|abr|vbr|asr|filesize|fps)
3320+ \s*(?P<op>%s)(?P<none_inclusive>\s*\?)?\s*
3321+ (?P<value>[0-9.]+(?:[kKmMgGtTpPeEzZyY]i?[Bb]?)?)
3322+ \]$
3323+ ''' % '|'.join(map(re.escape, OPERATORS.keys())))
3324+ m = operator_rex.search(format_spec)
3325+ if m:
3326+ try:
3327+ comparison_value = int(m.group('value'))
3328+ except ValueError:
3329+ comparison_value = parse_filesize(m.group('value'))
3330+ if comparison_value is None:
3331+ comparison_value = parse_filesize(m.group('value') + 'B')
3332+ if comparison_value is None:
3333+ raise ValueError(
3334+ 'Invalid value %r in format specification %r' % (
3335+ m.group('value'), format_spec))
3336+ op = OPERATORS[m.group('op')]
3337+
3338+ if not m:
3339+ STR_OPERATORS = {
3340+ '=': operator.eq,
3341+ '!=': operator.ne,
3342+ }
3343+ str_operator_rex = re.compile(r'''(?x)\s*\[
3344+ \s*(?P<key>ext|acodec|vcodec|container|protocol)
3345+ \s*(?P<op>%s)(?P<none_inclusive>\s*\?)?
3346+ \s*(?P<value>[a-zA-Z0-9_-]+)
3347+ \s*\]$
3348+ ''' % '|'.join(map(re.escape, STR_OPERATORS.keys())))
3349+ m = str_operator_rex.search(format_spec)
3350+ if m:
3351+ comparison_value = m.group('value')
3352+ op = STR_OPERATORS[m.group('op')]
3353+
3354+ if not m:
3355+ raise ValueError('Invalid format specification %r' % format_spec)
3356+
3357+ def _filter(f):
3358+ actual_value = f.get(m.group('key'))
3359+ if actual_value is None:
3360+ return m.group('none_inclusive')
3361+ return op(actual_value, comparison_value)
3362+ new_formats = [f for f in available_formats if _filter(f)]
3363+
3364+ new_format_spec = format_spec[:-len(m.group(0))]
3365+ if not new_format_spec:
3366+ new_format_spec = 'best'
3367+
3368+ return (new_format_spec, new_formats)
3369+
3370+ def select_format(self, format_spec, available_formats):
3371+ while format_spec.endswith(']'):
3372+ format_spec, available_formats = self._apply_format_filter(
3373+ format_spec, available_formats)
3374+ if not available_formats:
3375+ return None
3376+
3377+ if format_spec in ['best', 'worst', None]:
3378+ format_idx = 0 if format_spec == 'worst' else -1
3379+ audiovideo_formats = [
3380+ f for f in available_formats
3381+ if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
3382+ if audiovideo_formats:
3383+ return audiovideo_formats[format_idx]
3384+ # for audio only (soundcloud) or video only (imgur) urls, select the best/worst audio format
3385+ elif (all(f.get('acodec') != 'none' for f in available_formats) or
3386+ all(f.get('vcodec') != 'none' for f in available_formats)):
3387+ return available_formats[format_idx]
3388+ elif format_spec == 'bestaudio':
3389+ audio_formats = [
3390+ f for f in available_formats
3391+ if f.get('vcodec') == 'none']
3392+ if audio_formats:
3393+ return audio_formats[-1]
3394+ elif format_spec == 'worstaudio':
3395+ audio_formats = [
3396+ f for f in available_formats
3397+ if f.get('vcodec') == 'none']
3398+ if audio_formats:
3399+ return audio_formats[0]
3400+ elif format_spec == 'bestvideo':
3401+ video_formats = [
3402+ f for f in available_formats
3403+ if f.get('acodec') == 'none']
3404+ if video_formats:
3405+ return video_formats[-1]
3406+ elif format_spec == 'worstvideo':
3407+ video_formats = [
3408+ f for f in available_formats
3409+ if f.get('acodec') == 'none']
3410+ if video_formats:
3411+ return video_formats[0]
3412+ else:
3413+ extensions = ['mp4', 'flv', 'webm', '3gp', 'm4a', 'mp3', 'ogg', 'aac', 'wav']
3414+ if format_spec in extensions:
3415+ filter_f = lambda f: f['ext'] == format_spec
3416+ else:
3417+ filter_f = lambda f: f['format_id'] == format_spec
3418+ matches = list(filter(filter_f, available_formats))
3419+ if matches:
3420+ return matches[-1]
3421+ return None
3422+
3423+ def _calc_headers(self, info_dict):
3424+ res = std_headers.copy()
3425+
3426+ add_headers = info_dict.get('http_headers')
3427+ if add_headers:
3428+ res.update(add_headers)
3429+
3430+ cookies = self._calc_cookies(info_dict)
3431+ if cookies:
3432+ res['Cookie'] = cookies
3433+
3434+ return res
3435+
3436+ def _calc_cookies(self, info_dict):
3437+ pr = compat_urllib_request.Request(info_dict['url'])
3438+ self.cookiejar.add_cookie_header(pr)
3439+ return pr.get_header('Cookie')
3440+
3441+ def process_video_result(self, info_dict, download=True):
3442+ assert info_dict.get('_type', 'video') == 'video'
3443+
3444+ if 'id' not in info_dict:
3445+ raise ExtractorError('Missing "id" field in extractor result')
3446+ if 'title' not in info_dict:
3447+ raise ExtractorError('Missing "title" field in extractor result')
3448+
3449+ if 'playlist' not in info_dict:
3450+ # It isn't part of a playlist
3451+ info_dict['playlist'] = None
3452+ info_dict['playlist_index'] = None
3453+
3454+ thumbnails = info_dict.get('thumbnails')
3455+ if thumbnails is None:
3456+ thumbnail = info_dict.get('thumbnail')
3457+ if thumbnail:
3458+ info_dict['thumbnails'] = thumbnails = [{'url': thumbnail}]
3459+ if thumbnails:
3460+ thumbnails.sort(key=lambda t: (
3461+ t.get('preference'), t.get('width'), t.get('height'),
3462+ t.get('id'), t.get('url')))
3463+ for i, t in enumerate(thumbnails):
3464+ if t.get('width') and t.get('height'):
3465+ t['resolution'] = '%dx%d' % (t['width'], t['height'])
3466+ if t.get('id') is None:
3467+ t['id'] = '%d' % i
3468+
3469+ if thumbnails and 'thumbnail' not in info_dict:
3470+ info_dict['thumbnail'] = thumbnails[-1]['url']
3471+
3472+ if 'display_id' not in info_dict and 'id' in info_dict:
3473+ info_dict['display_id'] = info_dict['id']
3474+
3475+ if info_dict.get('upload_date') is None and info_dict.get('timestamp') is not None:
3476+ # Working around out-of-range timestamp values (e.g. negative ones on Windows,
3477+ # see http://bugs.python.org/issue1646728)
3478+ try:
3479+ upload_date = datetime.datetime.utcfromtimestamp(info_dict['timestamp'])
3480+ info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
3481+ except (ValueError, OverflowError, OSError):
3482+ pass
3483+
3484+ if self.params.get('listsubtitles', False):
3485+ if 'automatic_captions' in info_dict:
3486+ self.list_subtitles(info_dict['id'], info_dict.get('automatic_captions'), 'automatic captions')
3487+ self.list_subtitles(info_dict['id'], info_dict.get('subtitles'), 'subtitles')
3488+ return
3489+ info_dict['requested_subtitles'] = self.process_subtitles(
3490+ info_dict['id'], info_dict.get('subtitles'),
3491+ info_dict.get('automatic_captions'))
3492+
3493+ # We now pick which formats have to be downloaded
3494+ if info_dict.get('formats') is None:
3495+ # There's only one format available
3496+ formats = [info_dict]
3497+ else:
3498+ formats = info_dict['formats']
3499+
3500+ if not formats:
3501+ raise ExtractorError('No video formats found!')
3502+
3503+ formats_dict = {}
3504+
3505+ # We check that all the formats have the format and format_id fields
3506+ for i, format in enumerate(formats):
3507+ if 'url' not in format:
3508+ raise ExtractorError('Missing "url" key in result (index %d)' % i)
3509+
3510+ if format.get('format_id') is None:
3511+ format['format_id'] = compat_str(i)
3512+ format_id = format['format_id']
3513+ if format_id not in formats_dict:
3514+ formats_dict[format_id] = []
3515+ formats_dict[format_id].append(format)
3516+
3517+ # Make sure all formats have unique format_id
3518+ for format_id, ambiguous_formats in formats_dict.items():
3519+ if len(ambiguous_formats) > 1:
3520+ for i, format in enumerate(ambiguous_formats):
3521+ format['format_id'] = '%s-%d' % (format_id, i)
3522+
3523+ for i, format in enumerate(formats):
3524+ if format.get('format') is None:
3525+ format['format'] = '{id} - {res}{note}'.format(
3526+ id=format['format_id'],
3527+ res=self.format_resolution(format),
3528+ note=' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
3529+ )
3530+ # Automatically determine file extension if missing
3531+ if 'ext' not in format:
3532+ format['ext'] = determine_ext(format['url']).lower()
3533+ # Add HTTP headers, so that external programs can use them from the
3534+ # json output
3535+ full_format_info = info_dict.copy()
3536+ full_format_info.update(format)
3537+ format['http_headers'] = self._calc_headers(full_format_info)
3538+
3539+ # TODO Central sorting goes here
3540+
3541+ if formats[0] is not info_dict:
3542+ # only set the 'formats' fields if the original info_dict list them
3543+ # otherwise we end up with a circular reference, the first (and unique)
3544+ # element in the 'formats' field in info_dict is info_dict itself,
3545+ # wich can't be exported to json
3546+ info_dict['formats'] = formats
3547+ if self.params.get('listformats'):
3548+ self.list_formats(info_dict)
3549+ return
3550+ if self.params.get('list_thumbnails'):
3551+ self.list_thumbnails(info_dict)
3552+ return
3553+
3554+ req_format = self.params.get('format')
3555+ if req_format is None:
3556+ req_format_list = []
3557+ if (self.params.get('outtmpl', DEFAULT_OUTTMPL) != '-' and
3558+ info_dict['extractor'] in ['youtube', 'ted']):
3559+ merger = FFmpegMergerPP(self)
3560+ if merger.available and merger.can_merge():
3561+ req_format_list.append('bestvideo+bestaudio')
3562+ req_format_list.append('best')
3563+ req_format = '/'.join(req_format_list)
3564+ formats_to_download = []
3565+ if req_format == 'all':
3566+ formats_to_download = formats
3567+ else:
3568+ for rfstr in req_format.split(','):
3569+ # We can accept formats requested in the format: 34/5/best, we pick
3570+ # the first that is available, starting from left
3571+ req_formats = rfstr.split('/')
3572+ for rf in req_formats:
3573+ if re.match(r'.+?\+.+?', rf) is not None:
3574+ # Two formats have been requested like '137+139'
3575+ format_1, format_2 = rf.split('+')
3576+ formats_info = (self.select_format(format_1, formats),
3577+ self.select_format(format_2, formats))
3578+ if all(formats_info):
3579+ # The first format must contain the video and the
3580+ # second the audio
3581+ if formats_info[0].get('vcodec') == 'none':
3582+ self.report_error('The first format must '
3583+ 'contain the video, try using '
3584+ '"-f %s+%s"' % (format_2, format_1))
3585+ return
3586+ output_ext = (
3587+ formats_info[0]['ext']
3588+ if self.params.get('merge_output_format') is None
3589+ else self.params['merge_output_format'])
3590+ selected_format = {
3591+ 'requested_formats': formats_info,
3592+ 'format': '%s+%s' % (formats_info[0].get('format'),
3593+ formats_info[1].get('format')),
3594+ 'format_id': '%s+%s' % (formats_info[0].get('format_id'),
3595+ formats_info[1].get('format_id')),
3596+ 'width': formats_info[0].get('width'),
3597+ 'height': formats_info[0].get('height'),
3598+ 'resolution': formats_info[0].get('resolution'),
3599+ 'fps': formats_info[0].get('fps'),
3600+ 'vcodec': formats_info[0].get('vcodec'),
3601+ 'vbr': formats_info[0].get('vbr'),
3602+ 'stretched_ratio': formats_info[0].get('stretched_ratio'),
3603+ 'acodec': formats_info[1].get('acodec'),
3604+ 'abr': formats_info[1].get('abr'),
3605+ 'ext': output_ext,
3606+ }
3607+ else:
3608+ selected_format = None
3609+ else:
3610+ selected_format = self.select_format(rf, formats)
3611+ if selected_format is not None:
3612+ formats_to_download.append(selected_format)
3613+ break
3614+ if not formats_to_download:
3615+ raise ExtractorError('requested format not available',
3616+ expected=True)
3617+
3618+ if download:
3619+ if len(formats_to_download) > 1:
3620+ self.to_screen('[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download)))
3621+ for format in formats_to_download:
3622+ new_info = dict(info_dict)
3623+ new_info.update(format)
3624+ self.process_info(new_info)
3625+ # We update the info dict with the best quality format (backwards compatibility)
3626+ info_dict.update(formats_to_download[-1])
3627+ return info_dict
3628+
3629+ def process_subtitles(self, video_id, normal_subtitles, automatic_captions):
3630+ """Select the requested subtitles and their format"""
3631+ available_subs = {}
3632+ if normal_subtitles and self.params.get('writesubtitles'):
3633+ available_subs.update(normal_subtitles)
3634+ if automatic_captions and self.params.get('writeautomaticsub'):
3635+ for lang, cap_info in automatic_captions.items():
3636+ if lang not in available_subs:
3637+ available_subs[lang] = cap_info
3638+
3639+ if (not self.params.get('writesubtitles') and not
3640+ self.params.get('writeautomaticsub') or not
3641+ available_subs):
3642+ return None
3643+
3644+ if self.params.get('allsubtitles', False):
3645+ requested_langs = available_subs.keys()
3646+ else:
3647+ if self.params.get('subtitleslangs', False):
3648+ requested_langs = self.params.get('subtitleslangs')
3649+ elif 'en' in available_subs:
3650+ requested_langs = ['en']
3651+ else:
3652+ requested_langs = [list(available_subs.keys())[0]]
3653+
3654+ formats_query = self.params.get('subtitlesformat', 'best')
3655+ formats_preference = formats_query.split('/') if formats_query else []
3656+ subs = {}
3657+ for lang in requested_langs:
3658+ formats = available_subs.get(lang)
3659+ if formats is None:
3660+ self.report_warning('%s subtitles not available for %s' % (lang, video_id))
3661+ continue
3662+ for ext in formats_preference:
3663+ if ext == 'best':
3664+ f = formats[-1]
3665+ break
3666+ matches = list(filter(lambda f: f['ext'] == ext, formats))
3667+ if matches:
3668+ f = matches[-1]
3669+ break
3670+ else:
3671+ f = formats[-1]
3672+ self.report_warning(
3673+ 'No subtitle format found matching "%s" for language %s, '
3674+ 'using %s' % (formats_query, lang, f['ext']))
3675+ subs[lang] = f
3676+ return subs
3677+
3678+ def process_info(self, info_dict):
3679+ """Process a single resolved IE result."""
3680+
3681+ assert info_dict.get('_type', 'video') == 'video'
3682+
3683+ max_downloads = self.params.get('max_downloads')
3684+ if max_downloads is not None:
3685+ if self._num_downloads >= int(max_downloads):
3686+ raise MaxDownloadsReached()
3687+
3688+ info_dict['fulltitle'] = info_dict['title']
3689+ if len(info_dict['title']) > 200:
3690+ info_dict['title'] = info_dict['title'][:197] + '...'
3691+
3692+ if 'format' not in info_dict:
3693+ info_dict['format'] = info_dict['ext']
3694+
3695+ reason = self._match_entry(info_dict, incomplete=False)
3696+ if reason is not None:
3697+ self.to_screen('[download] ' + reason)
3698+ return
3699+
3700+ self._num_downloads += 1
3701+
3702+ info_dict['_filename'] = filename = self.prepare_filename(info_dict)
3703+
3704+ # Forced printings
3705+ if self.params.get('forcetitle', False):
3706+ self.to_stdout(info_dict['fulltitle'])
3707+ if self.params.get('forceid', False):
3708+ self.to_stdout(info_dict['id'])
3709+ if self.params.get('forceurl', False):
3710+ if info_dict.get('requested_formats') is not None:
3711+ for f in info_dict['requested_formats']:
3712+ self.to_stdout(f['url'] + f.get('play_path', ''))
3713+ else:
3714+ # For RTMP URLs, also include the playpath
3715+ self.to_stdout(info_dict['url'] + info_dict.get('play_path', ''))
3716+ if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None:
3717+ self.to_stdout(info_dict['thumbnail'])
3718+ if self.params.get('forcedescription', False) and info_dict.get('description') is not None:
3719+ self.to_stdout(info_dict['description'])
3720+ if self.params.get('forcefilename', False) and filename is not None:
3721+ self.to_stdout(filename)
3722+ if self.params.get('forceduration', False) and info_dict.get('duration') is not None:
3723+ self.to_stdout(formatSeconds(info_dict['duration']))
3724+ if self.params.get('forceformat', False):
3725+ self.to_stdout(info_dict['format'])
3726+ if self.params.get('forcejson', False):
3727+ self.to_stdout(json.dumps(info_dict))
3728+
3729+ # Do nothing else if in simulate mode
3730+ if self.params.get('simulate', False):
3731+ return
3732+
3733+ if filename is None:
3734+ return
3735+
3736+ try:
3737+ dn = os.path.dirname(sanitize_path(encodeFilename(filename)))
3738+ if dn and not os.path.exists(dn):
3739+ os.makedirs(dn)
3740+ except (OSError, IOError) as err:
3741+ self.report_error('unable to create directory ' + compat_str(err))
3742+ return
3743+
3744+ if self.params.get('writedescription', False):
3745+ descfn = replace_extension(filename, 'description', info_dict.get('ext'))
3746+ if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(descfn)):
3747+ self.to_screen('[info] Video description is already present')
3748+ elif info_dict.get('description') is None:
3749+ self.report_warning('There\'s no description to write.')
3750+ else:
3751+ try:
3752+ self.to_screen('[info] Writing video description to: ' + descfn)
3753+ with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
3754+ descfile.write(info_dict['description'])
3755+ except (OSError, IOError):
3756+ self.report_error('Cannot write description file ' + descfn)
3757+ return
3758+
3759+ if self.params.get('writeannotations', False):
3760+ annofn = replace_extension(filename, 'annotations.xml', info_dict.get('ext'))
3761+ if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(annofn)):
3762+ self.to_screen('[info] Video annotations are already present')
3763+ else:
3764+ try:
3765+ self.to_screen('[info] Writing video annotations to: ' + annofn)
3766+ with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
3767+ annofile.write(info_dict['annotations'])
3768+ except (KeyError, TypeError):
3769+ self.report_warning('There are no annotations to write.')
3770+ except (OSError, IOError):
3771+ self.report_error('Cannot write annotations file: ' + annofn)
3772+ return
3773+
3774+ subtitles_are_requested = any([self.params.get('writesubtitles', False),
3775+ self.params.get('writeautomaticsub')])
3776+
3777+ if subtitles_are_requested and info_dict.get('requested_subtitles'):
3778+ # subtitles download errors are already managed as troubles in relevant IE
3779+ # that way it will silently go on when used with unsupporting IE
3780+ subtitles = info_dict['requested_subtitles']
3781+ ie = self.get_info_extractor(info_dict['extractor_key'])
3782+ for sub_lang, sub_info in subtitles.items():
3783+ sub_format = sub_info['ext']
3784+ if sub_info.get('data') is not None:
3785+ sub_data = sub_info['data']
3786+ else:
3787+ try:
3788+ sub_data = ie._download_webpage(
3789+ sub_info['url'], info_dict['id'], note=False)
3790+ except ExtractorError as err:
3791+ self.report_warning('Unable to download subtitle for "%s": %s' %
3792+ (sub_lang, compat_str(err.cause)))
3793+ continue
3794+ try:
3795+ sub_filename = subtitles_filename(filename, sub_lang, sub_format)
3796+ if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
3797+ self.to_screen('[info] Video subtitle %s.%s is already_present' % (sub_lang, sub_format))
3798+ else:
3799+ self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
3800+ with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
3801+ subfile.write(sub_data)
3802+ except (OSError, IOError):
3803+ self.report_error('Cannot write subtitles file ' + sub_filename)
3804+ return
3805+
3806+ if self.params.get('writeinfojson', False):
3807+ infofn = replace_extension(filename, 'info.json', info_dict.get('ext'))
3808+ if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(infofn)):
3809+ self.to_screen('[info] Video description metadata is already present')
3810+ else:
3811+ self.to_screen('[info] Writing video description metadata as JSON to: ' + infofn)
3812+ try:
3813+ write_json_file(self.filter_requested_info(info_dict), infofn)
3814+ except (OSError, IOError):
3815+ self.report_error('Cannot write metadata to JSON file ' + infofn)
3816+ return
3817+
3818+ self._write_thumbnails(info_dict, filename)
3819+
3820+ if not self.params.get('skip_download', False):
3821+ try:
3822+ def dl(name, info):
3823+ fd = get_suitable_downloader(info, self.params)(self, self.params)
3824+ for ph in self._progress_hooks:
3825+ fd.add_progress_hook(ph)
3826+ if self.params.get('verbose'):
3827+ self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
3828+ return fd.download(name, info)
3829+
3830+ if info_dict.get('requested_formats') is not None:
3831+ downloaded = []
3832+ success = True
3833+ merger = FFmpegMergerPP(self)
3834+ if not merger.available:
3835+ postprocessors = []
3836+ self.report_warning('You have requested multiple '
3837+ 'formats but ffmpeg or avconv are not installed.'
3838+ ' The formats won\'t be merged.')
3839+ else:
3840+ postprocessors = [merger]
3841+
3842+ def compatible_formats(formats):
3843+ video, audio = formats
3844+ # Check extension
3845+ video_ext, audio_ext = audio.get('ext'), video.get('ext')
3846+ if video_ext and audio_ext:
3847+ COMPATIBLE_EXTS = (
3848+ ('mp3', 'mp4', 'm4a', 'm4p', 'm4b', 'm4r', 'm4v'),
3849+ ('webm')
3850+ )
3851+ for exts in COMPATIBLE_EXTS:
3852+ if video_ext in exts and audio_ext in exts:
3853+ return True
3854+ # TODO: Check acodec/vcodec
3855+ return False
3856+
3857+ filename_real_ext = os.path.splitext(filename)[1][1:]
3858+ filename_wo_ext = (
3859+ os.path.splitext(filename)[0]
3860+ if filename_real_ext == info_dict['ext']
3861+ else filename)
3862+ requested_formats = info_dict['requested_formats']
3863+ if self.params.get('merge_output_format') is None and not compatible_formats(requested_formats):
3864+ info_dict['ext'] = 'mkv'
3865+ self.report_warning(
3866+ 'Requested formats are incompatible for merge and will be merged into mkv.')
3867+ # Ensure filename always has a correct extension for successful merge
3868+ filename = '%s.%s' % (filename_wo_ext, info_dict['ext'])
3869+ if os.path.exists(encodeFilename(filename)):
3870+ self.to_screen(
3871+ '[download] %s has already been downloaded and '
3872+ 'merged' % filename)
3873+ else:
3874+ for f in requested_formats:
3875+ new_info = dict(info_dict)
3876+ new_info.update(f)
3877+ fname = self.prepare_filename(new_info)
3878+ fname = prepend_extension(fname, 'f%s' % f['format_id'], new_info['ext'])
3879+ downloaded.append(fname)
3880+ partial_success = dl(fname, new_info)
3881+ success = success and partial_success
3882+ info_dict['__postprocessors'] = postprocessors
3883+ info_dict['__files_to_merge'] = downloaded
3884+ else:
3885+ # Just a single file
3886+ success = dl(filename, info_dict)
3887+ except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
3888+ self.report_error('unable to download video data: %s' % str(err))
3889+ return
3890+ except (OSError, IOError) as err:
3891+ raise UnavailableVideoError(err)
3892+ except (ContentTooShortError, ) as err:
3893+ self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
3894+ return
3895+
3896+ if success:
3897+ # Fixup content
3898+ fixup_policy = self.params.get('fixup')
3899+ if fixup_policy is None:
3900+ fixup_policy = 'detect_or_warn'
3901+
3902+ stretched_ratio = info_dict.get('stretched_ratio')
3903+ if stretched_ratio is not None and stretched_ratio != 1:
3904+ if fixup_policy == 'warn':
3905+ self.report_warning('%s: Non-uniform pixel ratio (%s)' % (
3906+ info_dict['id'], stretched_ratio))
3907+ elif fixup_policy == 'detect_or_warn':
3908+ stretched_pp = FFmpegFixupStretchedPP(self)
3909+ if stretched_pp.available:
3910+ info_dict.setdefault('__postprocessors', [])
3911+ info_dict['__postprocessors'].append(stretched_pp)
3912+ else:
3913+ self.report_warning(
3914+ '%s: Non-uniform pixel ratio (%s). Install ffmpeg or avconv to fix this automatically.' % (
3915+ info_dict['id'], stretched_ratio))
3916+ else:
3917+ assert fixup_policy in ('ignore', 'never')
3918+
3919+ if info_dict.get('requested_formats') is None and info_dict.get('container') == 'm4a_dash':
3920+ if fixup_policy == 'warn':
3921+ self.report_warning('%s: writing DASH m4a. Only some players support this container.' % (
3922+ info_dict['id']))
3923+ elif fixup_policy == 'detect_or_warn':
3924+ fixup_pp = FFmpegFixupM4aPP(self)
3925+ if fixup_pp.available:
3926+ info_dict.setdefault('__postprocessors', [])
3927+ info_dict['__postprocessors'].append(fixup_pp)
3928+ else:
3929+ self.report_warning(
3930+ '%s: writing DASH m4a. Only some players support this container. Install ffmpeg or avconv to fix this automatically.' % (
3931+ info_dict['id']))
3932+ else:
3933+ assert fixup_policy in ('ignore', 'never')
3934+
3935+ try:
3936+ self.post_process(filename, info_dict)
3937+ except (PostProcessingError) as err:
3938+ self.report_error('postprocessing: %s' % str(err))
3939+ return
3940+ self.record_download_archive(info_dict)
3941+
3942+ def download(self, url_list):
3943+ """Download a given list of URLs."""
3944+ outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
3945+ if (len(url_list) > 1 and
3946+ '%' not in outtmpl and
3947+ self.params.get('max_downloads') != 1):
3948+ raise SameFileError(outtmpl)
3949+
3950+ for url in url_list:
3951+ try:
3952+ # It also downloads the videos
3953+ res = self.extract_info(
3954+ url, force_generic_extractor=self.params.get('force_generic_extractor', False))
3955+ except UnavailableVideoError:
3956+ self.report_error('unable to download video')
3957+ except MaxDownloadsReached:
3958+ self.to_screen('[info] Maximum number of downloaded files reached.')
3959+ raise
3960+ else:
3961+ if self.params.get('dump_single_json', False):
3962+ self.to_stdout(json.dumps(res))
3963+
3964+ return self._download_retcode
3965+
3966+ def download_with_info_file(self, info_filename):
3967+ with contextlib.closing(fileinput.FileInput(
3968+ [info_filename], mode='r',
3969+ openhook=fileinput.hook_encoded('utf-8'))) as f:
3970+ # FileInput doesn't have a read method, we can't call json.load
3971+ info = self.filter_requested_info(json.loads('\n'.join(f)))
3972+ try:
3973+ self.process_ie_result(info, download=True)
3974+ except DownloadError:
3975+ webpage_url = info.get('webpage_url')
3976+ if webpage_url is not None:
3977+ self.report_warning('The info failed to download, trying with "%s"' % webpage_url)
3978+ return self.download([webpage_url])
3979+ else:
3980+ raise
3981+ return self._download_retcode
3982+
3983+ @staticmethod
3984+ def filter_requested_info(info_dict):
3985+ return dict(
3986+ (k, v) for k, v in info_dict.items()
3987+ if k not in ['requested_formats', 'requested_subtitles'])
3988+
3989+ def post_process(self, filename, ie_info):
3990+ """Run all the postprocessors on the given file."""
3991+ info = dict(ie_info)
3992+ info['filepath'] = filename
3993+ pps_chain = []
3994+ if ie_info.get('__postprocessors') is not None:
3995+ pps_chain.extend(ie_info['__postprocessors'])
3996+ pps_chain.extend(self._pps)
3997+ for pp in pps_chain:
3998+ files_to_delete = []
3999+ try:
4000+ files_to_delete, info = pp.run(info)
4001+ except PostProcessingError as e:
4002+ self.report_error(e.msg)
4003+ if files_to_delete and not self.params.get('keepvideo', False):
4004+ for old_filename in files_to_delete:
4005+ self.to_screen('Deleting original file %s (pass -k to keep)' % old_filename)
4006+ try:
4007+ os.remove(encodeFilename(old_filename))
4008+ except (IOError, OSError):
4009+ self.report_warning('Unable to remove downloaded original file')
4010+
4011+ def _make_archive_id(self, info_dict):
4012+ # Future-proof against any change in case
4013+ # and backwards compatibility with prior versions
4014+ extractor = info_dict.get('extractor_key')
4015+ if extractor is None:
4016+ if 'id' in info_dict:
4017+ extractor = info_dict.get('ie_key') # key in a playlist
4018+ if extractor is None:
4019+ return None # Incomplete video information
4020+ return extractor.lower() + ' ' + info_dict['id']
4021+
4022+ def in_download_archive(self, info_dict):
4023+ fn = self.params.get('download_archive')
4024+ if fn is None:
4025+ return False
4026+
4027+ vid_id = self._make_archive_id(info_dict)
4028+ if vid_id is None:
4029+ return False # Incomplete video information
4030+
4031+ try:
4032+ with locked_file(fn, 'r', encoding='utf-8') as archive_file:
4033+ for line in archive_file:
4034+ if line.strip() == vid_id:
4035+ return True
4036+ except IOError as ioe:
4037+ if ioe.errno != errno.ENOENT:
4038+ raise
4039+ return False
4040+
4041+ def record_download_archive(self, info_dict):
4042+ fn = self.params.get('download_archive')
4043+ if fn is None:
4044+ return
4045+ vid_id = self._make_archive_id(info_dict)
4046+ assert vid_id
4047+ with locked_file(fn, 'a', encoding='utf-8') as archive_file:
4048+ archive_file.write(vid_id + '\n')
4049+
4050+ @staticmethod
4051+ def format_resolution(format, default='unknown'):
4052+ if format.get('vcodec') == 'none':
4053+ return 'audio only'
4054+ if format.get('resolution') is not None:
4055+ return format['resolution']
4056+ if format.get('height') is not None:
4057+ if format.get('width') is not None:
4058+ res = '%sx%s' % (format['width'], format['height'])
4059+ else:
4060+ res = '%sp' % format['height']
4061+ elif format.get('width') is not None:
4062+ res = '?x%d' % format['width']
4063+ else:
4064+ res = default
4065+ return res
4066+
4067+ def _format_note(self, fdict):
4068+ res = ''
4069+ if fdict.get('ext') in ['f4f', 'f4m']:
4070+ res += '(unsupported) '
4071+ if fdict.get('format_note') is not None:
4072+ res += fdict['format_note'] + ' '
4073+ if fdict.get('tbr') is not None:
4074+ res += '%4dk ' % fdict['tbr']
4075+ if fdict.get('container') is not None:
4076+ if res:
4077+ res += ', '
4078+ res += '%s container' % fdict['container']
4079+ if (fdict.get('vcodec') is not None and
4080+ fdict.get('vcodec') != 'none'):
4081+ if res:
4082+ res += ', '
4083+ res += fdict['vcodec']
4084+ if fdict.get('vbr') is not None:
4085+ res += '@'
4086+ elif fdict.get('vbr') is not None and fdict.get('abr') is not None:
4087+ res += 'video@'
4088+ if fdict.get('vbr') is not None:
4089+ res += '%4dk' % fdict['vbr']
4090+ if fdict.get('fps') is not None:
4091+ res += ', %sfps' % fdict['fps']
4092+ if fdict.get('acodec') is not None:
4093+ if res:
4094+ res += ', '
4095+ if fdict['acodec'] == 'none':
4096+ res += 'video only'
4097+ else:
4098+ res += '%-5s' % fdict['acodec']
4099+ elif fdict.get('abr') is not None:
4100+ if res:
4101+ res += ', '
4102+ res += 'audio'
4103+ if fdict.get('abr') is not None:
4104+ res += '@%3dk' % fdict['abr']
4105+ if fdict.get('asr') is not None:
4106+ res += ' (%5dHz)' % fdict['asr']
4107+ if fdict.get('filesize') is not None:
4108+ if res:
4109+ res += ', '
4110+ res += format_bytes(fdict['filesize'])
4111+ elif fdict.get('filesize_approx') is not None:
4112+ if res:
4113+ res += ', '
4114+ res += '~' + format_bytes(fdict['filesize_approx'])
4115+ return res
4116+
4117+ def list_formats(self, info_dict):
4118+ formats = info_dict.get('formats', [info_dict])
4119+ table = [
4120+ [f['format_id'], f['ext'], self.format_resolution(f), self._format_note(f)]
4121+ for f in formats
4122+ if f.get('preference') is None or f['preference'] >= -1000]
4123+ if len(formats) > 1:
4124+ table[-1][-1] += (' ' if table[-1][-1] else '') + '(best)'
4125+
4126+ header_line = ['format code', 'extension', 'resolution', 'note']
4127+ self.to_screen(
4128+ '[info] Available formats for %s:\n%s' %
4129+ (info_dict['id'], render_table(header_line, table)))
4130+
4131+ def list_thumbnails(self, info_dict):
4132+ thumbnails = info_dict.get('thumbnails')
4133+ if not thumbnails:
4134+ tn_url = info_dict.get('thumbnail')
4135+ if tn_url:
4136+ thumbnails = [{'id': '0', 'url': tn_url}]
4137+ else:
4138+ self.to_screen(
4139+ '[info] No thumbnails present for %s' % info_dict['id'])
4140+ return
4141+
4142+ self.to_screen(
4143+ '[info] Thumbnails for %s:' % info_dict['id'])
4144+ self.to_screen(render_table(
4145+ ['ID', 'width', 'height', 'URL'],
4146+ [[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails]))
4147+
4148+ def list_subtitles(self, video_id, subtitles, name='subtitles'):
4149+ if not subtitles:
4150+ self.to_screen('%s has no %s' % (video_id, name))
4151+ return
4152+ self.to_screen(
4153+ 'Available %s for %s:' % (name, video_id))
4154+ self.to_screen(render_table(
4155+ ['Language', 'formats'],
4156+ [[lang, ', '.join(f['ext'] for f in reversed(formats))]
4157+ for lang, formats in subtitles.items()]))
4158+
4159+ def urlopen(self, req):
4160+ """ Start an HTTP download """
4161+
4162+ # According to RFC 3986, URLs can not contain non-ASCII characters, however this is not
4163+ # always respected by websites, some tend to give out URLs with non percent-encoded
4164+ # non-ASCII characters (see telemb.py, ard.py [#3412])
4165+ # urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
4166+ # To work around aforementioned issue we will replace request's original URL with
4167+ # percent-encoded one
4168+ req_is_string = isinstance(req, compat_basestring)
4169+ url = req if req_is_string else req.get_full_url()
4170+ url_escaped = escape_url(url)
4171+
4172+ # Substitute URL if any change after escaping
4173+ if url != url_escaped:
4174+ if req_is_string:
4175+ req = url_escaped
4176+ else:
4177+ req_type = HEADRequest if req.get_method() == 'HEAD' else compat_urllib_request.Request
4178+ req = req_type(
4179+ url_escaped, data=req.data, headers=req.headers,
4180+ origin_req_host=req.origin_req_host, unverifiable=req.unverifiable)
4181+
4182+ return self._opener.open(req, timeout=self._socket_timeout)
4183+
4184+ def print_debug_header(self):
4185+ if not self.params.get('verbose'):
4186+ return
4187+
4188+ if type('') is not compat_str:
4189+ # Python 2.6 on SLES11 SP1 (https://github.com/rg3/youtube-dl/issues/3326)
4190+ self.report_warning(
4191+ 'Your Python is broken! Update to a newer and supported version')
4192+
4193+ stdout_encoding = getattr(
4194+ sys.stdout, 'encoding', 'missing (%s)' % type(sys.stdout).__name__)
4195+ encoding_str = (
4196+ '[debug] Encodings: locale %s, fs %s, out %s, pref %s\n' % (
4197+ locale.getpreferredencoding(),
4198+ sys.getfilesystemencoding(),
4199+ stdout_encoding,
4200+ self.get_encoding()))
4201+ write_string(encoding_str, encoding=None)
4202+
4203+ self._write_string('[debug] youtube-dl version ' + __version__ + '\n')
4204+ try:
4205+ sp = subprocess.Popen(
4206+ ['git', 'rev-parse', '--short', 'HEAD'],
4207+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
4208+ cwd=os.path.dirname(os.path.abspath(__file__)))
4209+ out, err = sp.communicate()
4210+ out = out.decode().strip()
4211+ if re.match('[0-9a-f]+', out):
4212+ self._write_string('[debug] Git HEAD: ' + out + '\n')
4213+ except Exception:
4214+ try:
4215+ sys.exc_clear()
4216+ except Exception:
4217+ pass
4218+ self._write_string('[debug] Python version %s - %s\n' % (
4219+ platform.python_version(), platform_name()))
4220+
4221+ exe_versions = FFmpegPostProcessor.get_versions(self)
4222+ exe_versions['rtmpdump'] = rtmpdump_version()
4223+ exe_str = ', '.join(
4224+ '%s %s' % (exe, v)
4225+ for exe, v in sorted(exe_versions.items())
4226+ if v
4227+ )
4228+ if not exe_str:
4229+ exe_str = 'none'
4230+ self._write_string('[debug] exe versions: %s\n' % exe_str)
4231+
4232+ proxy_map = {}
4233+ for handler in self._opener.handlers:
4234+ if hasattr(handler, 'proxies'):
4235+ proxy_map.update(handler.proxies)
4236+ self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
4237+
4238+ if self.params.get('call_home', False):
4239+ ipaddr = self.urlopen('https://yt-dl.org/ip').read().decode('utf-8')
4240+ self._write_string('[debug] Public IP address: %s\n' % ipaddr)
4241+ latest_version = self.urlopen(
4242+ 'https://yt-dl.org/latest/version').read().decode('utf-8')
4243+ if version_tuple(latest_version) > version_tuple(__version__):
4244+ self.report_warning(
4245+ 'You are using an outdated version (newest version: %s)! '
4246+ 'See https://yt-dl.org/update if you need help updating.' %
4247+ latest_version)
4248+
4249+ def _setup_opener(self):
4250+ timeout_val = self.params.get('socket_timeout')
4251+ self._socket_timeout = 600 if timeout_val is None else float(timeout_val)
4252+
4253+ opts_cookiefile = self.params.get('cookiefile')
4254+ opts_proxy = self.params.get('proxy')
4255+
4256+ if opts_cookiefile is None:
4257+ self.cookiejar = compat_cookiejar.CookieJar()
4258+ else:
4259+ self.cookiejar = compat_cookiejar.MozillaCookieJar(
4260+ opts_cookiefile)
4261+ if os.access(opts_cookiefile, os.R_OK):
4262+ self.cookiejar.load()
4263+
4264+ cookie_processor = compat_urllib_request.HTTPCookieProcessor(
4265+ self.cookiejar)
4266+ if opts_proxy is not None:
4267+ if opts_proxy == '':
4268+ proxies = {}
4269+ else:
4270+ proxies = {'http': opts_proxy, 'https': opts_proxy}
4271+ else:
4272+ proxies = compat_urllib_request.getproxies()
4273+ # Set HTTPS proxy to HTTP one if given (https://github.com/rg3/youtube-dl/issues/805)
4274+ if 'http' in proxies and 'https' not in proxies:
4275+ proxies['https'] = proxies['http']
4276+ proxy_handler = PerRequestProxyHandler(proxies)
4277+
4278+ debuglevel = 1 if self.params.get('debug_printtraffic') else 0
4279+ https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel)
4280+ ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)
4281+ opener = compat_urllib_request.build_opener(
4282+ proxy_handler, https_handler, cookie_processor, ydlh)
4283+
4284+ # Delete the default user-agent header, which would otherwise apply in
4285+ # cases where our custom HTTP handler doesn't come into play
4286+ # (See https://github.com/rg3/youtube-dl/issues/1309 for details)
4287+ opener.addheaders = []
4288+ self._opener = opener
4289+
4290+ def encode(self, s):
4291+ if isinstance(s, bytes):
4292+ return s # Already encoded
4293+
4294+ try:
4295+ return s.encode(self.get_encoding())
4296+ except UnicodeEncodeError as err:
4297+ err.reason = err.reason + '. Check your system encoding configuration or use the --encoding option.'
4298+ raise
4299+
4300+ def get_encoding(self):
4301+ encoding = self.params.get('encoding')
4302+ if encoding is None:
4303+ encoding = preferredencoding()
4304+ return encoding
4305+
4306+ def _write_thumbnails(self, info_dict, filename):
4307+ if self.params.get('writethumbnail', False):
4308+ thumbnails = info_dict.get('thumbnails')
4309+ if thumbnails:
4310+ thumbnails = [thumbnails[-1]]
4311+ elif self.params.get('write_all_thumbnails', False):
4312+ thumbnails = info_dict.get('thumbnails')
4313+ else:
4314+ return
4315+
4316+ if not thumbnails:
4317+ # No thumbnails present, so return immediately
4318+ return
4319+
4320+ for t in thumbnails:
4321+ thumb_ext = determine_ext(t['url'], 'jpg')
4322+ suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
4323+ thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
4324+ t['filename'] = thumb_filename = os.path.splitext(filename)[0] + suffix + '.' + thumb_ext
4325+
4326+ if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
4327+ self.to_screen('[%s] %s: Thumbnail %sis already present' %
4328+ (info_dict['extractor'], info_dict['id'], thumb_display_id))
4329+ else:
4330+ self.to_screen('[%s] %s: Downloading thumbnail %s...' %
4331+ (info_dict['extractor'], info_dict['id'], thumb_display_id))
4332+ try:
4333+ uf = self.urlopen(t['url'])
4334+ with open(thumb_filename, 'wb') as thumbf:
4335+ shutil.copyfileobj(uf, thumbf)
4336+ self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
4337+ (info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
4338+ except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
4339+ self.report_warning('Unable to download thumbnail "%s": %s' %
4340+ (t['url'], compat_str(err)))
4341
4342=== added file 'openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__init__.py'
4343--- openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__init__.py 1970-01-01 00:00:00 +0000
4344+++ openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__init__.py 2015-09-23 12:21:33 +0000
4345@@ -0,0 +1,418 @@
4346+#!/usr/bin/env python
4347+# -*- coding: utf-8 -*-
4348+
4349+from __future__ import unicode_literals
4350+
4351+__license__ = 'Public Domain'
4352+
4353+import codecs
4354+import io
4355+import os
4356+import random
4357+import shlex
4358+import sys
4359+
4360+
4361+from .options import (
4362+ parseOpts,
4363+)
4364+from .compat import (
4365+ compat_expanduser,
4366+ compat_getpass,
4367+ compat_print,
4368+ workaround_optparse_bug9161,
4369+)
4370+from .utils import (
4371+ DateRange,
4372+ decodeOption,
4373+ DEFAULT_OUTTMPL,
4374+ DownloadError,
4375+ match_filter_func,
4376+ MaxDownloadsReached,
4377+ preferredencoding,
4378+ read_batch_urls,
4379+ SameFileError,
4380+ setproctitle,
4381+ std_headers,
4382+ write_string,
4383+)
4384+from .update import update_self
4385+from .downloader import (
4386+ FileDownloader,
4387+)
4388+from .extractor import gen_extractors, list_extractors
4389+from .YoutubeDL import YoutubeDL
4390+
4391+
4392+def _real_main(argv=None):
4393+ # Compatibility fixes for Windows
4394+ if sys.platform == 'win32':
4395+ # https://github.com/rg3/youtube-dl/issues/820
4396+ codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
4397+
4398+ workaround_optparse_bug9161()
4399+
4400+ setproctitle('youtube-dl')
4401+
4402+ parser, opts, args = parseOpts(argv)
4403+
4404+ # Set user agent
4405+ if opts.user_agent is not None:
4406+ std_headers['User-Agent'] = opts.user_agent
4407+
4408+ # Set referer
4409+ if opts.referer is not None:
4410+ std_headers['Referer'] = opts.referer
4411+
4412+ # Custom HTTP headers
4413+ if opts.headers is not None:
4414+ for h in opts.headers:
4415+ if h.find(':', 1) < 0:
4416+ parser.error('wrong header formatting, it should be key:value, not "%s"' % h)
4417+ key, value = h.split(':', 2)
4418+ if opts.verbose:
4419+ write_string('[debug] Adding header from command line option %s:%s\n' % (key, value))
4420+ std_headers[key] = value
4421+
4422+ # Dump user agent
4423+ if opts.dump_user_agent:
4424+ compat_print(std_headers['User-Agent'])
4425+ sys.exit(0)
4426+
4427+ # Batch file verification
4428+ batch_urls = []
4429+ if opts.batchfile is not None:
4430+ try:
4431+ if opts.batchfile == '-':
4432+ batchfd = sys.stdin
4433+ else:
4434+ batchfd = io.open(opts.batchfile, 'r', encoding='utf-8', errors='ignore')
4435+ batch_urls = read_batch_urls(batchfd)
4436+ if opts.verbose:
4437+ write_string('[debug] Batch file urls: ' + repr(batch_urls) + '\n')
4438+ except IOError:
4439+ sys.exit('ERROR: batch file could not be read')
4440+ all_urls = batch_urls + args
4441+ all_urls = [url.strip() for url in all_urls]
4442+ _enc = preferredencoding()
4443+ all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
4444+
4445+ if opts.list_extractors:
4446+ for ie in list_extractors(opts.age_limit):
4447+ compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
4448+ matchedUrls = [url for url in all_urls if ie.suitable(url)]
4449+ for mu in matchedUrls:
4450+ compat_print(' ' + mu)
4451+ sys.exit(0)
4452+ if opts.list_extractor_descriptions:
4453+ for ie in list_extractors(opts.age_limit):
4454+ if not ie._WORKING:
4455+ continue
4456+ desc = getattr(ie, 'IE_DESC', ie.IE_NAME)
4457+ if desc is False:
4458+ continue
4459+ if hasattr(ie, 'SEARCH_KEY'):
4460+ _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
4461+ _COUNTS = ('', '5', '10', 'all')
4462+ desc += ' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
4463+ compat_print(desc)
4464+ sys.exit(0)
4465+
4466+ # Conflicting, missing and erroneous options
4467+ if opts.usenetrc and (opts.username is not None or opts.password is not None):
4468+ parser.error('using .netrc conflicts with giving username/password')
4469+ if opts.password is not None and opts.username is None:
4470+ parser.error('account username missing\n')
4471+ if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
4472+ parser.error('using output template conflicts with using title, video ID or auto number')
4473+ if opts.usetitle and opts.useid:
4474+ parser.error('using title conflicts with using video ID')
4475+ if opts.username is not None and opts.password is None:
4476+ opts.password = compat_getpass('Type account password and press [Return]: ')
4477+ if opts.ratelimit is not None:
4478+ numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
4479+ if numeric_limit is None:
4480+ parser.error('invalid rate limit specified')
4481+ opts.ratelimit = numeric_limit
4482+ if opts.min_filesize is not None:
4483+ numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
4484+ if numeric_limit is None:
4485+ parser.error('invalid min_filesize specified')
4486+ opts.min_filesize = numeric_limit
4487+ if opts.max_filesize is not None:
4488+ numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
4489+ if numeric_limit is None:
4490+ parser.error('invalid max_filesize specified')
4491+ opts.max_filesize = numeric_limit
4492+ if opts.retries is not None:
4493+ if opts.retries in ('inf', 'infinite'):
4494+ opts_retries = float('inf')
4495+ else:
4496+ try:
4497+ opts_retries = int(opts.retries)
4498+ except (TypeError, ValueError):
4499+ parser.error('invalid retry count specified')
4500+ if opts.buffersize is not None:
4501+ numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
4502+ if numeric_buffersize is None:
4503+ parser.error('invalid buffer size specified')
4504+ opts.buffersize = numeric_buffersize
4505+ if opts.playliststart <= 0:
4506+ raise ValueError('Playlist start must be positive')
4507+ if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart:
4508+ raise ValueError('Playlist end must be greater than playlist start')
4509+ if opts.extractaudio:
4510+ if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
4511+ parser.error('invalid audio format specified')
4512+ if opts.audioquality:
4513+ opts.audioquality = opts.audioquality.strip('k').strip('K')
4514+ if not opts.audioquality.isdigit():
4515+ parser.error('invalid audio quality specified')
4516+ if opts.recodevideo is not None:
4517+ if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv', 'avi']:
4518+ parser.error('invalid video recode format specified')
4519+ if opts.convertsubtitles is not None:
4520+ if opts.convertsubtitles not in ['srt', 'vtt', 'ass']:
4521+ parser.error('invalid subtitle format specified')
4522+
4523+ if opts.date is not None:
4524+ date = DateRange.day(opts.date)
4525+ else:
4526+ date = DateRange(opts.dateafter, opts.datebefore)
4527+
4528+ # Do not download videos when there are audio-only formats
4529+ if opts.extractaudio and not opts.keepvideo and opts.format is None:
4530+ opts.format = 'bestaudio/best'
4531+
4532+ # --all-sub automatically sets --write-sub if --write-auto-sub is not given
4533+ # this was the old behaviour if only --all-sub was given.
4534+ if opts.allsubtitles and not opts.writeautomaticsub:
4535+ opts.writesubtitles = True
4536+
4537+ outtmpl = ((opts.outtmpl is not None and opts.outtmpl) or
4538+ (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s') or
4539+ (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s') or
4540+ (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s') or
4541+ (opts.usetitle and '%(title)s-%(id)s.%(ext)s') or
4542+ (opts.useid and '%(id)s.%(ext)s') or
4543+ (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s') or
4544+ DEFAULT_OUTTMPL)
4545+ if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
4546+ parser.error('Cannot download a video and extract audio into the same'
4547+ ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
4548+ ' template'.format(outtmpl))
4549+
4550+ any_getting = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json
4551+ any_printing = opts.print_json
4552+ download_archive_fn = compat_expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
4553+
4554+ # PostProcessors
4555+ postprocessors = []
4556+ # Add the metadata pp first, the other pps will copy it
4557+ if opts.metafromtitle:
4558+ postprocessors.append({
4559+ 'key': 'MetadataFromTitle',
4560+ 'titleformat': opts.metafromtitle
4561+ })
4562+ if opts.addmetadata:
4563+ postprocessors.append({'key': 'FFmpegMetadata'})
4564+ if opts.extractaudio:
4565+ postprocessors.append({
4566+ 'key': 'FFmpegExtractAudio',
4567+ 'preferredcodec': opts.audioformat,
4568+ 'preferredquality': opts.audioquality,
4569+ 'nopostoverwrites': opts.nopostoverwrites,
4570+ })
4571+ if opts.recodevideo:
4572+ postprocessors.append({
4573+ 'key': 'FFmpegVideoConvertor',
4574+ 'preferedformat': opts.recodevideo,
4575+ })
4576+ if opts.convertsubtitles:
4577+ postprocessors.append({
4578+ 'key': 'FFmpegSubtitlesConvertor',
4579+ 'format': opts.convertsubtitles,
4580+ })
4581+ if opts.embedsubtitles:
4582+ postprocessors.append({
4583+ 'key': 'FFmpegEmbedSubtitle',
4584+ })
4585+ if opts.xattrs:
4586+ postprocessors.append({'key': 'XAttrMetadata'})
4587+ if opts.embedthumbnail:
4588+ already_have_thumbnail = opts.writethumbnail or opts.write_all_thumbnails
4589+ postprocessors.append({
4590+ 'key': 'EmbedThumbnail',
4591+ 'already_have_thumbnail': already_have_thumbnail
4592+ })
4593+ if not already_have_thumbnail:
4594+ opts.writethumbnail = True
4595+ # Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
4596+ # So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
4597+ if opts.exec_cmd:
4598+ postprocessors.append({
4599+ 'key': 'ExecAfterDownload',
4600+ 'exec_cmd': opts.exec_cmd,
4601+ })
4602+ if opts.xattr_set_filesize:
4603+ try:
4604+ import xattr
4605+ xattr # Confuse flake8
4606+ except ImportError:
4607+ parser.error('setting filesize xattr requested but python-xattr is not available')
4608+ external_downloader_args = None
4609+ if opts.external_downloader_args:
4610+ external_downloader_args = shlex.split(opts.external_downloader_args)
4611+ postprocessor_args = None
4612+ if opts.postprocessor_args:
4613+ postprocessor_args = shlex.split(opts.postprocessor_args)
4614+ match_filter = (
4615+ None if opts.match_filter is None
4616+ else match_filter_func(opts.match_filter))
4617+
4618+ ydl_opts = {
4619+ 'usenetrc': opts.usenetrc,
4620+ 'username': opts.username,
4621+ 'password': opts.password,
4622+ 'twofactor': opts.twofactor,
4623+ 'videopassword': opts.videopassword,
4624+ 'quiet': (opts.quiet or any_getting or any_printing),
4625+ 'no_warnings': opts.no_warnings,
4626+ 'forceurl': opts.geturl,
4627+ 'forcetitle': opts.gettitle,
4628+ 'forceid': opts.getid,
4629+ 'forcethumbnail': opts.getthumbnail,
4630+ 'forcedescription': opts.getdescription,
4631+ 'forceduration': opts.getduration,
4632+ 'forcefilename': opts.getfilename,
4633+ 'forceformat': opts.getformat,
4634+ 'forcejson': opts.dumpjson or opts.print_json,
4635+ 'dump_single_json': opts.dump_single_json,
4636+ 'simulate': opts.simulate or any_getting,
4637+ 'skip_download': opts.skip_download,
4638+ 'format': opts.format,
4639+ 'listformats': opts.listformats,
4640+ 'outtmpl': outtmpl,
4641+ 'autonumber_size': opts.autonumber_size,
4642+ 'restrictfilenames': opts.restrictfilenames,
4643+ 'ignoreerrors': opts.ignoreerrors,
4644+ 'force_generic_extractor': opts.force_generic_extractor,
4645+ 'ratelimit': opts.ratelimit,
4646+ 'nooverwrites': opts.nooverwrites,
4647+ 'retries': opts_retries,
4648+ 'buffersize': opts.buffersize,
4649+ 'noresizebuffer': opts.noresizebuffer,
4650+ 'continuedl': opts.continue_dl,
4651+ 'noprogress': opts.noprogress,
4652+ 'progress_with_newline': opts.progress_with_newline,
4653+ 'playliststart': opts.playliststart,
4654+ 'playlistend': opts.playlistend,
4655+ 'playlistreverse': opts.playlist_reverse,
4656+ 'noplaylist': opts.noplaylist,
4657+ 'logtostderr': opts.outtmpl == '-',
4658+ 'consoletitle': opts.consoletitle,
4659+ 'nopart': opts.nopart,
4660+ 'updatetime': opts.updatetime,
4661+ 'writedescription': opts.writedescription,
4662+ 'writeannotations': opts.writeannotations,
4663+ 'writeinfojson': opts.writeinfojson,
4664+ 'writethumbnail': opts.writethumbnail,
4665+ 'write_all_thumbnails': opts.write_all_thumbnails,
4666+ 'writesubtitles': opts.writesubtitles,
4667+ 'writeautomaticsub': opts.writeautomaticsub,
4668+ 'allsubtitles': opts.allsubtitles,
4669+ 'listsubtitles': opts.listsubtitles,
4670+ 'subtitlesformat': opts.subtitlesformat,
4671+ 'subtitleslangs': opts.subtitleslangs,
4672+ 'matchtitle': decodeOption(opts.matchtitle),
4673+ 'rejecttitle': decodeOption(opts.rejecttitle),
4674+ 'max_downloads': opts.max_downloads,
4675+ 'prefer_free_formats': opts.prefer_free_formats,
4676+ 'verbose': opts.verbose,
4677+ 'dump_intermediate_pages': opts.dump_intermediate_pages,
4678+ 'write_pages': opts.write_pages,
4679+ 'test': opts.test,
4680+ 'keepvideo': opts.keepvideo,
4681+ 'min_filesize': opts.min_filesize,
4682+ 'max_filesize': opts.max_filesize,
4683+ 'min_views': opts.min_views,
4684+ 'max_views': opts.max_views,
4685+ 'daterange': date,
4686+ 'cachedir': opts.cachedir,
4687+ 'youtube_print_sig_code': opts.youtube_print_sig_code,
4688+ 'age_limit': opts.age_limit,
4689+ 'download_archive': download_archive_fn,
4690+ 'cookiefile': opts.cookiefile,
4691+ 'nocheckcertificate': opts.no_check_certificate,
4692+ 'prefer_insecure': opts.prefer_insecure,
4693+ 'proxy': opts.proxy,
4694+ 'socket_timeout': opts.socket_timeout,
4695+ 'bidi_workaround': opts.bidi_workaround,
4696+ 'debug_printtraffic': opts.debug_printtraffic,
4697+ 'prefer_ffmpeg': opts.prefer_ffmpeg,
4698+ 'include_ads': opts.include_ads,
4699+ 'default_search': opts.default_search,
4700+ 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
4701+ 'encoding': opts.encoding,
4702+ 'extract_flat': opts.extract_flat,
4703+ 'merge_output_format': opts.merge_output_format,
4704+ 'postprocessors': postprocessors,
4705+ 'fixup': opts.fixup,
4706+ 'source_address': opts.source_address,
4707+ 'call_home': opts.call_home,
4708+ 'sleep_interval': opts.sleep_interval,
4709+ 'external_downloader': opts.external_downloader,
4710+ 'list_thumbnails': opts.list_thumbnails,
4711+ 'playlist_items': opts.playlist_items,
4712+ 'xattr_set_filesize': opts.xattr_set_filesize,
4713+ 'match_filter': match_filter,
4714+ 'no_color': opts.no_color,
4715+ 'ffmpeg_location': opts.ffmpeg_location,
4716+ 'hls_prefer_native': opts.hls_prefer_native,
4717+ 'external_downloader_args': external_downloader_args,
4718+ 'postprocessor_args': postprocessor_args,
4719+ 'cn_verification_proxy': opts.cn_verification_proxy,
4720+ }
4721+
4722+ with YoutubeDL(ydl_opts) as ydl:
4723+ # Update version
4724+ if opts.update_self:
4725+ update_self(ydl.to_screen, opts.verbose)
4726+
4727+ # Remove cache dir
4728+ if opts.rm_cachedir:
4729+ ydl.cache.remove()
4730+
4731+ # Maybe do nothing
4732+ if (len(all_urls) < 1) and (opts.load_info_filename is None):
4733+ if opts.update_self or opts.rm_cachedir:
4734+ sys.exit()
4735+
4736+ ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
4737+ parser.error(
4738+ 'You must provide at least one URL.\n'
4739+ 'Type youtube-dl --help to see a list of all options.')
4740+
4741+ try:
4742+ if opts.load_info_filename is not None:
4743+ retcode = ydl.download_with_info_file(opts.load_info_filename)
4744+ else:
4745+ retcode = ydl.download(all_urls)
4746+ except MaxDownloadsReached:
4747+ ydl.to_screen('--max-download limit reached, aborting.')
4748+ retcode = 101
4749+
4750+ sys.exit(retcode)
4751+
4752+
4753+def main(argv=None):
4754+ try:
4755+ _real_main(argv)
4756+ except DownloadError:
4757+ sys.exit(1)
4758+ except SameFileError:
4759+ sys.exit('ERROR: fixed output name but more than one file to download')
4760+ except KeyboardInterrupt:
4761+ sys.exit('\nERROR: Interrupted by user')
4762+
4763+__all__ = ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']
4764
4765=== added file 'openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__main__.py'
4766--- openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__main__.py 1970-01-01 00:00:00 +0000
4767+++ openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/__main__.py 2015-09-23 12:21:33 +0000
4768@@ -0,0 +1,19 @@
4769+#!/usr/bin/env python
4770+from __future__ import unicode_literals
4771+
4772+# Execute with
4773+# $ python youtube_dl/__main__.py (2.6+)
4774+# $ python -m youtube_dl (2.7+)
4775+
4776+import sys
4777+
4778+if __package__ is None and not hasattr(sys, "frozen"):
4779+ # direct call of __main__.py
4780+ import os.path
4781+ path = os.path.realpath(os.path.abspath(__file__))
4782+ sys.path.append(os.path.dirname(os.path.dirname(path)))
4783+
4784+import youtube_dl
4785+
4786+if __name__ == '__main__':
4787+ youtube_dl.main()
4788
4789=== added file 'openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/aes.py'
4790--- openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/aes.py 1970-01-01 00:00:00 +0000
4791+++ openlp/plugins/planningcenter/lib/youtube-dl/youtube_dl/aes.py 2015-09-23 12:21:33 +0000
4792@@ -0,0 +1,331 @@
4793+from __future__ import unicode_literals
4794+
4795+import base64
4796+from math import ceil
4797+
4798+from .utils import bytes_to_intlist, intlist_to_bytes
4799+
4800+BLOCK_SIZE_BYTES = 16
4801+
4802+
4803+def aes_ctr_decrypt(data, key, counter):
4804+ """
4805+ Decrypt with aes in counter mode
4806+
4807+ @param {int[]} data cipher
4808+ @param {int[]} key 16/24/32-Byte cipher key
4809+ @param {instance} counter Instance whose next_value function (@returns {int[]} 16-Byte block)
4810+ returns the next counter block
4811+ @returns {int[]} decrypted data
4812+ """
4813+ expanded_key = key_expansion(key)
4814+ block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES))
4815+
4816+ decrypted_data = []
4817+ for i in range(block_count):
4818+ counter_block = counter.next_value()
4819+ block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
4820+ block += [0] * (BLOCK_SIZE_BYTES - len(block))
4821+
4822+ cipher_counter_block = aes_encrypt(counter_block, expanded_key)
4823+ decrypted_data += xor(block, cipher_counter_block)
4824+ decrypted_data = decrypted_data[:len(data)]
4825+
4826+ return decrypted_data
4827+
4828+
4829+def aes_cbc_decrypt(data, key, iv):
4830+ """
4831+ Decrypt with aes in CBC mode
4832+
4833+ @param {int[]} data cipher
4834+ @param {int[]} key 16/24/32-Byte cipher key
4835+ @param {int[]} iv 16-Byte IV
4836+ @returns {int[]} decrypted data
4837+ """
4838+ expanded_key = key_expansion(key)
4839+ block_count = int(ceil(float(len(data)) / BLOCK_SIZE_BYTES))
4840+
4841+ decrypted_data = []
4842+ previous_cipher_block = iv
4843+ for i in range(block_count):
4844+ block = data[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES]
4845+ block += [0] * (BLOCK_SIZE_BYTES - len(block))
4846+
4847+ decrypted_block = aes_decrypt(block, expanded_key)
4848+ decrypted_data += xor(decrypted_block, previous_cipher_block)
4849+ previous_cipher_block = block
4850+ decrypted_data = decrypted_data[:len(data)]
4851+
4852+ return decrypted_data
4853+
4854+
4855+def key_expansion(data):
4856+ """
4857+ Generate key schedule
4858+
4859+ @param {int[]} data 16/24/32-Byte cipher key
4860+ @returns {int[]} 176/208/240-Byte expanded key
4861+ """
4862+ data = data[:] # copy
4863+ rcon_iteration = 1
4864+ key_size_bytes = len(data)
4865+ expanded_key_size_bytes = (key_size_bytes // 4 + 7) * BLOCK_SIZE_BYTES
4866+
4867+ while len(data) < expanded_key_size_bytes:
4868+ temp = data[-4:]
4869+ temp = key_schedule_core(temp, rcon_iteration)
4870+ rcon_iteration += 1
4871+ data += xor(temp, data[-key_size_bytes: 4 - key_size_bytes])
4872+
4873+ for _ in range(3):
4874+ temp = data[-4:]
4875+ data += xor(temp, data[-key_size_bytes: 4 - key_size_bytes])
4876+
4877+ if key_size_bytes == 32:
4878+ temp = data[-4:]
4879+ temp = sub_bytes(temp)
4880+ data += xor(temp, data[-key_size_bytes: 4 - key_size_bytes])
4881+
4882+ for _ in range(3 if key_size_bytes == 32 else 2 if key_size_bytes == 24 else 0):
4883+ temp = data[-4:]
4884+ data += xor(temp, data[-key_size_bytes: 4 - key_size_bytes])
4885+ data = data[:expanded_key_size_bytes]
4886+
4887+ return data
4888+
4889+
4890+def aes_encrypt(data, expanded_key):
4891+ """
4892+ Encrypt one block with aes
4893+
4894+ @param {int[]} data 16-Byte state
4895+ @param {int[]} expanded_key 176/208/240-Byte expanded key
4896+ @returns {int[]} 16-Byte cipher
4897+ """
4898+ rounds = len(expanded_key) // BLOCK_SIZE_BYTES - 1
4899+
4900+ data = xor(data, expanded_key[:BLOCK_SIZE_BYTES])
4901+ for i in range(1, rounds + 1):
4902+ data = sub_bytes(data)
4903+ data = shift_rows(data)
4904+ if i != rounds:
4905+ data = mix_columns(data)
4906+ data = xor(data, expanded_key[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES])
4907+
4908+ return data
4909+
4910+
4911+def aes_decrypt(data, expanded_key):
4912+ """
4913+ Decrypt one block with aes
4914+
4915+ @param {int[]} data 16-Byte cipher
4916+ @param {int[]} expanded_key 176/208/240-Byte expanded key
4917+ @returns {int[]} 16-Byte state
4918+ """
4919+ rounds = len(expanded_key) // BLOCK_SIZE_BYTES - 1
4920+
4921+ for i in range(rounds, 0, -1):
4922+ data = xor(data, expanded_key[i * BLOCK_SIZE_BYTES: (i + 1) * BLOCK_SIZE_BYTES])
4923+ if i != rounds:
4924+ data = mix_columns_inv(data)
4925+ data = shift_rows_inv(data)
4926+ data = sub_bytes_inv(data)
4927+ data = xor(data, expanded_key[:BLOCK_SIZE_BYTES])
4928+
4929+ return data
4930+
4931+
4932+def aes_decrypt_text(data, password, key_size_bytes):
4933+ """
4934+ Decrypt text
4935+ - The first 8 Bytes of decoded 'data' are the 8 high Bytes of the counter
4936+ - The cipher key is retrieved by encrypting the first 16 Byte of 'password'
4937+ with the first 'key_size_bytes' Bytes from 'password' (if necessary filled with 0's)
4938+ - Mode of operation is 'counter'
4939+
4940+ @param {str} data Base64 encoded string
4941+ @param {str,unicode} password Password (will be encoded with utf-8)
4942+ @param {int} key_size_bytes Possible values: 16 for 128-Bit, 24 for 192-Bit or 32 for 256-Bit
4943+ @returns {str} Decrypted data
4944+ """
4945+ NONCE_LENGTH_BYTES = 8
4946+
4947+ data = bytes_to_intlist(base64.b64decode(data.encode('utf-8')))
4948+ password = bytes_to_intlist(password.encode('utf-8'))
4949+
4950+ key = password[:key_size_bytes] + [0] * (key_size_bytes - len(password))
4951+ key = aes_encrypt(key[:BLOCK_SIZE_BYTES], key_expansion(key)) * (key_size_bytes // BLOCK_SIZE_BYTES)
4952+
4953+ nonce = data[:NONCE_LENGTH_BYTES]
4954+ cipher = data[NONCE_LENGTH_BYTES:]
4955+
4956+ class Counter:
4957+ __value = nonce + [0] * (BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES)
4958+
4959+ def next_value(self):
4960+ temp = self.__value
4961+ self.__value = inc(self.__value)
4962+ return temp
4963+
4964+ decrypted_data = aes_ctr_decrypt(cipher, key, Counter())
4965+ plaintext = intlist_to_bytes(decrypted_data)
4966+
4967+ return plaintext
4968+
4969+RCON = (0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36)
4970+SBOX = (0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
4971+ 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
4972+ 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
4973+ 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
4974+ 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
4975+ 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
4976+ 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
4977+ 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
4978+ 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
4979+ 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
4980+ 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
4981+ 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
4982+ 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
4983+ 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
4984+ 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
4985+ 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16)
4986+SBOX_INV = (0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
4987+ 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
4988+ 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
4989+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
4990+ 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
4991+ 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
4992+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
4993+ 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
4994+ 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
4995+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
4996+ 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
4997+ 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
4998+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
4999+ 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
5000+ 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
The diff has been truncated for viewing.