├── test
├── __init__.py
├── swftests
│ ├── .gitignore
│ ├── EqualsOperator.as
│ ├── DictCall.as
│ ├── ConstantInt.as
│ ├── StringBasics.as
│ ├── StringConversion.as
│ ├── StaticAssignment.as
│ ├── ClassConstruction.as
│ ├── StringCharCodeAt.as
│ ├── LocalVars.as
│ ├── StaticRetrieval.as
│ ├── ClassCall.as
│ ├── PrivateCall.as
│ ├── ConstArrayAccess.as
│ ├── PrivateVoidCall.as
│ ├── MemberAssignment.as
│ ├── ArrayAccess.as
│ └── NeOperator.as
├── test_postprocessors.py
├── test_netrc.py
├── test_update.py
├── parameters.json
├── test_iqiyi_sdk_interpreter.py
├── test_execution.py
├── test_age_restriction.py
├── versions.json
└── test_cache.py
├── docs
├── .gitignore
└── index.rst
├── youtube_dl
├── version.py
├── __main__.py
├── extractor
│ ├── embedly.py
│ ├── engadget.py
│ ├── lci.py
│ ├── m6.py
│ ├── anitube.py
│ ├── myvidster.py
│ ├── ku6.py
│ ├── videodetective.py
│ ├── cliprs.py
│ ├── cbssports.py
│ ├── ina.py
│ ├── ebaumsworld.py
│ ├── makertv.py
│ ├── formula1.py
│ ├── teachingchannel.py
│ ├── skysports.py
│ ├── tutv.py
│ ├── people.py
│ ├── cnbc.py
│ ├── nerdcubed.py
│ ├── savefrom.py
│ ├── gameinformer.py
│ ├── learnr.py
│ ├── commonprotocols.py
│ ├── fusion.py
│ ├── nuevo.py
│ ├── lovehomeporn.py
│ ├── gputechconf.py
│ ├── freevideo.py
│ ├── restudy.py
│ ├── hentaistigma.py
│ ├── tvland.py
│ ├── freespeech.py
│ ├── sonyliv.py
│ ├── defense.py
│ ├── rottentomatoes.py
│ ├── youjizz.py
│ ├── aljazeera.py
│ ├── __init__.py
│ ├── criterion.py
│ ├── macgamestore.py
│ ├── dropbox.py
│ ├── keek.py
│ ├── dreisat.py
│ ├── hark.py
│ ├── foxsports.py
│ ├── echomsk.py
│ ├── telequebec.py
│ ├── lemonde.py
│ ├── spike.py
│ ├── bild.py
│ ├── screencastomatic.py
│ ├── helsinki.py
│ ├── howcast.py
│ ├── thestar.py
│ ├── freesound.py
│ ├── vodplatform.py
│ ├── academicearth.py
│ ├── biqle.py
│ ├── slutload.py
│ ├── commonmistakes.py
│ ├── sportschau.py
│ ├── moviezine.py
│ ├── newgrounds.py
│ ├── reverbnation.py
│ ├── fczenit.py
│ ├── ro220.py
│ ├── xbef.py
│ ├── miaopai.py
│ ├── nintendo.py
│ ├── vidzi.py
│ ├── tv3.py
│ ├── ehow.py
│ ├── phoenix.py
│ ├── ruleporn.py
│ ├── odatv.py
│ ├── oktoberfesttv.py
│ ├── parliamentliveuk.py
│ ├── glide.py
│ ├── thisamericanlife.py
│ ├── ruhd.py
│ ├── videopremium.py
│ ├── makerschannel.py
│ ├── goshgay.py
│ ├── rmcdecouverte.py
│ ├── yourupload.py
│ ├── fktv.py
│ ├── historicfilms.py
│ ├── xnxx.py
│ ├── rudo.py
│ ├── streetvoice.py
│ ├── tlc.py
│ ├── sztvhu.py
│ ├── behindkink.py
│ ├── tvnoe.py
│ ├── footyroom.py
│ ├── weiqitv.py
│ ├── ir90tv.py
│ ├── cinchcast.py
│ ├── thescene.py
│ ├── localnews8.py
│ ├── charlierose.py
│ ├── morningstar.py
│ └── kankan.py
├── postprocessor
│ ├── execafterdownload.py
│ ├── __init__.py
│ └── metadatafromtitle.py
└── downloader
│ ├── __init__.py
│ └── rtsp.py
├── devscripts
├── SizeOfImage.patch
├── SizeOfImage_w.patch
├── fish-completion.in
├── posix-locale.sh
├── lazy_load_template.py
├── gh-pages
│ ├── update-copyright.py
│ ├── sign-versions.py
│ ├── generate-download.py
│ ├── update-sites.py
│ └── add-version.py
├── make_readme.py
├── zsh-completion.in
├── make_issue_template.py
├── make_contributing.py
├── bash-completion.in
├── bash-completion.py
├── generate_aes_testdata.py
├── make_supportedsites.py
├── show-downloads-statistics.py
├── zsh-completion.py
└── fish-completion.py
├── bin
└── youtube-dl
├── MANIFEST.in
├── setup.cfg
├── .travis.yml
├── .gitignore
├── tox.ini
├── youtube-dl.plugin.zsh
├── .github
└── PULL_REQUEST_TEMPLATE.md
└── LICENSE
/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _build/
2 |
--------------------------------------------------------------------------------
/test/swftests/.gitignore:
--------------------------------------------------------------------------------
1 | *.swf
2 |
--------------------------------------------------------------------------------
/youtube_dl/version.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | __version__ = '2016.09.19'
4 |
--------------------------------------------------------------------------------
/devscripts/SizeOfImage.patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey-wenjun/youtube-dl/master/devscripts/SizeOfImage.patch
--------------------------------------------------------------------------------
/devscripts/SizeOfImage_w.patch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey-wenjun/youtube-dl/master/devscripts/SizeOfImage_w.patch
--------------------------------------------------------------------------------
/bin/youtube-dl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import youtube_dl
4 |
5 | if __name__ == '__main__':
6 | youtube_dl.main()
7 |
--------------------------------------------------------------------------------
/devscripts/fish-completion.in:
--------------------------------------------------------------------------------
1 |
2 | {{commands}}
3 |
4 |
5 | complete --command youtube-dl --arguments ":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater :ythistory"
6 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include test/*.py
3 | include test/*.json
4 | include youtube-dl.bash-completion
5 | include youtube-dl.fish
6 | include youtube-dl.1
7 | recursive-include docs Makefile conf.py *.rst
8 |
--------------------------------------------------------------------------------
/devscripts/posix-locale.sh:
--------------------------------------------------------------------------------
1 |
2 | # source this file in your shell to get a POSIX locale (which will break many programs, but that's kind of the point)
3 |
4 | export LC_ALL=POSIX
5 | export LANG=POSIX
6 | export LANGUAGE=POSIX
7 |
--------------------------------------------------------------------------------
/test/swftests/EqualsOperator.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: false
3 |
4 | package {
5 | public class EqualsOperator {
6 | public static function main():Boolean{
7 | return 1 == 2;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/swftests/DictCall.as:
--------------------------------------------------------------------------------
1 | // input: [{"x": 1, "y": 2}]
2 | // output: 3
3 |
4 | package {
5 | public class DictCall {
6 | public static function main(d:Object):int{
7 | return d.x + d.y;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = True
3 |
4 | [flake8]
5 | exclude = youtube_dl/extractor/__init__.py,devscripts/buildserver.py,devscripts/lazy_load_template.py,devscripts/make_issue_template.py,setup.py,build,.git
6 | ignore = E402,E501,E731
7 |
--------------------------------------------------------------------------------
/test/swftests/ConstantInt.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 2
3 |
4 | package {
5 | public class ConstantInt {
6 | private static const x:int = 2;
7 |
8 | public static function main():int{
9 | return x;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/swftests/StringBasics.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 3
3 |
4 | package {
5 | public class StringBasics {
6 | public static function main():int{
7 | var s:String = "abc";
8 | return s.length;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/swftests/StringConversion.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 2
3 |
4 | package {
5 | public class StringConversion {
6 | public static function main():int{
7 | var s:String = String(99);
8 | return s.length;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/swftests/StaticAssignment.as:
--------------------------------------------------------------------------------
1 | // input: [1]
2 | // output: 1
3 |
4 | package {
5 | public class StaticAssignment {
6 | public static var v:int;
7 |
8 | public static function main(a:int):int{
9 | v = a;
10 | return v;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/swftests/ClassConstruction.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 0
3 |
4 | package {
5 | public class ClassConstruction {
6 | public static function main():int{
7 | var f:Foo = new Foo();
8 | return 0;
9 | }
10 | }
11 | }
12 |
13 | class Foo {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/test/swftests/StringCharCodeAt.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 9897
3 |
4 | package {
5 | public class StringCharCodeAt {
6 | public static function main():int{
7 | var s:String = "abc";
8 | return s.charCodeAt(1) * 100 + s.charCodeAt();
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/swftests/LocalVars.as:
--------------------------------------------------------------------------------
1 | // input: [1, 2]
2 | // output: 3
3 |
4 | package {
5 | public class LocalVars {
6 | public static function main(a:int, b:int):int{
7 | var c:int = a + b + b;
8 | var d:int = c - b;
9 | var e:int = d;
10 | return e;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/swftests/StaticRetrieval.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 1
3 |
4 | package {
5 | public class StaticRetrieval {
6 | public static var v:int;
7 |
8 | public static function main():int{
9 | if (v) {
10 | return 0;
11 | } else {
12 | return 1;
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/swftests/ClassCall.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 121
3 |
4 | package {
5 | public class ClassCall {
6 | public static function main():int{
7 | var f:OtherClass = new OtherClass();
8 | return f.func(100,20);
9 | }
10 | }
11 | }
12 |
13 | class OtherClass {
14 | public function func(x: int, y: int):int {
15 | return x+y+1;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.6"
4 | - "2.7"
5 | - "3.2"
6 | - "3.3"
7 | - "3.4"
8 | - "3.5"
9 | sudo: false
10 | script: nosetests test --verbose
11 | notifications:
12 | email:
13 | - filippo.valsorda@gmail.com
14 | - yasoob.khld@gmail.com
15 | # irc:
16 | # channels:
17 | # - "irc.freenode.org#youtube-dl"
18 | # skip_join: true
19 |
--------------------------------------------------------------------------------
/test/swftests/PrivateCall.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 9
3 |
4 | package {
5 | public class PrivateCall {
6 | public static function main():int{
7 | var f:OtherClass = new OtherClass();
8 | return f.func();
9 | }
10 | }
11 | }
12 |
13 | class OtherClass {
14 | private function pf():int {
15 | return 9;
16 | }
17 |
18 | public function func():int {
19 | return this.pf();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/swftests/ConstArrayAccess.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 4
3 |
4 | package {
5 | public class ConstArrayAccess {
6 | private static const x:int = 2;
7 | private static const ar:Array = ["42", "3411"];
8 |
9 | public static function main():int{
10 | var c:ConstArrayAccess = new ConstArrayAccess();
11 | return c.f();
12 | }
13 |
14 | public function f(): int {
15 | return ar[1].length;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/swftests/PrivateVoidCall.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 9
3 |
4 | package {
5 | public class PrivateVoidCall {
6 | public static function main():int{
7 | var f:OtherClass = new OtherClass();
8 | f.func();
9 | return 9;
10 | }
11 | }
12 | }
13 |
14 | class OtherClass {
15 | private function pf():void {
16 | ;
17 | }
18 |
19 | public function func():void {
20 | this.pf();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/swftests/MemberAssignment.as:
--------------------------------------------------------------------------------
1 | // input: [1]
2 | // output: 2
3 |
4 | package {
5 | public class MemberAssignment {
6 | public var v:int;
7 |
8 | public function g():int {
9 | return this.v;
10 | }
11 |
12 | public function f(a:int):int{
13 | this.v = a;
14 | return this.v + this.g();
15 | }
16 |
17 | public static function main(a:int): int {
18 | var v:MemberAssignment = new MemberAssignment();
19 | return v.f(a);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/swftests/ArrayAccess.as:
--------------------------------------------------------------------------------
1 | // input: [["a", "b", "c", "d"]]
2 | // output: ["c", "b", "a", "d"]
3 |
4 | package {
5 | public class ArrayAccess {
6 | public static function main(ar:Array):Array {
7 | var aa:ArrayAccess = new ArrayAccess();
8 | return aa.f(ar, 2);
9 | }
10 |
11 | private function f(ar:Array, num:Number):Array{
12 | var x:String = ar[0];
13 | var y:String = ar[num % ar.length];
14 | ar[0] = y;
15 | ar[num] = x;
16 | return ar;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/swftests/NeOperator.as:
--------------------------------------------------------------------------------
1 | // input: []
2 | // output: 123
3 |
4 | package {
5 | public class NeOperator {
6 | public static function main(): int {
7 | var res:int = 0;
8 | if (1 != 2) {
9 | res += 3;
10 | } else {
11 | res += 4;
12 | }
13 | if (2 != 2) {
14 | res += 10;
15 | } else {
16 | res += 20;
17 | }
18 | if (9 == 9) {
19 | res += 100;
20 | }
21 | return res;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/devscripts/lazy_load_template.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | import re
5 |
6 |
7 | class LazyLoadExtractor(object):
8 | _module = None
9 |
10 | @classmethod
11 | def ie_key(cls):
12 | return cls.__name__[:-2]
13 |
14 | def __new__(cls, *args, **kwargs):
15 | mod = __import__(cls._module, fromlist=(cls.__name__,))
16 | real_cls = getattr(mod, cls.__name__)
17 | instance = real_cls.__new__(real_cls)
18 | instance.__init__(*args, **kwargs)
19 | return instance
20 |
--------------------------------------------------------------------------------
/youtube_dl/__main__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import unicode_literals
3 |
4 | # Execute with
5 | # $ python youtube_dl/__main__.py (2.6+)
6 | # $ python -m youtube_dl (2.7+)
7 |
8 | import sys
9 |
10 | if __package__ is None and not hasattr(sys, 'frozen'):
11 | # direct call of __main__.py
12 | import os.path
13 | path = os.path.realpath(os.path.abspath(__file__))
14 | sys.path.insert(0, os.path.dirname(os.path.dirname(path)))
15 |
16 | import youtube_dl
17 |
18 | if __name__ == '__main__':
19 | youtube_dl.main()
20 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to youtube-dl's documentation!
2 | ======================================
3 |
4 | *youtube-dl* is a command-line program to download videos from YouTube.com and more sites.
5 | It can also be used in Python code.
6 |
7 | Developer guide
8 | ---------------
9 |
10 | This section contains information for using *youtube-dl* from Python programs.
11 |
12 | .. toctree::
13 | :maxdepth: 2
14 |
15 | module_guide
16 |
17 | Indices and tables
18 | ==================
19 |
20 | * :ref:`genindex`
21 | * :ref:`modindex`
22 | * :ref:`search`
23 |
24 |
--------------------------------------------------------------------------------
/test/test_postprocessors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import unicode_literals
4 |
5 | # Allow direct execution
6 | import os
7 | import sys
8 | import unittest
9 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10 |
11 | from youtube_dl.postprocessor import MetadataFromTitlePP
12 |
13 |
14 | class TestMetadataFromTitle(unittest.TestCase):
15 | def test_format_to_regex(self):
16 | pp = MetadataFromTitlePP(None, '%(title)s - %(artist)s')
17 | self.assertEqual(pp._titleregex, '(?P
.+)\ \-\ (?P.+)')
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | *.class
4 | *~
5 | *.DS_Store
6 | wine-py2exe/
7 | py2exe.log
8 | *.kate-swp
9 | build/
10 | dist/
11 | MANIFEST
12 | README.txt
13 | youtube-dl.1
14 | youtube-dl.bash-completion
15 | youtube-dl.fish
16 | youtube_dl/extractor/lazy_extractors.py
17 | youtube-dl
18 | youtube-dl.exe
19 | youtube-dl.tar.gz
20 | .coverage
21 | cover/
22 | updates_key.pem
23 | *.egg-info
24 | *.srt
25 | *.sbv
26 | *.vtt
27 | *.flv
28 | *.mp4
29 | *.m4a
30 | *.m4v
31 | *.mp3
32 | *.part
33 | *.swp
34 | test/testdata
35 | test/local_parameters.json
36 | .tox
37 | youtube-dl.zsh
38 |
39 | # IntelliJ related files
40 | .idea
41 | *.iml
42 |
43 | tmp/
44 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py26,py27,py33,py34,py35
3 | [testenv]
4 | deps =
5 | nose
6 | coverage
7 | # We need a valid $HOME for test_compat_expanduser
8 | passenv = HOME
9 | defaultargs = test --exclude test_download.py --exclude test_age_restriction.py
10 | --exclude test_subtitles.py --exclude test_write_annotations.py
11 | --exclude test_youtube_lists.py --exclude test_iqiyi_sdk_interpreter.py
12 | --exclude test_socks.py
13 | commands = nosetests --verbose {posargs:{[testenv]defaultargs}} # --with-coverage --cover-package=youtube_dl --cover-html
14 | # test.test_download:TestDownload.test_NowVideo
15 |
--------------------------------------------------------------------------------
/devscripts/gh-pages/update-copyright.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 |
4 | from __future__ import with_statement, unicode_literals
5 |
6 | import datetime
7 | import glob
8 | import io # For Python 2 compatibility
9 | import os
10 | import re
11 |
12 | year = str(datetime.datetime.now().year)
13 | for fn in glob.glob('*.html*'):
14 | with io.open(fn, encoding='utf-8') as f:
15 | content = f.read()
16 | newc = re.sub(r'(?PCopyright © 2006-)(?P[0-9]{4})', 'Copyright © 2006-' + year, content)
17 | if content != newc:
18 | tmpFn = fn + '.part'
19 | with io.open(tmpFn, 'wt', encoding='utf-8') as outf:
20 | outf.write(newc)
21 | os.rename(tmpFn, fn)
22 |
--------------------------------------------------------------------------------
/test/test_netrc.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | import os
5 | import sys
6 | import unittest
7 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8 |
9 |
10 | from youtube_dl.extractor import (
11 | gen_extractors,
12 | )
13 |
14 |
15 | class TestNetRc(unittest.TestCase):
16 | def test_netrc_present(self):
17 | for ie in gen_extractors():
18 | if not hasattr(ie, '_login'):
19 | continue
20 | self.assertTrue(
21 | hasattr(ie, '_NETRC_MACHINE'),
22 | 'Extractor %s supports login, but is missing a _NETRC_MACHINE property' % ie.IE_NAME)
23 |
24 |
25 | if __name__ == '__main__':
26 | unittest.main()
27 |
--------------------------------------------------------------------------------
/devscripts/make_readme.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import io
4 | import sys
5 | import re
6 |
7 | README_FILE = 'README.md'
8 | helptext = sys.stdin.read()
9 |
10 | if isinstance(helptext, bytes):
11 | helptext = helptext.decode('utf-8')
12 |
13 | with io.open(README_FILE, encoding='utf-8') as f:
14 | oldreadme = f.read()
15 |
16 | header = oldreadme[:oldreadme.index('# OPTIONS')]
17 | footer = oldreadme[oldreadme.index('# CONFIGURATION'):]
18 |
19 | options = helptext[helptext.index(' General Options:') + 19:]
20 | options = re.sub(r'(?m)^ (\w.+)$', r'## \1', options)
21 | options = '# OPTIONS\n' + options + '\n'
22 |
23 | with io.open(README_FILE, 'w', encoding='utf-8') as f:
24 | f.write(header)
25 | f.write(options)
26 | f.write(footer)
27 |
--------------------------------------------------------------------------------
/devscripts/zsh-completion.in:
--------------------------------------------------------------------------------
1 | #compdef youtube-dl
2 |
3 | __youtube_dl() {
4 | local curcontext="$curcontext" fileopts diropts cur prev
5 | typeset -A opt_args
6 | fileopts="{{fileopts}}"
7 | diropts="{{diropts}}"
8 | cur=$words[CURRENT]
9 | case $cur in
10 | :)
11 | _arguments '*: :(::ytfavorites ::ytrecommended ::ytsubscriptions ::ytwatchlater ::ythistory)'
12 | ;;
13 | *)
14 | prev=$words[CURRENT-1]
15 | if [[ ${prev} =~ ${fileopts} ]]; then
16 | _path_files
17 | elif [[ ${prev} =~ ${diropts} ]]; then
18 | _path_files -/
19 | elif [[ ${prev} == "--recode-video" ]]; then
20 | _arguments '*: :(mp4 flv ogg webm mkv)'
21 | else
22 | _arguments '*: :({{flags}})'
23 | fi
24 | ;;
25 | esac
26 | }
27 |
28 | __youtube_dl
--------------------------------------------------------------------------------
/youtube_dl/extractor/embedly.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 | from ..compat import compat_urllib_parse_unquote
6 |
7 |
8 | class EmbedlyIE(InfoExtractor):
9 | _VALID_URL = r'https?://(?:www|cdn\.)?embedly\.com/widgets/media\.html\?(?:[^#]*?&)?url=(?P[^#&]+)'
10 | _TESTS = [{
11 | 'url': 'https://cdn.embedly.com/widgets/media.html?src=http%3A%2F%2Fwww.youtube.com%2Fembed%2Fvideoseries%3Flist%3DUUGLim4T2loE5rwCMdpCIPVg&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DSU4fj_aEMVw%26list%3DUUGLim4T2loE5rwCMdpCIPVg&image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FSU4fj_aEMVw%2Fhqdefault.jpg&key=8ee8a2e6a8cc47aab1a5ee67f9a178e0&type=text%2Fhtml&schema=youtube&autoplay=1',
12 | 'only_matching': True,
13 | }]
14 |
15 | def _real_extract(self, url):
16 | return self.url_result(compat_urllib_parse_unquote(self._match_id(url)))
17 |
--------------------------------------------------------------------------------
/devscripts/make_issue_template.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import unicode_literals
3 |
4 | import io
5 | import optparse
6 |
7 |
8 | def main():
9 | parser = optparse.OptionParser(usage='%prog INFILE OUTFILE')
10 | options, args = parser.parse_args()
11 | if len(args) != 2:
12 | parser.error('Expected an input and an output filename')
13 |
14 | infile, outfile = args
15 |
16 | with io.open(infile, encoding='utf-8') as inf:
17 | issue_template_tmpl = inf.read()
18 |
19 | # Get the version from youtube_dl/version.py without importing the package
20 | exec(compile(open('youtube_dl/version.py').read(),
21 | 'youtube_dl/version.py', 'exec'))
22 |
23 | out = issue_template_tmpl % {'version': locals()['__version__']}
24 |
25 | with io.open(outfile, 'w', encoding='utf-8') as outf:
26 | outf.write(out)
27 |
28 | if __name__ == '__main__':
29 | main()
30 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/engadget.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 |
5 |
6 | class EngadgetIE(InfoExtractor):
7 | _VALID_URL = r'https?://(?:www\.)?engadget\.com/video/(?P[^/?#]+)'
8 |
9 | _TESTS = [{
10 | # video with 5min ID
11 | 'url': 'http://www.engadget.com/video/518153925/',
12 | 'md5': 'c6820d4828a5064447a4d9fc73f312c9',
13 | 'info_dict': {
14 | 'id': '518153925',
15 | 'ext': 'mp4',
16 | 'title': 'Samsung Galaxy Tab Pro 8.4 Review',
17 | },
18 | 'add_ie': ['FiveMin'],
19 | }, {
20 | # video with vidible ID
21 | 'url': 'https://www.engadget.com/video/57a28462134aa15a39f0421a/',
22 | 'only_matching': True,
23 | }]
24 |
25 | def _real_extract(self, url):
26 | video_id = self._match_id(url)
27 | return self.url_result('aol-video:%s' % video_id)
28 |
--------------------------------------------------------------------------------
/devscripts/make_contributing.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import unicode_literals
3 |
4 | import io
5 | import optparse
6 | import re
7 |
8 |
9 | def main():
10 | parser = optparse.OptionParser(usage='%prog INFILE OUTFILE')
11 | options, args = parser.parse_args()
12 | if len(args) != 2:
13 | parser.error('Expected an input and an output filename')
14 |
15 | infile, outfile = args
16 |
17 | with io.open(infile, encoding='utf-8') as inf:
18 | readme = inf.read()
19 |
20 | bug_text = re.search(
21 | r'(?s)#\s*BUGS\s*[^\n]*\s*(.*?)#\s*COPYRIGHT', readme).group(1)
22 | dev_text = re.search(
23 | r'(?s)(#\s*DEVELOPER INSTRUCTIONS.*?)#\s*EMBEDDING YOUTUBE-DL',
24 | readme).group(1)
25 |
26 | out = bug_text + dev_text
27 |
28 | with io.open(outfile, 'w', encoding='utf-8') as outf:
29 | outf.write(out)
30 |
31 | if __name__ == '__main__':
32 | main()
33 |
--------------------------------------------------------------------------------
/youtube_dl/postprocessor/execafterdownload.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import subprocess
4 |
5 | from .common import PostProcessor
6 | from ..compat import compat_shlex_quote
7 | from ..utils import PostProcessingError
8 |
9 |
10 | class ExecAfterDownloadPP(PostProcessor):
11 | def __init__(self, downloader, exec_cmd):
12 | super(ExecAfterDownloadPP, self).__init__(downloader)
13 | self.exec_cmd = exec_cmd
14 |
15 | def run(self, information):
16 | cmd = self.exec_cmd
17 | if '{}' not in cmd:
18 | cmd += ' {}'
19 |
20 | cmd = cmd.replace('{}', compat_shlex_quote(information['filepath']))
21 |
22 | self._downloader.to_screen('[exec] Executing command: %s' % cmd)
23 | retCode = subprocess.call(cmd, shell=True)
24 | if retCode != 0:
25 | raise PostProcessingError(
26 | 'Command returned error code %d' % retCode)
27 |
28 | return [], information
29 |
--------------------------------------------------------------------------------
/devscripts/bash-completion.in:
--------------------------------------------------------------------------------
1 | __youtube_dl()
2 | {
3 | local cur prev opts fileopts diropts keywords
4 | COMPREPLY=()
5 | cur="${COMP_WORDS[COMP_CWORD]}"
6 | prev="${COMP_WORDS[COMP_CWORD-1]}"
7 | opts="{{flags}}"
8 | keywords=":ytfavorites :ytrecommended :ytsubscriptions :ytwatchlater :ythistory"
9 | fileopts="-a|--batch-file|--download-archive|--cookies|--load-info"
10 | diropts="--cache-dir"
11 |
12 | if [[ ${prev} =~ ${fileopts} ]]; then
13 | COMPREPLY=( $(compgen -f -- ${cur}) )
14 | return 0
15 | elif [[ ${prev} =~ ${diropts} ]]; then
16 | COMPREPLY=( $(compgen -d -- ${cur}) )
17 | return 0
18 | fi
19 |
20 | if [[ ${cur} =~ : ]]; then
21 | COMPREPLY=( $(compgen -W "${keywords}" -- ${cur}) )
22 | return 0
23 | elif [[ ${cur} == * ]] ; then
24 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
25 | return 0
26 | fi
27 | }
28 |
29 | complete -F __youtube_dl youtube-dl
30 |
--------------------------------------------------------------------------------
/devscripts/bash-completion.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import unicode_literals
3 |
4 | import os
5 | from os.path import dirname as dirn
6 | import sys
7 |
8 | sys.path.insert(0, dirn(dirn((os.path.abspath(__file__)))))
9 | import youtube_dl
10 |
11 | BASH_COMPLETION_FILE = "youtube-dl.bash-completion"
12 | BASH_COMPLETION_TEMPLATE = "devscripts/bash-completion.in"
13 |
14 |
15 | def build_completion(opt_parser):
16 | opts_flag = []
17 | for group in opt_parser.option_groups:
18 | for option in group.option_list:
19 | # for every long flag
20 | opts_flag.append(option.get_opt_string())
21 | with open(BASH_COMPLETION_TEMPLATE) as f:
22 | template = f.read()
23 | with open(BASH_COMPLETION_FILE, "w") as f:
24 | # just using the special char
25 | filled_template = template.replace("{{flags}}", " ".join(opts_flag))
26 | f.write(filled_template)
27 |
28 | parser = youtube_dl.parseOpts()[0]
29 | build_completion(parser)
30 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/lci.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class LCIIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?lci\.fr/[^/]+/[\w-]+-(?P\d+)\.html'
9 | _TEST = {
10 | 'url': 'http://www.lci.fr/international/etats-unis-a-j-62-hillary-clinton-reste-sans-voix-2001679.html',
11 | 'md5': '2fdb2538b884d4d695f9bd2bde137e6c',
12 | 'info_dict': {
13 | 'id': '13244802',
14 | 'ext': 'mp4',
15 | 'title': 'Hillary Clinton et sa quinte de toux, en plein meeting',
16 | 'description': 'md5:a4363e3a960860132f8124b62f4a01c9',
17 | }
18 | }
19 |
20 | def _real_extract(self, url):
21 | video_id = self._match_id(url)
22 | webpage = self._download_webpage(url, video_id)
23 | wat_id = self._search_regex(r'data-watid=[\'"](\d+)', webpage, 'wat id')
24 | return self.url_result('wat:' + wat_id, 'Wat', wat_id)
25 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/m6.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class M6IE(InfoExtractor):
8 | IE_NAME = 'm6'
9 | _VALID_URL = r'https?://(?:www\.)?m6\.fr/[^/]+/videos/(?P\d+)-[^\.]+\.html'
10 |
11 | _TEST = {
12 | 'url': 'http://www.m6.fr/emission-les_reines_du_shopping/videos/11323908-emeline_est_la_reine_du_shopping_sur_le_theme_ma_fete_d_8217_anniversaire.html',
13 | 'md5': '242994a87de2c316891428e0176bcb77',
14 | 'info_dict': {
15 | 'id': '11323908',
16 | 'ext': 'mp4',
17 | 'title': 'Emeline est la Reine du Shopping sur le thème « Ma fête d’anniversaire ! »',
18 | 'description': 'md5:1212ae8fb4b7baa4dc3886c5676007c2',
19 | 'duration': 100,
20 | }
21 | }
22 |
23 | def _real_extract(self, url):
24 | video_id = self._match_id(url)
25 | return self.url_result('6play:%s' % video_id, 'SixPlay', video_id)
26 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/anitube.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .nuevo import NuevoBaseIE
4 |
5 |
6 | class AnitubeIE(NuevoBaseIE):
7 | IE_NAME = 'anitube.se'
8 | _VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P\d+)'
9 |
10 | _TEST = {
11 | 'url': 'http://www.anitube.se/video/36621',
12 | 'md5': '59d0eeae28ea0bc8c05e7af429998d43',
13 | 'info_dict': {
14 | 'id': '36621',
15 | 'ext': 'mp4',
16 | 'title': 'Recorder to Randoseru 01',
17 | 'duration': 180.19,
18 | },
19 | 'skip': 'Blocked in the US',
20 | }
21 |
22 | def _real_extract(self, url):
23 | video_id = self._match_id(url)
24 |
25 | webpage = self._download_webpage(url, video_id)
26 | key = self._search_regex(
27 | r'src=["\']https?://[^/]+/embed/([A-Za-z0-9_-]+)', webpage, 'key')
28 |
29 | return self._extract_nuevo(
30 | 'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, video_id)
31 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/myvidster.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 |
5 |
6 | class MyVidsterIE(InfoExtractor):
7 | _VALID_URL = r'https?://(?:www\.)?myvidster\.com/video/(?P\d+)/'
8 |
9 | _TEST = {
10 | 'url': 'http://www.myvidster.com/video/32059805/Hot_chemistry_with_raw_love_making',
11 | 'md5': '95296d0231c1363222c3441af62dc4ca',
12 | 'info_dict': {
13 | 'id': '3685814',
14 | 'title': 'md5:7d8427d6d02c4fbcef50fe269980c749',
15 | 'upload_date': '20141027',
16 | 'uploader': 'utkualp',
17 | 'ext': 'mp4',
18 | 'age_limit': 18,
19 | },
20 | 'add_ie': ['XHamster'],
21 | }
22 |
23 | def _real_extract(self, url):
24 | video_id = self._match_id(url)
25 | webpage = self._download_webpage(url, video_id)
26 |
27 | return self.url_result(self._html_search_regex(
28 | r'rel="videolink" href="(?P.*)">',
29 | webpage, 'real video url'))
30 |
--------------------------------------------------------------------------------
/devscripts/gh-pages/sign-versions.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from __future__ import unicode_literals, with_statement
3 |
4 | import rsa
5 | import json
6 | from binascii import hexlify
7 |
8 | try:
9 | input = raw_input
10 | except NameError:
11 | pass
12 |
13 | versions_info = json.load(open('update/versions.json'))
14 | if 'signature' in versions_info:
15 | del versions_info['signature']
16 |
17 | print('Enter the PKCS1 private key, followed by a blank line:')
18 | privkey = b''
19 | while True:
20 | try:
21 | line = input()
22 | except EOFError:
23 | break
24 | if line == '':
25 | break
26 | privkey += line.encode('ascii') + b'\n'
27 | privkey = rsa.PrivateKey.load_pkcs1(privkey)
28 |
29 | signature = hexlify(rsa.pkcs1.sign(json.dumps(versions_info, sort_keys=True).encode('utf-8'), privkey, 'SHA-256')).decode()
30 | print('signature: ' + signature)
31 |
32 | versions_info['signature'] = signature
33 | with open('update/versions.json', 'w') as versionsf:
34 | json.dump(versions_info, versionsf, indent=4, sort_keys=True)
35 |
--------------------------------------------------------------------------------
/youtube-dl.plugin.zsh:
--------------------------------------------------------------------------------
1 | # This allows the youtube-dl command to be installed in ZSH using antigen.
2 | # Antigen is a bundle manager. It allows you to enhance the functionality of
3 | # your zsh session by installing bundles and themes easily.
4 |
5 | # Antigen documentation:
6 | # http://antigen.sharats.me/
7 | # https://github.com/zsh-users/antigen
8 |
9 | # Install youtube-dl:
10 | # antigen bundle rg3/youtube-dl
11 | # Bundles installed by antigen are available for use immediately.
12 |
13 | # Update youtube-dl (and all other antigen bundles):
14 | # antigen update
15 |
16 | # The antigen command will download the git repository to a folder and then
17 | # execute an enabling script (this file). The complete process for loading the
18 | # code is documented here:
19 | # https://github.com/zsh-users/antigen#notes-on-writing-plugins
20 |
21 | # This specific script just aliases youtube-dl to the python script that this
22 | # library provides. This requires updating the PYTHONPATH to ensure that the
23 | # full set of code can be located.
24 | alias youtube-dl="PYTHONPATH=$(dirname $0) $(dirname $0)/bin/youtube-dl"
25 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/ku6.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 |
5 |
6 | class Ku6IE(InfoExtractor):
7 | _VALID_URL = r'https?://v\.ku6\.com/show/(?P[a-zA-Z0-9\-\_]+)(?:\.)*html'
8 | _TEST = {
9 | 'url': 'http://v.ku6.com/show/JG-8yS14xzBr4bCn1pu0xw...html',
10 | 'md5': '01203549b9efbb45f4b87d55bdea1ed1',
11 | 'info_dict': {
12 | 'id': 'JG-8yS14xzBr4bCn1pu0xw',
13 | 'ext': 'f4v',
14 | 'title': 'techniques test',
15 | }
16 | }
17 |
18 | def _real_extract(self, url):
19 | video_id = self._match_id(url)
20 | webpage = self._download_webpage(url, video_id)
21 |
22 | title = self._html_search_regex(
23 | r'(.*?)
', webpage, 'title')
24 | dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
25 | jsonData = self._download_json(dataUrl, video_id)
26 | downloadUrl = jsonData['data']['f']
27 |
28 | return {
29 | 'id': video_id,
30 | 'title': title,
31 | 'url': downloadUrl
32 | }
33 |
--------------------------------------------------------------------------------
/youtube_dl/postprocessor/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .embedthumbnail import EmbedThumbnailPP
4 | from .ffmpeg import (
5 | FFmpegPostProcessor,
6 | FFmpegEmbedSubtitlePP,
7 | FFmpegExtractAudioPP,
8 | FFmpegFixupStretchedPP,
9 | FFmpegFixupM3u8PP,
10 | FFmpegFixupM4aPP,
11 | FFmpegMergerPP,
12 | FFmpegMetadataPP,
13 | FFmpegVideoConvertorPP,
14 | FFmpegSubtitlesConvertorPP,
15 | )
16 | from .xattrpp import XAttrMetadataPP
17 | from .execafterdownload import ExecAfterDownloadPP
18 | from .metadatafromtitle import MetadataFromTitlePP
19 |
20 |
21 | def get_postprocessor(key):
22 | return globals()[key + 'PP']
23 |
24 |
25 | __all__ = [
26 | 'EmbedThumbnailPP',
27 | 'ExecAfterDownloadPP',
28 | 'FFmpegEmbedSubtitlePP',
29 | 'FFmpegExtractAudioPP',
30 | 'FFmpegFixupM3u8PP',
31 | 'FFmpegFixupM4aPP',
32 | 'FFmpegFixupStretchedPP',
33 | 'FFmpegMergerPP',
34 | 'FFmpegMetadataPP',
35 | 'FFmpegPostProcessor',
36 | 'FFmpegSubtitlesConvertorPP',
37 | 'FFmpegVideoConvertorPP',
38 | 'MetadataFromTitlePP',
39 | 'XAttrMetadataPP',
40 | ]
41 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/videodetective.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 | from ..compat import compat_urlparse
5 | from .internetvideoarchive import InternetVideoArchiveIE
6 |
7 |
8 | class VideoDetectiveIE(InfoExtractor):
9 | _VALID_URL = r'https?://(?:www\.)?videodetective\.com/[^/]+/[^/]+/(?P\d+)'
10 |
11 | _TEST = {
12 | 'url': 'http://www.videodetective.com/movies/kick-ass-2/194487',
13 | 'info_dict': {
14 | 'id': '194487',
15 | 'ext': 'mp4',
16 | 'title': 'KICK-ASS 2',
17 | 'description': 'md5:c189d5b7280400630a1d3dd17eaa8d8a',
18 | },
19 | 'params': {
20 | # m3u8 download
21 | 'skip_download': True,
22 | },
23 | }
24 |
25 | def _real_extract(self, url):
26 | video_id = self._match_id(url)
27 | webpage = self._download_webpage(url, video_id)
28 | og_video = self._og_search_video_url(webpage)
29 | query = compat_urlparse.urlparse(og_video).query
30 | return self.url_result(InternetVideoArchiveIE._build_json_url(query), ie=InternetVideoArchiveIE.ie_key())
31 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/cliprs.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .onet import OnetBaseIE
5 |
6 |
7 | class ClipRsIE(OnetBaseIE):
8 | _VALID_URL = r'https?://(?:www\.)?clip\.rs/(?P[^/]+)/\d+'
9 | _TEST = {
10 | 'url': 'http://www.clip.rs/premijera-frajle-predstavljaju-novi-spot-za-pesmu-moli-me-moli/3732',
11 | 'md5': 'c412d57815ba07b56f9edc7b5d6a14e5',
12 | 'info_dict': {
13 | 'id': '1488842.1399140381',
14 | 'ext': 'mp4',
15 | 'title': 'PREMIJERA Frajle predstavljaju novi spot za pesmu Moli me, moli',
16 | 'description': 'md5:56ce2c3b4ab31c5a2e0b17cb9a453026',
17 | 'duration': 229,
18 | 'timestamp': 1459850243,
19 | 'upload_date': '20160405',
20 | }
21 | }
22 |
23 | def _real_extract(self, url):
24 | display_id = self._match_id(url)
25 |
26 | webpage = self._download_webpage(url, display_id)
27 |
28 | mvp_id = self._search_mvp_id(webpage)
29 |
30 | info_dict = self._extract_from_id(mvp_id, webpage)
31 | info_dict['display_id'] = display_id
32 |
33 | return info_dict
34 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/cbssports.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .cbs import CBSBaseIE
4 |
5 |
6 | class CBSSportsIE(CBSBaseIE):
7 | _VALID_URL = r'https?://(?:www\.)?cbssports\.com/video/player/[^/]+/(?P\d+)'
8 |
9 | _TESTS = [{
10 | 'url': 'http://www.cbssports.com/video/player/videos/708337219968/0/ben-simmons-the-next-lebron?-not-so-fast',
11 | 'info_dict': {
12 | 'id': '708337219968',
13 | 'ext': 'mp4',
14 | 'title': 'Ben Simmons the next LeBron? Not so fast',
15 | 'description': 'md5:854294f627921baba1f4b9a990d87197',
16 | 'timestamp': 1466293740,
17 | 'upload_date': '20160618',
18 | 'uploader': 'CBSI-NEW',
19 | },
20 | 'params': {
21 | # m3u8 download
22 | 'skip_download': True,
23 | }
24 | }]
25 |
26 | def _extract_video_info(self, filter_query, video_id):
27 | return self._extract_feed_info('dJ5BDC', 'VxxJg8Ymh8sE', filter_query, video_id)
28 |
29 | def _real_extract(self, url):
30 | video_id = self._match_id(url)
31 | return self._extract_video_info('byId=%s' % video_id, video_id)
32 |
--------------------------------------------------------------------------------
/devscripts/gh-pages/generate-download.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from __future__ import unicode_literals
3 |
4 | import hashlib
5 | import urllib.request
6 | import json
7 |
8 | versions_info = json.load(open('update/versions.json'))
9 | version = versions_info['latest']
10 | URL = versions_info['versions'][version]['bin'][0]
11 |
12 | data = urllib.request.urlopen(URL).read()
13 |
14 | # Read template page
15 | with open('download.html.in', 'r', encoding='utf-8') as tmplf:
16 | template = tmplf.read()
17 |
18 | sha256sum = hashlib.sha256(data).hexdigest()
19 | template = template.replace('@PROGRAM_VERSION@', version)
20 | template = template.replace('@PROGRAM_URL@', URL)
21 | template = template.replace('@PROGRAM_SHA256SUM@', sha256sum)
22 | template = template.replace('@EXE_URL@', versions_info['versions'][version]['exe'][0])
23 | template = template.replace('@EXE_SHA256SUM@', versions_info['versions'][version]['exe'][1])
24 | template = template.replace('@TAR_URL@', versions_info['versions'][version]['tar'][0])
25 | template = template.replace('@TAR_SHA256SUM@', versions_info['versions'][version]['tar'][1])
26 | with open('download.html', 'w', encoding='utf-8') as dlf:
27 | dlf.write(template)
28 |
--------------------------------------------------------------------------------
/devscripts/gh-pages/update-sites.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from __future__ import unicode_literals
3 |
4 | import sys
5 | import os
6 | import textwrap
7 |
8 | # We must be able to import youtube_dl
9 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
10 |
11 | import youtube_dl
12 |
13 |
14 | def main():
15 | with open('supportedsites.html.in', 'r', encoding='utf-8') as tmplf:
16 | template = tmplf.read()
17 |
18 | ie_htmls = []
19 | for ie in youtube_dl.list_extractors(age_limit=None):
20 | ie_html = '{}'.format(ie.IE_NAME)
21 | ie_desc = getattr(ie, 'IE_DESC', None)
22 | if ie_desc is False:
23 | continue
24 | elif ie_desc is not None:
25 | ie_html += ': {}'.format(ie.IE_DESC)
26 | if not ie.working():
27 | ie_html += ' (Currently broken)'
28 | ie_htmls.append('{}'.format(ie_html))
29 |
30 | template = template.replace('@SITES@', textwrap.indent('\n'.join(ie_htmls), '\t'))
31 |
32 | with open('supportedsites.html', 'w', encoding='utf-8') as sitesf:
33 | sitesf.write(template)
34 |
35 | if __name__ == '__main__':
36 | main()
37 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Please follow the guide below
2 |
3 | - You will be asked some questions, please read them **carefully** and answer honestly
4 | - Put an `x` into all the boxes [ ] relevant to your *pull request* (like that [x])
5 | - Use *Preview* tab to see how your *pull request* will actually look like
6 |
7 | ---
8 |
9 | ### Before submitting a *pull request* make sure you have:
10 | - [ ] At least skimmed through [adding new extractor tutorial](https://github.com/rg3/youtube-dl#adding-support-for-a-new-site) and [youtube-dl coding conventions](https://github.com/rg3/youtube-dl#youtube-dl-coding-conventions) sections
11 | - [ ] [Searched](https://github.com/rg3/youtube-dl/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
12 |
13 | ### What is the purpose of your *pull request*?
14 | - [ ] Bug fix
15 | - [ ] New extractor
16 | - [ ] New feature
17 |
18 | ---
19 |
20 | ### Description of your *pull request* and other information
21 |
22 | Explanation of your *pull request* in arbitrary form goes here. Please make sure the description explains the purpose and effect of your *pull request* and is worded well enough to be understood. Provide as much context and examples as possible.
23 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/ina.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | import re
5 |
6 | from .common import InfoExtractor
7 |
8 |
9 | class InaIE(InfoExtractor):
10 | _VALID_URL = r'https?://(?:www\.)?ina\.fr/video/(?PI?[A-Z0-9]+)'
11 | _TEST = {
12 | 'url': 'http://www.ina.fr/video/I12055569/francois-hollande-je-crois-que-c-est-clair-video.html',
13 | 'md5': 'a667021bf2b41f8dc6049479d9bb38a3',
14 | 'info_dict': {
15 | 'id': 'I12055569',
16 | 'ext': 'mp4',
17 | 'title': 'François Hollande "Je crois que c\'est clair"',
18 | }
19 | }
20 |
21 | def _real_extract(self, url):
22 | mobj = re.match(self._VALID_URL, url)
23 |
24 | video_id = mobj.group('id')
25 | mrss_url = 'http://player.ina.fr/notices/%s.mrss' % video_id
26 | info_doc = self._download_xml(mrss_url, video_id)
27 |
28 | self.report_extraction(video_id)
29 |
30 | video_url = info_doc.find('.//{http://search.yahoo.com/mrss/}player').attrib['url']
31 |
32 | return {
33 | 'id': video_id,
34 | 'url': video_url,
35 | 'title': info_doc.find('.//title').text,
36 | }
37 |
--------------------------------------------------------------------------------
/test/test_update.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import unicode_literals
4 |
5 | # Allow direct execution
6 | import os
7 | import sys
8 | import unittest
9 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10 |
11 |
12 | import json
13 | from youtube_dl.update import rsa_verify
14 |
15 |
16 | class TestUpdate(unittest.TestCase):
17 | def test_rsa_verify(self):
18 | UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
19 | with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'versions.json'), 'rb') as f:
20 | versions_info = f.read().decode()
21 | versions_info = json.loads(versions_info)
22 | signature = versions_info['signature']
23 | del versions_info['signature']
24 | self.assertTrue(rsa_verify(
25 | json.dumps(versions_info, sort_keys=True).encode('utf-8'),
26 | signature, UPDATES_RSA_KEY))
27 |
28 |
29 | if __name__ == '__main__':
30 | unittest.main()
31 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/ebaumsworld.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 |
5 |
6 | class EbaumsWorldIE(InfoExtractor):
7 | _VALID_URL = r'https?://(?:www\.)?ebaumsworld\.com/videos/[^/]+/(?P\d+)'
8 |
9 | _TEST = {
10 | 'url': 'http://www.ebaumsworld.com/videos/a-giant-python-opens-the-door/83367677/',
11 | 'info_dict': {
12 | 'id': '83367677',
13 | 'ext': 'mp4',
14 | 'title': 'A Giant Python Opens The Door',
15 | 'description': 'This is how nightmares start...',
16 | 'uploader': 'jihadpizza',
17 | },
18 | }
19 |
20 | def _real_extract(self, url):
21 | video_id = self._match_id(url)
22 | config = self._download_xml(
23 | 'http://www.ebaumsworld.com/video/player/%s' % video_id, video_id)
24 | video_url = config.find('file').text
25 |
26 | return {
27 | 'id': video_id,
28 | 'title': config.find('title').text,
29 | 'url': video_url,
30 | 'description': config.find('description').text,
31 | 'thumbnail': config.find('image').text,
32 | 'uploader': config.find('username').text,
33 | }
34 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/makertv.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class MakerTVIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:(?:www\.)?maker\.tv/(?:[^/]+/)*video|makerplayer.com/embed/maker)/(?P[a-zA-Z0-9]{12})'
9 | _TEST = {
10 | 'url': 'http://www.maker.tv/video/Fh3QgymL9gsc',
11 | 'md5': 'ca237a53a8eb20b6dc5bd60564d4ab3e',
12 | 'info_dict': {
13 | 'id': 'Fh3QgymL9gsc',
14 | 'ext': 'mp4',
15 | 'title': 'Maze Runner: The Scorch Trials Official Movie Review',
16 | 'description': 'md5:11ff3362d7ef1d679fdb649f6413975a',
17 | 'upload_date': '20150918',
18 | 'timestamp': 1442549540,
19 | }
20 | }
21 |
22 | def _real_extract(self, url):
23 | video_id = self._match_id(url)
24 | webpage = self._download_webpage(url, video_id)
25 | jwplatform_id = self._search_regex(r'jw_?id="([^"]+)"', webpage, 'jwplatform id')
26 |
27 | return {
28 | '_type': 'url_transparent',
29 | 'id': video_id,
30 | 'url': 'jwplatform:%s' % jwplatform_id,
31 | 'ie_key': 'JWPlatform',
32 | }
33 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/formula1.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class Formula1IE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?formula1\.com/(?:content/fom-website/)?en/video/\d{4}/\d{1,2}/(?P.+?)\.html'
9 | _TESTS = [{
10 | 'url': 'http://www.formula1.com/content/fom-website/en/video/2016/5/Race_highlights_-_Spain_2016.html',
11 | 'md5': '8c79e54be72078b26b89e0e111c0502b',
12 | 'info_dict': {
13 | 'id': 'JvYXJpMzE6pArfHWm5ARp5AiUmD-gibV',
14 | 'ext': 'flv',
15 | 'title': 'Race highlights - Spain 2016',
16 | },
17 | 'add_ie': ['Ooyala'],
18 | }, {
19 | 'url': 'http://www.formula1.com/en/video/2016/5/Race_highlights_-_Spain_2016.html',
20 | 'only_matching': True,
21 | }]
22 |
23 | def _real_extract(self, url):
24 | display_id = self._match_id(url)
25 | webpage = self._download_webpage(url, display_id)
26 | ooyala_embed_code = self._search_regex(
27 | r'data-videoid="([^"]+)"', webpage, 'ooyala embed code')
28 | return self.url_result(
29 | 'ooyala:%s' % ooyala_embed_code, 'Ooyala', ooyala_embed_code)
30 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/teachingchannel.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import re
4 |
5 | from .common import InfoExtractor
6 | from .ooyala import OoyalaIE
7 |
8 |
9 | class TeachingChannelIE(InfoExtractor):
10 | _VALID_URL = r'https?://(?:www\.)?teachingchannel\.org/videos/(?P.+)'
11 |
12 | _TEST = {
13 | 'url': 'https://www.teachingchannel.org/videos/teacher-teaming-evolution',
14 | 'md5': '3d6361864d7cac20b57c8784da17166f',
15 | 'info_dict': {
16 | 'id': 'F3bnlzbToeI6pLEfRyrlfooIILUjz4nM',
17 | 'ext': 'mp4',
18 | 'title': 'A History of Teaming',
19 | 'description': 'md5:2a9033db8da81f2edffa4c99888140b3',
20 | 'duration': 422.255,
21 | },
22 | 'params': {
23 | 'skip_download': True,
24 | },
25 | 'add_ie': ['Ooyala'],
26 | }
27 |
28 | def _real_extract(self, url):
29 | mobj = re.match(self._VALID_URL, url)
30 | title = mobj.group('title')
31 | webpage = self._download_webpage(url, title)
32 | ooyala_code = self._search_regex(
33 | r'data-embed-code=\'(.+?)\'', webpage, 'ooyala code')
34 |
35 | return OoyalaIE._build_url_result(ooyala_code)
36 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/skysports.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class SkySportsIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?skysports\.com/watch/video/(?P[0-9]+)'
9 | _TEST = {
10 | 'url': 'http://www.skysports.com/watch/video/10328419/bale-its-our-time-to-shine',
11 | 'md5': 'c44a1db29f27daf9a0003e010af82100',
12 | 'info_dict': {
13 | 'id': '10328419',
14 | 'ext': 'flv',
15 | 'title': 'Bale: Its our time to shine',
16 | 'description': 'md5:9fd1de3614d525f5addda32ac3c482c9',
17 | },
18 | 'add_ie': ['Ooyala'],
19 | }
20 |
21 | def _real_extract(self, url):
22 | video_id = self._match_id(url)
23 | webpage = self._download_webpage(url, video_id)
24 |
25 | return {
26 | '_type': 'url_transparent',
27 | 'id': video_id,
28 | 'url': 'ooyala:%s' % self._search_regex(
29 | r'data-video-id="([^"]+)"', webpage, 'ooyala id'),
30 | 'title': self._og_search_title(webpage),
31 | 'description': self._og_search_description(webpage),
32 | 'ie_key': 'Ooyala',
33 | }
34 |
--------------------------------------------------------------------------------
/test/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "consoletitle": false,
3 | "continuedl": true,
4 | "forcedescription": false,
5 | "forcefilename": false,
6 | "forceformat": false,
7 | "forcethumbnail": false,
8 | "forcetitle": false,
9 | "forceurl": false,
10 | "format": "best",
11 | "ignoreerrors": false,
12 | "listformats": null,
13 | "logtostderr": false,
14 | "matchtitle": null,
15 | "max_downloads": null,
16 | "nooverwrites": false,
17 | "nopart": false,
18 | "noprogress": false,
19 | "outtmpl": "%(id)s.%(ext)s",
20 | "password": null,
21 | "playlistend": -1,
22 | "playliststart": 1,
23 | "prefer_free_formats": false,
24 | "quiet": false,
25 | "ratelimit": null,
26 | "rejecttitle": null,
27 | "retries": 10,
28 | "simulate": false,
29 | "subtitleslang": null,
30 | "subtitlesformat": "best",
31 | "test": true,
32 | "updatetime": true,
33 | "usenetrc": false,
34 | "username": null,
35 | "verbose": true,
36 | "writedescription": false,
37 | "writeinfojson": true,
38 | "writesubtitles": false,
39 | "allsubtitles": false,
40 | "listssubtitles": false,
41 | "socket_timeout": 20,
42 | "fixup": "never"
43 | }
44 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/tutv.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import base64
4 |
5 | from .common import InfoExtractor
6 | from ..compat import compat_parse_qs
7 |
8 |
9 | class TutvIE(InfoExtractor):
10 | _VALID_URL = r'https?://(?:www\.)?tu\.tv/videos/(?P[^/?]+)'
11 | _TEST = {
12 | 'url': 'http://tu.tv/videos/robots-futbolistas',
13 | 'md5': '0cd9e28ad270488911b0d2a72323395d',
14 | 'info_dict': {
15 | 'id': '2973058',
16 | 'ext': 'mp4',
17 | 'title': 'Robots futbolistas',
18 | },
19 | }
20 |
21 | def _real_extract(self, url):
22 | video_id = self._match_id(url)
23 | webpage = self._download_webpage(url, video_id)
24 |
25 | internal_id = self._search_regex(r'codVideo=([0-9]+)', webpage, 'internal video ID')
26 |
27 | data_content = self._download_webpage(
28 | 'http://tu.tv/flvurl.php?codVideo=%s' % internal_id, video_id, 'Downloading video info')
29 | video_url = base64.b64decode(compat_parse_qs(data_content)['kpt'][0].encode('utf-8')).decode('utf-8')
30 |
31 | return {
32 | 'id': internal_id,
33 | 'url': video_url,
34 | 'title': self._og_search_title(webpage),
35 | }
36 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/people.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class PeopleIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?people\.com/people/videos/0,,(?P\d+),00\.html'
9 |
10 | _TEST = {
11 | 'url': 'http://www.people.com/people/videos/0,,20995451,00.html',
12 | 'info_dict': {
13 | 'id': 'ref:20995451',
14 | 'ext': 'mp4',
15 | 'title': 'Astronaut Love Triangle Victim Speaks Out: “The Crime in 2007 Hasn’t Defined Us”',
16 | 'description': 'Colleen Shipman speaks to PEOPLE for the first time about life after the attack',
17 | 'thumbnail': 're:^https?://.*\.jpg',
18 | 'duration': 246.318,
19 | 'timestamp': 1458720585,
20 | 'upload_date': '20160323',
21 | 'uploader_id': '416418724',
22 | },
23 | 'params': {
24 | 'skip_download': True,
25 | },
26 | 'add_ie': ['BrightcoveNew'],
27 | }
28 |
29 | def _real_extract(self, url):
30 | return self.url_result(
31 | 'http://players.brightcove.net/416418724/default_default/index.html?videoId=ref:%s'
32 | % self._match_id(url), 'BrightcoveNew')
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/cnbc.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 | from ..utils import smuggle_url
6 |
7 |
8 | class CNBCIE(InfoExtractor):
9 | _VALID_URL = r'https?://video\.cnbc\.com/gallery/\?video=(?P[0-9]+)'
10 | _TEST = {
11 | 'url': 'http://video.cnbc.com/gallery/?video=3000503714',
12 | 'info_dict': {
13 | 'id': '3000503714',
14 | 'ext': 'mp4',
15 | 'title': 'Fighting zombies is big business',
16 | 'description': 'md5:0c100d8e1a7947bd2feec9a5550e519e',
17 | 'timestamp': 1459332000,
18 | 'upload_date': '20160330',
19 | 'uploader': 'NBCU-CNBC',
20 | },
21 | 'params': {
22 | # m3u8 download
23 | 'skip_download': True,
24 | },
25 | }
26 |
27 | def _real_extract(self, url):
28 | video_id = self._match_id(url)
29 | return {
30 | '_type': 'url_transparent',
31 | 'ie_key': 'ThePlatform',
32 | 'url': smuggle_url(
33 | 'http://link.theplatform.com/s/gZWlPC/media/guid/2408950221/%s?mbr=true&manifest=m3u' % video_id,
34 | {'force_smil_url': True}),
35 | 'id': video_id,
36 | }
37 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/nerdcubed.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | import datetime
5 |
6 | from .common import InfoExtractor
7 |
8 |
9 | class NerdCubedFeedIE(InfoExtractor):
10 | _VALID_URL = r'https?://(?:www\.)?nerdcubed\.co\.uk/feed\.json'
11 | _TEST = {
12 | 'url': 'http://www.nerdcubed.co.uk/feed.json',
13 | 'info_dict': {
14 | 'id': 'nerdcubed-feed',
15 | 'title': 'nerdcubed.co.uk feed',
16 | },
17 | 'playlist_mincount': 1300,
18 | }
19 |
20 | def _real_extract(self, url):
21 | feed = self._download_json(url, url, 'Downloading NerdCubed JSON feed')
22 |
23 | entries = [{
24 | '_type': 'url',
25 | 'title': feed_entry['title'],
26 | 'uploader': feed_entry['source']['name'] if feed_entry['source'] else None,
27 | 'upload_date': datetime.datetime.strptime(feed_entry['date'], '%Y-%m-%d').strftime('%Y%m%d'),
28 | 'url': 'http://www.youtube.com/watch?v=' + feed_entry['youtube_id'],
29 | } for feed_entry in feed]
30 |
31 | return {
32 | '_type': 'playlist',
33 | 'title': 'nerdcubed.co.uk feed',
34 | 'id': 'nerdcubed-feed',
35 | 'entries': entries,
36 | }
37 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/savefrom.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | import os.path
5 | import re
6 |
7 | from .common import InfoExtractor
8 |
9 |
10 | class SaveFromIE(InfoExtractor):
11 | IE_NAME = 'savefrom.net'
12 | _VALID_URL = r'https?://[^.]+\.savefrom\.net/\#url=(?P.*)$'
13 |
14 | _TEST = {
15 | 'url': 'http://en.savefrom.net/#url=http://youtube.com/watch?v=UlVRAPW2WJY&utm_source=youtube.com&utm_medium=short_domains&utm_campaign=ssyoutube.com',
16 | 'info_dict': {
17 | 'id': 'UlVRAPW2WJY',
18 | 'ext': 'mp4',
19 | 'title': 'About Team Radical MMA | MMA Fighting',
20 | 'upload_date': '20120816',
21 | 'uploader': 'Howcast',
22 | 'uploader_id': 'Howcast',
23 | 'description': 're:(?s).* Hi, my name is Rene Dreifuss\. And I\'m here to show you some MMA.*',
24 | },
25 | 'params': {
26 | 'skip_download': True
27 | }
28 | }
29 |
30 | def _real_extract(self, url):
31 | mobj = re.match(self._VALID_URL, url)
32 | video_id = os.path.splitext(url.split('/')[-1])[0]
33 | return {
34 | '_type': 'url',
35 | 'id': video_id,
36 | 'url': mobj.group('url'),
37 | }
38 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/gameinformer.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class GameInformerIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?gameinformer\.com/(?:[^/]+/)*(?P.+)\.aspx'
9 | _TEST = {
10 | 'url': 'http://www.gameinformer.com/b/features/archive/2015/09/26/replay-animal-crossing.aspx',
11 | 'md5': '292f26da1ab4beb4c9099f1304d2b071',
12 | 'info_dict': {
13 | 'id': '4515472681001',
14 | 'ext': 'mp4',
15 | 'title': 'Replay - Animal Crossing',
16 | 'description': 'md5:2e211891b215c85d061adc7a4dd2d930',
17 | 'timestamp': 1443457610,
18 | 'upload_date': '20150928',
19 | 'uploader_id': '694940074001',
20 | },
21 | }
22 | BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/694940074001/default_default/index.html?videoId=%s'
23 |
24 | def _real_extract(self, url):
25 | display_id = self._match_id(url)
26 | webpage = self._download_webpage(url, display_id)
27 | brightcove_id = self._search_regex(r"getVideo\('[^']+video_id=(\d+)", webpage, 'brightcove id')
28 | return self.url_result(self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id, 'BrightcoveNew', brightcove_id)
29 |
--------------------------------------------------------------------------------
/test/test_iqiyi_sdk_interpreter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from __future__ import unicode_literals
4 |
5 | # Allow direct execution
6 | import os
7 | import sys
8 | import unittest
9 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10 |
11 | from test.helper import FakeYDL
12 | from youtube_dl.extractor import IqiyiIE
13 |
14 |
15 | class IqiyiIEWithCredentials(IqiyiIE):
16 | def _get_login_info(self):
17 | return 'foo', 'bar'
18 |
19 |
20 | class WarningLogger(object):
21 | def __init__(self):
22 | self.messages = []
23 |
24 | def warning(self, msg):
25 | self.messages.append(msg)
26 |
27 | def debug(self, msg):
28 | pass
29 |
30 | def error(self, msg):
31 | pass
32 |
33 |
34 | class TestIqiyiSDKInterpreter(unittest.TestCase):
35 | def test_iqiyi_sdk_interpreter(self):
36 | '''
37 | Test the functionality of IqiyiSDKInterpreter by trying to log in
38 |
39 | If `sign` is incorrect, /validate call throws an HTTP 556 error
40 | '''
41 | logger = WarningLogger()
42 | ie = IqiyiIEWithCredentials(FakeYDL({'logger': logger}))
43 | ie._login()
44 | self.assertTrue('unable to log in:' in logger.messages[0])
45 |
46 | if __name__ == '__main__':
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/devscripts/generate_aes_testdata.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import codecs
4 | import subprocess
5 |
6 | import os
7 | import sys
8 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9 |
10 | from youtube_dl.utils import intlist_to_bytes
11 | from youtube_dl.aes import aes_encrypt, key_expansion
12 |
13 | secret_msg = b'Secret message goes here'
14 |
15 |
16 | def hex_str(int_list):
17 | return codecs.encode(intlist_to_bytes(int_list), 'hex')
18 |
19 |
20 | def openssl_encode(algo, key, iv):
21 | cmd = ['openssl', 'enc', '-e', '-' + algo, '-K', hex_str(key), '-iv', hex_str(iv)]
22 | prog = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
23 | out, _ = prog.communicate(secret_msg)
24 | return out
25 |
26 | iv = key = [0x20, 0x15] + 14 * [0]
27 |
28 | r = openssl_encode('aes-128-cbc', key, iv)
29 | print('aes_cbc_decrypt')
30 | print(repr(r))
31 |
32 | password = key
33 | new_key = aes_encrypt(password, key_expansion(password))
34 | r = openssl_encode('aes-128-ctr', new_key, iv)
35 | print('aes_decrypt_text 16')
36 | print(repr(r))
37 |
38 | password = key + 16 * [0]
39 | new_key = aes_encrypt(password, key_expansion(password)) * (32 // 16)
40 | r = openssl_encode('aes-256-ctr', new_key, iv)
41 | print('aes_decrypt_text 32')
42 | print(repr(r))
43 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/learnr.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class LearnrIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?learnr\.pro/view/video/(?P[0-9]+)'
9 | _TEST = {
10 | 'url': 'http://www.learnr.pro/view/video/51624-web-development-tutorial-for-beginners-1-how-to-build-webpages-with-html-css-javascript',
11 | 'md5': '3719fdf0a68397f49899e82c308a89de',
12 | 'info_dict': {
13 | 'id': '51624',
14 | 'ext': 'mp4',
15 | 'title': 'Web Development Tutorial for Beginners (#1) - How to build webpages with HTML, CSS, Javascript',
16 | 'description': 'md5:b36dbfa92350176cdf12b4d388485503',
17 | 'uploader': 'LearnCode.academy',
18 | 'uploader_id': 'learncodeacademy',
19 | 'upload_date': '20131021',
20 | },
21 | 'add_ie': ['Youtube'],
22 | }
23 |
24 | def _real_extract(self, url):
25 | video_id = self._match_id(url)
26 | webpage = self._download_webpage(url, video_id)
27 |
28 | return {
29 | '_type': 'url_transparent',
30 | 'url': self._search_regex(
31 | r"videoId\s*:\s*'([^']+)'", webpage, 'youtube id'),
32 | 'id': video_id,
33 | }
34 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/commonprotocols.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import os
4 |
5 | from .common import InfoExtractor
6 | from ..compat import (
7 | compat_urllib_parse_unquote,
8 | compat_urlparse,
9 | )
10 | from ..utils import url_basename
11 |
12 |
13 | class RtmpIE(InfoExtractor):
14 | IE_DESC = False # Do not list
15 | _VALID_URL = r'(?i)rtmp[est]?://.+'
16 |
17 | _TESTS = [{
18 | 'url': 'rtmp://cp44293.edgefcs.net/ondemand?auth=daEcTdydfdqcsb8cZcDbAaCbhamacbbawaS-bw7dBb-bWG-GqpGFqCpNCnGoyL&aifp=v001&slist=public/unsecure/audio/2c97899446428e4301471a8cb72b4b97--audio--pmg-20110908-0900a_flv_aac_med_int.mp4',
19 | 'only_matching': True,
20 | }, {
21 | 'url': 'rtmp://edge.live.hitbox.tv/live/dimak',
22 | 'only_matching': True,
23 | }]
24 |
25 | def _real_extract(self, url):
26 | video_id = compat_urllib_parse_unquote(os.path.splitext(url.rstrip('/').split('/')[-1])[0])
27 | title = compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0])
28 | return {
29 | 'id': video_id,
30 | 'title': title,
31 | 'formats': [{
32 | 'url': url,
33 | 'ext': 'flv',
34 | 'format_id': compat_urlparse.urlparse(url).scheme,
35 | }],
36 | }
37 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/fusion.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 | from .ooyala import OoyalaIE
5 |
6 |
7 | class FusionIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?fusion\.net/video/(?P\d+)'
9 | _TESTS = [{
10 | 'url': 'http://fusion.net/video/201781/u-s-and-panamanian-forces-work-together-to-stop-a-vessel-smuggling-drugs/',
11 | 'info_dict': {
12 | 'id': 'ZpcWNoMTE6x6uVIIWYpHh0qQDjxBuq5P',
13 | 'ext': 'mp4',
14 | 'title': 'U.S. and Panamanian forces work together to stop a vessel smuggling drugs',
15 | 'description': 'md5:0cc84a9943c064c0f46b128b41b1b0d7',
16 | 'duration': 140.0,
17 | },
18 | 'params': {
19 | 'skip_download': True,
20 | },
21 | 'add_ie': ['Ooyala'],
22 | }, {
23 | 'url': 'http://fusion.net/video/201781',
24 | 'only_matching': True,
25 | }]
26 |
27 | def _real_extract(self, url):
28 | display_id = self._match_id(url)
29 | webpage = self._download_webpage(url, display_id)
30 |
31 | ooyala_code = self._search_regex(
32 | r'data-video-id=(["\'])(?P.+?)\1',
33 | webpage, 'ooyala code', group='code')
34 |
35 | return OoyalaIE._build_url_result(ooyala_code)
36 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/nuevo.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 | from ..utils import (
7 | float_or_none,
8 | xpath_text
9 | )
10 |
11 |
12 | class NuevoBaseIE(InfoExtractor):
13 | def _extract_nuevo(self, config_url, video_id):
14 | config = self._download_xml(
15 | config_url, video_id, transform_source=lambda s: s.strip())
16 |
17 | title = xpath_text(config, './title', 'title', fatal=True).strip()
18 | video_id = xpath_text(config, './mediaid', default=video_id)
19 | thumbnail = xpath_text(config, ['./image', './thumb'])
20 | duration = float_or_none(xpath_text(config, './duration'))
21 |
22 | formats = []
23 | for element_name, format_id in (('file', 'sd'), ('filehd', 'hd')):
24 | video_url = xpath_text(config, element_name)
25 | if video_url:
26 | formats.append({
27 | 'url': video_url,
28 | 'format_id': format_id,
29 | })
30 | self._check_formats(formats, video_id)
31 |
32 | return {
33 | 'id': video_id,
34 | 'title': title,
35 | 'thumbnail': thumbnail,
36 | 'duration': duration,
37 | 'formats': formats
38 | }
39 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/lovehomeporn.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | import re
4 |
5 | from .nuevo import NuevoBaseIE
6 |
7 |
8 | class LoveHomePornIE(NuevoBaseIE):
9 | _VALID_URL = r'https?://(?:www\.)?lovehomeporn\.com/video/(?P\d+)(?:/(?P[^/?#&]+))?'
10 | _TEST = {
11 | 'url': 'http://lovehomeporn.com/video/48483/stunning-busty-brunette-girlfriend-sucking-and-riding-a-big-dick#menu',
12 | 'info_dict': {
13 | 'id': '48483',
14 | 'display_id': 'stunning-busty-brunette-girlfriend-sucking-and-riding-a-big-dick',
15 | 'ext': 'mp4',
16 | 'title': 'Stunning busty brunette girlfriend sucking and riding a big dick',
17 | 'age_limit': 18,
18 | 'duration': 238.47,
19 | },
20 | 'params': {
21 | 'skip_download': True,
22 | }
23 | }
24 |
25 | def _real_extract(self, url):
26 | mobj = re.match(self._VALID_URL, url)
27 | video_id = mobj.group('id')
28 | display_id = mobj.group('display_id')
29 |
30 | info = self._extract_nuevo(
31 | 'http://lovehomeporn.com/media/nuevo/config.php?key=%s' % video_id,
32 | video_id)
33 | info.update({
34 | 'display_id': display_id,
35 | 'age_limit': 18
36 | })
37 | return info
38 |
--------------------------------------------------------------------------------
/devscripts/make_supportedsites.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import unicode_literals
3 |
4 | import io
5 | import optparse
6 | import os
7 | import sys
8 |
9 |
10 | # Import youtube_dl
11 | ROOT_DIR = os.path.join(os.path.dirname(__file__), '..')
12 | sys.path.insert(0, ROOT_DIR)
13 | import youtube_dl
14 |
15 |
16 | def main():
17 | parser = optparse.OptionParser(usage='%prog OUTFILE.md')
18 | options, args = parser.parse_args()
19 | if len(args) != 1:
20 | parser.error('Expected an output filename')
21 |
22 | outfile, = args
23 |
24 | def gen_ies_md(ies):
25 | for ie in ies:
26 | ie_md = '**{0}**'.format(ie.IE_NAME)
27 | ie_desc = getattr(ie, 'IE_DESC', None)
28 | if ie_desc is False:
29 | continue
30 | if ie_desc is not None:
31 | ie_md += ': {0}'.format(ie.IE_DESC)
32 | if not ie.working():
33 | ie_md += ' (Currently broken)'
34 | yield ie_md
35 |
36 | ies = sorted(youtube_dl.gen_extractors(), key=lambda i: i.IE_NAME.lower())
37 | out = '# Supported sites\n' + ''.join(
38 | ' - ' + md + '\n'
39 | for md in gen_ies_md(ies))
40 |
41 | with io.open(outfile, 'w', encoding='utf-8') as outf:
42 | outf.write(out)
43 |
44 | if __name__ == '__main__':
45 | main()
46 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/gputechconf.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class GPUTechConfIE(InfoExtractor):
8 | _VALID_URL = r'https?://on-demand\.gputechconf\.com/gtc/2015/video/S(?P\d+)\.html'
9 | _TEST = {
10 | 'url': 'http://on-demand.gputechconf.com/gtc/2015/video/S5156.html',
11 | 'md5': 'a8862a00a0fd65b8b43acc5b8e33f798',
12 | 'info_dict': {
13 | 'id': '5156',
14 | 'ext': 'mp4',
15 | 'title': 'Coordinating More Than 3 Million CUDA Threads for Social Network Analysis',
16 | 'duration': 1219,
17 | }
18 | }
19 |
20 | def _real_extract(self, url):
21 | video_id = self._match_id(url)
22 | webpage = self._download_webpage(url, video_id)
23 |
24 | root_path = self._search_regex(
25 | r'var\s+rootPath\s*=\s*"([^"]+)', webpage, 'root path',
26 | default='http://evt.dispeak.com/nvidia/events/gtc15/')
27 | xml_file_id = self._search_regex(
28 | r'var\s+xmlFileId\s*=\s*"([^"]+)', webpage, 'xml file id')
29 |
30 | return {
31 | '_type': 'url_transparent',
32 | 'id': video_id,
33 | 'url': '%sxml/%s.xml' % (root_path, xml_file_id),
34 | 'ie_key': 'DigitallySpeaking',
35 | }
36 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/freevideo.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 | from ..utils import ExtractorError
5 |
6 |
7 | class FreeVideoIE(InfoExtractor):
8 | _VALID_URL = r'^https?://www.freevideo.cz/vase-videa/(?P[^.]+)\.html(?:$|[?#])'
9 |
10 | _TEST = {
11 | 'url': 'http://www.freevideo.cz/vase-videa/vysukany-zadecek-22033.html',
12 | 'info_dict': {
13 | 'id': 'vysukany-zadecek-22033',
14 | 'ext': 'mp4',
15 | 'title': 'vysukany-zadecek-22033',
16 | 'age_limit': 18,
17 | },
18 | 'skip': 'Blocked outside .cz',
19 | }
20 |
21 | def _real_extract(self, url):
22 | video_id = self._match_id(url)
23 | webpage, handle = self._download_webpage_handle(url, video_id)
24 | if '//www.czechav.com/' in handle.geturl():
25 | raise ExtractorError(
26 | 'Access to freevideo is blocked from your location',
27 | expected=True)
28 |
29 | video_url = self._search_regex(
30 | r'\s+url: "(http://[a-z0-9-]+.cdn.freevideo.cz/stream/.*?/video.mp4)"',
31 | webpage, 'video URL')
32 |
33 | return {
34 | 'id': video_id,
35 | 'url': video_url,
36 | 'title': video_id,
37 | 'age_limit': 18,
38 | }
39 |
--------------------------------------------------------------------------------
/devscripts/gh-pages/add-version.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from __future__ import unicode_literals
3 |
4 | import json
5 | import sys
6 | import hashlib
7 | import os.path
8 |
9 |
10 | if len(sys.argv) <= 1:
11 | print('Specify the version number as parameter')
12 | sys.exit()
13 | version = sys.argv[1]
14 |
15 | with open('update/LATEST_VERSION', 'w') as f:
16 | f.write(version)
17 |
18 | versions_info = json.load(open('update/versions.json'))
19 | if 'signature' in versions_info:
20 | del versions_info['signature']
21 |
22 | new_version = {}
23 |
24 | filenames = {
25 | 'bin': 'youtube-dl',
26 | 'exe': 'youtube-dl.exe',
27 | 'tar': 'youtube-dl-%s.tar.gz' % version}
28 | build_dir = os.path.join('..', '..', 'build', version)
29 | for key, filename in filenames.items():
30 | url = 'https://yt-dl.org/downloads/%s/%s' % (version, filename)
31 | fn = os.path.join(build_dir, filename)
32 | with open(fn, 'rb') as f:
33 | data = f.read()
34 | if not data:
35 | raise ValueError('File %s is empty!' % fn)
36 | sha256sum = hashlib.sha256(data).hexdigest()
37 | new_version[key] = (url, sha256sum)
38 |
39 | versions_info['versions'][version] = new_version
40 | versions_info['latest'] = version
41 |
42 | with open('update/versions.json', 'w') as jsonf:
43 | json.dump(versions_info, jsonf, indent=4, sort_keys=True)
44 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/restudy.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from .common import InfoExtractor
5 |
6 |
7 | class RestudyIE(InfoExtractor):
8 | _VALID_URL = r'https?://(?:www\.)?restudy\.dk/video/play/id/(?P[0-9]+)'
9 | _TEST = {
10 | 'url': 'https://www.restudy.dk/video/play/id/1637',
11 | 'info_dict': {
12 | 'id': '1637',
13 | 'ext': 'flv',
14 | 'title': 'Leiden-frosteffekt',
15 | 'description': 'Denne video er et eksperiment med flydende kvælstof.',
16 | },
17 | 'params': {
18 | # rtmp download
19 | 'skip_download': True,
20 | }
21 | }
22 |
23 | def _real_extract(self, url):
24 | video_id = self._match_id(url)
25 |
26 | webpage = self._download_webpage(url, video_id)
27 |
28 | title = self._og_search_title(webpage).strip()
29 | description = self._og_search_description(webpage).strip()
30 |
31 | formats = self._extract_smil_formats(
32 | 'https://www.restudy.dk/awsmedia/SmilDirectory/video_%s.xml' % video_id,
33 | video_id)
34 | self._sort_formats(formats)
35 |
36 | return {
37 | 'id': video_id,
38 | 'title': title,
39 | 'description': description,
40 | 'formats': formats,
41 | }
42 |
--------------------------------------------------------------------------------
/youtube_dl/extractor/hentaistigma.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from .common import InfoExtractor
4 |
5 |
6 | class HentaiStigmaIE(InfoExtractor):
7 | _VALID_URL = r'^https?://hentai\.animestigma\.com/(?P[^/]+)'
8 | _TEST = {
9 | 'url': 'http://hentai.animestigma.com/inyouchuu-etsu-bonus/',
10 | 'md5': '4e3d07422a68a4cc363d8f57c8bf0d23',
11 | 'info_dict': {
12 | 'id': 'inyouchuu-etsu-bonus',
13 | 'ext': 'mp4',
14 | 'title': 'Inyouchuu Etsu Bonus',
15 | 'age_limit': 18,
16 | }
17 | }
18 |
19 | def _real_extract(self, url):
20 | video_id = self._match_id(url)
21 |
22 | webpage = self._download_webpage(url, video_id)
23 |
24 | title = self._html_search_regex(
25 | r']+class="posttitle"[^>]*>]*>([^<]+)',
26 | webpage, 'title')
27 | wrap_url = self._html_search_regex(
28 | r'