Compare commits
148 Commits
2014.10.23
...
2014.11.02
Author | SHA1 | Date | |
---|---|---|---|
ffae28ae18 | |||
d9116714f2 | |||
08965906a8 | |||
5263cdfcf9 | |||
b2a68d14cf | |||
6e1cff9c33 | |||
72975729c8 | |||
d319948b6a | |||
9a4bf889f9 | |||
2a834bdb21 | |||
0d2c141865 | |||
5ec39d8b96 | |||
7b6de3728a | |||
a51d3aa001 | |||
2c8e03d937 | |||
fbb21cf528 | |||
b8a618f898 | |||
feb74960eb | |||
d65d628613 | |||
ac645ac7d0 | |||
7d11297f3f | |||
6ad4013d40 | |||
dbd1283d31 | |||
c451d4f553 | |||
8abec2c8bb | |||
a9bad429b3 | |||
50c8266ef0 | |||
00edd4f9be | |||
ee966928af | |||
e5193599ec | |||
01d663bca3 | |||
e0c51cdadc | |||
9334f8f17a | |||
632256d9ec | |||
03df7baa6a | |||
3511266bc3 | |||
9fdece5d34 | |||
bbf1092ad0 | |||
9ef55c5bbc | |||
48a24ab746 | |||
68acdbda9d | |||
27c542c06f | |||
aaa399d2f6 | |||
b2e6a1c14c | |||
8cc3eba79a | |||
b0fb6d4db1 | |||
81515ad9f6 | |||
8112d4b284 | |||
bf7aa6301b | |||
aea856621f | |||
f24a5a2faa | |||
ecfe623422 | |||
4a6c94288a | |||
10e3d73472 | |||
15956b5aa1 | |||
586f7082ef | |||
d6d9186f0d | |||
2e9ff8f362 | |||
6407432333 | |||
f744c0f398 | |||
249efaf44b | |||
8d32abff9e | |||
94f052cbf4 | |||
446a03bd96 | |||
6009b69f81 | |||
3d6047113c | |||
9dec99303d | |||
7706927370 | |||
3adba6fa2a | |||
f46a8702cc | |||
8d11b59bbb | |||
cf501a23d2 | |||
2bcae58d46 | |||
c9f08154a3 | |||
526b276fd7 | |||
77ec444d9a | |||
bfc2bedcfc | |||
83855f3a1f | |||
50b51830fb | |||
3d6eed9b52 | |||
1a253e134c | |||
6194bb1419 | |||
37d66e7f1e | |||
70b7e3fbb6 | |||
579657ad87 | |||
5f82b129e0 | |||
64269e4d01 | |||
d481699a7a | |||
5894a4f4ee | |||
09e5d6a6e5 | |||
274b12b5a8 | |||
23be51d8ce | |||
488447455d | |||
d28b517154 | |||
a7e97f6db1 | |||
639a422d21 | |||
f889cea109 | |||
1bdeb7be2e | |||
699151bcb1 | |||
911344e5ac | |||
03936f6e6d | |||
b13ccb1b87 | |||
f64f8a4662 | |||
681b9caa9c | |||
0eb9fb9f24 | |||
9a76f416ce | |||
603821161f | |||
d3c72db894 | |||
43d9718fb9 | |||
7fc54e5262 | |||
ec9c978481 | |||
d36cae46d8 | |||
fdfefa1b9c | |||
724d031893 | |||
63e0be3415 | |||
c64ed2a310 | |||
cdc5cb7c2b | |||
8efd06aa42 | |||
7f9ced64cb | |||
7608815cc2 | |||
5823eda139 | |||
e82c1e9a6e | |||
1ede5b2481 | |||
964ae0a122 | |||
98e1d28982 | |||
2c26df763c | |||
018e835594 | |||
e65e06fbe2 | |||
95ee84421e | |||
2acfe95f58 | |||
b5a14350b9 | |||
8d81f872fb | |||
36f1c90497 | |||
057a5206cc | |||
9e9bc793f3 | |||
5c565ac9e7 | |||
67500bf939 | |||
b1edd7a48a | |||
2c63ccec78 | |||
f2f2c0c2c6 | |||
bfd91588f3 | |||
1b10a011ec | |||
8230018c20 | |||
ce519b747e | |||
a86c73cf80 | |||
3741302a10 | |||
fc66e4a0d5 | |||
4644ac5527 |
82
AUTHORS
Normal file
82
AUTHORS
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
Ricardo Garcia Gonzalez
|
||||||
|
Danny Colligan
|
||||||
|
Benjamin Johnson
|
||||||
|
Vasyl' Vavrychuk
|
||||||
|
Witold Baryluk
|
||||||
|
Paweł Paprota
|
||||||
|
Gergely Imreh
|
||||||
|
Rogério Brito
|
||||||
|
Philipp Hagemeister
|
||||||
|
Sören Schulze
|
||||||
|
Kevin Ngo
|
||||||
|
Ori Avtalion
|
||||||
|
shizeeg
|
||||||
|
Filippo Valsorda
|
||||||
|
Christian Albrecht
|
||||||
|
Dave Vasilevsky
|
||||||
|
Jaime Marquínez Ferrándiz
|
||||||
|
Jeff Crouse
|
||||||
|
Osama Khalid
|
||||||
|
Michael Walter
|
||||||
|
M. Yasoob Ullah Khalid
|
||||||
|
Julien Fraichard
|
||||||
|
Johny Mo Swag
|
||||||
|
Axel Noack
|
||||||
|
Albert Kim
|
||||||
|
Pierre Rudloff
|
||||||
|
Huarong Huo
|
||||||
|
Ismael Mejía
|
||||||
|
Steffan 'Ruirize' James
|
||||||
|
Andras Elso
|
||||||
|
Jelle van der Waa
|
||||||
|
Marcin Cieślak
|
||||||
|
Anton Larionov
|
||||||
|
Takuya Tsuchida
|
||||||
|
Sergey M.
|
||||||
|
Michael Orlitzky
|
||||||
|
Chris Gahan
|
||||||
|
Saimadhav Heblikar
|
||||||
|
Mike Col
|
||||||
|
Oleg Prutz
|
||||||
|
pulpe
|
||||||
|
Andreas Schmitz
|
||||||
|
Michael Kaiser
|
||||||
|
Niklas Laxström
|
||||||
|
David Triendl
|
||||||
|
Anthony Weems
|
||||||
|
David Wagner
|
||||||
|
Juan C. Olivares
|
||||||
|
Mattias Harrysson
|
||||||
|
phaer
|
||||||
|
Sainyam Kapoor
|
||||||
|
Nicolas Évrard
|
||||||
|
Jason Normore
|
||||||
|
Hoje Lee
|
||||||
|
Adam Thalhammer
|
||||||
|
Georg Jähnig
|
||||||
|
Ralf Haring
|
||||||
|
Koki Takahashi
|
||||||
|
Ariset Llerena
|
||||||
|
Adam Malcontenti-Wilson
|
||||||
|
Tobias Bell
|
||||||
|
Naglis Jonaitis
|
||||||
|
Charles Chen
|
||||||
|
Hassaan Ali
|
||||||
|
Dobrosław Żybort
|
||||||
|
David Fabijan
|
||||||
|
Sebastian Haas
|
||||||
|
Alexander Kirk
|
||||||
|
Erik Johnson
|
||||||
|
Keith Beckman
|
||||||
|
Ole Ernst
|
||||||
|
Aaron McDaniel (mcd1992)
|
||||||
|
Magnus Kolstad
|
||||||
|
Hari Padmanaban
|
||||||
|
Carlos Ramos
|
||||||
|
5moufl
|
||||||
|
lenaten
|
||||||
|
Dennis Scheiba
|
||||||
|
Damon Timm
|
||||||
|
winwon
|
||||||
|
Xavier Beynon
|
||||||
|
Gabriel Schubiner
|
2
Makefile
2
Makefile
@ -1,7 +1,7 @@
|
|||||||
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
|
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish
|
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part
|
||||||
|
|
||||||
cleanall: clean
|
cleanall: clean
|
||||||
rm -f youtube-dl youtube-dl.exe
|
rm -f youtube-dl youtube-dl.exe
|
||||||
|
22
README.md
22
README.md
@ -69,6 +69,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
configuration in ~/.config/youtube-dl.conf
|
configuration in ~/.config/youtube-dl.conf
|
||||||
(%APPDATA%/youtube-dl/config.txt on
|
(%APPDATA%/youtube-dl/config.txt on
|
||||||
Windows)
|
Windows)
|
||||||
|
--flat-playlist Do not extract the videos of a playlist,
|
||||||
|
only list them.
|
||||||
|
|
||||||
## Video Selection:
|
## Video Selection:
|
||||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||||
@ -197,6 +199,10 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
-j, --dump-json simulate, quiet but print JSON information.
|
-j, --dump-json simulate, quiet but print JSON information.
|
||||||
See --output for a description of available
|
See --output for a description of available
|
||||||
keys.
|
keys.
|
||||||
|
-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 information in a single line.
|
||||||
--newline output progress bar as new lines
|
--newline output progress bar as new lines
|
||||||
--no-progress do not print progress bar
|
--no-progress do not print progress bar
|
||||||
--console-title display progress in console titlebar
|
--console-title display progress in console titlebar
|
||||||
@ -375,7 +381,7 @@ Again, from then on you'll be able to update with `sudo youtube-dl -U`.
|
|||||||
|
|
||||||
YouTube changed their playlist format in March 2014 and later on, so you'll need at least youtube-dl 2014.07.25 to download all YouTube videos.
|
YouTube changed their playlist format in March 2014 and later on, so you'll need at least youtube-dl 2014.07.25 to download all YouTube videos.
|
||||||
|
|
||||||
If you have installed youtube-dl with a package manager, pip, setup.py or a tarball, please use that to update. Note that Ubuntu packages do not seem to get updated anymore. Since we are not affiliated with Ubuntu, there is little we can do. Feel free to report bugs to the Ubuntu packaging guys - all they have to do is update the package to a somewhat recent version. See above for a way to update.
|
If you have installed youtube-dl with a package manager, pip, setup.py or a tarball, please use that to update. Note that Ubuntu packages do not seem to get updated anymore. Since we are not affiliated with Ubuntu, there is little we can do. Feel free to [report bugs](https://bugs.launchpad.net/ubuntu/+source/youtube-dl/+filebug) to the [Ubuntu packaging guys](mailto:ubuntu-motu@lists.ubuntu.com?subject=outdated%20version%20of%20youtube-dl) - all they have to do is update the package to a somewhat recent version. See above for a way to update.
|
||||||
|
|
||||||
### Do I always have to pass in `--max-quality FORMAT`, or `-citw`?
|
### Do I always have to pass in `--max-quality FORMAT`, or `-citw`?
|
||||||
|
|
||||||
@ -505,6 +511,20 @@ If you want to add support for a new site, you can follow this quick list (assum
|
|||||||
|
|
||||||
In any case, thank you very much for your contributions!
|
In any case, thank you very much for your contributions!
|
||||||
|
|
||||||
|
# EMBEDDING YOUTUBE-DL
|
||||||
|
|
||||||
|
youtube-dl makes the best effort to be a good command-line program, and thus should be callable from any programming language. If you encounter any problems parsing its output, feel free to [create a report](https://github.com/rg3/youtube-dl/issues/new).
|
||||||
|
|
||||||
|
From a Python program, you can embed youtube-dl in a more powerful fashion, like this:
|
||||||
|
|
||||||
|
import youtube_dl
|
||||||
|
|
||||||
|
ydl_opts = {}
|
||||||
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
|
||||||
|
|
||||||
|
Most likely, you'll want to use various options. For a list of what can be done, have a look at [youtube_dl/YoutubeDL.py](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L69). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
|
||||||
|
|
||||||
# BUGS
|
# BUGS
|
||||||
|
|
||||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email.
|
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email.
|
||||||
|
@ -44,8 +44,8 @@ copyright = u'2014, Ricardo Garcia Gonzalez'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
import youtube_dl
|
from youtube_dl.version import __version__
|
||||||
version = youtube_dl.__version__
|
version = __version__
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ def expect_info_dict(self, expected_dict, got_dict):
|
|||||||
info_dict_str = ''.join(
|
info_dict_str = ''.join(
|
||||||
' %s: %s,\n' % (_repr(k), _repr(v))
|
' %s: %s,\n' % (_repr(k), _repr(v))
|
||||||
for k, v in test_info_dict.items())
|
for k, v in test_info_dict.items())
|
||||||
write_string('\n"info_dict": {' + info_dict_str + '}\n', out=sys.stderr)
|
write_string('\n"info_dict": {\n' + info_dict_str + '}\n', out=sys.stderr)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
missing_keys,
|
missing_keys,
|
||||||
'Missing keys in test definition: %s' % (
|
'Missing keys in test definition: %s' % (
|
||||||
@ -171,3 +171,13 @@ def assertGreaterEqual(self, got, expected, msg=None):
|
|||||||
if msg is None:
|
if msg is None:
|
||||||
msg = '%r not greater than or equal to %r' % (got, expected)
|
msg = '%r not greater than or equal to %r' % (got, expected)
|
||||||
self.assertTrue(got >= expected, msg)
|
self.assertTrue(got >= expected, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def expect_warnings(ydl, warnings_re):
|
||||||
|
real_warning = ydl.report_warning
|
||||||
|
|
||||||
|
def _report_warning(w):
|
||||||
|
if not any(re.search(w_re, w) for w_re in warnings_re):
|
||||||
|
real_warning(w)
|
||||||
|
|
||||||
|
ydl.report_warning = _report_warning
|
||||||
|
@ -8,6 +8,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
|
|
||||||
from test.helper import (
|
from test.helper import (
|
||||||
assertGreaterEqual,
|
assertGreaterEqual,
|
||||||
|
expect_warnings,
|
||||||
get_params,
|
get_params,
|
||||||
gettestcases,
|
gettestcases,
|
||||||
expect_info_dict,
|
expect_info_dict,
|
||||||
@ -100,6 +101,7 @@ def generator(test_case):
|
|||||||
if status['status'] == 'finished':
|
if status['status'] == 'finished':
|
||||||
finished_hook_called.add(status['filename'])
|
finished_hook_called.add(status['filename'])
|
||||||
ydl.add_progress_hook(_hook)
|
ydl.add_progress_hook(_hook)
|
||||||
|
expect_warnings(ydl, test_case.get('expected_warnings', []))
|
||||||
|
|
||||||
def get_tc_filename(tc):
|
def get_tc_filename(tc):
|
||||||
return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
|
return tc.get('file') or ydl.prepare_filename(tc.get('info_dict', {}))
|
||||||
@ -183,7 +185,9 @@ def generator(test_case):
|
|||||||
md5_for_file = _file_md5(tc_filename)
|
md5_for_file = _file_md5(tc_filename)
|
||||||
self.assertEqual(md5_for_file, tc['md5'])
|
self.assertEqual(md5_for_file, tc['md5'])
|
||||||
info_json_fn = os.path.splitext(tc_filename)[0] + '.info.json'
|
info_json_fn = os.path.splitext(tc_filename)[0] + '.info.json'
|
||||||
self.assertTrue(os.path.exists(info_json_fn))
|
self.assertTrue(
|
||||||
|
os.path.exists(info_json_fn),
|
||||||
|
'Missing info file %s' % info_json_fn)
|
||||||
with io.open(info_json_fn, encoding='utf-8') as infof:
|
with io.open(info_json_fn, encoding='utf-8') as infof:
|
||||||
info_dict = json.load(infof)
|
info_dict = json.load(infof)
|
||||||
|
|
||||||
|
@ -45,6 +45,9 @@ from youtube_dl.utils import (
|
|||||||
escape_rfc3986,
|
escape_rfc3986,
|
||||||
escape_url,
|
escape_url,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
|
get_filesystem_encoding,
|
||||||
|
compat_getenv,
|
||||||
|
compat_expanduser,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -286,6 +289,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(parse_iso8601('2014-03-23T23:04:26+0100'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T23:04:26+0100'), 1395612266)
|
||||||
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
|
||||||
self.assertEqual(parse_iso8601('2014-03-23T22:04:26Z'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26Z'), 1395612266)
|
||||||
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26.1234Z'), 1395612266)
|
||||||
|
|
||||||
def test_strip_jsonp(self):
|
def test_strip_jsonp(self):
|
||||||
stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);')
|
stripped = strip_jsonp('cb ([ {"id":"532cb",\n\n\n"x":\n3}\n]\n);')
|
||||||
@ -355,5 +359,17 @@ class TestUtil(unittest.TestCase):
|
|||||||
on = js_to_json('{"abc": true}')
|
on = js_to_json('{"abc": true}')
|
||||||
self.assertEqual(json.loads(on), {'abc': True})
|
self.assertEqual(json.loads(on), {'abc': True})
|
||||||
|
|
||||||
|
def test_compat_getenv(self):
|
||||||
|
test_str = 'тест'
|
||||||
|
os.environ['YOUTUBE-DL-TEST'] = (test_str if sys.version_info >= (3, 0)
|
||||||
|
else test_str.encode(get_filesystem_encoding()))
|
||||||
|
self.assertEqual(compat_getenv('YOUTUBE-DL-TEST'), test_str)
|
||||||
|
|
||||||
|
def test_compat_expanduser(self):
|
||||||
|
test_str = 'C:\Documents and Settings\тест\Application Data'
|
||||||
|
os.environ['HOME'] = (test_str if sys.version_info >= (3, 0)
|
||||||
|
else test_str.encode(get_filesystem_encoding()))
|
||||||
|
self.assertEqual(compat_expanduser('~'), test_str)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -24,6 +24,7 @@ if os.name == 'nt':
|
|||||||
|
|
||||||
from .utils import (
|
from .utils import (
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
|
compat_expanduser,
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
@ -61,7 +62,7 @@ from .utils import (
|
|||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .extractor import get_info_extractor, gen_extractors
|
from .extractor import get_info_extractor, gen_extractors
|
||||||
from .downloader import get_suitable_downloader
|
from .downloader import get_suitable_downloader
|
||||||
from .postprocessor import FFmpegMergerPP
|
from .postprocessor import FFmpegMergerPP, FFmpegPostProcessor
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
||||||
|
|
||||||
@ -107,6 +108,8 @@ class YoutubeDL(object):
|
|||||||
forcefilename: Force printing final filename.
|
forcefilename: Force printing final filename.
|
||||||
forceduration: Force printing duration.
|
forceduration: Force printing duration.
|
||||||
forcejson: Force printing info_dict as JSON.
|
forcejson: Force printing info_dict as JSON.
|
||||||
|
dump_single_json: Force printing the info_dict of the whole playlist
|
||||||
|
(or video) as a single JSON line.
|
||||||
simulate: Do not download the video files.
|
simulate: Do not download the video files.
|
||||||
format: Video format code.
|
format: Video format code.
|
||||||
format_limit: Highest quality format to try.
|
format_limit: Highest quality format to try.
|
||||||
@ -165,6 +168,8 @@ class YoutubeDL(object):
|
|||||||
'auto' for elaborate guessing
|
'auto' for elaborate guessing
|
||||||
encoding: Use this encoding instead of the system-specified.
|
encoding: Use this encoding instead of the system-specified.
|
||||||
extract_flat: Do not resolve URLs, return the immediate result.
|
extract_flat: Do not resolve URLs, return the immediate result.
|
||||||
|
Pass in 'in_playlist' to only show this behavior for
|
||||||
|
playlist items.
|
||||||
|
|
||||||
The following parameters are not used by YoutubeDL itself, they are used by
|
The following parameters are not used by YoutubeDL itself, they are used by
|
||||||
the FileDownloader:
|
the FileDownloader:
|
||||||
@ -184,7 +189,7 @@ class YoutubeDL(object):
|
|||||||
_num_downloads = None
|
_num_downloads = None
|
||||||
_screen_file = None
|
_screen_file = None
|
||||||
|
|
||||||
def __init__(self, params=None):
|
def __init__(self, params=None, auto_init=True):
|
||||||
"""Create a FileDownloader object with the given options."""
|
"""Create a FileDownloader object with the given options."""
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
@ -241,6 +246,10 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
self._setup_opener()
|
self._setup_opener()
|
||||||
|
|
||||||
|
if auto_init:
|
||||||
|
self.print_debug_header()
|
||||||
|
self.add_default_info_extractors()
|
||||||
|
|
||||||
def add_info_extractor(self, ie):
|
def add_info_extractor(self, ie):
|
||||||
"""Add an InfoExtractor object to the end of the list."""
|
"""Add an InfoExtractor object to the end of the list."""
|
||||||
self._ies.append(ie)
|
self._ies.append(ie)
|
||||||
@ -447,7 +456,7 @@ class YoutubeDL(object):
|
|||||||
template_dict = collections.defaultdict(lambda: 'NA', template_dict)
|
template_dict = collections.defaultdict(lambda: 'NA', template_dict)
|
||||||
|
|
||||||
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||||
tmpl = os.path.expanduser(outtmpl)
|
tmpl = compat_expanduser(outtmpl)
|
||||||
filename = tmpl % template_dict
|
filename = tmpl % template_dict
|
||||||
return filename
|
return filename
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
@ -568,8 +577,12 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
result_type = ie_result.get('_type', 'video')
|
result_type = ie_result.get('_type', 'video')
|
||||||
|
|
||||||
if self.params.get('extract_flat', False):
|
if result_type in ('url', 'url_transparent'):
|
||||||
if result_type in ('url', 'url_transparent'):
|
extract_flat = self.params.get('extract_flat', False)
|
||||||
|
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info) or
|
||||||
|
extract_flat is True):
|
||||||
|
if self.params.get('forcejson', False):
|
||||||
|
self.to_stdout(json.dumps(ie_result))
|
||||||
return ie_result
|
return ie_result
|
||||||
|
|
||||||
if result_type == 'video':
|
if result_type == 'video':
|
||||||
@ -897,6 +910,8 @@ class YoutubeDL(object):
|
|||||||
if self.params.get('forcejson', False):
|
if self.params.get('forcejson', False):
|
||||||
info_dict['_filename'] = filename
|
info_dict['_filename'] = filename
|
||||||
self.to_stdout(json.dumps(info_dict))
|
self.to_stdout(json.dumps(info_dict))
|
||||||
|
if self.params.get('dump_single_json', False):
|
||||||
|
info_dict['_filename'] = filename
|
||||||
|
|
||||||
# Do nothing else if in simulate mode
|
# Do nothing else if in simulate mode
|
||||||
if self.params.get('simulate', False):
|
if self.params.get('simulate', False):
|
||||||
@ -1015,7 +1030,7 @@ class YoutubeDL(object):
|
|||||||
downloaded = []
|
downloaded = []
|
||||||
success = True
|
success = True
|
||||||
merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
|
merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
|
||||||
if not merger._get_executable():
|
if not merger._executable:
|
||||||
postprocessors = []
|
postprocessors = []
|
||||||
self.report_warning('You have requested multiple '
|
self.report_warning('You have requested multiple '
|
||||||
'formats but ffmpeg or avconv are not installed.'
|
'formats but ffmpeg or avconv are not installed.'
|
||||||
@ -1064,12 +1079,15 @@ class YoutubeDL(object):
|
|||||||
for url in url_list:
|
for url in url_list:
|
||||||
try:
|
try:
|
||||||
#It also downloads the videos
|
#It also downloads the videos
|
||||||
self.extract_info(url)
|
res = self.extract_info(url)
|
||||||
except UnavailableVideoError:
|
except UnavailableVideoError:
|
||||||
self.report_error('unable to download video')
|
self.report_error('unable to download video')
|
||||||
except MaxDownloadsReached:
|
except MaxDownloadsReached:
|
||||||
self.to_screen('[info] Maximum number of downloaded files reached.')
|
self.to_screen('[info] Maximum number of downloaded files reached.')
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
if self.params.get('dump_single_json', False):
|
||||||
|
self.to_stdout(json.dumps(res))
|
||||||
|
|
||||||
return self._download_retcode
|
return self._download_retcode
|
||||||
|
|
||||||
@ -1193,6 +1211,8 @@ class YoutubeDL(object):
|
|||||||
res += 'video@'
|
res += 'video@'
|
||||||
if fdict.get('vbr') is not None:
|
if fdict.get('vbr') is not None:
|
||||||
res += '%4dk' % fdict['vbr']
|
res += '%4dk' % fdict['vbr']
|
||||||
|
if fdict.get('fps') is not None:
|
||||||
|
res += ', %sfps' % fdict['fps']
|
||||||
if fdict.get('acodec') is not None:
|
if fdict.get('acodec') is not None:
|
||||||
if res:
|
if res:
|
||||||
res += ', '
|
res += ', '
|
||||||
@ -1297,8 +1317,18 @@ class YoutubeDL(object):
|
|||||||
sys.exc_clear()
|
sys.exc_clear()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self._write_string('[debug] Python version %s - %s' %
|
self._write_string('[debug] Python version %s - %s\n' % (
|
||||||
(platform.python_version(), platform_name()) + '\n')
|
platform.python_version(), platform_name()))
|
||||||
|
|
||||||
|
exe_versions = FFmpegPostProcessor.get_versions()
|
||||||
|
exe_str = ', '.join(
|
||||||
|
'%s %s' % (exe, v)
|
||||||
|
for exe, v in sorted(exe_versions.items())
|
||||||
|
if v
|
||||||
|
)
|
||||||
|
if not exe_str:
|
||||||
|
exe_str = 'none'
|
||||||
|
self._write_string('[debug] exe versions: %s\n' % exe_str)
|
||||||
|
|
||||||
proxy_map = {}
|
proxy_map = {}
|
||||||
for handler in self._opener.handlers:
|
for handler in self._opener.handlers:
|
||||||
|
@ -1,86 +1,6 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__authors__ = (
|
|
||||||
'Ricardo Garcia Gonzalez',
|
|
||||||
'Danny Colligan',
|
|
||||||
'Benjamin Johnson',
|
|
||||||
'Vasyl\' Vavrychuk',
|
|
||||||
'Witold Baryluk',
|
|
||||||
'Paweł Paprota',
|
|
||||||
'Gergely Imreh',
|
|
||||||
'Rogério Brito',
|
|
||||||
'Philipp Hagemeister',
|
|
||||||
'Sören Schulze',
|
|
||||||
'Kevin Ngo',
|
|
||||||
'Ori Avtalion',
|
|
||||||
'shizeeg',
|
|
||||||
'Filippo Valsorda',
|
|
||||||
'Christian Albrecht',
|
|
||||||
'Dave Vasilevsky',
|
|
||||||
'Jaime Marquínez Ferrándiz',
|
|
||||||
'Jeff Crouse',
|
|
||||||
'Osama Khalid',
|
|
||||||
'Michael Walter',
|
|
||||||
'M. Yasoob Ullah Khalid',
|
|
||||||
'Julien Fraichard',
|
|
||||||
'Johny Mo Swag',
|
|
||||||
'Axel Noack',
|
|
||||||
'Albert Kim',
|
|
||||||
'Pierre Rudloff',
|
|
||||||
'Huarong Huo',
|
|
||||||
'Ismael Mejía',
|
|
||||||
'Steffan \'Ruirize\' James',
|
|
||||||
'Andras Elso',
|
|
||||||
'Jelle van der Waa',
|
|
||||||
'Marcin Cieślak',
|
|
||||||
'Anton Larionov',
|
|
||||||
'Takuya Tsuchida',
|
|
||||||
'Sergey M.',
|
|
||||||
'Michael Orlitzky',
|
|
||||||
'Chris Gahan',
|
|
||||||
'Saimadhav Heblikar',
|
|
||||||
'Mike Col',
|
|
||||||
'Oleg Prutz',
|
|
||||||
'pulpe',
|
|
||||||
'Andreas Schmitz',
|
|
||||||
'Michael Kaiser',
|
|
||||||
'Niklas Laxström',
|
|
||||||
'David Triendl',
|
|
||||||
'Anthony Weems',
|
|
||||||
'David Wagner',
|
|
||||||
'Juan C. Olivares',
|
|
||||||
'Mattias Harrysson',
|
|
||||||
'phaer',
|
|
||||||
'Sainyam Kapoor',
|
|
||||||
'Nicolas Évrard',
|
|
||||||
'Jason Normore',
|
|
||||||
'Hoje Lee',
|
|
||||||
'Adam Thalhammer',
|
|
||||||
'Georg Jähnig',
|
|
||||||
'Ralf Haring',
|
|
||||||
'Koki Takahashi',
|
|
||||||
'Ariset Llerena',
|
|
||||||
'Adam Malcontenti-Wilson',
|
|
||||||
'Tobias Bell',
|
|
||||||
'Naglis Jonaitis',
|
|
||||||
'Charles Chen',
|
|
||||||
'Hassaan Ali',
|
|
||||||
'Dobrosław Żybort',
|
|
||||||
'David Fabijan',
|
|
||||||
'Sebastian Haas',
|
|
||||||
'Alexander Kirk',
|
|
||||||
'Erik Johnson',
|
|
||||||
'Keith Beckman',
|
|
||||||
'Ole Ernst',
|
|
||||||
'Aaron McDaniel (mcd1992)',
|
|
||||||
'Magnus Kolstad',
|
|
||||||
'Hari Padmanaban',
|
|
||||||
'Carlos Ramos',
|
|
||||||
'5moufl',
|
|
||||||
'lenaten',
|
|
||||||
)
|
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
@ -94,6 +14,7 @@ from .options import (
|
|||||||
parseOpts,
|
parseOpts,
|
||||||
)
|
)
|
||||||
from .utils import (
|
from .utils import (
|
||||||
|
compat_expanduser,
|
||||||
compat_getpass,
|
compat_getpass,
|
||||||
compat_print,
|
compat_print,
|
||||||
DateRange,
|
DateRange,
|
||||||
@ -255,8 +176,6 @@ def _real_main(argv=None):
|
|||||||
date = DateRange.day(opts.date)
|
date = DateRange.day(opts.date)
|
||||||
else:
|
else:
|
||||||
date = DateRange(opts.dateafter, opts.datebefore)
|
date = DateRange(opts.dateafter, opts.datebefore)
|
||||||
if opts.default_search not in ('auto', 'auto_warning', 'error', 'fixup_error', None) and ':' not in opts.default_search:
|
|
||||||
parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
|
|
||||||
|
|
||||||
# Do not download videos when there are audio-only formats
|
# Do not download videos when there are audio-only formats
|
||||||
if opts.extractaudio and not opts.keepvideo and opts.format is None:
|
if opts.extractaudio and not opts.keepvideo and opts.format is None:
|
||||||
@ -284,8 +203,8 @@ def _real_main(argv=None):
|
|||||||
u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
||||||
u' template'.format(outtmpl))
|
u' template'.format(outtmpl))
|
||||||
|
|
||||||
any_printing = 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
|
any_printing = 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
|
||||||
download_archive_fn = os.path.expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
|
download_archive_fn = compat_expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
|
||||||
|
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'usenetrc': opts.usenetrc,
|
'usenetrc': opts.usenetrc,
|
||||||
@ -304,8 +223,9 @@ def _real_main(argv=None):
|
|||||||
'forcefilename': opts.getfilename,
|
'forcefilename': opts.getfilename,
|
||||||
'forceformat': opts.getformat,
|
'forceformat': opts.getformat,
|
||||||
'forcejson': opts.dumpjson,
|
'forcejson': opts.dumpjson,
|
||||||
'simulate': opts.simulate,
|
'dump_single_json': opts.dump_single_json,
|
||||||
'skip_download': (opts.skip_download or opts.simulate or any_printing),
|
'simulate': opts.simulate or any_printing,
|
||||||
|
'skip_download': opts.skip_download,
|
||||||
'format': opts.format,
|
'format': opts.format,
|
||||||
'format_limit': opts.format_limit,
|
'format_limit': opts.format_limit,
|
||||||
'listformats': opts.listformats,
|
'listformats': opts.listformats,
|
||||||
@ -369,12 +289,10 @@ def _real_main(argv=None):
|
|||||||
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
||||||
'encoding': opts.encoding,
|
'encoding': opts.encoding,
|
||||||
'exec_cmd': opts.exec_cmd,
|
'exec_cmd': opts.exec_cmd,
|
||||||
|
'extract_flat': opts.extract_flat,
|
||||||
}
|
}
|
||||||
|
|
||||||
with YoutubeDL(ydl_opts) as ydl:
|
with YoutubeDL(ydl_opts) as ydl:
|
||||||
ydl.print_debug_header()
|
|
||||||
ydl.add_default_info_extractors()
|
|
||||||
|
|
||||||
# PostProcessors
|
# PostProcessors
|
||||||
# Add the metadata pp first, the other pps will copy it
|
# Add the metadata pp first, the other pps will copy it
|
||||||
if opts.addmetadata:
|
if opts.addmetadata:
|
||||||
|
@ -9,6 +9,7 @@ import shutil
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from .utils import (
|
from .utils import (
|
||||||
|
compat_expanduser,
|
||||||
write_json_file,
|
write_json_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ class Cache(object):
|
|||||||
if res is None:
|
if res is None:
|
||||||
cache_root = os.environ.get('XDG_CACHE_HOME', '~/.cache')
|
cache_root = os.environ.get('XDG_CACHE_HOME', '~/.cache')
|
||||||
res = os.path.join(cache_root, 'youtube-dl')
|
res = os.path.join(cache_root, 'youtube-dl')
|
||||||
return os.path.expanduser(res)
|
return compat_expanduser(res)
|
||||||
|
|
||||||
def _get_cache_fn(self, section, key, dtype):
|
def _get_cache_fn(self, section, key, dtype):
|
||||||
assert re.match(r'^[a-zA-Z0-9_.-]+$', section), \
|
assert re.match(r'^[a-zA-Z0-9_.-]+$', section), \
|
||||||
|
@ -244,9 +244,16 @@ class F4mFD(FileDownloader):
|
|||||||
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
||||||
|
|
||||||
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
||||||
bootstrap = base64.b64decode(doc.find(_add_ns('bootstrapInfo')).text)
|
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
||||||
|
if bootstrap_node.text is None:
|
||||||
|
bootstrap_url = compat_urlparse.urljoin(
|
||||||
|
base_url, bootstrap_node.attrib['url'])
|
||||||
|
bootstrap = self.ydl.urlopen(bootstrap_url).read()
|
||||||
|
else:
|
||||||
|
bootstrap = base64.b64decode(bootstrap_node.text)
|
||||||
metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
|
metadata = base64.b64decode(media.find(_add_ns('metadata')).text)
|
||||||
boot_info = read_bootstrap_info(bootstrap)
|
boot_info = read_bootstrap_info(bootstrap)
|
||||||
|
|
||||||
fragments_list = build_fragments_list(boot_info)
|
fragments_list = build_fragments_list(boot_info)
|
||||||
if self.params.get('test', False):
|
if self.params.get('test', False):
|
||||||
# We only download the first fragment
|
# We only download the first fragment
|
||||||
|
@ -20,12 +20,14 @@ from .arte import (
|
|||||||
ArteTVDDCIE,
|
ArteTVDDCIE,
|
||||||
ArteTVEmbedIE,
|
ArteTVEmbedIE,
|
||||||
)
|
)
|
||||||
|
from .audiomack import AudiomackIE
|
||||||
from .auengine import AUEngineIE
|
from .auengine import AUEngineIE
|
||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
from .bandcamp import BandcampIE, BandcampAlbumIE
|
from .bandcamp import BandcampIE, BandcampAlbumIE
|
||||||
from .bbccouk import BBCCoUkIE
|
from .bbccouk import BBCCoUkIE
|
||||||
from .beeg import BeegIE
|
from .beeg import BeegIE
|
||||||
from .behindkink import BehindKinkIE
|
from .behindkink import BehindKinkIE
|
||||||
|
from .bild import BildIE
|
||||||
from .bilibili import BiliBiliIE
|
from .bilibili import BiliBiliIE
|
||||||
from .blinkx import BlinkxIE
|
from .blinkx import BlinkxIE
|
||||||
from .bliptv import BlipTVIE, BlipTVUserIE
|
from .bliptv import BlipTVIE, BlipTVUserIE
|
||||||
@ -60,9 +62,11 @@ from .comedycentral import ComedyCentralIE, ComedyCentralShowsIE
|
|||||||
from .condenast import CondeNastIE
|
from .condenast import CondeNastIE
|
||||||
from .cracked import CrackedIE
|
from .cracked import CrackedIE
|
||||||
from .criterion import CriterionIE
|
from .criterion import CriterionIE
|
||||||
from .crunchyroll import CrunchyrollIE
|
from .crunchyroll import (
|
||||||
|
CrunchyrollIE,
|
||||||
|
CrunchyrollShowPlaylistIE
|
||||||
|
)
|
||||||
from .cspan import CSpanIE
|
from .cspan import CSpanIE
|
||||||
from .d8 import D8IE
|
|
||||||
from .dailymotion import (
|
from .dailymotion import (
|
||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
DailymotionPlaylistIE,
|
DailymotionPlaylistIE,
|
||||||
@ -134,6 +138,7 @@ from .gamestar import GameStarIE
|
|||||||
from .gametrailers import GametrailersIE
|
from .gametrailers import GametrailersIE
|
||||||
from .gdcvault import GDCVaultIE
|
from .gdcvault import GDCVaultIE
|
||||||
from .generic import GenericIE
|
from .generic import GenericIE
|
||||||
|
from .glide import GlideIE
|
||||||
from .globo import GloboIE
|
from .globo import GloboIE
|
||||||
from .godtube import GodTubeIE
|
from .godtube import GodTubeIE
|
||||||
from .golem import GolemIE
|
from .golem import GolemIE
|
||||||
@ -183,6 +188,7 @@ from .kontrtube import KontrTubeIE
|
|||||||
from .krasview import KrasViewIE
|
from .krasview import KrasViewIE
|
||||||
from .ku6 import Ku6IE
|
from .ku6 import Ku6IE
|
||||||
from .la7 import LA7IE
|
from .la7 import LA7IE
|
||||||
|
from .laola1tv import Laola1TvIE
|
||||||
from .lifenews import LifeNewsIE
|
from .lifenews import LifeNewsIE
|
||||||
from .liveleak import LiveLeakIE
|
from .liveleak import LiveLeakIE
|
||||||
from .livestream import (
|
from .livestream import (
|
||||||
@ -245,7 +251,7 @@ from .newstube import NewstubeIE
|
|||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
from .nhl import NHLIE, NHLVideocenterIE
|
from .nhl import NHLIE, NHLVideocenterIE
|
||||||
from .niconico import NiconicoIE
|
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
||||||
from .ninegag import NineGagIE
|
from .ninegag import NineGagIE
|
||||||
from .noco import NocoIE
|
from .noco import NocoIE
|
||||||
from .normalboots import NormalbootsIE
|
from .normalboots import NormalbootsIE
|
||||||
@ -274,6 +280,7 @@ from .orf import (
|
|||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
from .patreon import PatreonIE
|
from .patreon import PatreonIE
|
||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
|
from .phoenix import PhoenixIE
|
||||||
from .photobucket import PhotobucketIE
|
from .photobucket import PhotobucketIE
|
||||||
from .planetaplay import PlanetaPlayIE
|
from .planetaplay import PlanetaPlayIE
|
||||||
from .played import PlayedIE
|
from .played import PlayedIE
|
||||||
@ -287,6 +294,7 @@ from .pornoxo import PornoXOIE
|
|||||||
from .promptfile import PromptFileIE
|
from .promptfile import PromptFileIE
|
||||||
from .prosiebensat1 import ProSiebenSat1IE
|
from .prosiebensat1 import ProSiebenSat1IE
|
||||||
from .pyvideo import PyvideoIE
|
from .pyvideo import PyvideoIE
|
||||||
|
from .quickvid import QuickVidIE
|
||||||
from .radiofrance import RadioFranceIE
|
from .radiofrance import RadioFranceIE
|
||||||
from .rai import RaiIE
|
from .rai import RaiIE
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
@ -349,6 +357,7 @@ from .spike import SpikeIE
|
|||||||
from .sport5 import Sport5IE
|
from .sport5 import Sport5IE
|
||||||
from .sportbox import SportBoxIE
|
from .sportbox import SportBoxIE
|
||||||
from .sportdeutschland import SportDeutschlandIE
|
from .sportdeutschland import SportDeutschlandIE
|
||||||
|
from .srmediathek import SRMediathekIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
from .steam import SteamIE
|
from .steam import SteamIE
|
||||||
from .streamcloud import StreamcloudIE
|
from .streamcloud import StreamcloudIE
|
||||||
@ -422,6 +431,7 @@ from .videopremium import VideoPremiumIE
|
|||||||
from .videott import VideoTtIE
|
from .videott import VideoTtIE
|
||||||
from .videoweed import VideoWeedIE
|
from .videoweed import VideoWeedIE
|
||||||
from .vidme import VidmeIE
|
from .vidme import VidmeIE
|
||||||
|
from .vidzi import VidziIE
|
||||||
from .vimeo import (
|
from .vimeo import (
|
||||||
VimeoIE,
|
VimeoIE,
|
||||||
VimeoAlbumIE,
|
VimeoAlbumIE,
|
||||||
@ -441,6 +451,7 @@ from .viki import VikiIE
|
|||||||
from .vk import VKIE
|
from .vk import VKIE
|
||||||
from .vodlocker import VodlockerIE
|
from .vodlocker import VodlockerIE
|
||||||
from .vporn import VpornIE
|
from .vporn import VpornIE
|
||||||
|
from .vrt import VRTIE
|
||||||
from .vube import VubeIE
|
from .vube import VubeIE
|
||||||
from .vuclip import VuClipIE
|
from .vuclip import VuClipIE
|
||||||
from .vulture import VultureIE
|
from .vulture import VultureIE
|
||||||
@ -490,10 +501,8 @@ from .youtube import (
|
|||||||
YoutubeUserIE,
|
YoutubeUserIE,
|
||||||
YoutubeWatchLaterIE,
|
YoutubeWatchLaterIE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .zdf import ZDFIE
|
from .zdf import ZDFIE
|
||||||
|
|
||||||
|
|
||||||
_ALL_CLASSES = [
|
_ALL_CLASSES = [
|
||||||
klass
|
klass
|
||||||
for name, klass in globals().items()
|
for name, klass in globals().items()
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .generic import GenericIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
@ -12,6 +13,7 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
|
parse_xml,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +56,11 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage:
|
if '>Der gewünschte Beitrag ist nicht mehr verfügbar.<' in webpage:
|
||||||
raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
|
raise ExtractorError('Video %s is no longer available' % video_id, expected=True)
|
||||||
|
|
||||||
|
if re.search(r'[\?&]rss($|[=&])', url):
|
||||||
|
doc = parse_xml(webpage)
|
||||||
|
if doc.tag == 'rss':
|
||||||
|
return GenericIE()._extract_rss(url, video_id, doc)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
[r'<h1(?:\s+class="boxTopHeadline")?>(.*?)</h1>',
|
||||||
r'<meta name="dcterms.title" content="(.*?)"/>',
|
r'<meta name="dcterms.title" content="(.*?)"/>',
|
||||||
|
69
youtube_dl/extractor/audiomack.py
Normal file
69
youtube_dl/extractor/audiomack.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .soundcloud import SoundcloudIE
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class AudiomackIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?audiomack\.com/song/(?P<id>[\w/-]+)'
|
||||||
|
IE_NAME = 'audiomack'
|
||||||
|
_TESTS = [
|
||||||
|
#hosted on audiomack
|
||||||
|
{
|
||||||
|
'url': 'http://www.audiomack.com/song/roosh-williams/extraordinary',
|
||||||
|
'info_dict':
|
||||||
|
{
|
||||||
|
'id' : 'roosh-williams/extraordinary',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Roosh Williams - Extraordinary'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
#hosted on soundcloud via audiomack
|
||||||
|
{
|
||||||
|
'url': 'http://www.audiomack.com/song/xclusiveszone/take-kare',
|
||||||
|
'file': '172419696.mp3',
|
||||||
|
'info_dict':
|
||||||
|
{
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Young Thug ft Lil Wayne - Take Kare',
|
||||||
|
"upload_date": "20141016",
|
||||||
|
"description": "New track produced by London On Da Track called “Take Kare\"\n\nhttp://instagram.com/theyoungthugworld\nhttps://www.facebook.com/ThuggerThuggerCashMoney\n",
|
||||||
|
"uploader": "Young Thug World"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
api_response = self._download_json(
|
||||||
|
"http://www.audiomack.com/api/music/url/song/%s?_=%d" % (
|
||||||
|
video_id, time.time()),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
if "url" not in api_response:
|
||||||
|
raise ExtractorError("Unable to deduce api url of song")
|
||||||
|
realurl = api_response["url"]
|
||||||
|
|
||||||
|
#Audiomack wraps a lot of soundcloud tracks in their branded wrapper
|
||||||
|
# - if so, pass the work off to the soundcloud extractor
|
||||||
|
if SoundcloudIE.suitable(realurl):
|
||||||
|
return {'_type': 'url', 'url': realurl, 'ie_key': 'Soundcloud'}
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
artist = self._html_search_regex(
|
||||||
|
r'<span class="artist">(.*?)</span>', webpage, "artist")
|
||||||
|
songtitle = self._html_search_regex(
|
||||||
|
r'<h1 class="profile-title song-title"><span class="artist">.*?</span>(.*?)</h1>',
|
||||||
|
webpage, "title")
|
||||||
|
title = artist + " - " + songtitle
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': realurl,
|
||||||
|
}
|
@ -24,8 +24,7 @@ class AUEngineIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
title = self._html_search_regex(r'<title>(?P<title>.+?)</title>', webpage, 'title')
|
title = self._html_search_regex(r'<title>(?P<title>.+?)</title>', webpage, 'title')
|
||||||
|
39
youtube_dl/extractor/bild.py
Normal file
39
youtube_dl/extractor/bild.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class BildIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?bild\.de/(?:[^/]+/)+(?P<display_id>[^/]+)-(?P<id>\d+)(?:,auto=true)?\.bild\.html'
|
||||||
|
IE_DESC = 'Bild.de'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.bild.de/video/clip/apple-ipad-air/das-koennen-die-neuen-ipads-38184146.bild.html',
|
||||||
|
'md5': 'dd495cbd99f2413502a1713a1156ac8a',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '38184146',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'BILD hat sie getestet',
|
||||||
|
'thumbnail': 'http://bilder.bild.de/fotos/stand-das-koennen-die-neuen-ipads-38184138/Bild/1.bild.jpg',
|
||||||
|
'duration': 196,
|
||||||
|
'description': 'Mit dem iPad Air 2 und dem iPad Mini 3 hat Apple zwei neue Tablet-Modelle präsentiert. BILD-Reporter Sven Stein durfte die Geräte bereits testen. ',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
xml_url = url.split(".bild.html")[0] + ",view=xml.bild.xml"
|
||||||
|
doc = self._download_xml(xml_url, video_id)
|
||||||
|
|
||||||
|
duration = int_or_none(doc.attrib.get('duration'), scale=1000)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': doc.attrib['ueberschrift'],
|
||||||
|
'description': doc.attrib.get('text'),
|
||||||
|
'url': doc.attrib['src'],
|
||||||
|
'thumbnail': doc.attrib.get('img'),
|
||||||
|
'duration': duration,
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
@ -7,15 +7,21 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_basename,
|
url_basename,
|
||||||
|
qualities,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CanalplusIE(InfoExtractor):
|
class CanalplusIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.canalplus\.fr/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
|
IE_DESC = 'canalplus.fr, piwiplus.fr and d8.tv'
|
||||||
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/cplus/%s'
|
_VALID_URL = r'https?://(?:www\.(?P<site>canalplus\.fr|piwiplus\.fr|d8\.tv)/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
|
||||||
IE_NAME = 'canalplus.fr'
|
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/%s/%s'
|
||||||
|
_SITE_ID_MAP = {
|
||||||
|
'canalplus.fr': 'cplus',
|
||||||
|
'piwiplus.fr': 'teletoon',
|
||||||
|
'd8.tv': 'd8',
|
||||||
|
}
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470',
|
'url': 'http://www.canalplus.fr/c-infos-documentaires/pid1830-c-zapping.html?vid=922470',
|
||||||
'md5': '3db39fb48b9685438ecf33a1078023e4',
|
'md5': '3db39fb48b9685438ecf33a1078023e4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -25,36 +31,73 @@ class CanalplusIE(InfoExtractor):
|
|||||||
'description': 'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013',
|
'description': 'Le meilleur de toutes les chaînes, tous les jours.\nEmission du 26 août 2013',
|
||||||
'upload_date': '20130826',
|
'upload_date': '20130826',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.piwiplus.fr/videos-piwi/pid1405-le-labyrinthe-boing-super-ranger.html?vid=1108190',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1108190',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Le labyrinthe - Boing super ranger',
|
||||||
|
'description': 'md5:4cea7a37153be42c1ba2c1d3064376ff',
|
||||||
|
'upload_date': '20140724',
|
||||||
|
},
|
||||||
|
'skip': 'Only works from France',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.d8.tv/d8-docs-mags/pid6589-d8-campagne-intime.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '966289',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Campagne intime - Documentaire exceptionnel',
|
||||||
|
'description': 'md5:d2643b799fb190846ae09c61e59a859f',
|
||||||
|
'upload_date': '20131108',
|
||||||
|
},
|
||||||
|
'skip': 'videos get deleted after a while',
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.groupdict().get('id')
|
video_id = mobj.groupdict().get('id')
|
||||||
|
|
||||||
|
site_id = self._SITE_ID_MAP[mobj.group('site') or 'canal']
|
||||||
|
|
||||||
# Beware, some subclasses do not define an id group
|
# Beware, some subclasses do not define an id group
|
||||||
display_id = url_basename(mobj.group('path'))
|
display_id = url_basename(mobj.group('path'))
|
||||||
|
|
||||||
if video_id is None:
|
if video_id is None:
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
video_id = self._search_regex(r'<canal:player videoId="(\d+)"', webpage, 'video id')
|
video_id = self._search_regex(
|
||||||
|
r'<canal:player[^>]+?videoId="(\d+)"', webpage, 'video id')
|
||||||
|
|
||||||
info_url = self._VIDEO_INFO_TEMPLATE % video_id
|
info_url = self._VIDEO_INFO_TEMPLATE % (site_id, video_id)
|
||||||
doc = self._download_xml(info_url, video_id, 'Downloading video XML')
|
doc = self._download_xml(info_url, video_id, 'Downloading video XML')
|
||||||
|
|
||||||
video_info = [video for video in doc if video.find('ID').text == video_id][0]
|
video_info = [video for video in doc if video.find('ID').text == video_id][0]
|
||||||
media = video_info.find('MEDIA')
|
media = video_info.find('MEDIA')
|
||||||
infos = video_info.find('INFOS')
|
infos = video_info.find('INFOS')
|
||||||
|
|
||||||
preferences = ['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS']
|
preference = qualities(['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS'])
|
||||||
|
|
||||||
formats = [
|
formats = []
|
||||||
{
|
for fmt in media.find('VIDEOS'):
|
||||||
'url': fmt.text + '?hdcore=2.11.3' if fmt.tag == 'HDS' else fmt.text,
|
format_url = fmt.text
|
||||||
'format_id': fmt.tag,
|
if not format_url:
|
||||||
'ext': 'mp4' if fmt.tag == 'HLS' else 'flv',
|
continue
|
||||||
'preference': preferences.index(fmt.tag) if fmt.tag in preferences else -1,
|
format_id = fmt.tag
|
||||||
} for fmt in media.find('VIDEOS') if fmt.text
|
if format_id == 'HLS':
|
||||||
]
|
hls_formats = self._extract_m3u8_formats(format_url, video_id, 'flv')
|
||||||
|
for fmt in hls_formats:
|
||||||
|
fmt['preference'] = preference(format_id)
|
||||||
|
formats.extend(hls_formats)
|
||||||
|
elif format_id == 'HDS':
|
||||||
|
hds_formats = self._extract_f4m_formats(format_url + '?hdcore=2.11.3', video_id)
|
||||||
|
for fmt in hds_formats:
|
||||||
|
fmt['preference'] = preference(format_id)
|
||||||
|
formats.extend(hds_formats)
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': format_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'preference': preference(format_id),
|
||||||
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -59,12 +59,9 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
|
|
||||||
vidurl = self._search_regex(
|
vidurl = self._search_regex(
|
||||||
r'\'vidurl\'\s*:\s*"([^\']+)"', playerdata, 'vidurl').replace('\\/', '/')
|
r'\'vidurl\'\s*:\s*"([^\']+)"', playerdata, 'vidurl').replace('\\/', '/')
|
||||||
vidid = self._search_regex(
|
|
||||||
r'\'vidid\'\s*:\s*"([^\']+)"', playerdata, 'vidid')
|
|
||||||
videoserver = self._html_search_regex(
|
|
||||||
r"'videoserver'\s*:\s*'([^']+)'", playerdata, 'videoserver')
|
|
||||||
|
|
||||||
videolist_url = 'http://%s/vod/smil:%s.smil/jwplayer.smil' % (videoserver, vidid)
|
videolist_url = self._search_regex(
|
||||||
|
r"file\s*:\s*'(http.+?/jwplayer\.smil)'", playerdata, 'jwplayer.smil')
|
||||||
videolist = self._download_xml(videolist_url, video_id, 'Downloading videolist XML')
|
videolist = self._download_xml(videolist_url, video_id, 'Downloading videolist XML')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
@ -4,7 +4,6 @@ import json
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import int_or_none
|
|
||||||
|
|
||||||
|
|
||||||
_translation_table = {
|
_translation_table = {
|
||||||
@ -39,9 +38,7 @@ class CliphunterIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_title = self._search_regex(
|
video_title = self._search_regex(
|
||||||
|
@ -72,6 +72,7 @@ class InfoExtractor(object):
|
|||||||
* acodec Name of the audio codec in use
|
* acodec Name of the audio codec in use
|
||||||
* asr Audio sampling rate in Hertz
|
* asr Audio sampling rate in Hertz
|
||||||
* vbr Average video bitrate in KBit/s
|
* vbr Average video bitrate in KBit/s
|
||||||
|
* fps Frame rate
|
||||||
* vcodec Name of the video codec in use
|
* vcodec Name of the video codec in use
|
||||||
* container Name of the container format
|
* container Name of the container format
|
||||||
* filesize The number of bytes, if known in advance
|
* filesize The number of bytes, if known in advance
|
||||||
@ -89,6 +90,10 @@ class InfoExtractor(object):
|
|||||||
format, irrespective of the file format.
|
format, irrespective of the file format.
|
||||||
-1 for default (order by other properties),
|
-1 for default (order by other properties),
|
||||||
-2 or smaller for less than default.
|
-2 or smaller for less than default.
|
||||||
|
* source_preference Order number for this video source
|
||||||
|
(quality takes higher priority)
|
||||||
|
-1 for default (order by other properties),
|
||||||
|
-2 or smaller for less than default.
|
||||||
* http_referer HTTP Referer header value to set.
|
* http_referer HTTP Referer header value to set.
|
||||||
* http_method HTTP method to use for the download.
|
* http_method HTTP method to use for the download.
|
||||||
* http_headers A dictionary of additional HTTP headers
|
* http_headers A dictionary of additional HTTP headers
|
||||||
@ -238,7 +243,6 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
||||||
""" Returns a tuple (page content as string, URL handle) """
|
""" Returns a tuple (page content as string, URL handle) """
|
||||||
|
|
||||||
# Strip hashes from the URL (#1038)
|
# Strip hashes from the URL (#1038)
|
||||||
if isinstance(url_or_request, (compat_str, str)):
|
if isinstance(url_or_request, (compat_str, str)):
|
||||||
url_or_request = url_or_request.partition('#')[0]
|
url_or_request = url_or_request.partition('#')[0]
|
||||||
@ -247,6 +251,10 @@ class InfoExtractor(object):
|
|||||||
if urlh is False:
|
if urlh is False:
|
||||||
assert not fatal
|
assert not fatal
|
||||||
return False
|
return False
|
||||||
|
content = self._webpage_read_content(urlh, url_or_request, video_id, note, errnote, fatal)
|
||||||
|
return (content, urlh)
|
||||||
|
|
||||||
|
def _webpage_read_content(self, urlh, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
||||||
content_type = urlh.headers.get('Content-Type', '')
|
content_type = urlh.headers.get('Content-Type', '')
|
||||||
webpage_bytes = urlh.read()
|
webpage_bytes = urlh.read()
|
||||||
m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
|
m = re.match(r'[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\s*;\s*charset=(.+)', content_type)
|
||||||
@ -305,7 +313,7 @@ class InfoExtractor(object):
|
|||||||
msg += ' Visit %s for more details' % blocked_iframe
|
msg += ' Visit %s for more details' % blocked_iframe
|
||||||
raise ExtractorError(msg, expected=True)
|
raise ExtractorError(msg, expected=True)
|
||||||
|
|
||||||
return (content, urlh)
|
return content
|
||||||
|
|
||||||
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
||||||
""" Returns the data of the page as a string """
|
""" Returns the data of the page as a string """
|
||||||
@ -611,14 +619,16 @@ class InfoExtractor(object):
|
|||||||
f.get('vbr') if f.get('vbr') is not None else -1,
|
f.get('vbr') if f.get('vbr') is not None else -1,
|
||||||
f.get('abr') if f.get('abr') is not None else -1,
|
f.get('abr') if f.get('abr') is not None else -1,
|
||||||
audio_ext_preference,
|
audio_ext_preference,
|
||||||
|
f.get('fps') if f.get('fps') is not None else -1,
|
||||||
f.get('filesize') if f.get('filesize') is not None else -1,
|
f.get('filesize') if f.get('filesize') is not None else -1,
|
||||||
f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
|
f.get('filesize_approx') if f.get('filesize_approx') is not None else -1,
|
||||||
|
f.get('source_preference') if f.get('source_preference') is not None else -1,
|
||||||
f.get('format_id'),
|
f.get('format_id'),
|
||||||
)
|
)
|
||||||
formats.sort(key=_formats_key)
|
formats.sort(key=_formats_key)
|
||||||
|
|
||||||
def http_scheme(self):
|
def http_scheme(self):
|
||||||
""" Either "https:" or "https:", depending on the user's preferences """
|
""" Either "http:" or "https:", depending on the user's preferences """
|
||||||
return (
|
return (
|
||||||
'http:'
|
'http:'
|
||||||
if self._downloader.params.get('prefer_insecure', False)
|
if self._downloader.params.get('prefer_insecure', False)
|
||||||
@ -681,7 +691,10 @@ class InfoExtractor(object):
|
|||||||
if re.match(r'^https?://', u)
|
if re.match(r'^https?://', u)
|
||||||
else compat_urlparse.urljoin(m3u8_url, u))
|
else compat_urlparse.urljoin(m3u8_url, u))
|
||||||
|
|
||||||
m3u8_doc = self._download_webpage(m3u8_url, video_id)
|
m3u8_doc = self._download_webpage(
|
||||||
|
m3u8_url, video_id,
|
||||||
|
note='Downloading m3u8 information',
|
||||||
|
errnote='Failed to download m3u8 information')
|
||||||
last_info = None
|
last_info = None
|
||||||
kv_rex = re.compile(
|
kv_rex = re.compile(
|
||||||
r'(?P<key>[a-zA-Z_-]+)=(?P<val>"[^"]+"|[^",]+)(?:,|$)')
|
r'(?P<key>[a-zA-Z_-]+)=(?P<val>"[^"]+"|[^",]+)(?:,|$)')
|
||||||
|
@ -24,6 +24,7 @@ from ..aes import (
|
|||||||
aes_cbc_decrypt,
|
aes_cbc_decrypt,
|
||||||
inc,
|
inc,
|
||||||
)
|
)
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class CrunchyrollIE(SubtitlesInfoExtractor):
|
class CrunchyrollIE(SubtitlesInfoExtractor):
|
||||||
@ -39,6 +40,7 @@ class CrunchyrollIE(SubtitlesInfoExtractor):
|
|||||||
'thumbnail': 'http://img1.ak.crunchyroll.com/i/spire1-tmb/20c6b5e10f1a47b10516877d3c039cae1380951166_full.jpg',
|
'thumbnail': 'http://img1.ak.crunchyroll.com/i/spire1-tmb/20c6b5e10f1a47b10516877d3c039cae1380951166_full.jpg',
|
||||||
'uploader': 'Yomiuri Telecasting Corporation (YTV)',
|
'uploader': 'Yomiuri Telecasting Corporation (YTV)',
|
||||||
'upload_date': '20131013',
|
'upload_date': '20131013',
|
||||||
|
'url': 're:(?!.*&)',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp
|
# rtmp
|
||||||
@ -107,19 +109,17 @@ class CrunchyrollIE(SubtitlesInfoExtractor):
|
|||||||
decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv))
|
decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv))
|
||||||
return zlib.decompress(decrypted_data)
|
return zlib.decompress(decrypted_data)
|
||||||
|
|
||||||
def _convert_subtitles_to_srt(self, subtitles):
|
def _convert_subtitles_to_srt(self, sub_root):
|
||||||
output = ''
|
output = ''
|
||||||
for i, (start, end, text) in enumerate(re.findall(r'<event [^>]*?start="([^"]+)" [^>]*?end="([^"]+)" [^>]*?text="([^"]+)"[^>]*?>', subtitles), 1):
|
|
||||||
start = start.replace('.', ',')
|
for i, event in enumerate(sub_root.findall('./events/event'), 1):
|
||||||
end = end.replace('.', ',')
|
start = event.attrib['start'].replace('.', ',')
|
||||||
text = clean_html(text)
|
end = event.attrib['end'].replace('.', ',')
|
||||||
text = text.replace('\\N', '\n')
|
text = event.attrib['text'].replace('\\N', '\n')
|
||||||
if not text:
|
|
||||||
continue
|
|
||||||
output += '%d\n%s --> %s\n%s\n\n' % (i, start, end, text)
|
output += '%d\n%s --> %s\n%s\n\n' % (i, start, end, text)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _convert_subtitles_to_ass(self, subtitles):
|
def _convert_subtitles_to_ass(self, sub_root):
|
||||||
output = ''
|
output = ''
|
||||||
|
|
||||||
def ass_bool(strvalue):
|
def ass_bool(strvalue):
|
||||||
@ -128,10 +128,6 @@ class CrunchyrollIE(SubtitlesInfoExtractor):
|
|||||||
assvalue = '-1'
|
assvalue = '-1'
|
||||||
return assvalue
|
return assvalue
|
||||||
|
|
||||||
sub_root = xml.etree.ElementTree.fromstring(subtitles)
|
|
||||||
if not sub_root:
|
|
||||||
return output
|
|
||||||
|
|
||||||
output = '[Script Info]\n'
|
output = '[Script Info]\n'
|
||||||
output += 'Title: %s\n' % sub_root.attrib["title"]
|
output += 'Title: %s\n' % sub_root.attrib["title"]
|
||||||
output += 'ScriptType: v4.00+\n'
|
output += 'ScriptType: v4.00+\n'
|
||||||
@ -237,12 +233,14 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
streamdata_req.data = 'req=RpcApiVideoEncode%5FGetStreamInfo&video%5Fencode%5Fquality='+stream_quality+'&media%5Fid='+stream_id+'&video%5Fformat='+stream_format
|
streamdata_req.data = 'req=RpcApiVideoEncode%5FGetStreamInfo&video%5Fencode%5Fquality='+stream_quality+'&media%5Fid='+stream_id+'&video%5Fformat='+stream_format
|
||||||
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
streamdata_req.add_header('Content-Length', str(len(streamdata_req.data)))
|
streamdata_req.add_header('Content-Length', str(len(streamdata_req.data)))
|
||||||
streamdata = self._download_webpage(streamdata_req, video_id, note='Downloading media info for '+video_format)
|
streamdata = self._download_xml(
|
||||||
video_url = self._search_regex(r'<host>([^<]+)', streamdata, 'video_url')
|
streamdata_req, video_id,
|
||||||
video_play_path = self._search_regex(r'<file>([^<]+)', streamdata, 'video_play_path')
|
note='Downloading media info for %s' % video_format)
|
||||||
|
video_url = streamdata.find('.//host').text
|
||||||
|
video_play_path = streamdata.find('.//file').text
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'play_path': video_play_path,
|
'play_path': video_play_path,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
'format_id': video_format,
|
'format_id': video_format,
|
||||||
@ -266,10 +264,13 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
||||||
if not lang_code:
|
if not lang_code:
|
||||||
continue
|
continue
|
||||||
|
sub_root = xml.etree.ElementTree.fromstring(subtitle)
|
||||||
|
if not sub_root:
|
||||||
|
subtitles[lang_code] = ''
|
||||||
if sub_format == 'ass':
|
if sub_format == 'ass':
|
||||||
subtitles[lang_code] = self._convert_subtitles_to_ass(subtitle)
|
subtitles[lang_code] = self._convert_subtitles_to_ass(sub_root)
|
||||||
else:
|
else:
|
||||||
subtitles[lang_code] = self._convert_subtitles_to_srt(subtitle)
|
subtitles[lang_code] = self._convert_subtitles_to_srt(sub_root)
|
||||||
|
|
||||||
if self._downloader.params.get('listsubtitles', False):
|
if self._downloader.params.get('listsubtitles', False):
|
||||||
self._list_available_subtitles(video_id, subtitles)
|
self._list_available_subtitles(video_id, subtitles)
|
||||||
@ -285,3 +286,40 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CrunchyrollShowPlaylistIE(InfoExtractor):
|
||||||
|
IE_NAME = "crunchyroll:playlist"
|
||||||
|
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?!(?:news|anime-news|library|forum|launchcalendar|lineup|store|comics|freetrial|login))(?P<id>[\w\-]+))/?$'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.crunchyroll.com/a-bridge-to-the-starry-skies-hoshizora-e-kakaru-hashi',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'a-bridge-to-the-starry-skies-hoshizora-e-kakaru-hashi',
|
||||||
|
'title': 'A Bridge to the Starry Skies - Hoshizora e Kakaru Hashi'
|
||||||
|
},
|
||||||
|
'playlist_count': 13,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
show_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, show_id)
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'(?s)<h1[^>]*>\s*<span itemprop="name">(.*?)</span>',
|
||||||
|
webpage, 'title')
|
||||||
|
episode_paths = re.findall(
|
||||||
|
r'(?s)<li id="showview_videos_media_[0-9]+"[^>]+>.*?<a href="([^"]+)"',
|
||||||
|
webpage)
|
||||||
|
entries = [
|
||||||
|
self.url_result('http://www.crunchyroll.com' + ep, 'Crunchyroll')
|
||||||
|
for ep in episode_paths
|
||||||
|
]
|
||||||
|
entries.reverse()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': show_id,
|
||||||
|
'title': title,
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
# encoding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .canalplus import CanalplusIE
|
|
||||||
|
|
||||||
|
|
||||||
class D8IE(CanalplusIE):
|
|
||||||
_VALID_URL = r'https?://www\.d8\.tv/.*?/(?P<path>.*)'
|
|
||||||
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/d8/%s'
|
|
||||||
IE_NAME = 'd8.tv'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.d8.tv/d8-docs-mags/pid6589-d8-campagne-intime.html',
|
|
||||||
'file': '966289.flv',
|
|
||||||
'info_dict': {
|
|
||||||
'title': 'Campagne intime - Documentaire exceptionnel',
|
|
||||||
'description': 'md5:d2643b799fb190846ae09c61e59a859f',
|
|
||||||
'upload_date': '20131108',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
'skip': 'videos get deleted after a while',
|
|
||||||
}
|
|
@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .subtitles import SubtitlesInfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
from .common import ExtractorError
|
from .common import ExtractorError
|
||||||
from ..utils import parse_iso8601
|
from ..utils import parse_iso8601
|
||||||
@ -25,8 +23,7 @@ class DRTVIE(SubtitlesInfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
programcard = self._download_json(
|
programcard = self._download_json(
|
||||||
'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON')
|
'http://www.dr.dk/mu/programcard/expanded/%s' % video_id, video_id, 'Downloading video JSON')
|
||||||
@ -35,7 +32,7 @@ class DRTVIE(SubtitlesInfoExtractor):
|
|||||||
|
|
||||||
title = data['Title']
|
title = data['Title']
|
||||||
description = data['Description']
|
description = data['Description']
|
||||||
timestamp = parse_iso8601(data['CreatedTime'][:-5])
|
timestamp = parse_iso8601(data['CreatedTime'])
|
||||||
|
|
||||||
thumbnail = None
|
thumbnail = None
|
||||||
duration = None
|
duration = None
|
||||||
|
@ -1,49 +1,48 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
import re
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
|
||||||
determine_ext,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FazIE(InfoExtractor):
|
class FazIE(InfoExtractor):
|
||||||
IE_NAME = u'faz.net'
|
IE_NAME = 'faz.net'
|
||||||
_VALID_URL = r'https?://www\.faz\.net/multimedia/videos/.*?-(?P<id>\d+)\.html'
|
_VALID_URL = r'https?://www\.faz\.net/multimedia/videos/.*?-(?P<id>\d+)\.html'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html',
|
'url': 'http://www.faz.net/multimedia/videos/stockholm-chemie-nobelpreis-fuer-drei-amerikanische-forscher-12610585.html',
|
||||||
u'file': u'12610585.mp4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '12610585',
|
||||||
u'title': u'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher',
|
'ext': 'mp4',
|
||||||
u'description': u'md5:1453fbf9a0d041d985a47306192ea253',
|
'title': 'Stockholm: Chemie-Nobelpreis für drei amerikanische Forscher',
|
||||||
|
'description': 'md5:1453fbf9a0d041d985a47306192ea253',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
self.to_screen(video_id)
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
config_xml_url = self._search_regex(r'writeFLV\(\'(.+?)\',', webpage,
|
config_xml_url = self._search_regex(
|
||||||
u'config xml url')
|
r'writeFLV\(\'(.+?)\',', webpage, 'config xml url')
|
||||||
config = self._download_xml(config_xml_url, video_id,
|
config = self._download_xml(
|
||||||
u'Downloading config xml')
|
config_xml_url, video_id, 'Downloading config xml')
|
||||||
|
|
||||||
encodings = config.find('ENCODINGS')
|
encodings = config.find('ENCODINGS')
|
||||||
formats = []
|
formats = []
|
||||||
for code in ['LOW', 'HIGH', 'HQ']:
|
for pref, code in enumerate(['LOW', 'HIGH', 'HQ']):
|
||||||
encoding = encodings.find(code)
|
encoding = encodings.find(code)
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
continue
|
continue
|
||||||
encoding_url = encoding.find('FILENAME').text
|
encoding_url = encoding.find('FILENAME').text
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': encoding_url,
|
'url': encoding_url,
|
||||||
'ext': determine_ext(encoding_url),
|
|
||||||
'format_id': code.lower(),
|
'format_id': code.lower(),
|
||||||
|
'quality': pref,
|
||||||
})
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
descr = self._html_search_regex(r'<p class="Content Copy">(.*?)</p>', webpage, u'description')
|
descr = self._html_search_regex(
|
||||||
|
r'<p class="Content Copy">(.*?)</p>', webpage, 'description', fatal=False)
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': self._og_search_title(webpage),
|
'title': self._og_search_title(webpage),
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
|
||||||
get_element_by_id,
|
get_element_by_id,
|
||||||
clean_html,
|
clean_html,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FKTVIE(InfoExtractor):
|
class FKTVIE(InfoExtractor):
|
||||||
IE_NAME = u'fernsehkritik.tv'
|
IE_NAME = 'fernsehkritik.tv'
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik\.tv/folge-(?P<ep>[0-9]+)(?:/.*)?'
|
_VALID_URL = r'http://(?:www\.)?fernsehkritik\.tv/folge-(?P<ep>[0-9]+)(?:/.*)?'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://fernsehkritik.tv/folge-1',
|
'url': 'http://fernsehkritik.tv/folge-1',
|
||||||
u'file': u'00011.flv',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '00011',
|
||||||
u'title': u'Folge 1 vom 10. April 2007',
|
'ext': 'flv',
|
||||||
u'description': u'md5:fb4818139c7cfe6907d4b83412a6864f',
|
'title': 'Folge 1 vom 10. April 2007',
|
||||||
|
'description': 'md5:fb4818139c7cfe6907d4b83412a6864f',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ class FKTVIE(InfoExtractor):
|
|||||||
start_webpage = self._download_webpage('http://fernsehkritik.tv/folge-%d/Start' % episode,
|
start_webpage = self._download_webpage('http://fernsehkritik.tv/folge-%d/Start' % episode,
|
||||||
episode)
|
episode)
|
||||||
playlist = self._search_regex(r'playlist = (\[.*?\]);', start_webpage,
|
playlist = self._search_regex(r'playlist = (\[.*?\]);', start_webpage,
|
||||||
u'playlist', flags=re.DOTALL)
|
'playlist', flags=re.DOTALL)
|
||||||
files = json.loads(re.sub('{[^{}]*?}', '{}', playlist))
|
files = json.loads(re.sub('{[^{}]*?}', '{}', playlist))
|
||||||
# TODO: return a single multipart video
|
# TODO: return a single multipart video
|
||||||
videos = []
|
videos = []
|
||||||
@ -42,7 +44,6 @@ class FKTVIE(InfoExtractor):
|
|||||||
videos.append({
|
videos.append({
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': determine_ext(video_url),
|
|
||||||
'title': clean_html(get_element_by_id('eptitle', start_webpage)),
|
'title': clean_html(get_element_by_id('eptitle', start_webpage)),
|
||||||
'description': clean_html(get_element_by_id('contentlist', start_webpage)),
|
'description': clean_html(get_element_by_id('contentlist', start_webpage)),
|
||||||
'thumbnail': video_thumbnail
|
'thumbnail': video_thumbnail
|
||||||
@ -51,14 +52,15 @@ class FKTVIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class FKTVPosteckeIE(InfoExtractor):
|
class FKTVPosteckeIE(InfoExtractor):
|
||||||
IE_NAME = u'fernsehkritik.tv:postecke'
|
IE_NAME = 'fernsehkritik.tv:postecke'
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?fernsehkritik\.tv/inline-video/postecke\.php\?(.*&)?ep=(?P<ep>[0-9]+)(&|$)'
|
_VALID_URL = r'http://(?:www\.)?fernsehkritik\.tv/inline-video/postecke\.php\?(.*&)?ep=(?P<ep>[0-9]+)(&|$)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://fernsehkritik.tv/inline-video/postecke.php?iframe=true&width=625&height=440&ep=120',
|
'url': 'http://fernsehkritik.tv/inline-video/postecke.php?iframe=true&width=625&height=440&ep=120',
|
||||||
u'file': u'0120.flv',
|
'md5': '262f0adbac80317412f7e57b4808e5c4',
|
||||||
u'md5': u'262f0adbac80317412f7e57b4808e5c4',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '0120',
|
||||||
u"title": u"Postecke 120"
|
'ext': 'flv',
|
||||||
|
'title': 'Postecke 120',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +73,7 @@ class FKTVPosteckeIE(InfoExtractor):
|
|||||||
video_url = 'http://dl%d.fernsehkritik.tv/postecke/postecke%d.flv' % (server, episode)
|
video_url = 'http://dl%d.fernsehkritik.tv/postecke/postecke%d.flv' % (server, episode)
|
||||||
video_title = 'Postecke %d' % episode
|
video_title = 'Postecke %d' % episode
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': determine_ext(video_url),
|
'title': video_title,
|
||||||
'title': video_title,
|
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,6 @@ class FranceTvInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
'url': 'http://www.francetvinfo.fr/replay-jt/france-3/soir-3/jt-grand-soir-3-lundi-26-aout-2013_393427.html',
|
||||||
'md5': '9cecf35f99c4079c199e9817882a9a1c',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '84981923',
|
'id': '84981923',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
@ -8,7 +8,7 @@ from ..utils import ExtractorError
|
|||||||
|
|
||||||
|
|
||||||
class FunnyOrDieIE(InfoExtractor):
|
class FunnyOrDieIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])'
|
_VALID_URL = r'https?://(?:www\.)?funnyordie\.com/(?P<type>embed|articles|videos)/(?P<id>[0-9a-f]+)(?:$|[?#/])'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version',
|
'url': 'http://www.funnyordie.com/videos/0732f586d7/heart-shaped-box-literal-video-version',
|
||||||
'md5': 'bcd81e0c4f26189ee09be362ad6e6ba9',
|
'md5': 'bcd81e0c4f26189ee09be362ad6e6ba9',
|
||||||
@ -29,6 +29,9 @@ class FunnyOrDieIE(InfoExtractor):
|
|||||||
'description': 'Please use this to sell something. www.jonlajoie.com',
|
'description': 'Please use this to sell something. www.jonlajoie.com',
|
||||||
'thumbnail': 're:^http:.*\.jpg$',
|
'thumbnail': 're:^http:.*\.jpg$',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.funnyordie.com/articles/ebf5e34fc8/10-hours-of-walking-in-nyc-as-a-man',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -325,7 +325,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
'uploader': 'www.handjobhub.com',
|
'uploader': 'www.handjobhub.com',
|
||||||
'title': 'Busty Blonde Siri Tit Fuck While Wank at Handjob Hub',
|
'title': 'Busty Blonde Siri Tit Fuck While Wank at HandjobHub.com',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# RSS feed
|
# RSS feed
|
||||||
@ -389,8 +389,35 @@ class GenericIE(InfoExtractor):
|
|||||||
'title': 'Conversation about Hexagonal Rails Part 1 - ThoughtWorks',
|
'title': 'Conversation about Hexagonal Rails Part 1 - ThoughtWorks',
|
||||||
'duration': 1715.0,
|
'duration': 1715.0,
|
||||||
'uploader': 'thoughtworks.wistia.com',
|
'uploader': 'thoughtworks.wistia.com',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
# Direct download with broken HEAD
|
||||||
|
{
|
||||||
|
'url': 'http://ai-radio.org:8000/radio.opus',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'radio',
|
||||||
|
'ext': 'opus',
|
||||||
|
'title': 'radio',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # infinite live stream
|
||||||
|
},
|
||||||
|
'expected_warnings': [
|
||||||
|
r'501.*Not Implemented'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
# Soundcloud embed
|
||||||
|
{
|
||||||
|
'url': 'http://nakedsecurity.sophos.com/2014/10/29/sscc-171-are-you-sure-that-1234-is-a-bad-password-podcast/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '174391317',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'description': 'md5:ff867d6b555488ad3c52572bb33d432c',
|
||||||
|
'uploader': 'Sophos Security',
|
||||||
|
'title': 'Chet Chat 171 - Oct 29, 2014',
|
||||||
|
'upload_date': '20141029',
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def report_following_redirect(self, new_url):
|
def report_following_redirect(self, new_url):
|
||||||
@ -487,7 +514,8 @@ class GenericIE(InfoExtractor):
|
|||||||
'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
|
'Set --default-search "ytsearch" (or run youtube-dl "ytsearch:%s" ) to search YouTube'
|
||||||
) % (url, url), expected=True)
|
) % (url, url), expected=True)
|
||||||
else:
|
else:
|
||||||
assert ':' in default_search
|
if ':' not in default_search:
|
||||||
|
default_search += ':'
|
||||||
return self.url_result(default_search + url)
|
return self.url_result(default_search + url)
|
||||||
|
|
||||||
url, smuggled_data = unsmuggle_url(url)
|
url, smuggled_data = unsmuggle_url(url)
|
||||||
@ -502,14 +530,14 @@ class GenericIE(InfoExtractor):
|
|||||||
self.to_screen('%s: Requesting header' % video_id)
|
self.to_screen('%s: Requesting header' % video_id)
|
||||||
|
|
||||||
head_req = HEADRequest(url)
|
head_req = HEADRequest(url)
|
||||||
response = self._request_webpage(
|
head_response = self._request_webpage(
|
||||||
head_req, video_id,
|
head_req, video_id,
|
||||||
note=False, errnote='Could not send HEAD request to %s' % url,
|
note=False, errnote='Could not send HEAD request to %s' % url,
|
||||||
fatal=False)
|
fatal=False)
|
||||||
|
|
||||||
if response is not False:
|
if head_response is not False:
|
||||||
# Check for redirect
|
# Check for redirect
|
||||||
new_url = response.geturl()
|
new_url = head_response.geturl()
|
||||||
if url != new_url:
|
if url != new_url:
|
||||||
self.report_following_redirect(new_url)
|
self.report_following_redirect(new_url)
|
||||||
if force_videoid:
|
if force_videoid:
|
||||||
@ -517,34 +545,35 @@ class GenericIE(InfoExtractor):
|
|||||||
new_url, {'force_videoid': force_videoid})
|
new_url, {'force_videoid': force_videoid})
|
||||||
return self.url_result(new_url)
|
return self.url_result(new_url)
|
||||||
|
|
||||||
# Check for direct link to a video
|
full_response = None
|
||||||
content_type = response.headers.get('Content-Type', '')
|
if head_response is False:
|
||||||
m = re.match(r'^(?P<type>audio|video|application(?=/ogg$))/(?P<format_id>.+)$', content_type)
|
full_response = self._request_webpage(url, video_id)
|
||||||
if m:
|
head_response = full_response
|
||||||
upload_date = response.headers.get('Last-Modified')
|
|
||||||
if upload_date:
|
# Check for direct link to a video
|
||||||
upload_date = unified_strdate(upload_date)
|
content_type = head_response.headers.get('Content-Type', '')
|
||||||
return {
|
m = re.match(r'^(?P<type>audio|video|application(?=/ogg$))/(?P<format_id>.+)$', content_type)
|
||||||
'id': video_id,
|
if m:
|
||||||
'title': os.path.splitext(url_basename(url))[0],
|
upload_date = unified_strdate(
|
||||||
'formats': [{
|
head_response.headers.get('Last-Modified'))
|
||||||
'format_id': m.group('format_id'),
|
return {
|
||||||
'url': url,
|
'id': video_id,
|
||||||
'vcodec': 'none' if m.group('type') == 'audio' else None
|
'title': os.path.splitext(url_basename(url))[0],
|
||||||
}],
|
'formats': [{
|
||||||
'upload_date': upload_date,
|
'format_id': m.group('format_id'),
|
||||||
}
|
'url': url,
|
||||||
|
'vcodec': 'none' if m.group('type') == 'audio' else None
|
||||||
|
}],
|
||||||
|
'upload_date': upload_date,
|
||||||
|
}
|
||||||
|
|
||||||
if not self._downloader.params.get('test', False) and not is_intentional:
|
if not self._downloader.params.get('test', False) and not is_intentional:
|
||||||
self._downloader.report_warning('Falling back on generic information extractor.')
|
self._downloader.report_warning('Falling back on generic information extractor.')
|
||||||
|
|
||||||
try:
|
if full_response:
|
||||||
|
webpage = self._webpage_read_content(full_response, url, video_id)
|
||||||
|
else:
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
except ValueError:
|
|
||||||
# since this is the last-resort InfoExtractor, if
|
|
||||||
# this error is thrown, it'll be thrown here
|
|
||||||
raise ExtractorError('Failed to download URL: %s' % url)
|
|
||||||
|
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
# Is it an RSS feed?
|
# Is it an RSS feed?
|
||||||
@ -634,7 +663,8 @@ class GenericIE(InfoExtractor):
|
|||||||
<iframe[^>]+?src=|
|
<iframe[^>]+?src=|
|
||||||
data-video-url=|
|
data-video-url=|
|
||||||
<embed[^>]+?src=|
|
<embed[^>]+?src=|
|
||||||
embedSWF\(?:\s*
|
embedSWF\(?:\s*|
|
||||||
|
new\s+SWFObject\(
|
||||||
)
|
)
|
||||||
(["\'])
|
(["\'])
|
||||||
(?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
|
(?P<url>(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/
|
||||||
@ -820,7 +850,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Look for embeded soundcloud player
|
# Look for embeded soundcloud player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
|
r'<iframe\s+(?:[a-zA-Z0-9_-]+="[^"]+"\s+)*src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
|
||||||
webpage)
|
webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
url = unescapeHTML(mobj.group('url'))
|
url = unescapeHTML(mobj.group('url'))
|
||||||
@ -857,7 +887,7 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.url_result(mobj.group('url'), 'SBS')
|
return self.url_result(mobj.group('url'), 'SBS')
|
||||||
|
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>https?://m\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
|
r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
|
||||||
webpage)
|
webpage)
|
||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(mobj.group('url'), 'MLB')
|
return self.url_result(mobj.group('url'), 'MLB')
|
||||||
@ -915,7 +945,7 @@ class GenericIE(InfoExtractor):
|
|||||||
found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
|
found = filter_video(re.findall(r'<meta.*?property="og:video".*?content="(.*?)"', webpage))
|
||||||
if not found:
|
if not found:
|
||||||
# HTML5 video
|
# HTML5 video
|
||||||
found = re.findall(r'(?s)<video[^<]*(?:>.*?<source[^>]+)? src="([^"]+)"', webpage)
|
found = re.findall(r'(?s)<video[^<]*(?:>.*?<source[^>]*)?\s+src="([^"]+)"', webpage)
|
||||||
if not found:
|
if not found:
|
||||||
found = re.search(
|
found = re.search(
|
||||||
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
|
r'(?i)<meta\s+(?=(?:[a-z-]+="[^"]+"\s+)*http-equiv="refresh")'
|
||||||
|
40
youtube_dl/extractor/glide.py
Normal file
40
youtube_dl/extractor/glide.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class GlideIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Glide mobile video messages (glide.me)'
|
||||||
|
_VALID_URL = r'https?://share\.glide\.me/(?P<id>[A-Za-z0-9\-=_+]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://share.glide.me/UZF8zlmuQbe4mr+7dCiQ0w==',
|
||||||
|
'md5': '4466372687352851af2d131cfaa8a4c7',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'UZF8zlmuQbe4mr+7dCiQ0w==',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Damon Timm\'s Glide message',
|
||||||
|
'thumbnail': 're:^https?://.*?\.cloudfront\.net/.*\.jpg$',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<title>(.*?)</title>', webpage, 'title')
|
||||||
|
video_url = self.http_scheme() + self._search_regex(
|
||||||
|
r'<source src="(.*?)" type="video/mp4">', webpage, 'video URL')
|
||||||
|
thumbnail_url = self._search_regex(
|
||||||
|
r'<img id="video-thumbnail" src="(.*?)"',
|
||||||
|
webpage, 'thumbnail url', fatal=False)
|
||||||
|
thumbnail = (
|
||||||
|
thumbnail_url if thumbnail_url is None
|
||||||
|
else self.http_scheme() + thumbnail_url)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
@ -46,9 +46,9 @@ class GorillaVidIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '3rso4kdn6f9m',
|
'id': '3rso4kdn6f9m',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Micro Pig piglets ready on 16th July 2009',
|
'title': 'Micro Pig piglets ready on 16th July 2009-bG0PdrCdxUc',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
},
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://movpod.in/0wguyyxi1yca',
|
'url': 'http://movpod.in/0wguyyxi1yca',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
@ -1,37 +1,33 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
|
||||||
|
|
||||||
class HarkIE(InfoExtractor):
|
class HarkIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.hark\.com/clips/(.+?)-.+'
|
_VALID_URL = r'https?://www\.hark\.com/clips/(?P<id>.+?)-.+'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u'url': u'http://www.hark.com/clips/mmbzyhkgny-obama-beyond-the-afghan-theater-we-only-target-al-qaeda-on-may-23-2013',
|
'url': 'http://www.hark.com/clips/mmbzyhkgny-obama-beyond-the-afghan-theater-we-only-target-al-qaeda-on-may-23-2013',
|
||||||
u'file': u'mmbzyhkgny.mp3',
|
'md5': '6783a58491b47b92c7c1af5a77d4cbee',
|
||||||
u'md5': u'6783a58491b47b92c7c1af5a77d4cbee',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': 'mmbzyhkgny',
|
||||||
u'title': u"Obama: 'Beyond The Afghan Theater, We Only Target Al Qaeda' on May 23, 2013",
|
'ext': 'mp3',
|
||||||
u'description': u'President Barack Obama addressed the nation live on May 23, 2013 in a speech aimed at addressing counter-terrorism policies including the use of drone strikes, detainees at Guantanamo Bay prison facility, and American citizens who are terrorists.',
|
'title': 'Obama: \'Beyond The Afghan Theater, We Only Target Al Qaeda\' on May 23, 2013',
|
||||||
u'duration': 11,
|
'description': 'President Barack Obama addressed the nation live on May 23, 2013 in a speech aimed at addressing counter-terrorism policies including the use of drone strikes, detainees at Guantanamo Bay prison facility, and American citizens who are terrorists.',
|
||||||
|
'duration': 11,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group(1)
|
data = self._download_json(
|
||||||
json_url = "http://www.hark.com/clips/%s.json" %(video_id)
|
'http://www.hark.com/clips/%s.json' % video_id, video_id)
|
||||||
info_json = self._download_webpage(json_url, video_id)
|
|
||||||
info = json.loads(info_json)
|
|
||||||
final_url = info['url']
|
|
||||||
|
|
||||||
return {'id': video_id,
|
return {
|
||||||
'url' : final_url,
|
'id': video_id,
|
||||||
'title': info['name'],
|
'url': data['url'],
|
||||||
'ext': determine_ext(final_url),
|
'title': data['name'],
|
||||||
'description': info['description'],
|
'description': data.get('description'),
|
||||||
'thumbnail': info['image_original'],
|
'thumbnail': data.get('image_original'),
|
||||||
'duration': info['duration'],
|
'duration': data.get('duration'),
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
get_meta_content,
|
get_meta_content,
|
||||||
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,20 +29,26 @@ class HeiseIE(InfoExtractor):
|
|||||||
'timestamp': 1411812600,
|
'timestamp': 1411812600,
|
||||||
'upload_date': '20140927',
|
'upload_date': '20140927',
|
||||||
'description': 'In uplink-Episode 3.3 geht es darum, wie man sich von Cloud-Anbietern emanzipieren kann, worauf man beim Kauf einer Tastatur achten sollte und was Smartphones über uns verraten.',
|
'description': 'In uplink-Episode 3.3 geht es darum, wie man sich von Cloud-Anbietern emanzipieren kann, worauf man beim Kauf einer Tastatur achten sollte und was Smartphones über uns verraten.',
|
||||||
|
'thumbnail': 're:https?://.*\.jpg$',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
json_url = self._search_regex(
|
|
||||||
r'json_url:\s*"([^"]+)"', webpage, 'json URL')
|
container_id = self._search_regex(
|
||||||
config = self._download_json(json_url, video_id)
|
r'<div class="videoplayerjw".*?data-container="([0-9]+)"',
|
||||||
|
webpage, 'container ID')
|
||||||
|
sequenz_id = self._search_regex(
|
||||||
|
r'<div class="videoplayerjw".*?data-sequenz="([0-9]+)"',
|
||||||
|
webpage, 'sequenz ID')
|
||||||
|
data_url = 'http://www.heise.de/videout/feed?container=%s&sequenz=%s' % (container_id, sequenz_id)
|
||||||
|
doc = self._download_xml(data_url, video_id)
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'thumbnail': config.get('poster'),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'timestamp': parse_iso8601(get_meta_content('date', webpage)),
|
'timestamp': parse_iso8601(get_meta_content('date', webpage)),
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
}
|
}
|
||||||
@ -49,32 +56,19 @@ class HeiseIE(InfoExtractor):
|
|||||||
title = get_meta_content('fulltitle', webpage)
|
title = get_meta_content('fulltitle', webpage)
|
||||||
if title:
|
if title:
|
||||||
info['title'] = title
|
info['title'] = title
|
||||||
elif config.get('title'):
|
|
||||||
info['title'] = config['title']
|
|
||||||
else:
|
else:
|
||||||
info['title'] = self._og_search_title(webpage)
|
info['title'] = self._og_search_title(webpage)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for t, rs in config['formats'].items():
|
for source_node in doc.findall('.//{http://rss.jwpcdn.com/}source'):
|
||||||
if not rs or not hasattr(rs, 'items'):
|
label = source_node.attrib['label']
|
||||||
self._downloader.report_warning(
|
height = int_or_none(self._search_regex(
|
||||||
'formats: {0}: no resolutions'.format(t))
|
r'^(.*?_)?([0-9]+)p$', label, 'height', default=None))
|
||||||
continue
|
formats.append({
|
||||||
|
'url': source_node.attrib['file'],
|
||||||
for height_str, obj in rs.items():
|
'format_note': label,
|
||||||
format_id = '{0}_{1}'.format(t, height_str)
|
'height': height,
|
||||||
|
})
|
||||||
if not obj or not obj.get('url'):
|
|
||||||
self._downloader.report_warning(
|
|
||||||
'formats: {0}: no url'.format(format_id))
|
|
||||||
continue
|
|
||||||
|
|
||||||
formats.append({
|
|
||||||
'url': obj['url'],
|
|
||||||
'format_id': format_id,
|
|
||||||
'height': self._int(height_str, 'height'),
|
|
||||||
})
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
info['formats'] = formats
|
info['formats'] = formats
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
@ -20,13 +18,11 @@ class IconosquareIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
html_title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<title>(.+?)</title>',
|
r'<title>(.+?)(?: *\(Videos?\))? \| (?:Iconosquare|Statigram)</title>',
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
title = re.sub(r'(?: *\(Videos?\))? \| (?:Iconosquare|Statigram)$', '', html_title)
|
|
||||||
uploader_id = self._html_search_regex(
|
uploader_id = self._html_search_regex(
|
||||||
r'@([^ ]+)', title, 'uploader name', fatal=False)
|
r'@([^ ]+)', title, 'uploader name', fatal=False)
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
@ -21,22 +19,17 @@ class KickStarterIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'note': 'Embedded video (not using the native kickstarter video service)',
|
'note': 'Embedded video (not using the native kickstarter video service)',
|
||||||
'url': 'https://www.kickstarter.com/projects/597507018/pebble-e-paper-watch-for-iphone-and-android/posts/659178',
|
'url': 'https://www.kickstarter.com/projects/597507018/pebble-e-paper-watch-for-iphone-and-android/posts/659178',
|
||||||
'playlist': [
|
'info_dict': {
|
||||||
{
|
'id': '78704821',
|
||||||
'info_dict': {
|
'ext': 'mp4',
|
||||||
'id': '78704821',
|
'uploader_id': 'pebble',
|
||||||
'ext': 'mp4',
|
'uploader': 'Pebble Technology',
|
||||||
'uploader_id': 'pebble',
|
'title': 'Pebble iOS Notifications',
|
||||||
'uploader': 'Pebble Technology',
|
}
|
||||||
'title': 'Pebble iOS Notifications',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
m = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = m.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
@ -18,11 +16,11 @@ class Ku6IE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
title = self._search_regex(r'<h1 title=.*>(.*?)</h1>', webpage, 'title')
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<h1 title=.*>(.*?)</h1>', webpage, 'title')
|
||||||
dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
|
dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
|
||||||
jsonData = self._download_json(dataUrl, video_id)
|
jsonData = self._download_json(dataUrl, video_id)
|
||||||
downloadUrl = jsonData['data']['f']
|
downloadUrl = jsonData['data']['f']
|
||||||
|
77
youtube_dl/extractor/laola1tv.py
Normal file
77
youtube_dl/extractor/laola1tv.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class Laola1TvIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?laola1\.tv/(?P<lang>[a-z]+)-(?P<portal>[a-z]+)/.*?/(?P<id>[0-9]+)\.html'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.laola1.tv/de-de/live/bwf-bitburger-open-grand-prix-gold-court-1/250019.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '250019',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Bitburger Open Grand Prix Gold - Court 1',
|
||||||
|
'categories': ['Badminton'],
|
||||||
|
'uploader': 'BWF - Badminton World Federation',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_BROKEN = True # Not really - extractor works fine, but f4m downloader does not support live streams yet.
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
lang = mobj.group('lang')
|
||||||
|
portal = mobj.group('portal')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
iframe_url = self._search_regex(
|
||||||
|
r'<iframe[^>]*?class="main_tv_player"[^>]*?src="([^"]+)"',
|
||||||
|
webpage, 'iframe URL')
|
||||||
|
|
||||||
|
iframe = self._download_webpage(
|
||||||
|
iframe_url, video_id, note='Downloading iframe')
|
||||||
|
flashvars_m = re.findall(
|
||||||
|
r'flashvars\.([_a-zA-Z0-9]+)\s*=\s*"([^"]*)";', iframe)
|
||||||
|
flashvars = dict((m[0], m[1]) for m in flashvars_m)
|
||||||
|
|
||||||
|
xml_url = ('http://www.laola1.tv/server/hd_video.php?' +
|
||||||
|
'play=%s&partner=1&portal=%s&v5ident=&lang=%s' % (
|
||||||
|
video_id, portal, lang))
|
||||||
|
hd_doc = self._download_xml(xml_url, video_id)
|
||||||
|
|
||||||
|
title = hd_doc.find('.//video/title').text
|
||||||
|
flash_url = hd_doc.find('.//video/url').text
|
||||||
|
categories = hd_doc.find('.//video/meta_sports').text.split(',')
|
||||||
|
uploader = hd_doc.find('.//video/meta_organistation').text
|
||||||
|
|
||||||
|
ident = random.randint(10000000, 99999999)
|
||||||
|
token_url = '%s&ident=%s&klub=0&unikey=0×tamp=%s&auth=%s' % (
|
||||||
|
flash_url, ident, flashvars['timestamp'], flashvars['auth'])
|
||||||
|
|
||||||
|
token_doc = self._download_xml(
|
||||||
|
token_url, video_id, note='Downloading token')
|
||||||
|
token_attrib = token_doc.find('.//token').attrib
|
||||||
|
if token_attrib.get('auth') == 'blocked':
|
||||||
|
raise ExtractorError('Token error: ' % token_attrib.get('comment'))
|
||||||
|
|
||||||
|
video_url = '%s?hdnea=%s&hdcore=3.2.0' % (
|
||||||
|
token_attrib['url'], token_attrib['auth'])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'is_live': True,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
'uploader': uploader,
|
||||||
|
'categories': categories,
|
||||||
|
'ext': 'mp4',
|
||||||
|
}
|
||||||
|
|
@ -190,7 +190,8 @@ class LivestreamOriginalIE(InfoExtractor):
|
|||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': item.find('title').text,
|
'title': item.find('title').text,
|
||||||
'url': 'rtmp://extondemand.livestream.com/ondemand',
|
'url': 'rtmp://extondemand.livestream.com/ondemand',
|
||||||
'play_path': 'mp4:trans/dv15/mogulus-{0}.mp4'.format(path),
|
'play_path': 'trans/dv15/mogulus-{0}'.format(path),
|
||||||
|
'player_url': 'http://static.livestream.com/chromelessPlayer/v21/playerapi.swf?hash=5uetk&v=0803&classid=D27CDB6E-AE6D-11cf-96B8-444553540000&jsEnabled=false&wmode=opaque',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'thumbnail': thumbnail_url,
|
'thumbnail': thumbnail_url,
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class LRTIE(InfoExtractor):
|
|||||||
'id': '54391',
|
'id': '54391',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Septynios Kauno dienos',
|
'title': 'Septynios Kauno dienos',
|
||||||
'description': 'Kauno miesto ir apskrities naujienos',
|
'description': 'md5:24d84534c7dc76581e59f5689462411a',
|
||||||
'duration': 1783,
|
'duration': 1783,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
@ -32,9 +32,7 @@ class LRTIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' - LRT')
|
title = remove_end(self._og_search_title(webpage), ' - LRT')
|
||||||
|
@ -6,6 +6,7 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
compat_urlparse,
|
||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
strip_jsonp,
|
strip_jsonp,
|
||||||
@ -43,7 +44,7 @@ class MiTeleIE(InfoExtractor):
|
|||||||
if not domain.startswith('http'):
|
if not domain.startswith('http'):
|
||||||
# only happens in telecinco.es videos
|
# only happens in telecinco.es videos
|
||||||
domain = 'http://' + domain
|
domain = 'http://' + domain
|
||||||
info_url = compat_urllib_parse.urljoin(
|
info_url = compat_urlparse.urljoin(
|
||||||
domain,
|
domain,
|
||||||
compat_urllib_parse.unquote(embed_data['flashvars']['host'])
|
compat_urllib_parse.unquote(embed_data['flashvars']['host'])
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class MLBIE(InfoExtractor):
|
class MLBIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://m\.mlb\.com/(?:(?:.*?/)?video/(?:topic/[\da-z_-]+/)?v|shared/video/embed/embed\.html\?.*?\bcontent_id=)(?P<id>n?\d+)'
|
_VALID_URL = r'https?://m(?:lb)?\.mlb\.com/(?:(?:.*?/)?video/(?:topic/[\da-z_-]+/)?v|(?:shared/video/embed/embed\.html|[^/]+/video/play\.jsp)\?.*?\bcontent_id=)(?P<id>n?\d+)'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://m.mlb.com/sea/video/topic/51231442/v34698933/nymsea-ackley-robs-a-home-run-with-an-amazing-catch/?c_id=sea',
|
'url': 'http://m.mlb.com/sea/video/topic/51231442/v34698933/nymsea-ackley-robs-a-home-run-with-an-amazing-catch/?c_id=sea',
|
||||||
@ -72,6 +72,14 @@ class MLBIE(InfoExtractor):
|
|||||||
'url': 'http://m.mlb.com/shared/video/embed/embed.html?content_id=35692085&topic_id=6479266&width=400&height=224&property=mlb',
|
'url': 'http://m.mlb.com/shared/video/embed/embed.html?content_id=35692085&topic_id=6479266&width=400&height=224&property=mlb',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://mlb.mlb.com/shared/video/embed/embed.html?content_id=36599553',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://mlb.mlb.com/es/video/play.jsp?content_id=36599553',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -5,20 +5,20 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
str_to_int,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MotherlessIE(InfoExtractor):
|
class MotherlessIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://(?:www\.)?motherless\.com/(?P<id>[A-Z0-9]+)'
|
_VALID_URL = r'http://(?:www\.)?motherless\.com/(?:g/[a-z0-9_]+/)?(?P<id>[A-Z0-9]+)'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://motherless.com/AC3FFE1',
|
'url': 'http://motherless.com/AC3FFE1',
|
||||||
'md5': '5527fef81d2e529215dad3c2d744a7d9',
|
'md5': '310f62e325a9fafe64f68c0bccb6e75f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'AC3FFE1',
|
'id': 'AC3FFE1',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Fucked in the ass while playing PS3',
|
'title': 'Fucked in the ass while playing PS3',
|
||||||
'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'],
|
'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'],
|
||||||
'upload_date': '20100913',
|
'upload_date': '20100913',
|
||||||
@ -40,33 +40,51 @@ class MotherlessIE(InfoExtractor):
|
|||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://motherless.com/g/cosplay/633979F',
|
||||||
|
'md5': '0b2a43f447a49c3e649c93ad1fafa4a0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '633979F',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Turtlette',
|
||||||
|
'categories': ['superheroine heroine superher'],
|
||||||
|
'upload_date': '20140827',
|
||||||
|
'uploader_id': 'shade0230',
|
||||||
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
|
'age_limit': 18,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self,url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._html_search_regex(r'id="view-upload-title">\s+([^<]+)<', webpage, 'title')
|
title = self._html_search_regex(
|
||||||
|
r'id="view-upload-title">\s+([^<]+)<', webpage, 'title')
|
||||||
video_url = self._html_search_regex(r'setup\(\{\s+"file".+: "([^"]+)",', webpage, 'video_url')
|
video_url = self._html_search_regex(
|
||||||
|
r'setup\(\{\s+"file".+: "([^"]+)",', webpage, 'video URL')
|
||||||
age_limit = self._rta_search(webpage)
|
age_limit = self._rta_search(webpage)
|
||||||
|
view_count = str_to_int(self._html_search_regex(
|
||||||
view_count = self._html_search_regex(r'<strong>Views</strong>\s+([^<]+)<', webpage, 'view_count')
|
r'<strong>Views</strong>\s+([^<]+)<',
|
||||||
|
webpage, 'view count', fatal=False))
|
||||||
|
like_count = str_to_int(self._html_search_regex(
|
||||||
|
r'<strong>Favorited</strong>\s+([^<]+)<',
|
||||||
|
webpage, 'like count', fatal=False))
|
||||||
|
|
||||||
upload_date = self._html_search_regex(r'<strong>Uploaded</strong>\s+([^<]+)<', webpage, 'upload_date')
|
upload_date = self._html_search_regex(
|
||||||
|
r'<strong>Uploaded</strong>\s+([^<]+)<', webpage, 'upload date')
|
||||||
if 'Ago' in upload_date:
|
if 'Ago' in upload_date:
|
||||||
days = int(re.search(r'([0-9]+)', upload_date).group(1))
|
days = int(re.search(r'([0-9]+)', upload_date).group(1))
|
||||||
upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d')
|
upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d')
|
||||||
else:
|
else:
|
||||||
upload_date = unified_strdate(upload_date)
|
upload_date = unified_strdate(upload_date)
|
||||||
|
|
||||||
like_count = self._html_search_regex(r'<strong>Favorited</strong>\s+([^<]+)<', webpage, 'like_count')
|
|
||||||
|
|
||||||
comment_count = webpage.count('class="media-comment-contents"')
|
comment_count = webpage.count('class="media-comment-contents"')
|
||||||
uploader_id = self._html_search_regex(r'"thumb-member-username">\s+<a href="/m/([^"]+)"', webpage, 'uploader_id')
|
uploader_id = self._html_search_regex(
|
||||||
|
r'"thumb-member-username">\s+<a href="/m/([^"]+)"',
|
||||||
|
webpage, 'uploader_id')
|
||||||
|
|
||||||
categories = self._html_search_meta('keywords', webpage)
|
categories = self._html_search_meta('keywords', webpage)
|
||||||
if categories:
|
if categories:
|
||||||
@ -79,8 +97,8 @@ class MotherlessIE(InfoExtractor):
|
|||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'view_count': int_or_none(view_count.replace(',', '')),
|
'view_count': view_count,
|
||||||
'like_count': int_or_none(like_count.replace(',', '')),
|
'like_count': like_count,
|
||||||
'comment_count': comment_count,
|
'comment_count': comment_count,
|
||||||
'age_limit': age_limit,
|
'age_limit': age_limit,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
|
@ -7,6 +7,7 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
clean_html,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ class NaverIE(InfoExtractor):
|
|||||||
m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"',
|
m_id = re.search(r'var rmcPlayer = new nhn.rmcnmv.RMCVideoPlayer\("(.+?)", "(.+?)"',
|
||||||
webpage)
|
webpage)
|
||||||
if m_id is None:
|
if m_id is None:
|
||||||
|
m_error = re.search(
|
||||||
|
r'(?s)<div class="nation_error">\s*(?:<!--.*?-->)?\s*<p class="[^"]+">(?P<msg>.+?)</p>\s*</div>',
|
||||||
|
webpage)
|
||||||
|
if m_error:
|
||||||
|
raise ExtractorError(clean_html(m_error.group('msg')), expected=True)
|
||||||
raise ExtractorError('couldn\'t extract vid and key')
|
raise ExtractorError('couldn\'t extract vid and key')
|
||||||
vid = m_id.group(1)
|
vid = m_id.group(1)
|
||||||
key = m_id.group(2)
|
key = m_id.group(2)
|
||||||
|
@ -26,8 +26,7 @@ class NBCIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
theplatform_url = self._search_regex('class="video-player video-player-full" data-mpx-url="(.*?)"', webpage, 'theplatform url')
|
theplatform_url = self._search_regex('class="video-player video-player-full" data-mpx-url="(.*?)"', webpage, 'theplatform url')
|
||||||
if theplatform_url.startswith('//'):
|
if theplatform_url.startswith('//'):
|
||||||
@ -57,7 +56,7 @@ class NBCNewsIE(InfoExtractor):
|
|||||||
'md5': 'b2421750c9f260783721d898f4c42063',
|
'md5': 'b2421750c9f260783721d898f4c42063',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'I1wpAI_zmhsQ',
|
'id': 'I1wpAI_zmhsQ',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'How Twitter Reacted To The Snowden Interview',
|
'title': 'How Twitter Reacted To The Snowden Interview',
|
||||||
'description': 'md5:65a0bd5d76fe114f3c2727aa3a81fe64',
|
'description': 'md5:65a0bd5d76fe114f3c2727aa3a81fe64',
|
||||||
},
|
},
|
||||||
@ -97,6 +96,8 @@ class NBCNewsIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for base_url in base_urls:
|
for base_url in base_urls:
|
||||||
|
if not base_url:
|
||||||
|
continue
|
||||||
playlist_url = base_url + '?form=MPXNBCNewsAPI'
|
playlist_url = base_url + '?form=MPXNBCNewsAPI'
|
||||||
all_videos = self._download_json(playlist_url, title)['videos']
|
all_videos = self._download_json(playlist_url, title)['videos']
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
determine_ext,
|
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,21 +21,23 @@ class NHLBaseInfoExtractor(InfoExtractor):
|
|||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
initial_video_url = info['publishPoint']
|
initial_video_url = info['publishPoint']
|
||||||
data = compat_urllib_parse.urlencode({
|
if info['formats'] == '1':
|
||||||
'type': 'fvod',
|
data = compat_urllib_parse.urlencode({
|
||||||
'path': initial_video_url.replace('.mp4', '_sd.mp4'),
|
'type': 'fvod',
|
||||||
})
|
'path': initial_video_url.replace('.mp4', '_sd.mp4'),
|
||||||
path_url = 'http://video.nhl.com/videocenter/servlets/encryptvideopath?' + data
|
})
|
||||||
path_doc = self._download_xml(
|
path_url = 'http://video.nhl.com/videocenter/servlets/encryptvideopath?' + data
|
||||||
path_url, video_id, 'Downloading final video url')
|
path_doc = self._download_xml(
|
||||||
video_url = path_doc.find('path').text
|
path_url, video_id, 'Downloading final video url')
|
||||||
|
video_url = path_doc.find('path').text
|
||||||
|
else:
|
||||||
|
video_url = initial_video_url
|
||||||
|
|
||||||
join = compat_urlparse.urljoin
|
join = compat_urlparse.urljoin
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': info['name'],
|
'title': info['name'],
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'ext': determine_ext(video_url),
|
|
||||||
'description': info['description'],
|
'description': info['description'],
|
||||||
'duration': int(info['duration']),
|
'duration': int(info['duration']),
|
||||||
'thumbnail': join(join(video_url, '/u/'), info['bigImage']),
|
'thumbnail': join(join(video_url, '/u/'), info['bigImage']),
|
||||||
@ -46,10 +47,11 @@ class NHLBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
class NHLIE(NHLBaseInfoExtractor):
|
class NHLIE(NHLBaseInfoExtractor):
|
||||||
IE_NAME = 'nhl.com'
|
IE_NAME = 'nhl.com'
|
||||||
_VALID_URL = r'https?://video(?P<team>\.[^.]*)?\.nhl\.com/videocenter/console(?:\?(?:.*?[?&])?)id=(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://video(?P<team>\.[^.]*)?\.nhl\.com/videocenter/console(?:\?(?:.*?[?&])?)id=(?P<id>[0-9a-z-]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://video.canucks.nhl.com/videocenter/console?catid=6?id=453614',
|
'url': 'http://video.canucks.nhl.com/videocenter/console?catid=6?id=453614',
|
||||||
|
'md5': 'db704a4ea09e8d3988c85e36cc892d09',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '453614',
|
'id': '453614',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@ -58,6 +60,17 @@ class NHLIE(NHLBaseInfoExtractor):
|
|||||||
'duration': 18,
|
'duration': 18,
|
||||||
'upload_date': '20131006',
|
'upload_date': '20131006',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://video.nhl.com/videocenter/console?id=2014020024-628-h',
|
||||||
|
'md5': 'd22e82bc592f52d37d24b03531ee9696',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2014020024-628-h',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Alex Galchenyuk Goal on Ray Emery (14:40/3rd)',
|
||||||
|
'description': 'Home broadcast - Montreal Canadiens at Philadelphia Flyers - October 11, 2014',
|
||||||
|
'duration': 0,
|
||||||
|
'upload_date': '20141011',
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://video.flames.nhl.com/videocenter/console?id=630616',
|
'url': 'http://video.flames.nhl.com/videocenter/console?id=630616',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -146,3 +147,36 @@ class NiconicoIE(InfoExtractor):
|
|||||||
'duration': duration,
|
'duration': duration,
|
||||||
'webpage_url': webpage_url,
|
'webpage_url': webpage_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NiconicoPlaylistIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://www\.nicovideo\.jp/mylist/(?P<id>\d+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.nicovideo.jp/mylist/27411728',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '27411728',
|
||||||
|
'title': 'AKB48のオールナイトニッポン',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 225,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
list_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, list_id)
|
||||||
|
|
||||||
|
entries_json = self._search_regex(r'Mylist\.preload\(\d+, (\[.*\])\);',
|
||||||
|
webpage, 'entries')
|
||||||
|
entries = json.loads(entries_json)
|
||||||
|
entries = [{
|
||||||
|
'_type': 'url',
|
||||||
|
'ie_key': NiconicoIE.ie_key(),
|
||||||
|
'url': 'http://www.nicovideo.jp/watch/%s' % entry['item_id'],
|
||||||
|
} for entry in entries]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'title': self._search_regex(r'\s+name: "(.*?)"', webpage, 'title'),
|
||||||
|
'id': list_id,
|
||||||
|
'entries': entries,
|
||||||
|
}
|
||||||
|
31
youtube_dl/extractor/phoenix.py
Normal file
31
youtube_dl/extractor/phoenix.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .zdf import extract_from_xml_url
|
||||||
|
|
||||||
|
|
||||||
|
class PhoenixIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?phoenix\.de/content/(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.phoenix.de/content/884301',
|
||||||
|
'md5': 'ed249f045256150c92e72dbb70eadec6',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '884301',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Michael Krons mit Hans-Werner Sinn',
|
||||||
|
'description': 'Im Dialog - Sa. 25.10.14, 00.00 - 00.35 Uhr',
|
||||||
|
'upload_date': '20141025',
|
||||||
|
'uploader': 'Im Dialog',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
internal_id = self._search_regex(
|
||||||
|
r'<div class="phx_vod" id="phx_vod_([0-9]+)"',
|
||||||
|
webpage, 'internal video ID')
|
||||||
|
|
||||||
|
api_url = 'http://www.phoenix.de/php/zdfplayer-v1.3/data/beitragsDetails.php?ak=web&id=%s' % internal_id
|
||||||
|
return extract_from_xml_url(self, video_id, api_url)
|
@ -16,13 +16,14 @@ from ..aes import (
|
|||||||
|
|
||||||
|
|
||||||
class PornHubIE(InfoExtractor):
|
class PornHubIE(InfoExtractor):
|
||||||
_VALID_URL = r'^(?:https?://)?(?:www\.)?(?P<url>pornhub\.com/view_video\.php\?viewkey=(?P<videoid>[0-9a-f]+))'
|
_VALID_URL = r'^https?://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=(?P<id>[0-9a-f]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.pornhub.com/view_video.php?viewkey=648719015',
|
'url': 'http://www.pornhub.com/view_video.php?viewkey=648719015',
|
||||||
'file': '648719015.mp4',
|
|
||||||
'md5': '882f488fa1f0026f023f33576004a2ed',
|
'md5': '882f488fa1f0026f023f33576004a2ed',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"uploader": "BABES-COM",
|
'id': '648719015',
|
||||||
|
'ext': 'mp4',
|
||||||
|
"uploader": "Babes",
|
||||||
"title": "Seductive Indian beauty strips down and fingers her pink pussy",
|
"title": "Seductive Indian beauty strips down and fingers her pink pussy",
|
||||||
"age_limit": 18
|
"age_limit": 18
|
||||||
}
|
}
|
||||||
@ -35,9 +36,7 @@ class PornHubIE(InfoExtractor):
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('videoid')
|
|
||||||
url = 'http://www.' + mobj.group('url')
|
|
||||||
|
|
||||||
req = compat_urllib_request.Request(url)
|
req = compat_urllib_request.Request(url)
|
||||||
req.add_header('Cookie', 'age_verified=1')
|
req.add_header('Cookie', 'age_verified=1')
|
||||||
@ -45,7 +44,7 @@ class PornHubIE(InfoExtractor):
|
|||||||
|
|
||||||
video_title = self._html_search_regex(r'<h1 [^>]+>([^<]+)', webpage, 'title')
|
video_title = self._html_search_regex(r'<h1 [^>]+>([^<]+)', webpage, 'title')
|
||||||
video_uploader = self._html_search_regex(
|
video_uploader = self._html_search_regex(
|
||||||
r'(?s)From: .+?<(?:a href="/users/|<span class="username)[^>]+>(.+?)<',
|
r'(?s)From: .+?<(?:a href="/users/|a href="/channels/|<span class="username)[^>]+>(.+?)<',
|
||||||
webpage, 'uploader', fatal=False)
|
webpage, 'uploader', fatal=False)
|
||||||
thumbnail = self._html_search_regex(r'"image_url":"([^"]+)', webpage, 'thumbnail', fatal=False)
|
thumbnail = self._html_search_regex(r'"image_url":"([^"]+)', webpage, 'thumbnail', fatal=False)
|
||||||
if thumbnail:
|
if thumbnail:
|
||||||
|
@ -14,7 +14,6 @@ from ..utils import (
|
|||||||
|
|
||||||
class PromptFileIE(InfoExtractor):
|
class PromptFileIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?promptfile\.com/l/(?P<id>[0-9A-Z\-]+)'
|
_VALID_URL = r'https?://(?:www\.)?promptfile\.com/l/(?P<id>[0-9A-Z\-]+)'
|
||||||
_FILE_NOT_FOUND_REGEX = r'<div.+id="not_found_msg".+>.+</div>[^-]'
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.promptfile.com/l/D21B4746E9-F01462F0FF',
|
'url': 'http://www.promptfile.com/l/D21B4746E9-F01462F0FF',
|
||||||
'md5': 'd1451b6302da7215485837aaea882c4c',
|
'md5': 'd1451b6302da7215485837aaea882c4c',
|
||||||
@ -27,11 +26,10 @@ class PromptFileIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
if re.search(self._FILE_NOT_FOUND_REGEX, webpage) is not None:
|
if re.search(r'<div.+id="not_found_msg".+>(?!We are).+</div>[^-]', webpage) is not None:
|
||||||
raise ExtractorError('Video %s does not exist' % video_id,
|
raise ExtractorError('Video %s does not exist' % video_id,
|
||||||
expected=True)
|
expected=True)
|
||||||
|
|
||||||
|
51
youtube_dl/extractor/quickvid.py
Normal file
51
youtube_dl/extractor/quickvid.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
compat_urlparse,
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QuickVidIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(www\.)?quickvid\.org/watch\.php\?v=(?P<id>[a-zA-Z_0-9-]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://quickvid.org/watch.php?v=sUQT3RCG8dx',
|
||||||
|
'md5': 'c0c72dd473f260c06c808a05d19acdc5',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'sUQT3RCG8dx',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Nick Offerman\'s Summer Reading Recap',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:png|jpg|gif)$',
|
||||||
|
'view_count': int,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(r'<h2>(.*?)</h2>', webpage, 'title')
|
||||||
|
view_count = int_or_none(self._html_search_regex(
|
||||||
|
r'(?s)<div id="views">(.*?)</div>',
|
||||||
|
webpage, 'view count', fatal=False))
|
||||||
|
video_code = self._search_regex(
|
||||||
|
r'(?s)<video id="video"[^>]*>(.*?)</video>', webpage, 'video code')
|
||||||
|
formats = [
|
||||||
|
{
|
||||||
|
'url': compat_urlparse.urljoin(url, src),
|
||||||
|
'format_id': determine_ext(src, None),
|
||||||
|
} for src in re.findall('<source\s+src="([^"]+)"', video_code)
|
||||||
|
]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'view_count': view_count,
|
||||||
|
}
|
@ -1,43 +1,43 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import compat_urllib_parse_unquote
|
||||||
clean_html,
|
|
||||||
compat_parse_qs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Ro220IE(InfoExtractor):
|
class Ro220IE(InfoExtractor):
|
||||||
IE_NAME = '220.ro'
|
IE_NAME = '220.ro'
|
||||||
_VALID_URL = r'(?x)(?:https?://)?(?:www\.)?220\.ro/(?P<category>[^/]+)/(?P<shorttitle>[^/]+)/(?P<video_id>[^/]+)'
|
_VALID_URL = r'(?x)(?:https?://)?(?:www\.)?220\.ro/(?P<category>[^/]+)/(?P<shorttitle>[^/]+)/(?P<id>[^/]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
"url": "http://www.220.ro/sport/Luati-Le-Banii-Sez-4-Ep-1/LYV6doKo7f/",
|
'url': 'http://www.220.ro/sport/Luati-Le-Banii-Sez-4-Ep-1/LYV6doKo7f/',
|
||||||
'file': 'LYV6doKo7f.mp4',
|
|
||||||
'md5': '03af18b73a07b4088753930db7a34add',
|
'md5': '03af18b73a07b4088753930db7a34add',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"title": "Luati-le Banii sez 4 ep 1",
|
'id': 'LYV6doKo7f',
|
||||||
"description": "re:^Iata-ne reveniti dupa o binemeritata vacanta\. +Va astept si pe Facebook cu pareri si comentarii.$",
|
'ext': 'mp4',
|
||||||
|
'title': 'Luati-le Banii sez 4 ep 1',
|
||||||
|
'description': 're:^Iata-ne reveniti dupa o binemeritata vacanta\. +Va astept si pe Facebook cu pareri si comentarii.$',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('video_id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
flashVars_str = self._search_regex(
|
url = compat_urllib_parse_unquote(self._search_regex(
|
||||||
r'<param name="flashVars" value="([^"]+)"',
|
r'(?s)clip\s*:\s*{.*?url\s*:\s*\'([^\']+)\'', webpage, 'url'))
|
||||||
webpage, 'flashVars')
|
title = self._og_search_title(webpage)
|
||||||
flashVars = compat_parse_qs(flashVars_str)
|
description = self._og_search_description(webpage)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': 'sd',
|
||||||
|
'url': url,
|
||||||
|
'ext': 'mp4',
|
||||||
|
}]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'video',
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'ext': 'mp4',
|
'formats': formats,
|
||||||
'url': flashVars['videoURL'][0],
|
'title': title,
|
||||||
'title': flashVars['title'][0],
|
'description': description,
|
||||||
'description': clean_html(flashVars['desc'][0]),
|
'thumbnail': thumbnail,
|
||||||
'thumbnail': flashVars['preview'][0],
|
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ class RTLnowIE(InfoExtractor):
|
|||||||
'id': '99205',
|
'id': '99205',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Medicopter 117 - Angst!',
|
'title': 'Medicopter 117 - Angst!',
|
||||||
'description': 'md5:895b1df01639b5f61a04fc305a5cb94d',
|
'description': 're:^Im Therapiezentrum \'Sonnalm\' kommen durch eine Unachtsamkeit die für die B.handlung mit Phobikern gehaltenen Voglespinnen frei\. Eine Ausreißerin',
|
||||||
'thumbnail': 'http://autoimg.static-fra.de/superrtlnow/287529/1500x1500/image2.jpg',
|
'thumbnail': 'http://autoimg.static-fra.de/superrtlnow/287529/1500x1500/image2.jpg',
|
||||||
'upload_date': '20080928',
|
'upload_date': '20080928',
|
||||||
'duration': 2691,
|
'duration': 2691,
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
@ -21,19 +19,20 @@ class RUHDIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_url = self._html_search_regex(
|
video_url = self._html_search_regex(
|
||||||
r'<param name="src" value="([^"]+)"', webpage, 'video url')
|
r'<param name="src" value="([^"]+)"', webpage, 'video url')
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<title>([^<]+) RUHD.ru - Видео Высокого качества №1 в России!</title>', webpage, 'title')
|
r'<title>([^<]+) RUHD.ru - Видео Высокого качества №1 в России!</title>',
|
||||||
|
webpage, 'title')
|
||||||
description = self._html_search_regex(
|
description = self._html_search_regex(
|
||||||
r'(?s)<div id="longdesc">(.+?)<span id="showlink">', webpage, 'description', fatal=False)
|
r'(?s)<div id="longdesc">(.+?)<span id="showlink">',
|
||||||
|
webpage, 'description', fatal=False)
|
||||||
thumbnail = self._html_search_regex(
|
thumbnail = self._html_search_regex(
|
||||||
r'<param name="previewImage" value="([^"]+)"', webpage, 'thumbnail', fatal=False)
|
r'<param name="previewImage" value="([^"]+)"',
|
||||||
|
webpage, 'thumbnail', fatal=False)
|
||||||
if thumbnail:
|
if thumbnail:
|
||||||
thumbnail = 'http://www.ruhd.ru' + thumbnail
|
thumbnail = 'http://www.ruhd.ru' + thumbnail
|
||||||
|
|
||||||
|
@ -40,14 +40,15 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy',
|
'url': 'http://soundcloud.com/ethmusic/lostin-powers-she-so-heavy',
|
||||||
'file': '62986583.mp3',
|
|
||||||
'md5': 'ebef0a451b909710ed1d7787dddbf0d7',
|
'md5': 'ebef0a451b909710ed1d7787dddbf0d7',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
"upload_date": "20121011",
|
'id': '62986583',
|
||||||
"description": "No Downloads untill we record the finished version this weekend, i was too pumped n i had to post it , earl is prolly gonna b hella p.o'd",
|
'ext': 'mp3',
|
||||||
"uploader": "E.T. ExTerrestrial Music",
|
'upload_date': '20121011',
|
||||||
"title": "Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1",
|
'description': 'No Downloads untill we record the finished version this weekend, i was too pumped n i had to post it , earl is prolly gonna b hella p.o\'d',
|
||||||
"duration": 143,
|
'uploader': 'E.T. ExTerrestrial Music',
|
||||||
|
'title': 'Lostin Powers - She so Heavy (SneakPreview) Adrian Ackers Blueprint 1',
|
||||||
|
'duration': 143,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# not streamable song
|
# not streamable song
|
||||||
@ -103,7 +104,7 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'id': '128590877',
|
'id': '128590877',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': 'Bus Brakes',
|
'title': 'Bus Brakes',
|
||||||
'description': 'md5:0170be75dd395c96025d210d261c784e',
|
'description': 'md5:0053ca6396e8d2fd7b7e1595ef12ab66',
|
||||||
'uploader': 'oddsamples',
|
'uploader': 'oddsamples',
|
||||||
'upload_date': '20140109',
|
'upload_date': '20140109',
|
||||||
'duration': 17,
|
'duration': 17,
|
||||||
@ -140,6 +141,7 @@ class SoundcloudIE(InfoExtractor):
|
|||||||
'description': info['description'],
|
'description': info['description'],
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': int_or_none(info.get('duration'), 1000),
|
'duration': int_or_none(info.get('duration'), 1000),
|
||||||
|
'webpage_url': info.get('permalink_url'),
|
||||||
}
|
}
|
||||||
formats = []
|
formats = []
|
||||||
if info.get('downloadable', False):
|
if info.get('downloadable', False):
|
||||||
|
@ -7,7 +7,6 @@ from .common import InfoExtractor
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
int_or_none,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +25,6 @@ class SportBoxIE(InfoExtractor):
|
|||||||
'timestamp': 1411896237,
|
'timestamp': 1411896237,
|
||||||
'upload_date': '20140928',
|
'upload_date': '20140928',
|
||||||
'duration': 4846,
|
'duration': 4846,
|
||||||
'view_count': int,
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
@ -65,8 +63,6 @@ class SportBoxIE(InfoExtractor):
|
|||||||
r'<span itemprop="uploadDate">([^<]+)</span>', webpage, 'timestamp', fatal=False))
|
r'<span itemprop="uploadDate">([^<]+)</span>', webpage, 'timestamp', fatal=False))
|
||||||
duration = parse_duration(self._html_search_regex(
|
duration = parse_duration(self._html_search_regex(
|
||||||
r'<meta itemprop="duration" content="PT([^"]+)">', webpage, 'duration', fatal=False))
|
r'<meta itemprop="duration" content="PT([^"]+)">', webpage, 'duration', fatal=False))
|
||||||
view_count = int_or_none(self._html_search_regex(
|
|
||||||
r'<span>Просмотров: (\d+)</span>', player, 'view count', fatal=False))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@ -76,6 +72,5 @@ class SportBoxIE(InfoExtractor):
|
|||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'view_count': view_count,
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
43
youtube_dl/extractor/srmediathek.py
Normal file
43
youtube_dl/extractor/srmediathek.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import js_to_json
|
||||||
|
|
||||||
|
|
||||||
|
class SRMediathekIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Süddeutscher Rundfunk'
|
||||||
|
_VALID_URL = r'https?://sr-mediathek\.sr-online\.de/index\.php\?.*?&id=(?P<id>[0-9]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://sr-mediathek.sr-online.de/index.php?seite=7&id=28455',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '28455',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'sportarena (26.10.2014)',
|
||||||
|
'description': 'Ringen: KSV Köllerbach gegen Aachen-Walheim; Frauen-Fußball: 1. FC Saarbrücken gegen Sindelfingen; Motorsport: Rallye in Losheim; dazu: Interview mit Timo Bernhard; Turnen: TG Saar; Reitsport: Deutscher Voltigier-Pokal; Badminton: Interview mit Michael Fuchs ',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
murls = json.loads(js_to_json(self._search_regex(
|
||||||
|
r'var mediaURLs\s*=\s*(.*?);\n', webpage, 'video URLs')))
|
||||||
|
formats = [{'url': murl} for murl in murls]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = json.loads(js_to_json(self._search_regex(
|
||||||
|
r'var mediaTitles\s*=\s*(.*?);\n', webpage, 'title')))[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
}
|
@ -10,7 +10,6 @@ class SyfyIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.syfy.com/videos/Robot%20Combat%20League/Behind%20the%20Scenes/vid:2631458',
|
'url': 'http://www.syfy.com/videos/Robot%20Combat%20League/Behind%20the%20Scenes/vid:2631458',
|
||||||
'md5': 'e07de1d52c7278adbb9b9b1c93a66849',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'NmqMrGnXvmO1',
|
'id': 'NmqMrGnXvmO1',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
@ -6,6 +6,7 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
compat_str,
|
compat_str,
|
||||||
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
)
|
)
|
||||||
@ -34,10 +35,21 @@ class ThePlatformIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
if mobj.group('config'):
|
||||||
|
config_url = url+ '&form=json'
|
||||||
|
config_url = config_url.replace('swf/', 'config/')
|
||||||
|
config_url = config_url.replace('onsite/', 'onsite/config/')
|
||||||
|
config = self._download_json(config_url, video_id, 'Downloading config')
|
||||||
|
smil_url = config['releaseUrl'] + '&format=SMIL&formats=MPEG4&manifest=f4m'
|
||||||
|
else:
|
||||||
|
smil_url = ('http://link.theplatform.com/s/dJ5BDC/{0}/meta.smil?'
|
||||||
|
'format=smil&mbr=true'.format(video_id))
|
||||||
|
|
||||||
|
|
||||||
def _get_info(self, video_id, smil_url):
|
|
||||||
meta = self._download_xml(smil_url, video_id)
|
meta = self._download_xml(smil_url, video_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
error_msg = next(
|
error_msg = next(
|
||||||
n.attrib['abstract']
|
n.attrib['abstract']
|
||||||
@ -89,10 +101,14 @@ class ThePlatformIE(InfoExtractor):
|
|||||||
for f in switch.findall(_x('smil:video')):
|
for f in switch.findall(_x('smil:video')):
|
||||||
attr = f.attrib
|
attr = f.attrib
|
||||||
vbr = int(attr['system-bitrate']) // 1000
|
vbr = int(attr['system-bitrate']) // 1000
|
||||||
|
ext = determine_ext(attr['src'])
|
||||||
|
if ext == 'once':
|
||||||
|
ext = 'mp4'
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': compat_str(vbr),
|
'format_id': compat_str(vbr),
|
||||||
'url': attr['src'],
|
'url': attr['src'],
|
||||||
'vbr': vbr,
|
'vbr': vbr,
|
||||||
|
'ext': ext,
|
||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
@ -104,17 +120,3 @@ class ThePlatformIE(InfoExtractor):
|
|||||||
'thumbnail': info['defaultThumbnailUrl'],
|
'thumbnail': info['defaultThumbnailUrl'],
|
||||||
'duration': info['duration']//1000,
|
'duration': info['duration']//1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
if mobj.group('config'):
|
|
||||||
config_url = url+ '&form=json'
|
|
||||||
config_url = config_url.replace('swf/', 'config/')
|
|
||||||
config_url = config_url.replace('onsite/', 'onsite/config/')
|
|
||||||
config = self._download_json(config_url, video_id, 'Downloading config')
|
|
||||||
smil_url = config['releaseUrl'] + '&format=SMIL&formats=MPEG4&manifest=f4m'
|
|
||||||
else:
|
|
||||||
smil_url = ('http://link.theplatform.com/s/dJ5BDC/{0}/meta.smil?'
|
|
||||||
'format=smil&mbr=true'.format(video_id))
|
|
||||||
return self._get_info(video_id, smil_url)
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import xpath_text
|
||||||
|
|
||||||
|
|
||||||
class TruTubeIE(InfoExtractor):
|
class TruTubeIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?trutube\.tv/video/(?P<id>[0-9]+)/.*'
|
_VALID_URL = r'https?://(?:www\.)?trutube\.tv/(?:video/|nuevo/player/embed\.php\?v=)(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://trutube.tv/video/14880/Ramses-II-Proven-To-Be-A-Red-Headed-Caucasoid-',
|
'url': 'http://trutube.tv/video/14880/Ramses-II-Proven-To-Be-A-Red-Headed-Caucasoid-',
|
||||||
'md5': 'c5b6e301b0a2040b074746cbeaa26ca1',
|
'md5': 'c5b6e301b0a2040b074746cbeaa26ca1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -16,29 +15,26 @@ class TruTubeIE(InfoExtractor):
|
|||||||
'title': 'Ramses II - Proven To Be A Red Headed Caucasoid',
|
'title': 'Ramses II - Proven To Be A Red Headed Caucasoid',
|
||||||
'thumbnail': 're:^http:.*\.jpg$',
|
'thumbnail': 're:^http:.*\.jpg$',
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://trutube.tv/nuevo/player/embed.php?v=14880',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
config = self._download_xml(
|
||||||
video_title = self._og_search_title(webpage).strip()
|
'https://trutube.tv/nuevo/player/config.php?v=%s' % video_id,
|
||||||
thumbnail = self._search_regex(
|
video_id, transform_source=lambda s: s.strip())
|
||||||
r"var splash_img = '([^']+)';", webpage, 'thumbnail', fatal=False)
|
|
||||||
|
|
||||||
all_formats = re.finditer(
|
# filehd is always 404
|
||||||
r"var (?P<key>[a-z]+)_video_file\s*=\s*'(?P<url>[^']+)';", webpage)
|
video_url = xpath_text(config, './file', 'video URL', fatal=True)
|
||||||
formats = [{
|
title = xpath_text(config, './title', 'title')
|
||||||
'format_id': m.group('key'),
|
thumbnail = xpath_text(config, './image', ' thumbnail')
|
||||||
'quality': -i,
|
|
||||||
'url': m.group('url'),
|
|
||||||
} for i, m in enumerate(all_formats)]
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'url': video_url,
|
||||||
'formats': formats,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,6 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TumblrIE(InfoExtractor):
|
class TumblrIE(InfoExtractor):
|
||||||
@ -18,7 +15,7 @@ class TumblrIE(InfoExtractor):
|
|||||||
'id': '54196191430',
|
'id': '54196191430',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'tatiana maslany news, Orphan Black || DVD extra - behind the scenes ↳...',
|
'title': 'tatiana maslany news, Orphan Black || DVD extra - behind the scenes ↳...',
|
||||||
'description': 'md5:dfac39636969fe6bf1caa2d50405f069',
|
'description': 'md5:37db8211e40b50c7c44e95da14f630b7',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@ -27,7 +24,7 @@ class TumblrIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '90208453769',
|
'id': '90208453769',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '5SOS STRUM ;)',
|
'title': '5SOS STRUM ;]',
|
||||||
'description': 'md5:dba62ac8639482759c8eb10ce474586a',
|
'description': 'md5:dba62ac8639482759c8eb10ce474586a',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
}
|
}
|
||||||
@ -41,18 +38,12 @@ class TumblrIE(InfoExtractor):
|
|||||||
url = 'http://%s.tumblr.com/post/%s/' % (blog, video_id)
|
url = 'http://%s.tumblr.com/post/%s/' % (blog, video_id)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
re_video = r'src=\\x22(?P<video_url>http://%s\.tumblr\.com/video_file/%s/(.*?))\\x22 type=\\x22video/(?P<ext>.*?)\\x22' % (blog, video_id)
|
iframe_url = self._search_regex(
|
||||||
video = re.search(re_video, webpage)
|
r'src=\'(https?://www\.tumblr\.com/video/[^\']+)\'',
|
||||||
if video is None:
|
webpage, 'iframe url')
|
||||||
raise ExtractorError('Unable to extract video')
|
iframe = self._download_webpage(iframe_url, video_id)
|
||||||
video_url = video.group('video_url')
|
video_url = self._search_regex(r'<source src="([^"]+)"',
|
||||||
ext = video.group('ext')
|
iframe, 'video url')
|
||||||
|
|
||||||
video_thumbnail = self._search_regex(
|
|
||||||
r'posters.*?\[\\x22(.*?)\\x22',
|
|
||||||
webpage, 'thumbnail', fatal=False) # We pick the first poster
|
|
||||||
if video_thumbnail:
|
|
||||||
video_thumbnail = video_thumbnail.replace('\\\\/', '/')
|
|
||||||
|
|
||||||
# The only place where you can get a title, it's not complete,
|
# The only place where you can get a title, it's not complete,
|
||||||
# but searching in other places doesn't work for all videos
|
# but searching in other places doesn't work for all videos
|
||||||
@ -62,9 +53,9 @@ class TumblrIE(InfoExtractor):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'title': video_title,
|
'ext': 'mp4',
|
||||||
'description': self._html_search_meta('description', webpage),
|
'title': video_title,
|
||||||
'thumbnail': video_thumbnail,
|
'description': self._og_search_description(webpage),
|
||||||
'ext': ext,
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ class UstreamChannelIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '10874166',
|
'id': '10874166',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 54,
|
'playlist_mincount': 17,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -17,7 +17,7 @@ class VGTVIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '84196',
|
'id': '84196',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Hevnen er søt episode 10: Abu',
|
'title': 'Hevnen er søt episode 1:10 - Abu',
|
||||||
'description': 'md5:e25e4badb5f544b04341e14abdc72234',
|
'description': 'md5:e25e4badb5f544b04341e14abdc72234',
|
||||||
'thumbnail': 're:^https?://.*\.jpg',
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
'duration': 648.000,
|
'duration': 648.000,
|
||||||
@ -67,9 +67,7 @@ class VGTVIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
data = self._download_json(
|
data = self._download_json(
|
||||||
'http://svp.vg.no/svp/api/v1/vgtv/assets/%s?appName=vgtv-website' % video_id,
|
'http://svp.vg.no/svp/api/v1/vgtv/assets/%s?appName=vgtv-website' % video_id,
|
||||||
video_id, 'Downloading media JSON')
|
video_id, 'Downloading media JSON')
|
||||||
|
@ -1,55 +1,85 @@
|
|||||||
import json
|
from __future__ import unicode_literals
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ViddlerIE(InfoExtractor):
|
class ViddlerIE(InfoExtractor):
|
||||||
_VALID_URL = r'(?P<domain>https?://(?:www\.)?viddler\.com)/(?:v|embed|player)/(?P<id>[a-z0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?viddler\.com/(?:v|embed|player)/(?P<id>[a-z0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
u"url": u"http://www.viddler.com/v/43903784",
|
"url": "http://www.viddler.com/v/43903784",
|
||||||
u'file': u'43903784.mp4',
|
'md5': 'ae43ad7cb59431ce043f0ff7fa13cbf4',
|
||||||
u'md5': u'fbbaedf7813e514eb7ca30410f439ac9',
|
'info_dict': {
|
||||||
u'info_dict': {
|
'id': '43903784',
|
||||||
u"title": u"Video Made Easy",
|
'ext': 'mp4',
|
||||||
u"uploader": u"viddler",
|
"title": "Video Made Easy",
|
||||||
u"duration": 100.89,
|
'description': 'You don\'t need to be a professional to make high-quality video content. Viddler provides some quick and easy tips on how to produce great video content with limited resources. ',
|
||||||
|
"uploader": "viddler",
|
||||||
|
'timestamp': 1335371429,
|
||||||
|
'upload_date': '20120425',
|
||||||
|
"duration": 100.89,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'view_count': int,
|
||||||
|
'categories': ['video content', 'high quality video', 'video made easy', 'how to produce video with limited resources', 'viddler'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
embed_url = mobj.group('domain') + u'/embed/' + video_id
|
json_url = (
|
||||||
webpage = self._download_webpage(embed_url, video_id)
|
'http://api.viddler.com/api/v2/viddler.videos.getPlaybackDetails.json?video_id=%s&key=v0vhrt7bg2xq1vyxhkct' %
|
||||||
|
video_id)
|
||||||
|
data = self._download_json(json_url, video_id)['video']
|
||||||
|
|
||||||
video_sources_code = self._search_regex(
|
formats = []
|
||||||
r"(?ms)sources\s*:\s*(\{.*?\})", webpage, u'video URLs')
|
for filed in data['files']:
|
||||||
video_sources = json.loads(video_sources_code.replace("'", '"'))
|
if filed.get('status', 'ready') != 'ready':
|
||||||
|
continue
|
||||||
|
f = {
|
||||||
|
'format_id': filed['profile_id'],
|
||||||
|
'format_note': filed['profile_name'],
|
||||||
|
'url': self._proto_relative_url(filed['url']),
|
||||||
|
'width': int_or_none(filed.get('width')),
|
||||||
|
'height': int_or_none(filed.get('height')),
|
||||||
|
'filesize': int_or_none(filed.get('size')),
|
||||||
|
'ext': filed.get('ext'),
|
||||||
|
'source_preference': -1,
|
||||||
|
}
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
formats = [{
|
if filed.get('cdn_url'):
|
||||||
'url': video_url,
|
f = f.copy()
|
||||||
'format': format_id,
|
f['url'] = self._proto_relative_url(filed['cdn_url'])
|
||||||
} for video_url, format_id in video_sources.items()]
|
f['format_id'] = filed['profile_id'] + '-cdn'
|
||||||
|
f['source_preference'] = 1
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
if filed.get('html5_video_source'):
|
||||||
r"title\s*:\s*'([^']*)'", webpage, u'title')
|
f = f.copy()
|
||||||
uploader = self._html_search_regex(
|
f['url'] = self._proto_relative_url(
|
||||||
r"authorName\s*:\s*'([^']*)'", webpage, u'uploader', fatal=False)
|
filed['html5_video_source'])
|
||||||
duration_s = self._html_search_regex(
|
f['format_id'] = filed['profile_id'] + '-html5'
|
||||||
r"duration\s*:\s*([0-9.]*)", webpage, u'duration', fatal=False)
|
f['source_preference'] = 0
|
||||||
duration = float(duration_s) if duration_s else None
|
formats.append(f)
|
||||||
thumbnail = self._html_search_regex(
|
self._sort_formats(formats)
|
||||||
r"thumbnail\s*:\s*'([^']*)'",
|
|
||||||
webpage, u'thumbnail', fatal=False)
|
categories = [
|
||||||
|
t.get('text') for t in data.get('tags', []) if 'text' in t]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'video',
|
'_type': 'video',
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': data['title'],
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'uploader': uploader,
|
|
||||||
'duration': duration,
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'description': data.get('description'),
|
||||||
|
'timestamp': int_or_none(data.get('upload_time')),
|
||||||
|
'thumbnail': self._proto_relative_url(data.get('thumbnail_url')),
|
||||||
|
'uploader': data.get('author'),
|
||||||
|
'duration': float_or_none(data.get('length')),
|
||||||
|
'view_count': int_or_none(data.get('view_count')),
|
||||||
|
'categories': categories,
|
||||||
}
|
}
|
||||||
|
33
youtube_dl/extractor/vidzi.py
Normal file
33
youtube_dl/extractor/vidzi.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class VidziIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?vidzi\.tv/(?P<id>\w+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://vidzi.tv/cghql9yq6emu.html',
|
||||||
|
'md5': '4f16c71ca0c8c8635ab6932b5f3f1660',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'cghql9yq6emu',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'youtube-dl test video 1\\\\2\'3/4<5\\\\6ä7↭',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
video_url = self._html_search_regex(
|
||||||
|
r'{\s*file\s*:\s*"([^"]+)"\s*}', webpage, 'video url')
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'(?s)<h2 class="video-title">(.*?)</h2>', webpage, 'title')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'url': video_url,
|
||||||
|
}
|
||||||
|
|
@ -8,13 +8,11 @@ import itertools
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .subtitles import SubtitlesInfoExtractor
|
from .subtitles import SubtitlesInfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
clean_html,
|
|
||||||
compat_HTTPError,
|
compat_HTTPError,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_urllib_request,
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
get_element_by_attribute,
|
|
||||||
InAdvancePagedList,
|
InAdvancePagedList,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
@ -514,7 +512,7 @@ class VimeoReviewIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '91613211',
|
'id': '91613211',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Death by dogma versus assembling agile - Sander Hoogendoorn',
|
'title': 're:(?i)^Death by dogma versus assembling agile . Sander Hoogendoorn',
|
||||||
'uploader': 'DevWeek Events',
|
'uploader': 'DevWeek Events',
|
||||||
'duration': 2773,
|
'duration': 2773,
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
@ -70,7 +70,7 @@ class VineUserIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'Visa',
|
'id': 'Visa',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 47,
|
'playlist_mincount': 46,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -138,9 +138,19 @@ class VKIE(InfoExtractor):
|
|||||||
info_url = 'http://vk.com/al_video.php?act=show&al=1&video=%s' % video_id
|
info_url = 'http://vk.com/al_video.php?act=show&al=1&video=%s' % video_id
|
||||||
info_page = self._download_webpage(info_url, video_id)
|
info_page = self._download_webpage(info_url, video_id)
|
||||||
|
|
||||||
if re.search(r'<!>Please log in or <', info_page):
|
ERRORS = {
|
||||||
raise ExtractorError('This video is only available for registered users, '
|
r'>Видеозапись .*? была изъята из публичного доступа в связи с обращением правообладателя.<':
|
||||||
'use --username and --password options to provide account credentials.', expected=True)
|
'Video %s has been removed from public access due to rightholder complaint.',
|
||||||
|
r'<!>Please log in or <':
|
||||||
|
'Video %s is only available for registered users, '
|
||||||
|
'use --username and --password options to provide account credentials.',
|
||||||
|
'<!>Unknown error':
|
||||||
|
'Video %s does not exist.'
|
||||||
|
}
|
||||||
|
|
||||||
|
for error_re, error_msg in ERRORS.items():
|
||||||
|
if re.search(error_re, info_page):
|
||||||
|
raise ExtractorError(error_msg % video_id, expected=True)
|
||||||
|
|
||||||
m_yt = re.search(r'src="(http://www.youtube.com/.*?)"', info_page)
|
m_yt = re.search(r'src="(http://www.youtube.com/.*?)"', info_page)
|
||||||
if m_yt is not None:
|
if m_yt is not None:
|
||||||
|
95
youtube_dl/extractor/vrt.py
Normal file
95
youtube_dl/extractor/vrt.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import float_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class VRTIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:deredactie|sporza|cobra)\.be/cm/(?:[^/]+/)+(?P<id>[^/]+)/*'
|
||||||
|
_TESTS = [
|
||||||
|
# deredactie.be
|
||||||
|
{
|
||||||
|
'url': 'http://deredactie.be/cm/vrtnieuws/videozone/programmas/journaal/EP_141025_JOL',
|
||||||
|
'md5': '4cebde1eb60a53782d4f3992cbd46ec8',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2129880',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Het journaal L - 25/10/14',
|
||||||
|
'description': None,
|
||||||
|
'timestamp': 1414271750.949,
|
||||||
|
'upload_date': '20141025',
|
||||||
|
'duration': 929,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# sporza.be
|
||||||
|
{
|
||||||
|
'url': 'http://sporza.be/cm/sporza/videozone/programmas/extratime/EP_141020_Extra_time',
|
||||||
|
'md5': '11f53088da9bf8e7cfc42456697953ff',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2124639',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Bekijk Extra Time van 20 oktober',
|
||||||
|
'description': 'md5:83ac5415a4f1816c6a93f8138aef2426',
|
||||||
|
'timestamp': 1413835980.560,
|
||||||
|
'upload_date': '20141020',
|
||||||
|
'duration': 3238,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# cobra.be
|
||||||
|
{
|
||||||
|
'url': 'http://cobra.be/cm/cobra/videozone/rubriek/film-videozone/141022-mv-ellis-cafecorsari',
|
||||||
|
'md5': '78a2b060a5083c4f055449a72477409d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2126050',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Bret Easton Ellis in Café Corsari',
|
||||||
|
'description': 'md5:f699986e823f32fd6036c1855a724ee9',
|
||||||
|
'timestamp': 1413967500.494,
|
||||||
|
'upload_date': '20141022',
|
||||||
|
'duration': 661,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-video-id="([^"]+)_[^"]+"', webpage, 'video id', fatal=False)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
mobj = re.search(
|
||||||
|
r'data-video-iphone-server="(?P<server>[^"]+)"\s+data-video-iphone-path="(?P<path>[^"]+)"',
|
||||||
|
webpage)
|
||||||
|
if mobj:
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
'%s/%s' % (mobj.group('server'), mobj.group('path')),
|
||||||
|
video_id, 'mp4'))
|
||||||
|
mobj = re.search(r'data-video-src="(?P<src>[^"]+)"', webpage)
|
||||||
|
if mobj:
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
'%s/manifest.f4m' % mobj.group('src'), video_id))
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
description = self._og_search_description(webpage, default=None)
|
||||||
|
thumbnail = self._og_search_thumbnail(webpage)
|
||||||
|
timestamp = float_or_none(self._search_regex(
|
||||||
|
r'data-video-sitestat-pubdate="(\d+)"', webpage, 'timestamp', fatal=False), 1000)
|
||||||
|
duration = float_or_none(self._search_regex(
|
||||||
|
r'data-video-duration="(\d+)"', webpage, 'duration', fatal=False), 1000)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -37,7 +37,7 @@ class WimpIE(InfoExtractor):
|
|||||||
video_id = mobj.group(1)
|
video_id = mobj.group(1)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r's1\.addVariable\("file",\s*"([^"]+)"\);', webpage, 'video URL')
|
r"'file'\s*:\s*'([^']+)'", webpage, 'video URL')
|
||||||
if YoutubeIE.suitable(video_url):
|
if YoutubeIE.suitable(video_url):
|
||||||
self.to_screen('Found YouTube video')
|
self.to_screen('Found YouTube video')
|
||||||
return {
|
return {
|
||||||
|
@ -20,7 +20,7 @@ class XTubeIE(InfoExtractor):
|
|||||||
'id': 'kVTUy_G222_',
|
'id': 'kVTUy_G222_',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'strange erotica',
|
'title': 'strange erotica',
|
||||||
'description': 'surreal gay themed erotica...almost an ET kind of thing',
|
'description': 'http://www.xtube.com an ET kind of thing',
|
||||||
'uploader': 'greenshowers',
|
'uploader': 'greenshowers',
|
||||||
'duration': 450,
|
'duration': 450,
|
||||||
'age_limit': 18,
|
'age_limit': 18,
|
||||||
|
@ -13,7 +13,6 @@ class YnetIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://hot.ynet.co.il/home/0,7340,L-11659-99244,00.html',
|
'url': 'http://hot.ynet.co.il/home/0,7340,L-11659-99244,00.html',
|
||||||
'md5': '4b29cb57c3dddd57642b3f051f535b07',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'L-11659-99244',
|
'id': 'L-11659-99244',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
@ -22,7 +21,6 @@ class YnetIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://hot.ynet.co.il/home/0,7340,L-8859-84418,00.html',
|
'url': 'http://hot.ynet.co.il/home/0,7340,L-8859-84418,00.html',
|
||||||
'md5': '8194c2ea221e9a639cac96b6b0753dc5',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'L-8859-84418',
|
'id': 'L-8859-84418',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
@ -185,8 +185,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
self._download_webpage(
|
self._download_webpage(
|
||||||
req, None,
|
req, None,
|
||||||
note='Confirming age', errnote='Unable to confirm age')
|
note='Confirming age', errnote='Unable to confirm age',
|
||||||
return True
|
fatal=False)
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
if self._downloader is None:
|
if self._downloader is None:
|
||||||
@ -274,6 +274,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'138': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'138': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
'160': {'ext': 'mp4', 'height': 144, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'160': {'ext': 'mp4', 'height': 144, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
'264': {'ext': 'mp4', 'height': 1440, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'264': {'ext': 'mp4', 'height': 1440, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
|
'298': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40, 'fps': 60, 'vcodec': 'h264'},
|
||||||
|
'299': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40, 'fps': 60, 'vcodec': 'h264'},
|
||||||
|
'266': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40, 'vcodec': 'h264'},
|
||||||
|
|
||||||
# Dash mp4 audio
|
# Dash mp4 audio
|
||||||
'139': {'ext': 'm4a', 'format_note': 'DASH audio', 'vcodec': 'none', 'abr': 48, 'preference': -50},
|
'139': {'ext': 'm4a', 'format_note': 'DASH audio', 'vcodec': 'none', 'abr': 48, 'preference': -50},
|
||||||
@ -297,6 +300,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
'248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
'271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
'272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
'272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40},
|
||||||
|
'302': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40, 'fps': 60, 'vcodec': 'VP9'},
|
||||||
|
'303': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'acodec': 'none', 'preference': -40, 'fps': 60, 'vcodec': 'VP9'},
|
||||||
|
|
||||||
# Dash webm audio
|
# Dash webm audio
|
||||||
'171': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'abr': 128, 'preference': -50},
|
'171': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'abr': 128, 'preference': -50},
|
||||||
@ -1057,7 +1062,7 @@ class YoutubePlaylistIE(YoutubeBaseInfoExtractor):
|
|||||||
'note': 'issue #673',
|
'note': 'issue #673',
|
||||||
'url': 'PLBB231211A4F62143',
|
'url': 'PLBB231211A4F62143',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'title': 'Team Fortress 2 (Class-based LP)',
|
'title': '[OLD]Team Fortress 2 (Class-based LP)',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 26,
|
'playlist_mincount': 26,
|
||||||
}, {
|
}, {
|
||||||
|
@ -10,8 +10,84 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_from_xml_url(ie, video_id, xml_url):
|
||||||
|
doc = ie._download_xml(
|
||||||
|
xml_url, video_id,
|
||||||
|
note='Downloading video info',
|
||||||
|
errnote='Failed to download video info')
|
||||||
|
|
||||||
|
title = doc.find('.//information/title').text
|
||||||
|
description = doc.find('.//information/detail').text
|
||||||
|
duration = int(doc.find('.//details/lengthSec').text)
|
||||||
|
uploader_node = doc.find('.//details/originChannelTitle')
|
||||||
|
uploader = None if uploader_node is None else uploader_node.text
|
||||||
|
uploader_id_node = doc.find('.//details/originChannelId')
|
||||||
|
uploader_id = None if uploader_id_node is None else uploader_id_node.text
|
||||||
|
upload_date = unified_strdate(doc.find('.//details/airtime').text)
|
||||||
|
|
||||||
|
def xml_to_format(fnode):
|
||||||
|
video_url = fnode.find('url').text
|
||||||
|
is_available = 'http://www.metafilegenerator' not in video_url
|
||||||
|
|
||||||
|
format_id = fnode.attrib['basetype']
|
||||||
|
format_m = re.match(r'''(?x)
|
||||||
|
(?P<vcodec>[^_]+)_(?P<acodec>[^_]+)_(?P<container>[^_]+)_
|
||||||
|
(?P<proto>[^_]+)_(?P<index>[^_]+)_(?P<indexproto>[^_]+)
|
||||||
|
''', format_id)
|
||||||
|
|
||||||
|
ext = format_m.group('container')
|
||||||
|
proto = format_m.group('proto').lower()
|
||||||
|
|
||||||
|
quality = fnode.find('./quality').text
|
||||||
|
abr = int(fnode.find('./audioBitrate').text) // 1000
|
||||||
|
vbr_node = fnode.find('./videoBitrate')
|
||||||
|
vbr = None if vbr_node is None else int(vbr_node.text) // 1000
|
||||||
|
|
||||||
|
width_node = fnode.find('./width')
|
||||||
|
width = None if width_node is None else int_or_none(width_node.text)
|
||||||
|
height_node = fnode.find('./height')
|
||||||
|
height = None if height_node is None else int_or_none(height_node.text)
|
||||||
|
|
||||||
|
format_note = ''
|
||||||
|
if not format_note:
|
||||||
|
format_note = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'format_id': format_id + '-' + quality,
|
||||||
|
'url': video_url,
|
||||||
|
'ext': ext,
|
||||||
|
'acodec': format_m.group('acodec'),
|
||||||
|
'vcodec': format_m.group('vcodec'),
|
||||||
|
'abr': abr,
|
||||||
|
'vbr': vbr,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'filesize': int_or_none(fnode.find('./filesize').text),
|
||||||
|
'format_note': format_note,
|
||||||
|
'protocol': proto,
|
||||||
|
'_available': is_available,
|
||||||
|
}
|
||||||
|
|
||||||
|
format_nodes = doc.findall('.//formitaeten/formitaet')
|
||||||
|
formats = list(filter(
|
||||||
|
lambda f: f['_available'],
|
||||||
|
map(xml_to_format, format_nodes)))
|
||||||
|
ie._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'upload_date': upload_date,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ZDFIE(InfoExtractor):
|
class ZDFIE(InfoExtractor):
|
||||||
_VALID_URL = r'^https?://www\.zdf\.de/ZDFmediathek(?P<hash>#)?/(.*beitrag/(?:video/)?)(?P<video_id>[0-9]+)(?:/[^/?]+)?(?:\?.*)?'
|
_VALID_URL = r'^https?://www\.zdf\.de/ZDFmediathek(?P<hash>#)?/(.*beitrag/(?:video/)?)(?P<id>[0-9]+)(?:/[^/?]+)?(?:\?.*)?'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.zdf.de/ZDFmediathek/beitrag/video/2037704/ZDFspezial---Ende-des-Machtpokers--?bc=sts;stt',
|
'url': 'http://www.zdf.de/ZDFmediathek/beitrag/video/2037704/ZDFspezial---Ende-des-Machtpokers--?bc=sts;stt',
|
||||||
@ -29,81 +105,7 @@ class ZDFIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('video_id')
|
|
||||||
|
|
||||||
xml_url = 'http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
xml_url = 'http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||||
doc = self._download_xml(
|
return extract_from_xml_url(self, video_id, xml_url)
|
||||||
xml_url, video_id,
|
|
||||||
note='Downloading video info',
|
|
||||||
errnote='Failed to download video info')
|
|
||||||
|
|
||||||
title = doc.find('.//information/title').text
|
|
||||||
description = doc.find('.//information/detail').text
|
|
||||||
duration = int(doc.find('.//details/lengthSec').text)
|
|
||||||
uploader_node = doc.find('.//details/originChannelTitle')
|
|
||||||
uploader = None if uploader_node is None else uploader_node.text
|
|
||||||
uploader_id_node = doc.find('.//details/originChannelId')
|
|
||||||
uploader_id = None if uploader_id_node is None else uploader_id_node.text
|
|
||||||
upload_date = unified_strdate(doc.find('.//details/airtime').text)
|
|
||||||
|
|
||||||
def xml_to_format(fnode):
|
|
||||||
video_url = fnode.find('url').text
|
|
||||||
is_available = 'http://www.metafilegenerator' not in video_url
|
|
||||||
|
|
||||||
format_id = fnode.attrib['basetype']
|
|
||||||
format_m = re.match(r'''(?x)
|
|
||||||
(?P<vcodec>[^_]+)_(?P<acodec>[^_]+)_(?P<container>[^_]+)_
|
|
||||||
(?P<proto>[^_]+)_(?P<index>[^_]+)_(?P<indexproto>[^_]+)
|
|
||||||
''', format_id)
|
|
||||||
|
|
||||||
ext = format_m.group('container')
|
|
||||||
proto = format_m.group('proto').lower()
|
|
||||||
|
|
||||||
quality = fnode.find('./quality').text
|
|
||||||
abr = int(fnode.find('./audioBitrate').text) // 1000
|
|
||||||
vbr_node = fnode.find('./videoBitrate')
|
|
||||||
vbr = None if vbr_node is None else int(vbr_node.text) // 1000
|
|
||||||
|
|
||||||
width_node = fnode.find('./width')
|
|
||||||
width = None if width_node is None else int_or_none(width_node.text)
|
|
||||||
height_node = fnode.find('./height')
|
|
||||||
height = None if height_node is None else int_or_none(height_node.text)
|
|
||||||
|
|
||||||
format_note = ''
|
|
||||||
if not format_note:
|
|
||||||
format_note = None
|
|
||||||
|
|
||||||
return {
|
|
||||||
'format_id': format_id + '-' + quality,
|
|
||||||
'url': video_url,
|
|
||||||
'ext': ext,
|
|
||||||
'acodec': format_m.group('acodec'),
|
|
||||||
'vcodec': format_m.group('vcodec'),
|
|
||||||
'abr': abr,
|
|
||||||
'vbr': vbr,
|
|
||||||
'width': width,
|
|
||||||
'height': height,
|
|
||||||
'filesize': int_or_none(fnode.find('./filesize').text),
|
|
||||||
'format_note': format_note,
|
|
||||||
'protocol': proto,
|
|
||||||
'_available': is_available,
|
|
||||||
}
|
|
||||||
|
|
||||||
format_nodes = doc.findall('.//formitaeten/formitaet')
|
|
||||||
formats = list(filter(
|
|
||||||
lambda f: f['_available'],
|
|
||||||
map(xml_to_format, format_nodes)))
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'duration': duration,
|
|
||||||
'uploader': uploader,
|
|
||||||
'uploader_id': uploader_id,
|
|
||||||
'upload_date': upload_date,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,8 @@ import shlex
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .utils import (
|
from .utils import (
|
||||||
|
compat_expanduser,
|
||||||
|
compat_getenv,
|
||||||
get_term_width,
|
get_term_width,
|
||||||
write_string,
|
write_string,
|
||||||
)
|
)
|
||||||
@ -27,19 +29,19 @@ def parseOpts(overrideArguments=None):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _readUserConf():
|
def _readUserConf():
|
||||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
xdg_config_home = compat_getenv('XDG_CONFIG_HOME')
|
||||||
if xdg_config_home:
|
if xdg_config_home:
|
||||||
userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config')
|
userConfFile = os.path.join(xdg_config_home, 'youtube-dl', 'config')
|
||||||
if not os.path.isfile(userConfFile):
|
if not os.path.isfile(userConfFile):
|
||||||
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
||||||
else:
|
else:
|
||||||
userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl', 'config')
|
userConfFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl', 'config')
|
||||||
if not os.path.isfile(userConfFile):
|
if not os.path.isfile(userConfFile):
|
||||||
userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
userConfFile = os.path.join(compat_expanduser('~'), '.config', 'youtube-dl.conf')
|
||||||
userConf = _readOptions(userConfFile, None)
|
userConf = _readOptions(userConfFile, None)
|
||||||
|
|
||||||
if userConf is None:
|
if userConf is None:
|
||||||
appdata_dir = os.environ.get('appdata')
|
appdata_dir = compat_getenv('appdata')
|
||||||
if appdata_dir:
|
if appdata_dir:
|
||||||
userConf = _readOptions(
|
userConf = _readOptions(
|
||||||
os.path.join(appdata_dir, 'youtube-dl', 'config'),
|
os.path.join(appdata_dir, 'youtube-dl', 'config'),
|
||||||
@ -51,11 +53,11 @@ def parseOpts(overrideArguments=None):
|
|||||||
|
|
||||||
if userConf is None:
|
if userConf is None:
|
||||||
userConf = _readOptions(
|
userConf = _readOptions(
|
||||||
os.path.join(os.path.expanduser('~'), 'youtube-dl.conf'),
|
os.path.join(compat_expanduser('~'), 'youtube-dl.conf'),
|
||||||
default=None)
|
default=None)
|
||||||
if userConf is None:
|
if userConf is None:
|
||||||
userConf = _readOptions(
|
userConf = _readOptions(
|
||||||
os.path.join(os.path.expanduser('~'), 'youtube-dl.conf.txt'),
|
os.path.join(compat_expanduser('~'), 'youtube-dl.conf.txt'),
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
if userConf is None:
|
if userConf is None:
|
||||||
@ -159,6 +161,11 @@ def parseOpts(overrideArguments=None):
|
|||||||
'--ignore-config',
|
'--ignore-config',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: do not read the user configuration in ~/.config/youtube-dl.conf (%APPDATA%/youtube-dl/config.txt on Windows)')
|
help='Do not read configuration files. When given in the global configuration file /etc/youtube-dl.conf: do not read the user configuration in ~/.config/youtube-dl.conf (%APPDATA%/youtube-dl/config.txt on Windows)')
|
||||||
|
general.add_option(
|
||||||
|
'--flat-playlist',
|
||||||
|
action='store_const', dest='extract_flat', const='in_playlist',
|
||||||
|
default=False,
|
||||||
|
help='Do not extract the videos of a playlist, only list them.')
|
||||||
|
|
||||||
selection = optparse.OptionGroup(parser, 'Video Selection')
|
selection = optparse.OptionGroup(parser, 'Video Selection')
|
||||||
selection.add_option(
|
selection.add_option(
|
||||||
@ -412,6 +419,10 @@ def parseOpts(overrideArguments=None):
|
|||||||
'-j', '--dump-json',
|
'-j', '--dump-json',
|
||||||
action='store_true', dest='dumpjson', default=False,
|
action='store_true', dest='dumpjson', default=False,
|
||||||
help='simulate, quiet but print JSON information. See --output for a description of available keys.')
|
help='simulate, quiet but print JSON information. See --output for a description of available keys.')
|
||||||
|
verbosity.add_option(
|
||||||
|
'-J', '--dump-single-json',
|
||||||
|
action='store_true', dest='dump_single_json', default=False,
|
||||||
|
help='simulate, quiet but print JSON information for each command-line argument. If the URL refers to a playlist, dump the whole playlist information in a single line.')
|
||||||
verbosity.add_option(
|
verbosity.add_option(
|
||||||
'--newline',
|
'--newline',
|
||||||
action='store_true', dest='progress_with_newline', default=False,
|
action='store_true', dest='progress_with_newline', default=False,
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
|
|
||||||
from .atomicparsley import AtomicParsleyPP
|
from .atomicparsley import AtomicParsleyPP
|
||||||
from .ffmpeg import (
|
from .ffmpeg import (
|
||||||
|
FFmpegPostProcessor,
|
||||||
FFmpegAudioFixPP,
|
FFmpegAudioFixPP,
|
||||||
|
FFmpegEmbedSubtitlePP,
|
||||||
|
FFmpegExtractAudioPP,
|
||||||
FFmpegMergerPP,
|
FFmpegMergerPP,
|
||||||
FFmpegMetadataPP,
|
FFmpegMetadataPP,
|
||||||
FFmpegVideoConvertor,
|
FFmpegVideoConvertor,
|
||||||
FFmpegExtractAudioPP,
|
|
||||||
FFmpegEmbedSubtitlePP,
|
|
||||||
)
|
)
|
||||||
from .xattrpp import XAttrMetadataPP
|
from .xattrpp import XAttrMetadataPP
|
||||||
from .execafterdownload import ExecAfterDownloadPP
|
from .execafterdownload import ExecAfterDownloadPP
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AtomicParsleyPP',
|
'AtomicParsleyPP',
|
||||||
|
'ExecAfterDownloadPP',
|
||||||
'FFmpegAudioFixPP',
|
'FFmpegAudioFixPP',
|
||||||
|
'FFmpegEmbedSubtitlePP',
|
||||||
|
'FFmpegExtractAudioPP',
|
||||||
'FFmpegMergerPP',
|
'FFmpegMergerPP',
|
||||||
'FFmpegMetadataPP',
|
'FFmpegMetadataPP',
|
||||||
|
'FFmpegPostProcessor',
|
||||||
'FFmpegVideoConvertor',
|
'FFmpegVideoConvertor',
|
||||||
'FFmpegExtractAudioPP',
|
|
||||||
'FFmpegEmbedSubtitlePP',
|
|
||||||
'XAttrMetadataPP',
|
'XAttrMetadataPP',
|
||||||
'ExecAfterDownloadPP',
|
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@ -7,10 +8,10 @@ import time
|
|||||||
from .common import AudioConversionError, PostProcessor
|
from .common import AudioConversionError, PostProcessor
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
check_executable,
|
|
||||||
compat_subprocess_get_DEVNULL,
|
compat_subprocess_get_DEVNULL,
|
||||||
encodeArgument,
|
encodeArgument,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
is_outdated_version,
|
||||||
PostProcessingError,
|
PostProcessingError,
|
||||||
prepend_extension,
|
prepend_extension,
|
||||||
shell_quote,
|
shell_quote,
|
||||||
@ -18,6 +19,23 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_version(executable):
|
||||||
|
""" Returns the version of the specified executable,
|
||||||
|
or False if the executable is not present """
|
||||||
|
try:
|
||||||
|
out, err = subprocess.Popen(
|
||||||
|
[executable, '-version'],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
firstline = out.partition(b'\n')[0].decode('ascii', 'ignore')
|
||||||
|
m = re.search(r'version\s+([0-9._-a-zA-Z]+)', firstline)
|
||||||
|
if not m:
|
||||||
|
return u'present'
|
||||||
|
else:
|
||||||
|
return m.group(1)
|
||||||
|
|
||||||
|
|
||||||
class FFmpegPostProcessorError(PostProcessingError):
|
class FFmpegPostProcessorError(PostProcessingError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -25,31 +43,58 @@ class FFmpegPostProcessorError(PostProcessingError):
|
|||||||
class FFmpegPostProcessor(PostProcessor):
|
class FFmpegPostProcessor(PostProcessor):
|
||||||
def __init__(self, downloader=None, deletetempfiles=False):
|
def __init__(self, downloader=None, deletetempfiles=False):
|
||||||
PostProcessor.__init__(self, downloader)
|
PostProcessor.__init__(self, downloader)
|
||||||
self._exes = self.detect_executables()
|
self._versions = self.get_versions()
|
||||||
self._deletetempfiles = deletetempfiles
|
self._deletetempfiles = deletetempfiles
|
||||||
|
|
||||||
@staticmethod
|
def check_version(self):
|
||||||
def detect_executables():
|
if not self._executable:
|
||||||
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
|
raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.')
|
||||||
return dict((program, check_executable(program, ['-version'])) for program in programs)
|
|
||||||
|
|
||||||
def _get_executable(self):
|
REQUIRED_VERSION = '1.0'
|
||||||
|
if is_outdated_version(
|
||||||
|
self._versions[self._executable], REQUIRED_VERSION):
|
||||||
|
warning = u'Your copy of %s is outdated, update %s to version %s or newer if you encounter any errors.' % (
|
||||||
|
self._executable, self._executable, REQUIRED_VERSION)
|
||||||
|
if self._downloader:
|
||||||
|
self._downloader.report_warning(warning)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_versions():
|
||||||
|
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
|
||||||
|
return dict((program, get_version(program)) for program in programs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _executable(self):
|
||||||
if self._downloader.params.get('prefer_ffmpeg', False):
|
if self._downloader.params.get('prefer_ffmpeg', False):
|
||||||
return self._exes['ffmpeg'] or self._exes['avconv']
|
prefs = ('ffmpeg', 'avconv')
|
||||||
else:
|
else:
|
||||||
return self._exes['avconv'] or self._exes['ffmpeg']
|
prefs = ('avconv', 'ffmpeg')
|
||||||
|
for p in prefs:
|
||||||
|
if self._versions[p]:
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _probe_executable(self):
|
||||||
|
if self._downloader.params.get('prefer_ffmpeg', False):
|
||||||
|
prefs = ('ffprobe', 'avprobe')
|
||||||
|
else:
|
||||||
|
prefs = ('avprobe', 'ffprobe')
|
||||||
|
for p in prefs:
|
||||||
|
if self._versions[p]:
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
def _uses_avconv(self):
|
def _uses_avconv(self):
|
||||||
return self._get_executable() == self._exes['avconv']
|
return self._executable == 'avconv'
|
||||||
|
|
||||||
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
|
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
|
||||||
if not self._get_executable():
|
self.check_version()
|
||||||
raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.')
|
|
||||||
|
|
||||||
files_cmd = []
|
files_cmd = []
|
||||||
for path in input_paths:
|
for path in input_paths:
|
||||||
files_cmd.extend(['-i', encodeFilename(path, True)])
|
files_cmd.extend(['-i', encodeFilename(path, True)])
|
||||||
cmd = ([self._get_executable(), '-y'] + files_cmd
|
cmd = ([self._executable, '-y'] + files_cmd
|
||||||
+ [encodeArgument(o) for o in opts] +
|
+ [encodeArgument(o) for o in opts] +
|
||||||
[encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
|
[encodeFilename(self._ffmpeg_filename_argument(out_path), True)])
|
||||||
|
|
||||||
@ -85,11 +130,12 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
|||||||
self._nopostoverwrites = nopostoverwrites
|
self._nopostoverwrites = nopostoverwrites
|
||||||
|
|
||||||
def get_audio_codec(self, path):
|
def get_audio_codec(self, path):
|
||||||
if not self._exes['ffprobe'] and not self._exes['avprobe']:
|
|
||||||
|
if not self._probe_executable:
|
||||||
raise PostProcessingError(u'ffprobe or avprobe not found. Please install one.')
|
raise PostProcessingError(u'ffprobe or avprobe not found. Please install one.')
|
||||||
try:
|
try:
|
||||||
cmd = [
|
cmd = [
|
||||||
self._exes['avprobe'] or self._exes['ffprobe'],
|
self._probe_executable,
|
||||||
'-show_streams',
|
'-show_streams',
|
||||||
encodeFilename(self._ffmpeg_filename_argument(path), True)]
|
encodeFilename(self._ffmpeg_filename_argument(path), True)]
|
||||||
handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE)
|
handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE)
|
||||||
@ -182,14 +228,14 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
|||||||
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
if self._nopostoverwrites and os.path.exists(encodeFilename(new_path)):
|
||||||
self._downloader.to_screen(u'[youtube] Post-process file %s exists, skipping' % new_path)
|
self._downloader.to_screen(u'[youtube] Post-process file %s exists, skipping' % new_path)
|
||||||
else:
|
else:
|
||||||
self._downloader.to_screen(u'[' + self._get_executable() + '] Destination: ' + new_path)
|
self._downloader.to_screen(u'[' + self._executable + '] Destination: ' + new_path)
|
||||||
self.run_ffmpeg(path, new_path, acodec, more_opts)
|
self.run_ffmpeg(path, new_path, acodec, more_opts)
|
||||||
except:
|
except:
|
||||||
etype,e,tb = sys.exc_info()
|
etype,e,tb = sys.exc_info()
|
||||||
if isinstance(e, AudioConversionError):
|
if isinstance(e, AudioConversionError):
|
||||||
msg = u'audio conversion failed: ' + e.msg
|
msg = u'audio conversion failed: ' + e.msg
|
||||||
else:
|
else:
|
||||||
msg = u'error running ' + self._get_executable()
|
msg = u'error running ' + self._executable
|
||||||
raise PostProcessingError(msg)
|
raise PostProcessingError(msg)
|
||||||
|
|
||||||
# Try to update the date time for extracted audio file.
|
# Try to update the date time for extracted audio file.
|
||||||
|
@ -203,6 +203,82 @@ def compat_ord(c):
|
|||||||
if type(c) is int: return c
|
if type(c) is int: return c
|
||||||
else: return ord(c)
|
else: return ord(c)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 0):
|
||||||
|
compat_getenv = os.getenv
|
||||||
|
compat_expanduser = os.path.expanduser
|
||||||
|
else:
|
||||||
|
# Environment variables should be decoded with filesystem encoding.
|
||||||
|
# Otherwise it will fail if any non-ASCII characters present (see #3854 #3217 #2918)
|
||||||
|
|
||||||
|
def compat_getenv(key, default=None):
|
||||||
|
env = os.getenv(key, default)
|
||||||
|
if env:
|
||||||
|
env = env.decode(get_filesystem_encoding())
|
||||||
|
return env
|
||||||
|
|
||||||
|
# HACK: The default implementations of os.path.expanduser from cpython do not decode
|
||||||
|
# environment variables with filesystem encoding. We will work around this by
|
||||||
|
# providing adjusted implementations.
|
||||||
|
# The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
|
||||||
|
# for different platforms with correct environment variables decoding.
|
||||||
|
|
||||||
|
if os.name == 'posix':
|
||||||
|
def compat_expanduser(path):
|
||||||
|
"""Expand ~ and ~user constructions. If user or $HOME is unknown,
|
||||||
|
do nothing."""
|
||||||
|
if not path.startswith('~'):
|
||||||
|
return path
|
||||||
|
i = path.find('/', 1)
|
||||||
|
if i < 0:
|
||||||
|
i = len(path)
|
||||||
|
if i == 1:
|
||||||
|
if 'HOME' not in os.environ:
|
||||||
|
import pwd
|
||||||
|
userhome = pwd.getpwuid(os.getuid()).pw_dir
|
||||||
|
else:
|
||||||
|
userhome = compat_getenv('HOME')
|
||||||
|
else:
|
||||||
|
import pwd
|
||||||
|
try:
|
||||||
|
pwent = pwd.getpwnam(path[1:i])
|
||||||
|
except KeyError:
|
||||||
|
return path
|
||||||
|
userhome = pwent.pw_dir
|
||||||
|
userhome = userhome.rstrip('/')
|
||||||
|
return (userhome + path[i:]) or '/'
|
||||||
|
elif os.name == 'nt' or os.name == 'ce':
|
||||||
|
def compat_expanduser(path):
|
||||||
|
"""Expand ~ and ~user constructs.
|
||||||
|
|
||||||
|
If user or $HOME is unknown, do nothing."""
|
||||||
|
if path[:1] != '~':
|
||||||
|
return path
|
||||||
|
i, n = 1, len(path)
|
||||||
|
while i < n and path[i] not in '/\\':
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
if 'HOME' in os.environ:
|
||||||
|
userhome = compat_getenv('HOME')
|
||||||
|
elif 'USERPROFILE' in os.environ:
|
||||||
|
userhome = compat_getenv('USERPROFILE')
|
||||||
|
elif not 'HOMEPATH' in os.environ:
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
drive = compat_getenv('HOMEDRIVE')
|
||||||
|
except KeyError:
|
||||||
|
drive = ''
|
||||||
|
userhome = os.path.join(drive, compat_getenv('HOMEPATH'))
|
||||||
|
|
||||||
|
if i != 1: #~user
|
||||||
|
userhome = os.path.join(os.path.dirname(userhome), path[1:i])
|
||||||
|
|
||||||
|
return userhome + path[i:]
|
||||||
|
else:
|
||||||
|
compat_expanduser = os.path.expanduser
|
||||||
|
|
||||||
|
|
||||||
# This is not clearly defined otherwise
|
# This is not clearly defined otherwise
|
||||||
compiled_regex_type = type(re.compile(''))
|
compiled_regex_type = type(re.compile(''))
|
||||||
|
|
||||||
@ -849,7 +925,7 @@ def parse_iso8601(date_str, delimiter='T'):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
m = re.search(
|
m = re.search(
|
||||||
r'Z$| ?(?P<sign>\+|-)(?P<hours>[0-9]{2}):?(?P<minutes>[0-9]{2})$',
|
r'(\.[0-9]+)?(?:Z$| ?(?P<sign>\+|-)(?P<hours>[0-9]{2}):?(?P<minutes>[0-9]{2})$)',
|
||||||
date_str)
|
date_str)
|
||||||
if not m:
|
if not m:
|
||||||
timezone = datetime.timedelta()
|
timezone = datetime.timedelta()
|
||||||
@ -862,7 +938,7 @@ def parse_iso8601(date_str, delimiter='T'):
|
|||||||
timezone = datetime.timedelta(
|
timezone = datetime.timedelta(
|
||||||
hours=sign * int(m.group('hours')),
|
hours=sign * int(m.group('hours')),
|
||||||
minutes=sign * int(m.group('minutes')))
|
minutes=sign * int(m.group('minutes')))
|
||||||
date_format = '%Y-%m-%d{0}%H:%M:%S'.format(delimiter)
|
date_format = '%Y-%m-%d{0}%H:%M:%S'.format(delimiter)
|
||||||
dt = datetime.datetime.strptime(date_str, date_format) - timezone
|
dt = datetime.datetime.strptime(date_str, date_format) - timezone
|
||||||
return calendar.timegm(dt.timetuple())
|
return calendar.timegm(dt.timetuple())
|
||||||
|
|
||||||
@ -1207,11 +1283,14 @@ class locked_file(object):
|
|||||||
return self.f.read(*args)
|
return self.f.read(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_filesystem_encoding():
|
||||||
|
encoding = sys.getfilesystemencoding()
|
||||||
|
return encoding if encoding is not None else 'utf-8'
|
||||||
|
|
||||||
|
|
||||||
def shell_quote(args):
|
def shell_quote(args):
|
||||||
quoted_args = []
|
quoted_args = []
|
||||||
encoding = sys.getfilesystemencoding()
|
encoding = get_filesystem_encoding()
|
||||||
if encoding is None:
|
|
||||||
encoding = 'utf-8'
|
|
||||||
for a in args:
|
for a in args:
|
||||||
if isinstance(a, bytes):
|
if isinstance(a, bytes):
|
||||||
# We may get a filename encoded with 'encodeFilename'
|
# We may get a filename encoded with 'encodeFilename'
|
||||||
@ -1261,7 +1340,7 @@ def format_bytes(bytes):
|
|||||||
|
|
||||||
|
|
||||||
def get_term_width():
|
def get_term_width():
|
||||||
columns = os.environ.get('COLUMNS', None)
|
columns = compat_getenv('COLUMNS', None)
|
||||||
if columns:
|
if columns:
|
||||||
return int(columns)
|
return int(columns)
|
||||||
|
|
||||||
@ -1644,3 +1723,16 @@ def limit_length(s, length):
|
|||||||
if len(s) > length:
|
if len(s) > length:
|
||||||
return s[:length - len(ELLIPSES)] + ELLIPSES
|
return s[:length - len(ELLIPSES)] + ELLIPSES
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def version_tuple(v):
|
||||||
|
return [int(e) for e in v.split('.')]
|
||||||
|
|
||||||
|
|
||||||
|
def is_outdated_version(version, limit, assume_new=True):
|
||||||
|
if not version:
|
||||||
|
return not assume_new
|
||||||
|
try:
|
||||||
|
return version_tuple(version) < version_tuple(limit)
|
||||||
|
except ValueError:
|
||||||
|
return not assume_new
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2014.10.23'
|
__version__ = '2014.11.02'
|
||||||
|
Reference in New Issue
Block a user