Compare commits

...

46 Commits

Author SHA1 Message Date
Philipp Hagemeister
3b9f631c41 release 2014.11.20 2014-11-20 08:55:56 +01:00
Jaime Marquínez Ferrándiz
3ba098a6a5 Merge pull request #4247 from ivan/info-json
Fix #4246 and #4244 .info.json bugs
2014-11-20 08:16:42 +01:00
Ivan Kozik
1394646a0a Fix "ERROR: Cannot write metadata to JSON file" on Windows
Fixes #4246
2014-11-20 06:26:34 +00:00
Ivan Kozik
61ee5aeb73 Fix UnicodeEncodeError with --write-info-json on Python 2.7 + Windows
Fixes #4244
2014-11-20 06:26:34 +00:00
Philipp Hagemeister
07e378fa18 [compat] correct OptionGroup invocation for Python 3 (fixes #4243) 2014-11-20 07:21:12 +01:00
Philipp Hagemeister
e07e931375 Work around 2.7.0 deficencies (Fixes #4223) 2014-11-19 18:21:58 +01:00
Naglis Jonaitis
480b7c32a9 [rtlxl] Fix format order 2014-11-19 01:21:02 +02:00
Jaime Marquínez Ferrándiz
f56875f271 [test/test_compat] Restore the old value of the HOME environment variable
If the test was run before the YoutubeIE tests (for example by running
"nosetests -v test/test_compat.py test/test_download.py -m 'Youtube_1|compat_expand'"),
it wrote the signatures cache to the 'C:\Documents and Settings\тест\Application Data' folder.
It failed due to a problem in the cache code and the write_json_file function (fixed in f03e33b89a622af13fa5275c46b63aaa4814c499)
2014-11-19 00:02:24 +01:00
Jaime Marquínez Ferrándiz
92120217eb [cache] Fix writing to paths with unicode characters
* Use "compat_getenv"
* "write_json_file" now expects the filename to be a string
2014-11-19 00:02:24 +01:00
Naglis Jonaitis
37eddd3143 [rtlxl] Use m3u8 streams instead of f4m (#4115, #4118) 2014-11-19 00:26:44 +02:00
Philipp Hagemeister
0857baade3 [youtube] Add webm audio formats (Fixes #4229) 2014-11-18 11:08:37 +01:00
Philipp Hagemeister
23ad44b57b [youtube] Better error message for DASH manifest 2014-11-17 20:12:52 +01:00
Philipp Hagemeister
f48d3e9bbc [swfinterp] Improve undefined representation 2014-11-17 08:02:48 +01:00
Philipp Hagemeister
fbf94a7815 [swfinterp] Add more builtins and improve static variables 2014-11-17 07:54:06 +01:00
Philipp Hagemeister
1921b24551 [swfinterp] Add support for more complicated constants 2014-11-17 07:31:22 +01:00
Philipp Hagemeister
28e614de5c [utils] Remove stray u' 2014-11-17 07:16:12 +01:00
Philipp Hagemeister
cd9ad1d7e8 [swfinterp] Basic support for constants (only ints for now) 2014-11-17 07:14:02 +01:00
Philipp Hagemeister
162f54eca6 [swfinterp] Implement bitand and pushshort operators 2014-11-17 05:08:39 +01:00
Philipp Hagemeister
33a266f4ba [swfinterp] Implement charCodeAt 2014-11-17 05:03:46 +01:00
Philipp Hagemeister
6b592d93a2 [swfinterp] Formalize built-in classes 2014-11-17 04:54:54 +01:00
Philipp Hagemeister
4686ae4b64 [swfinterp] Implement various opcodes 2014-11-17 04:45:12 +01:00
Philipp Hagemeister
8d05f2c16a [swfinterp] Add support for void methods 2014-11-17 04:36:23 +01:00
Philipp Hagemeister
a4bb83956c [swfinterp] Implement pushtrue and pushfalse opcodes 2014-11-17 04:29:34 +01:00
Philipp Hagemeister
eb5376044c [swfinterp] Implement equals opcode 2014-11-17 04:27:51 +01:00
Philipp Hagemeister
3cbcff8a2d [swfinterp] Implement String basics 2014-11-17 04:25:10 +01:00
Philipp Hagemeister
e983cf5277 [swfinterp] Interpret yet more opcodes 2014-11-17 04:00:41 +01:00
Philipp Hagemeister
0ab1ca5501 [swfinterp] Better error message 2014-11-17 03:53:32 +01:00
Philipp Hagemeister
4baafa229d [swfinterp] Intepret more multinames 2014-11-17 03:46:23 +01:00
Philipp Hagemeister
7f3e33a147 [swfinterp] Implement member assignment 2014-11-17 01:27:34 +01:00
Philipp Hagemeister
b7558d9881 [swfinterp] Allow function patching 2014-11-17 01:27:15 +01:00
Philipp Hagemeister
a0f59cdcb4 [tests] Modernize 2014-11-16 15:17:48 +01:00
Philipp Hagemeister
a4bc433619 [__init__] Modernize 2014-11-16 15:08:34 +01:00
Philipp Hagemeister
b6b70730bf [downloader/common] Modernize 2014-11-16 15:06:59 +01:00
Philipp Hagemeister
6a68bb574a [eporner] Fix duration (Closes #4188) 2014-11-16 14:55:22 +01:00
Philipp Hagemeister
0cf166ad4f release 2014.11.16 2014-11-16 00:51:46 +01:00
Philipp Hagemeister
2707b50ffe [spiegel] Correct handling of redirects to spiegel.tv (Closes #4211) 2014-11-16 00:51:31 +01:00
Philipp Hagemeister
939fe70de0 [spiegeltv] Match hash-style URLs (Closes #4210) 2014-11-16 00:40:09 +01:00
Philipp Hagemeister
89c15fe0b3 [spiegeltv] Modernize 2014-11-16 00:33:51 +01:00
Jaime Marquínez Ferrándiz
ec5f601670 [utils] Fix "write_json_file" for unicode names in python 2.x (fixes #4125) 2014-11-15 22:00:32 +01:00
Naglis Jonaitis
8caa0c9779 [bliptv] Fix the resolve of lookup ID (Closes #4197) 2014-11-15 16:56:04 +02:00
Philipp Hagemeister
e2548b5b25 release 2014.11.15.1 2014-11-15 15:21:50 +01:00
Philipp Hagemeister
bbefcf04bf [goldenmoustache] Fix title (Closes #4203) 2014-11-15 15:21:34 +01:00
Philipp Hagemeister
c7b0add86f [compat] Work around kwargs bugs in old 2.6 Python releases (Fixes #3813) 2014-11-15 15:17:19 +01:00
Philipp Hagemeister
a0155d93d9 release 2014.11.15 2014-11-15 11:01:54 +01:00
Philipp Hagemeister
00d9ef0b70 [mailru] Adapt to new data format (Fixes #4201) 2014-11-15 11:01:17 +01:00
Philipp Hagemeister
0cc8888038 [crunchyroll] Remove NOP code (#2782) 2014-11-15 00:34:03 +01:00
35 changed files with 659 additions and 184 deletions

View File

@@ -0,0 +1,18 @@
// input: []
// output: 4
package {
public class ConstArrayAccess {
private static const x:int = 2;
private static const ar:Array = ["42", "3411"];
public static function main():int{
var c:ConstArrayAccess = new ConstArrayAccess();
return c.f();
}
public function f(): int {
return ar[1].length;
}
}
}

View File

@@ -0,0 +1,12 @@
// input: []
// output: 2
package {
public class ConstantInt {
private static const x:int = 2;
public static function main():int{
return x;
}
}
}

10
test/swftests/DictCall.as Normal file
View File

@@ -0,0 +1,10 @@
// input: [{"x": 1, "y": 2}]
// output: 3
package {
public class DictCall {
public static function main(d:Object):int{
return d.x + d.y;
}
}
}

View File

@@ -0,0 +1,10 @@
// input: []
// output: false
package {
public class EqualsOperator {
public static function main():Boolean{
return 1 == 2;
}
}
}

View File

@@ -0,0 +1,22 @@
// input: [1]
// output: 2
package {
public class MemberAssignment {
public var v:int;
public function g():int {
return this.v;
}
public function f(a:int):int{
this.v = a;
return this.v + this.g();
}
public static function main(a:int): int {
var v:MemberAssignment = new MemberAssignment();
return v.f(a);
}
}
}

View File

@@ -0,0 +1,24 @@
// input: []
// output: 123
package {
public class NeOperator {
public static function main(): int {
var res:int = 0;
if (1 != 2) {
res += 3;
} else {
res += 4;
}
if (2 != 2) {
res += 10;
} else {
res += 20;
}
if (9 == 9) {
res += 100;
}
return res;
}
}
}

View File

@@ -0,0 +1,22 @@
// input: []
// output: 9
package {
public class PrivateVoidCall {
public static function main():int{
var f:OtherClass = new OtherClass();
f.func();
return 9;
}
}
}
class OtherClass {
private function pf():void {
;
}
public function func():void {
this.pf();
}
}

View File

@@ -0,0 +1,11 @@
// input: []
// output: 3
package {
public class StringBasics {
public static function main():int{
var s:String = "abc";
return s.length;
}
}
}

View File

@@ -0,0 +1,11 @@
// input: []
// output: 9897
package {
public class StringCharCodeAt {
public static function main():int{
var s:String = "abc";
return s.charCodeAt(1) * 100 + s.charCodeAt();
}
}
}

View File

@@ -0,0 +1,11 @@
// input: []
// output: 2
package {
public class StringConversion {
public static function main():int{
var s:String = String(99);
return s.length;
}
}
}

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import unicode_literals
# Allow direct execution # Allow direct execution
import os import os
@@ -19,7 +20,7 @@ def _download_restricted(url, filename, age):
'age_limit': age, 'age_limit': age,
'skip_download': True, 'skip_download': True,
'writeinfojson': True, 'writeinfojson': True,
"outtmpl": "%(id)s.%(ext)s", 'outtmpl': '%(id)s.%(ext)s',
} }
ydl = YoutubeDL(params) ydl = YoutubeDL(params)
ydl.add_default_info_extractors() ydl.add_default_info_extractors()

View File

@@ -26,11 +26,13 @@ class TestCompat(unittest.TestCase):
self.assertEqual(compat_getenv('YOUTUBE-DL-TEST'), test_str) self.assertEqual(compat_getenv('YOUTUBE-DL-TEST'), test_str)
def test_compat_expanduser(self): def test_compat_expanduser(self):
old_home = os.environ.get('HOME')
test_str = 'C:\Documents and Settings\тест\Application Data' test_str = 'C:\Documents and Settings\тест\Application Data'
os.environ['HOME'] = ( os.environ['HOME'] = (
test_str if sys.version_info >= (3, 0) test_str if sys.version_info >= (3, 0)
else test_str.encode(get_filesystem_encoding())) else test_str.encode(get_filesystem_encoding()))
self.assertEqual(compat_expanduser('~'), test_str) self.assertEqual(compat_expanduser('~'), test_str)
os.environ['HOME'] = old_home
def test_all_present(self): def test_all_present(self):
import youtube_dl.compat import youtube_dl.compat

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import unicode_literals
# Allow direct execution # Allow direct execution
import os import os
import sys import sys
@@ -210,9 +212,9 @@ for n, test_case in enumerate(defs):
tname = 'test_' + str(test_case['name']) tname = 'test_' + str(test_case['name'])
i = 1 i = 1
while hasattr(TestDownload, tname): while hasattr(TestDownload, tname):
tname = 'test_' + str(test_case['name']) + '_' + str(i) tname = 'test_%s_%d' % (test_case['name'], i)
i += 1 i += 1
test_method.__name__ = tname test_method.__name__ = str(tname)
setattr(TestDownload, test_method.__name__, test_method) setattr(TestDownload, test_method.__name__, test_method)
del test_method del test_method

View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
from __future__ import unicode_literals
import unittest import unittest
import sys import sys
@@ -6,17 +9,19 @@ import subprocess
rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) rootDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
try: try:
_DEV_NULL = subprocess.DEVNULL _DEV_NULL = subprocess.DEVNULL
except AttributeError: except AttributeError:
_DEV_NULL = open(os.devnull, 'wb') _DEV_NULL = open(os.devnull, 'wb')
class TestExecution(unittest.TestCase): class TestExecution(unittest.TestCase):
def test_import(self): def test_import(self):
subprocess.check_call([sys.executable, '-c', 'import youtube_dl'], cwd=rootDir) subprocess.check_call([sys.executable, '-c', 'import youtube_dl'], cwd=rootDir)
def test_module_exec(self): def test_module_exec(self):
if sys.version_info >= (2,7): # Python 2.6 doesn't support package execution if sys.version_info >= (2, 7): # Python 2.6 doesn't support package execution
subprocess.check_call([sys.executable, '-m', 'youtube_dl', '--version'], cwd=rootDir, stdout=_DEV_NULL) subprocess.check_call([sys.executable, '-m', 'youtube_dl', '--version'], cwd=rootDir, stdout=_DEV_NULL)
def test_main_exec(self): def test_main_exec(self):

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import unicode_literals
# Allow direct execution # Allow direct execution
import os import os
@@ -74,7 +75,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
self.assertEqual(md5(subtitles['en']), '3cb210999d3e021bd6c7f0ea751eab06') self.assertEqual(md5(subtitles['en']), '3cb210999d3e021bd6c7f0ea751eab06')
def test_youtube_list_subtitles(self): def test_youtube_list_subtitles(self):
self.DL.expect_warning(u'Video doesn\'t have automatic captions') self.DL.expect_warning('Video doesn\'t have automatic captions')
self.DL.params['listsubtitles'] = True self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict() info_dict = self.getInfoDict()
self.assertEqual(info_dict, None) self.assertEqual(info_dict, None)
@@ -87,7 +88,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
self.assertTrue(subtitles['it'] is not None) self.assertTrue(subtitles['it'] is not None)
def test_youtube_nosubtitles(self): def test_youtube_nosubtitles(self):
self.DL.expect_warning(u'video doesn\'t have subtitles') self.DL.expect_warning('video doesn\'t have subtitles')
self.url = 'n5BB19UTcdA' self.url = 'n5BB19UTcdA'
self.DL.params['writesubtitles'] = True self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True self.DL.params['allsubtitles'] = True
@@ -101,7 +102,7 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
self.DL.params['subtitleslangs'] = langs self.DL.params['subtitleslangs'] = langs
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
for lang in langs: for lang in langs:
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
class TestDailymotionSubtitles(BaseTestSubtitles): class TestDailymotionSubtitles(BaseTestSubtitles):
@@ -130,20 +131,20 @@ class TestDailymotionSubtitles(BaseTestSubtitles):
self.assertEqual(len(subtitles.keys()), 5) self.assertEqual(len(subtitles.keys()), 5)
def test_list_subtitles(self): def test_list_subtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['listsubtitles'] = True self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict() info_dict = self.getInfoDict()
self.assertEqual(info_dict, None) self.assertEqual(info_dict, None)
def test_automatic_captions(self): def test_automatic_captions(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['writeautomaticsub'] = True self.DL.params['writeautomaticsub'] = True
self.DL.params['subtitleslang'] = ['en'] self.DL.params['subtitleslang'] = ['en']
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
self.assertTrue(len(subtitles.keys()) == 0) self.assertTrue(len(subtitles.keys()) == 0)
def test_nosubtitles(self): def test_nosubtitles(self):
self.DL.expect_warning(u'video doesn\'t have subtitles') self.DL.expect_warning('video doesn\'t have subtitles')
self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv' self.url = 'http://www.dailymotion.com/video/x12u166_le-zapping-tele-star-du-08-aout-2013_tv'
self.DL.params['writesubtitles'] = True self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True self.DL.params['allsubtitles'] = True
@@ -156,7 +157,7 @@ class TestDailymotionSubtitles(BaseTestSubtitles):
self.DL.params['subtitleslangs'] = langs self.DL.params['subtitleslangs'] = langs
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
for lang in langs: for lang in langs:
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
class TestTedSubtitles(BaseTestSubtitles): class TestTedSubtitles(BaseTestSubtitles):
@@ -185,13 +186,13 @@ class TestTedSubtitles(BaseTestSubtitles):
self.assertTrue(len(subtitles.keys()) >= 28) self.assertTrue(len(subtitles.keys()) >= 28)
def test_list_subtitles(self): def test_list_subtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['listsubtitles'] = True self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict() info_dict = self.getInfoDict()
self.assertEqual(info_dict, None) self.assertEqual(info_dict, None)
def test_automatic_captions(self): def test_automatic_captions(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['writeautomaticsub'] = True self.DL.params['writeautomaticsub'] = True
self.DL.params['subtitleslang'] = ['en'] self.DL.params['subtitleslang'] = ['en']
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
@@ -203,7 +204,7 @@ class TestTedSubtitles(BaseTestSubtitles):
self.DL.params['subtitleslangs'] = langs self.DL.params['subtitleslangs'] = langs
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
for lang in langs: for lang in langs:
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
class TestBlipTVSubtitles(BaseTestSubtitles): class TestBlipTVSubtitles(BaseTestSubtitles):
@@ -211,13 +212,13 @@ class TestBlipTVSubtitles(BaseTestSubtitles):
IE = BlipTVIE IE = BlipTVIE
def test_list_subtitles(self): def test_list_subtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['listsubtitles'] = True self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict() info_dict = self.getInfoDict()
self.assertEqual(info_dict, None) self.assertEqual(info_dict, None)
def test_allsubtitles(self): def test_allsubtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['writesubtitles'] = True self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True self.DL.params['allsubtitles'] = True
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
@@ -251,20 +252,20 @@ class TestVimeoSubtitles(BaseTestSubtitles):
self.assertEqual(set(subtitles.keys()), set(['de', 'en', 'es', 'fr'])) self.assertEqual(set(subtitles.keys()), set(['de', 'en', 'es', 'fr']))
def test_list_subtitles(self): def test_list_subtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['listsubtitles'] = True self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict() info_dict = self.getInfoDict()
self.assertEqual(info_dict, None) self.assertEqual(info_dict, None)
def test_automatic_captions(self): def test_automatic_captions(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['writeautomaticsub'] = True self.DL.params['writeautomaticsub'] = True
self.DL.params['subtitleslang'] = ['en'] self.DL.params['subtitleslang'] = ['en']
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
self.assertTrue(len(subtitles.keys()) == 0) self.assertTrue(len(subtitles.keys()) == 0)
def test_nosubtitles(self): def test_nosubtitles(self):
self.DL.expect_warning(u'video doesn\'t have subtitles') self.DL.expect_warning('video doesn\'t have subtitles')
self.url = 'http://vimeo.com/56015672' self.url = 'http://vimeo.com/56015672'
self.DL.params['writesubtitles'] = True self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True self.DL.params['allsubtitles'] = True
@@ -277,7 +278,7 @@ class TestVimeoSubtitles(BaseTestSubtitles):
self.DL.params['subtitleslangs'] = langs self.DL.params['subtitleslangs'] = langs
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
for lang in langs: for lang in langs:
self.assertTrue(subtitles.get(lang) is not None, u'Subtitles for \'%s\' not extracted' % lang) self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
class TestWallaSubtitles(BaseTestSubtitles): class TestWallaSubtitles(BaseTestSubtitles):
@@ -285,13 +286,13 @@ class TestWallaSubtitles(BaseTestSubtitles):
IE = WallaIE IE = WallaIE
def test_list_subtitles(self): def test_list_subtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['listsubtitles'] = True self.DL.params['listsubtitles'] = True
info_dict = self.getInfoDict() info_dict = self.getInfoDict()
self.assertEqual(info_dict, None) self.assertEqual(info_dict, None)
def test_allsubtitles(self): def test_allsubtitles(self):
self.DL.expect_warning(u'Automatic Captions not supported by this server') self.DL.expect_warning('Automatic Captions not supported by this server')
self.DL.params['writesubtitles'] = True self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True self.DL.params['allsubtitles'] = True
subtitles = self.getSubtitles() subtitles = self.getSubtitles()
@@ -299,7 +300,7 @@ class TestWallaSubtitles(BaseTestSubtitles):
self.assertEqual(md5(subtitles['heb']), 'e758c5d7cb982f6bef14f377ec7a3920') self.assertEqual(md5(subtitles['heb']), 'e758c5d7cb982f6bef14f377ec7a3920')
def test_nosubtitles(self): def test_nosubtitles(self):
self.DL.expect_warning(u'video doesn\'t have subtitles') self.DL.expect_warning('video doesn\'t have subtitles')
self.url = 'http://vod.walla.co.il/movie/2642630/one-direction-all-for-one' self.url = 'http://vod.walla.co.il/movie/2642630/one-direction-all-for-one'
self.DL.params['writesubtitles'] = True self.DL.params['writesubtitles'] = True
self.DL.params['allsubtitles'] = True self.DL.params['allsubtitles'] = True

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import unicode_literals
# Allow direct execution # Allow direct execution
import os import os

View File

@@ -218,6 +218,7 @@ class TestUtil(unittest.TestCase):
self.assertEqual(parse_duration('0m0s'), 0) self.assertEqual(parse_duration('0m0s'), 0)
self.assertEqual(parse_duration('0s'), 0) self.assertEqual(parse_duration('0s'), 0)
self.assertEqual(parse_duration('01:02:03.05'), 3723.05) self.assertEqual(parse_duration('01:02:03.05'), 3723.05)
self.assertEqual(parse_duration('T30M38S'), 1838)
def test_fix_xml_ampersands(self): def test_fix_xml_ampersands(self):
self.assertEqual( self.assertEqual(

View File

@@ -1001,7 +1001,7 @@ class YoutubeDL(object):
else: else:
self.to_screen('[info] Writing video description metadata as JSON to: ' + infofn) self.to_screen('[info] Writing video description metadata as JSON to: ' + infofn)
try: try:
write_json_file(info_dict, encodeFilename(infofn)) write_json_file(info_dict, infofn)
except (OSError, IOError): except (OSError, IOError):
self.report_error('Cannot write metadata to JSON file ' + infofn) self.report_error('Cannot write metadata to JSON file ' + infofn)
return return

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
__license__ = 'Public Domain' __license__ = 'Public Domain'
import codecs import codecs
@@ -17,6 +19,7 @@ from .compat import (
compat_expanduser, compat_expanduser,
compat_getpass, compat_getpass,
compat_print, compat_print,
workaround_optparse_bug9161,
) )
from .utils import ( from .utils import (
DateRange, DateRange,
@@ -55,7 +58,9 @@ def _real_main(argv=None):
# https://github.com/rg3/youtube-dl/issues/820 # https://github.com/rg3/youtube-dl/issues/820
codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
setproctitle(u'youtube-dl') workaround_optparse_bug9161()
setproctitle('youtube-dl')
parser, opts, args = parseOpts(argv) parser, opts, args = parseOpts(argv)
@@ -71,10 +76,10 @@ def _real_main(argv=None):
if opts.headers is not None: if opts.headers is not None:
for h in opts.headers: for h in opts.headers:
if h.find(':', 1) < 0: if h.find(':', 1) < 0:
parser.error(u'wrong header formatting, it should be key:value, not "%s"'%h) parser.error('wrong header formatting, it should be key:value, not "%s"'%h)
key, value = h.split(':', 2) key, value = h.split(':', 2)
if opts.verbose: if opts.verbose:
write_string(u'[debug] Adding header from command line option %s:%s\n'%(key, value)) write_string('[debug] Adding header from command line option %s:%s\n'%(key, value))
std_headers[key] = value std_headers[key] = value
# Dump user agent # Dump user agent
@@ -92,9 +97,9 @@ def _real_main(argv=None):
batchfd = io.open(opts.batchfile, 'r', encoding='utf-8', errors='ignore') batchfd = io.open(opts.batchfile, 'r', encoding='utf-8', errors='ignore')
batch_urls = read_batch_urls(batchfd) batch_urls = read_batch_urls(batchfd)
if opts.verbose: if opts.verbose:
write_string(u'[debug] Batch file urls: ' + repr(batch_urls) + u'\n') write_string('[debug] Batch file urls: ' + repr(batch_urls) + '\n')
except IOError: except IOError:
sys.exit(u'ERROR: batch file could not be read') sys.exit('ERROR: batch file could not be read')
all_urls = batch_urls + args all_urls = batch_urls + args
all_urls = [url.strip() for url in all_urls] all_urls = [url.strip() for url in all_urls]
_enc = preferredencoding() _enc = preferredencoding()
@@ -107,7 +112,7 @@ def _real_main(argv=None):
compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else '')) compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
matchedUrls = [url for url in all_urls if ie.suitable(url)] matchedUrls = [url for url in all_urls if ie.suitable(url)]
for mu in matchedUrls: for mu in matchedUrls:
compat_print(u' ' + mu) compat_print(' ' + mu)
sys.exit(0) sys.exit(0)
if opts.list_extractor_descriptions: if opts.list_extractor_descriptions:
for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()): for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
@@ -117,63 +122,63 @@ def _real_main(argv=None):
if desc is False: if desc is False:
continue continue
if hasattr(ie, 'SEARCH_KEY'): if hasattr(ie, 'SEARCH_KEY'):
_SEARCHES = (u'cute kittens', u'slithering pythons', u'falling cat', u'angry poodle', u'purple fish', u'running tortoise', u'sleeping bunny') _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny')
_COUNTS = (u'', u'5', u'10', u'all') _COUNTS = ('', '5', '10', 'all')
desc += u' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES)) desc += ' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
compat_print(desc) compat_print(desc)
sys.exit(0) sys.exit(0)
# Conflicting, missing and erroneous options # Conflicting, missing and erroneous options
if opts.usenetrc and (opts.username is not None or opts.password is not None): if opts.usenetrc and (opts.username is not None or opts.password is not None):
parser.error(u'using .netrc conflicts with giving username/password') parser.error('using .netrc conflicts with giving username/password')
if opts.password is not None and opts.username is None: if opts.password is not None and opts.username is None:
parser.error(u'account username missing\n') parser.error('account username missing\n')
if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid): if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
parser.error(u'using output template conflicts with using title, video ID or auto number') parser.error('using output template conflicts with using title, video ID or auto number')
if opts.usetitle and opts.useid: if opts.usetitle and opts.useid:
parser.error(u'using title conflicts with using video ID') parser.error('using title conflicts with using video ID')
if opts.username is not None and opts.password is None: if opts.username is not None and opts.password is None:
opts.password = compat_getpass(u'Type account password and press [Return]: ') opts.password = compat_getpass('Type account password and press [Return]: ')
if opts.ratelimit is not None: if opts.ratelimit is not None:
numeric_limit = FileDownloader.parse_bytes(opts.ratelimit) numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
if numeric_limit is None: if numeric_limit is None:
parser.error(u'invalid rate limit specified') parser.error('invalid rate limit specified')
opts.ratelimit = numeric_limit opts.ratelimit = numeric_limit
if opts.min_filesize is not None: if opts.min_filesize is not None:
numeric_limit = FileDownloader.parse_bytes(opts.min_filesize) numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
if numeric_limit is None: if numeric_limit is None:
parser.error(u'invalid min_filesize specified') parser.error('invalid min_filesize specified')
opts.min_filesize = numeric_limit opts.min_filesize = numeric_limit
if opts.max_filesize is not None: if opts.max_filesize is not None:
numeric_limit = FileDownloader.parse_bytes(opts.max_filesize) numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
if numeric_limit is None: if numeric_limit is None:
parser.error(u'invalid max_filesize specified') parser.error('invalid max_filesize specified')
opts.max_filesize = numeric_limit opts.max_filesize = numeric_limit
if opts.retries is not None: if opts.retries is not None:
try: try:
opts.retries = int(opts.retries) opts.retries = int(opts.retries)
except (TypeError, ValueError): except (TypeError, ValueError):
parser.error(u'invalid retry count specified') parser.error('invalid retry count specified')
if opts.buffersize is not None: if opts.buffersize is not None:
numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize) numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
if numeric_buffersize is None: if numeric_buffersize is None:
parser.error(u'invalid buffer size specified') parser.error('invalid buffer size specified')
opts.buffersize = numeric_buffersize opts.buffersize = numeric_buffersize
if opts.playliststart <= 0: if opts.playliststart <= 0:
raise ValueError(u'Playlist start must be positive') raise ValueError('Playlist start must be positive')
if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart: if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart:
raise ValueError(u'Playlist end must be greater than playlist start') raise ValueError('Playlist end must be greater than playlist start')
if opts.extractaudio: if opts.extractaudio:
if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']: if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
parser.error(u'invalid audio format specified') parser.error('invalid audio format specified')
if opts.audioquality: if opts.audioquality:
opts.audioquality = opts.audioquality.strip('k').strip('K') opts.audioquality = opts.audioquality.strip('k').strip('K')
if not opts.audioquality.isdigit(): if not opts.audioquality.isdigit():
parser.error(u'invalid audio quality specified') parser.error('invalid audio quality specified')
if opts.recodevideo is not None: if opts.recodevideo is not None:
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']: if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
parser.error(u'invalid video recode format specified') parser.error('invalid video recode format specified')
if opts.date is not None: if opts.date is not None:
date = DateRange.day(opts.date) date = DateRange.day(opts.date)
else: else:
@@ -193,17 +198,17 @@ def _real_main(argv=None):
if opts.outtmpl is not None: if opts.outtmpl is not None:
opts.outtmpl = opts.outtmpl.decode(preferredencoding()) opts.outtmpl = opts.outtmpl.decode(preferredencoding())
outtmpl =((opts.outtmpl is not None and opts.outtmpl) outtmpl =((opts.outtmpl is not None and opts.outtmpl)
or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s') or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s') or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s') or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s') or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
or (opts.useid and u'%(id)s.%(ext)s') or (opts.useid and '%(id)s.%(ext)s')
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s') or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
or DEFAULT_OUTTMPL) or DEFAULT_OUTTMPL)
if not os.path.splitext(outtmpl)[1] and opts.extractaudio: if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
parser.error(u'Cannot download a video and extract audio into the same' parser.error('Cannot download a video and extract audio into the same'
u' file! Use "{0}.%(ext)s" instead of "{0}" as the output' ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
u' template'.format(outtmpl)) ' 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 or opts.dump_single_json 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 = compat_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
@@ -330,7 +335,7 @@ def _real_main(argv=None):
# Maybe do nothing # Maybe do nothing
if (len(all_urls) < 1) and (opts.load_info_filename is None): if (len(all_urls) < 1) and (opts.load_info_filename is None):
if not (opts.update_self or opts.rm_cachedir): if not (opts.update_self or opts.rm_cachedir):
parser.error(u'you must provide at least one URL') parser.error('you must provide at least one URL')
else: else:
sys.exit() sys.exit()
@@ -340,7 +345,7 @@ def _real_main(argv=None):
else: else:
retcode = ydl.download(all_urls) retcode = ydl.download(all_urls)
except MaxDownloadsReached: except MaxDownloadsReached:
ydl.to_screen(u'--max-download limit reached, aborting.') ydl.to_screen('--max-download limit reached, aborting.')
retcode = 101 retcode = 101
sys.exit(retcode) sys.exit(retcode)
@@ -352,6 +357,6 @@ def main(argv=None):
except DownloadError: except DownloadError:
sys.exit(1) sys.exit(1)
except SameFileError: except SameFileError:
sys.exit(u'ERROR: fixed output name but more than one file to download') sys.exit('ERROR: fixed output name but more than one file to download')
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(u'\nERROR: Interrupted by user') sys.exit('\nERROR: Interrupted by user')

View File

@@ -8,7 +8,7 @@ import re
import shutil import shutil
import traceback import traceback
from .compat import compat_expanduser from .compat import compat_expanduser, compat_getenv
from .utils import write_json_file from .utils import write_json_file
@@ -19,7 +19,7 @@ class Cache(object):
def _get_root_dir(self): def _get_root_dir(self):
res = self._ydl.params.get('cachedir') res = self._ydl.params.get('cachedir')
if res is None: if res is None:
cache_root = os.environ.get('XDG_CACHE_HOME', '~/.cache') cache_root = compat_getenv('XDG_CACHE_HOME', '~/.cache')
res = os.path.join(cache_root, 'youtube-dl') res = os.path.join(cache_root, 'youtube-dl')
return compat_expanduser(res) return compat_expanduser(res)

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import getpass import getpass
import optparse
import os import os
import subprocess import subprocess
import sys import sys
@@ -288,6 +289,36 @@ if sys.version_info < (3, 0) and sys.platform == 'win32':
else: else:
compat_getpass = getpass.getpass compat_getpass = getpass.getpass
# Old 2.6 and 2.7 releases require kwargs to be bytes
try:
(lambda x: x)(**{'x': 0})
except TypeError:
def compat_kwargs(kwargs):
return dict((bytes(k), v) for k, v in kwargs.items())
else:
compat_kwargs = lambda kwargs: kwargs
# Fix https://github.com/rg3/youtube-dl/issues/4223
# See http://bugs.python.org/issue9161 for what is broken
def workaround_optparse_bug9161():
op = optparse.OptionParser()
og = optparse.OptionGroup(op, 'foo')
try:
og.add_option('-t')
except TypeError as te:
real_add_option = optparse.OptionGroup.add_option
def _compat_add_option(self, *args, **kwargs):
enc = lambda v: (
v.encode('ascii', 'replace') if isinstance(v, compat_str)
else v)
bargs = [enc(a) for a in args]
bkwargs = dict(
(k, enc(v)) for k, v in kwargs.items())
return real_add_option(self, *bargs, **bkwargs)
optparse.OptionGroup.add_option = _compat_add_option
__all__ = [ __all__ = [
'compat_HTTPError', 'compat_HTTPError',
@@ -299,6 +330,7 @@ __all__ = [
'compat_html_entities', 'compat_html_entities',
'compat_html_parser', 'compat_html_parser',
'compat_http_client', 'compat_http_client',
'compat_kwargs',
'compat_ord', 'compat_ord',
'compat_parse_qs', 'compat_parse_qs',
'compat_print', 'compat_print',
@@ -314,4 +346,5 @@ __all__ = [
'compat_xml_parse_error', 'compat_xml_parse_error',
'shlex_quote', 'shlex_quote',
'subprocess_check_output', 'subprocess_check_output',
'workaround_optparse_bug9161',
] ]

View File

@@ -1,3 +1,5 @@
from __future__ import unicode_literals
import os import os
import re import re
import sys import sys
@@ -159,14 +161,14 @@ class FileDownloader(object):
def temp_name(self, filename): def temp_name(self, filename):
"""Returns a temporary filename for the given filename.""" """Returns a temporary filename for the given filename."""
if self.params.get('nopart', False) or filename == u'-' or \ if self.params.get('nopart', False) or filename == '-' or \
(os.path.exists(encodeFilename(filename)) and not os.path.isfile(encodeFilename(filename))): (os.path.exists(encodeFilename(filename)) and not os.path.isfile(encodeFilename(filename))):
return filename return filename
return filename + u'.part' return filename + '.part'
def undo_temp_name(self, filename): def undo_temp_name(self, filename):
if filename.endswith(u'.part'): if filename.endswith('.part'):
return filename[:-len(u'.part')] return filename[:-len('.part')]
return filename return filename
def try_rename(self, old_filename, new_filename): def try_rename(self, old_filename, new_filename):
@@ -175,7 +177,7 @@ class FileDownloader(object):
return return
os.rename(encodeFilename(old_filename), encodeFilename(new_filename)) os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
except (IOError, OSError) as err: except (IOError, OSError) as err:
self.report_error(u'unable to rename file: %s' % compat_str(err)) self.report_error('unable to rename file: %s' % compat_str(err))
def try_utime(self, filename, last_modified_hdr): def try_utime(self, filename, last_modified_hdr):
"""Try to set the last-modified time of the given file.""" """Try to set the last-modified time of the given file."""
@@ -200,10 +202,10 @@ class FileDownloader(object):
def report_destination(self, filename): def report_destination(self, filename):
"""Report destination filename.""" """Report destination filename."""
self.to_screen(u'[download] Destination: ' + filename) self.to_screen('[download] Destination: ' + filename)
def _report_progress_status(self, msg, is_last_line=False): def _report_progress_status(self, msg, is_last_line=False):
fullmsg = u'[download] ' + msg fullmsg = '[download] ' + msg
if self.params.get('progress_with_newline', False): if self.params.get('progress_with_newline', False):
self.to_screen(fullmsg) self.to_screen(fullmsg)
else: else:
@@ -211,13 +213,13 @@ class FileDownloader(object):
prev_len = getattr(self, '_report_progress_prev_line_length', prev_len = getattr(self, '_report_progress_prev_line_length',
0) 0)
if prev_len > len(fullmsg): if prev_len > len(fullmsg):
fullmsg += u' ' * (prev_len - len(fullmsg)) fullmsg += ' ' * (prev_len - len(fullmsg))
self._report_progress_prev_line_length = len(fullmsg) self._report_progress_prev_line_length = len(fullmsg)
clear_line = u'\r' clear_line = '\r'
else: else:
clear_line = (u'\r\x1b[K' if sys.stderr.isatty() else u'\r') clear_line = ('\r\x1b[K' if sys.stderr.isatty() else '\r')
self.to_screen(clear_line + fullmsg, skip_eol=not is_last_line) self.to_screen(clear_line + fullmsg, skip_eol=not is_last_line)
self.to_console_title(u'youtube-dl ' + msg) self.to_console_title('youtube-dl ' + msg)
def report_progress(self, percent, data_len_str, speed, eta): def report_progress(self, percent, data_len_str, speed, eta):
"""Report download progress.""" """Report download progress."""
@@ -233,7 +235,7 @@ class FileDownloader(object):
percent_str = 'Unknown %' percent_str = 'Unknown %'
speed_str = self.format_speed(speed) speed_str = self.format_speed(speed)
msg = (u'%s of %s at %s ETA %s' % msg = ('%s of %s at %s ETA %s' %
(percent_str, data_len_str, speed_str, eta_str)) (percent_str, data_len_str, speed_str, eta_str))
self._report_progress_status(msg) self._report_progress_status(msg)
@@ -243,37 +245,37 @@ class FileDownloader(object):
downloaded_str = format_bytes(downloaded_data_len) downloaded_str = format_bytes(downloaded_data_len)
speed_str = self.format_speed(speed) speed_str = self.format_speed(speed)
elapsed_str = FileDownloader.format_seconds(elapsed) elapsed_str = FileDownloader.format_seconds(elapsed)
msg = u'%s at %s (%s)' % (downloaded_str, speed_str, elapsed_str) msg = '%s at %s (%s)' % (downloaded_str, speed_str, elapsed_str)
self._report_progress_status(msg) self._report_progress_status(msg)
def report_finish(self, data_len_str, tot_time): def report_finish(self, data_len_str, tot_time):
"""Report download finished.""" """Report download finished."""
if self.params.get('noprogress', False): if self.params.get('noprogress', False):
self.to_screen(u'[download] Download completed') self.to_screen('[download] Download completed')
else: else:
self._report_progress_status( self._report_progress_status(
(u'100%% of %s in %s' % ('100%% of %s in %s' %
(data_len_str, self.format_seconds(tot_time))), (data_len_str, self.format_seconds(tot_time))),
is_last_line=True) is_last_line=True)
def report_resuming_byte(self, resume_len): def report_resuming_byte(self, resume_len):
"""Report attempt to resume at given byte.""" """Report attempt to resume at given byte."""
self.to_screen(u'[download] Resuming download at byte %s' % resume_len) self.to_screen('[download] Resuming download at byte %s' % resume_len)
def report_retry(self, count, retries): def report_retry(self, count, retries):
"""Report retry in case of HTTP error 5xx""" """Report retry in case of HTTP error 5xx"""
self.to_screen(u'[download] Got server HTTP error. Retrying (attempt %d of %d)...' % (count, retries)) self.to_screen('[download] Got server HTTP error. Retrying (attempt %d of %d)...' % (count, retries))
def report_file_already_downloaded(self, file_name): def report_file_already_downloaded(self, file_name):
"""Report file has already been fully downloaded.""" """Report file has already been fully downloaded."""
try: try:
self.to_screen(u'[download] %s has already been downloaded' % file_name) self.to_screen('[download] %s has already been downloaded' % file_name)
except UnicodeEncodeError: except UnicodeEncodeError:
self.to_screen(u'[download] The file has already been downloaded') self.to_screen('[download] The file has already been downloaded')
def report_unable_to_resume(self): def report_unable_to_resume(self):
"""Report it was impossible to resume download.""" """Report it was impossible to resume download."""
self.to_screen(u'[download] Unable to resume') self.to_screen('[download] Unable to resume')
def download(self, filename, info_dict): def download(self, filename, info_dict):
"""Download to a filename using the info from info_dict """Download to a filename using the info from info_dict
@@ -293,7 +295,7 @@ class FileDownloader(object):
def real_download(self, filename, info_dict): def real_download(self, filename, info_dict):
"""Real download process. Redefine in subclasses.""" """Real download process. Redefine in subclasses."""
raise NotImplementedError(u'This method must be implemented by subclasses') raise NotImplementedError('This method must be implemented by subclasses')
def _hook_progress(self, status): def _hook_progress(self, status):
for ph in self._progress_hooks: for ph in self._progress_hooks:

View File

@@ -71,11 +71,12 @@ class BlipTVIE(SubtitlesInfoExtractor):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
lookup_id = mobj.group('lookup_id') lookup_id = mobj.group('lookup_id')
# See https://github.com/rg3/youtube-dl/issues/857 # See https://github.com/rg3/youtube-dl/issues/857 and
# https://github.com/rg3/youtube-dl/issues/4197
if lookup_id: if lookup_id:
info_page = self._download_webpage( info_page = self._download_webpage(
'http://blip.tv/play/%s.x?p=1' % lookup_id, lookup_id, 'Resolving lookup id') 'http://blip.tv/play/%s.x?p=1' % lookup_id, lookup_id, 'Resolving lookup id')
video_id = self._search_regex(r'data-episode-id="([0-9]+)', info_page, 'video_id') video_id = self._search_regex(r'config\.id\s*=\s*"([0-9]+)', info_page, 'video_id')
else: else:
video_id = mobj.group('id') video_id = mobj.group('id')

View File

@@ -264,8 +264,6 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
if not lang_code: if not lang_code:
continue continue
sub_root = xml.etree.ElementTree.fromstring(subtitle) 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(sub_root) subtitles[lang_code] = self._convert_subtitles_to_ass(sub_root)
else: else:

View File

@@ -20,7 +20,7 @@ class EpornerIE(InfoExtractor):
'display_id': 'Infamous-Tiffany-Teen-Strip-Tease-Video', 'display_id': 'Infamous-Tiffany-Teen-Strip-Tease-Video',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Infamous Tiffany Teen Strip Tease Video', 'title': 'Infamous Tiffany Teen Strip Tease Video',
'duration': 194, 'duration': 1838,
'view_count': int, 'view_count': int,
'age_limit': 18, 'age_limit': 18,
} }
@@ -57,9 +57,7 @@ class EpornerIE(InfoExtractor):
formats.append(fmt) formats.append(fmt)
self._sort_formats(formats) self._sort_formats(formats)
duration = parse_duration(self._search_regex( duration = parse_duration(self._html_search_meta('duration', webpage))
r'class="mbtim">([0-9:]+)</div>', webpage, 'duration',
fatal=False))
view_count = str_to_int(self._search_regex( view_count = str_to_int(self._search_regex(
r'id="cinemaviews">\s*([0-9,]+)\s*<small>views', r'id="cinemaviews">\s*([0-9,]+)\s*<small>views',
webpage, 'view count', fatal=False)) webpage, 'view count', fatal=False))

View File

@@ -10,7 +10,7 @@ from ..utils import (
class GoldenMoustacheIE(InfoExtractor): class GoldenMoustacheIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?goldenmoustache\.com/(?P<display_id>[\w-]+)-(?P<id>\d+)' _VALID_URL = r'https?://(?:www\.)?goldenmoustache\.com/(?P<display_id>[\w-]+)-(?P<id>\d+)'
_TEST = { _TESTS = [{
'url': 'http://www.goldenmoustache.com/suricate-le-poker-3700/', 'url': 'http://www.goldenmoustache.com/suricate-le-poker-3700/',
'md5': '0f904432fa07da5054d6c8beb5efb51a', 'md5': '0f904432fa07da5054d6c8beb5efb51a',
'info_dict': { 'info_dict': {
@@ -21,7 +21,18 @@ class GoldenMoustacheIE(InfoExtractor):
'thumbnail': 're:^https?://.*\.jpg$', 'thumbnail': 're:^https?://.*\.jpg$',
'view_count': int, 'view_count': int,
} }
}, {
'url': 'http://www.goldenmoustache.com/le-lab-tout-effacer-mc-fly-et-carlito-55249/',
'md5': '27f0c50fb4dd5f01dc9082fc67cd5700',
'info_dict': {
'id': '55249',
'ext': 'mp4',
'title': 'Le LAB - Tout Effacer (Mc Fly et Carlito)',
'description': 'md5:9b7fbf11023fb2250bd4b185e3de3b2a',
'thumbnail': 're:^https?://.*\.(?:png|jpg)$',
'view_count': int,
} }
}]
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
@@ -30,7 +41,7 @@ class GoldenMoustacheIE(InfoExtractor):
video_url = self._html_search_regex( video_url = self._html_search_regex(
r'data-src-type="mp4" data-src="([^"]+)"', webpage, 'video URL') r'data-src-type="mp4" data-src="([^"]+)"', webpage, 'video URL')
title = self._html_search_regex( title = self._html_search_regex(
r'<title>(.*?) - Golden Moustache</title>', webpage, 'title') r'<title>(.*?)(?: - Golden Moustache)?</title>', webpage, 'title')
thumbnail = self._og_search_thumbnail(webpage) thumbnail = self._og_search_thumbnail(webpage)
description = self._og_search_description(webpage) description = self._og_search_description(webpage)
view_count = int_or_none(self._html_search_regex( view_count = int_or_none(self._html_search_regex(

View File

@@ -16,7 +16,7 @@ class MailRuIE(InfoExtractor):
'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76', 'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
'md5': 'dea205f03120046894db4ebb6159879a', 'md5': 'dea205f03120046894db4ebb6159879a',
'info_dict': { 'info_dict': {
'id': '46301138', 'id': '46301138_76',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро', 'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
'timestamp': 1393232740, 'timestamp': 1393232740,
@@ -30,7 +30,7 @@ class MailRuIE(InfoExtractor):
'url': 'http://my.mail.ru/corp/hitech/video/news_hi-tech_mail_ru/1263.html', 'url': 'http://my.mail.ru/corp/hitech/video/news_hi-tech_mail_ru/1263.html',
'md5': '00a91a58c3402204dcced523777b475f', 'md5': '00a91a58c3402204dcced523777b475f',
'info_dict': { 'info_dict': {
'id': '46843144', 'id': '46843144_1263',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Samsung Galaxy S5 Hammer Smash Fail Battery Explosion', 'title': 'Samsung Galaxy S5 Hammer Smash Fail Battery Explosion',
'timestamp': 1397217632, 'timestamp': 1397217632,
@@ -54,30 +54,33 @@ class MailRuIE(InfoExtractor):
author = video_data['author'] author = video_data['author']
uploader = author['name'] uploader = author['name']
uploader_id = author['id'] uploader_id = author.get('id') or author.get('email')
view_count = video_data.get('views_count')
movie = video_data['movie'] meta_data = video_data['meta']
content_id = str(movie['contentId']) content_id = '%s_%s' % (
title = movie['title'] meta_data.get('accId', ''), meta_data['itemId'])
title = meta_data['title']
if title.endswith('.mp4'): if title.endswith('.mp4'):
title = title[:-4] title = title[:-4]
thumbnail = movie['poster'] thumbnail = meta_data['poster']
duration = movie['duration'] duration = meta_data['duration']
timestamp = meta_data['timestamp']
view_count = video_data['views_count']
formats = [ formats = [
{ {
'url': video['url'], 'url': video['url'],
'format_id': video['name'], 'format_id': video['key'],
'height': int(video['key'].rstrip('p'))
} for video in video_data['videos'] } for video in video_data['videos']
] ]
self._sort_formats(formats)
return { return {
'id': content_id, 'id': content_id,
'title': title, 'title': title,
'thumbnail': thumbnail, 'thumbnail': thumbnail,
'timestamp': video_data['timestamp'], 'timestamp': timestamp,
'uploader': uploader, 'uploader': uploader,
'uploader_id': uploader_id, 'uploader_id': uploader_id,
'duration': duration, 'duration': duration,

View File

@@ -28,8 +28,9 @@ class RtlXlIE(InfoExtractor):
mobj = re.match(self._VALID_URL, url) mobj = re.match(self._VALID_URL, url)
uuid = mobj.group('uuid') uuid = mobj.group('uuid')
# Use m3u8 streams (see https://github.com/rg3/youtube-dl/issues/4118)
info = self._download_json( info = self._download_json(
'http://www.rtl.nl/system/s4m/vfd/version=2/uuid=%s/fmt=flash/' % uuid, 'http://www.rtl.nl/system/s4m/vfd/version=2/uuid=%s/d=pc/fmt=adaptive/' % uuid,
uuid) uuid)
material = info['material'][0] material = info['material'][0]
@@ -39,11 +40,11 @@ class RtlXlIE(InfoExtractor):
subtitle = material['title'] or info['episodes'][0]['name'] subtitle = material['title'] or info['episodes'][0]['name']
videopath = material['videopath'] videopath = material['videopath']
f4m_url = 'http://manifest.us.rtl.nl' + videopath m3u8_url = 'http://manifest.us.rtl.nl' + videopath
formats = self._extract_f4m_formats(f4m_url, uuid) formats = self._extract_m3u8_formats(m3u8_url, uuid, ext='mp4')
video_urlpart = videopath.split('/flash/')[1][:-4] video_urlpart = videopath.split('/adaptive/')[1][:-4]
PG_URL_TEMPLATE = 'http://pg.us.rtl.nl/rtlxl/network/%s/progressive/%s.mp4' PG_URL_TEMPLATE = 'http://pg.us.rtl.nl/rtlxl/network/%s/progressive/%s.mp4'
formats.extend([ formats.extend([
@@ -54,9 +55,12 @@ class RtlXlIE(InfoExtractor):
{ {
'url': PG_URL_TEMPLATE % ('a3m', video_urlpart), 'url': PG_URL_TEMPLATE % ('a3m', video_urlpart),
'format_id': 'pg-hd', 'format_id': 'pg-hd',
'quality': 0,
} }
]) ])
self._sort_formats(formats)
return { return {
'id': uuid, 'id': uuid,
'title': '%s - %s' % (progname, subtitle), 'title': '%s - %s' % (progname, subtitle),

View File

@@ -5,6 +5,7 @@ import re
from .common import InfoExtractor from .common import InfoExtractor
from ..compat import compat_urlparse from ..compat import compat_urlparse
from .spiegeltv import SpiegeltvIE
class SpiegelIE(InfoExtractor): class SpiegelIE(InfoExtractor):
@@ -42,7 +43,11 @@ class SpiegelIE(InfoExtractor):
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, handle = self._download_webpage_handle(url, video_id)
# 302 to spiegel.tv, like http://www.spiegel.de/video/der-film-zum-wochenende-die-wahrheit-ueber-maenner-video-99003272.html
if SpiegeltvIE.suitable(handle.geturl()):
return self.url_result(handle.geturl(), 'Spiegeltv')
title = re.sub(r'\s+', ' ', self._html_search_regex( title = re.sub(r'\s+', ' ', self._html_search_regex(
r'(?s)<(?:h1|div) class="module-title"[^>]*>(.*?)</(?:h1|div)>', r'(?s)<(?:h1|div) class="module-title"[^>]*>(.*?)</(?:h1|div)>',

View File

@@ -1,13 +1,13 @@
# 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 float_or_none
class SpiegeltvIE(InfoExtractor): class SpiegeltvIE(InfoExtractor):
_VALID_URL = r'https?://(?:www\.)?spiegel\.tv/filme/(?P<id>[\-a-z0-9]+)' _VALID_URL = r'https?://(?:www\.)?spiegel\.tv/(?:#/)?filme/(?P<id>[\-a-z0-9]+)'
_TEST = { _TESTS = [{
'url': 'http://www.spiegel.tv/filme/flug-mh370/', 'url': 'http://www.spiegel.tv/filme/flug-mh370/',
'info_dict': { 'info_dict': {
'id': 'flug-mh370', 'id': 'flug-mh370',
@@ -20,12 +20,15 @@ class SpiegeltvIE(InfoExtractor):
# rtmp download # rtmp download
'skip_download': True, 'skip_download': True,
} }
} }, {
'url': 'http://www.spiegel.tv/#/filme/alleskino-die-wahrheit-ueber-maenner/',
'only_matching': True,
}]
def _real_extract(self, url): def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url) if '/#/' in url:
video_id = mobj.group('id') url = url.replace('/#/', '/')
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
title = self._html_search_regex(r'<h1.*?>(.*?)</h1>', webpage, 'title') title = self._html_search_regex(r'<h1.*?>(.*?)</h1>', webpage, 'title')
@@ -61,12 +64,8 @@ class SpiegeltvIE(InfoExtractor):
}) })
description = media_json['subtitle'] description = media_json['subtitle']
duration = media_json['duration_in_ms'] / 1000. duration = float_or_none(media_json.get('duration_in_ms'), scale=1000)
format = '16x9' if is_wide else '4x3'
if is_wide:
format = '16x9'
else:
format = '4x3'
url = server + 'mp4:' + uuid + '_spiegeltv_0500_' + format + '.m4v' url = server + 'mp4:' + uuid + '_spiegeltv_0500_' + format + '.m4v'

View File

@@ -307,6 +307,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
'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},
'172': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'abr': 256, 'preference': -50}, '172': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'abr': 256, 'preference': -50},
# Dash webm audio with opus inside
'249': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 50, 'preference': -50},
'250': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 70, 'preference': -50},
'251': {'ext': 'webm', 'vcodec': 'none', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 160, 'preference': -50},
# RTMP (unnamed) # RTMP (unnamed)
'_rtmp': {'protocol': 'rtmp'}, '_rtmp': {'protocol': 'rtmp'},
} }
@@ -991,7 +996,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
existing_format.update(f) existing_format.update(f)
except (ExtractorError, KeyError) as e: except (ExtractorError, KeyError) as e:
self.report_warning('Skipping DASH manifest: %s' % e, video_id) self.report_warning('Skipping DASH manifest: %r' % e, video_id)
self._sort_formats(formats) self._sort_formats(formats)

View File

@@ -8,6 +8,7 @@ import sys
from .compat import ( from .compat import (
compat_expanduser, compat_expanduser,
compat_getenv, compat_getenv,
compat_kwargs,
) )
from .utils import ( from .utils import (
get_term_width, get_term_width,
@@ -112,7 +113,7 @@ def parseOpts(overrideArguments=None):
'conflict_handler': 'resolve', 'conflict_handler': 'resolve',
} }
parser = optparse.OptionParser(**kw) parser = optparse.OptionParser(**compat_kwargs(kw))
general = optparse.OptionGroup(parser, 'General Options') general = optparse.OptionGroup(parser, 'General Options')
general.add_option( general.add_option(

View File

@@ -62,15 +62,17 @@ class _ScopeDict(dict):
class _AVMClass(object): class _AVMClass(object):
def __init__(self, name_idx, name): def __init__(self, name_idx, name, static_properties=None):
self.name_idx = name_idx self.name_idx = name_idx
self.name = name self.name = name
self.method_names = {} self.method_names = {}
self.method_idxs = {} self.method_idxs = {}
self.methods = {} self.methods = {}
self.method_pyfunctions = {} self.method_pyfunctions = {}
self.static_properties = static_properties if static_properties else {}
self.variables = _ScopeDict(self) self.variables = _ScopeDict(self)
self.constants = {}
def make_object(self): def make_object(self):
return _AVMClass_Object(self) return _AVMClass_Object(self)
@@ -148,8 +150,38 @@ def _read_byte(reader):
return res return res
StringClass = _AVMClass('(no name idx)', 'String')
ByteArrayClass = _AVMClass('(no name idx)', 'ByteArray')
TimerClass = _AVMClass('(no name idx)', 'Timer')
TimerEventClass = _AVMClass('(no name idx)', 'TimerEvent', {'TIMER': 'timer'})
_builtin_classes = {
StringClass.name: StringClass,
ByteArrayClass.name: ByteArrayClass,
TimerClass.name: TimerClass,
TimerEventClass.name: TimerEventClass,
}
class _Undefined(object):
def __bool__(self):
return False
__nonzero__ = __bool__
def __hash__(self):
return 0
def __str__(self):
return 'undefined'
__repr__ = __str__
undefined = _Undefined()
class SWFInterpreter(object): class SWFInterpreter(object):
def __init__(self, file_contents): def __init__(self, file_contents):
self._patched_functions = {
(TimerClass, 'addEventListener'): lambda params: undefined,
}
code_tag = next(tag code_tag = next(tag
for tag_code, tag in _extract_tags(file_contents) for tag_code, tag in _extract_tags(file_contents)
if tag_code == 82) if tag_code == 82)
@@ -170,11 +202,13 @@ class SWFInterpreter(object):
# Constant pool # Constant pool
int_count = u30() int_count = u30()
self.constant_ints = [0]
for _c in range(1, int_count): for _c in range(1, int_count):
s32() self.constant_ints.append(s32())
self.constant_uints = [0]
uint_count = u30() uint_count = u30()
for _c in range(1, uint_count): for _c in range(1, uint_count):
u32() self.constant_uints.append(u32())
double_count = u30() double_count = u30()
read_bytes(max(0, (double_count - 1)) * 8) read_bytes(max(0, (double_count - 1)) * 8)
string_count = u30() string_count = u30()
@@ -212,6 +246,10 @@ class SWFInterpreter(object):
u30() # namespace_idx u30() # namespace_idx
name_idx = u30() name_idx = u30()
self.multinames.append(self.constant_strings[name_idx]) self.multinames.append(self.constant_strings[name_idx])
elif kind == 0x09:
name_idx = u30()
u30()
self.multinames.append(self.constant_strings[name_idx])
else: else:
self.multinames.append(_Multiname(kind)) self.multinames.append(_Multiname(kind))
for _c2 in range(MULTINAME_SIZES[kind]): for _c2 in range(MULTINAME_SIZES[kind]):
@@ -258,13 +296,28 @@ class SWFInterpreter(object):
kind = kind_full & 0x0f kind = kind_full & 0x0f
attrs = kind_full >> 4 attrs = kind_full >> 4
methods = {} methods = {}
if kind in [0x00, 0x06]: # Slot or Const constants = None
if kind == 0x00: # Slot
u30() # Slot id u30() # Slot id
u30() # type_name_idx u30() # type_name_idx
vindex = u30() vindex = u30()
if vindex != 0: if vindex != 0:
read_byte() # vkind read_byte() # vkind
elif kind in [0x01, 0x02, 0x03]: # Method / Getter / Setter elif kind == 0x06: # Const
u30() # Slot id
u30() # type_name_idx
vindex = u30()
vkind = 'any'
if vindex != 0:
vkind = read_byte()
if vkind == 0x03: # Constant_Int
value = self.constant_ints[vindex]
elif vkind == 0x04: # Constant_UInt
value = self.constant_uints[vindex]
else:
return {}, None # Ignore silently for now
constants = {self.multinames[trait_name_idx]: value}
elif kind in (0x01, 0x02, 0x03): # Method / Getter / Setter
u30() # disp_id u30() # disp_id
method_idx = u30() method_idx = u30()
methods[self.multinames[trait_name_idx]] = method_idx methods[self.multinames[trait_name_idx]] = method_idx
@@ -283,7 +336,7 @@ class SWFInterpreter(object):
for _c3 in range(metadata_count): for _c3 in range(metadata_count):
u30() # metadata index u30() # metadata index
return methods return methods, constants
# Classes # Classes
class_count = u30() class_count = u30()
@@ -305,18 +358,22 @@ class SWFInterpreter(object):
u30() # iinit u30() # iinit
trait_count = u30() trait_count = u30()
for _c2 in range(trait_count): for _c2 in range(trait_count):
trait_methods = parse_traits_info() trait_methods, trait_constants = parse_traits_info()
avm_class.register_methods(trait_methods) avm_class.register_methods(trait_methods)
if trait_constants:
avm_class.constants.update(trait_constants)
assert len(classes) == class_count assert len(classes) == class_count
self._classes_by_name = dict((c.name, c) for c in classes) self._classes_by_name = dict((c.name, c) for c in classes)
for avm_class in classes: for avm_class in classes:
u30() # cinit avm_class.cinit_idx = u30()
trait_count = u30() trait_count = u30()
for _c2 in range(trait_count): for _c2 in range(trait_count):
trait_methods = parse_traits_info() trait_methods, trait_constants = parse_traits_info()
avm_class.register_methods(trait_methods) avm_class.register_methods(trait_methods)
if trait_constants:
avm_class.constants.update(trait_constants)
# Scripts # Scripts
script_count = u30() script_count = u30()
@@ -329,6 +386,7 @@ class SWFInterpreter(object):
# Method bodies # Method bodies
method_body_count = u30() method_body_count = u30()
Method = collections.namedtuple('Method', ['code', 'local_count']) Method = collections.namedtuple('Method', ['code', 'local_count'])
self._all_methods = []
for _c in range(method_body_count): for _c in range(method_body_count):
method_idx = u30() method_idx = u30()
u30() # max_stack u30() # max_stack
@@ -337,9 +395,10 @@ class SWFInterpreter(object):
u30() # max_scope_depth u30() # max_scope_depth
code_length = u30() code_length = u30()
code = read_bytes(code_length) code = read_bytes(code_length)
m = Method(code, local_count)
self._all_methods.append(m)
for avm_class in classes: for avm_class in classes:
if method_idx in avm_class.method_idxs: if method_idx in avm_class.method_idxs:
m = Method(code, local_count)
avm_class.methods[avm_class.method_idxs[method_idx]] = m avm_class.methods[avm_class.method_idxs[method_idx]] = m
exception_count = u30() exception_count = u30()
for _c2 in range(exception_count): for _c2 in range(exception_count):
@@ -354,13 +413,27 @@ class SWFInterpreter(object):
assert p + code_reader.tell() == len(code_tag) assert p + code_reader.tell() == len(code_tag)
def extract_class(self, class_name): def patch_function(self, avm_class, func_name, f):
self._patched_functions[(avm_class, func_name)] = f
def extract_class(self, class_name, call_cinit=True):
try: try:
return self._classes_by_name[class_name] res = self._classes_by_name[class_name]
except KeyError: except KeyError:
raise ExtractorError('Class %r not found' % class_name) raise ExtractorError('Class %r not found' % class_name)
if call_cinit and hasattr(res, 'cinit_idx'):
res.register_methods({'$cinit': res.cinit_idx})
res.methods['$cinit'] = self._all_methods[res.cinit_idx]
cinit = self.extract_function(res, '$cinit')
cinit([])
return res
def extract_function(self, avm_class, func_name): def extract_function(self, avm_class, func_name):
p = self._patched_functions.get((avm_class, func_name))
if p:
return p
if func_name in avm_class.method_pyfunctions: if func_name in avm_class.method_pyfunctions:
return avm_class.method_pyfunctions[func_name] return avm_class.method_pyfunctions[func_name]
if func_name in self._classes_by_name: if func_name in self._classes_by_name:
@@ -379,10 +452,15 @@ class SWFInterpreter(object):
registers = [avm_class.variables] + list(args) + [None] * m.local_count registers = [avm_class.variables] + list(args) + [None] * m.local_count
stack = [] stack = []
scopes = collections.deque([ scopes = collections.deque([
self._classes_by_name, avm_class.variables]) self._classes_by_name, avm_class.constants, avm_class.variables])
while True: while True:
opcode = _read_byte(coder) opcode = _read_byte(coder)
if opcode == 17: # iftrue if opcode == 9: # label
pass # Spec says: "Do nothing."
elif opcode == 16: # jump
offset = s24()
coder.seek(coder.tell() + offset)
elif opcode == 17: # iftrue
offset = s24() offset = s24()
value = stack.pop() value = stack.pop()
if value: if value:
@@ -392,9 +470,40 @@ class SWFInterpreter(object):
value = stack.pop() value = stack.pop()
if not value: if not value:
coder.seek(coder.tell() + offset) coder.seek(coder.tell() + offset)
elif opcode == 19: # ifeq
offset = s24()
value2 = stack.pop()
value1 = stack.pop()
if value2 == value1:
coder.seek(coder.tell() + offset)
elif opcode == 20: # ifne
offset = s24()
value2 = stack.pop()
value1 = stack.pop()
if value2 != value1:
coder.seek(coder.tell() + offset)
elif opcode == 21: # iflt
offset = s24()
value2 = stack.pop()
value1 = stack.pop()
if value1 < value2:
coder.seek(coder.tell() + offset)
elif opcode == 32: # pushnull
stack.append(None)
elif opcode == 33: # pushundefined
stack.append(undefined)
elif opcode == 36: # pushbyte elif opcode == 36: # pushbyte
v = _read_byte(coder) v = _read_byte(coder)
stack.append(v) stack.append(v)
elif opcode == 37: # pushshort
v = u30()
stack.append(v)
elif opcode == 38: # pushtrue
stack.append(True)
elif opcode == 39: # pushfalse
stack.append(False)
elif opcode == 40: # pushnan
stack.append(float('NaN'))
elif opcode == 42: # dup elif opcode == 42: # dup
value = stack[-1] value = stack[-1]
stack.append(value) stack.append(value)
@@ -419,11 +528,31 @@ class SWFInterpreter(object):
[stack.pop() for _ in range(arg_count)])) [stack.pop() for _ in range(arg_count)]))
obj = stack.pop() obj = stack.pop()
if isinstance(obj, _AVMClass_Object): if obj == StringClass:
if mname == 'String':
assert len(args) == 1
assert isinstance(args[0], (
int, compat_str, _Undefined))
if args[0] == undefined:
res = 'undefined'
else:
res = compat_str(args[0])
stack.append(res)
continue
else:
raise NotImplementedError(
'Function String.%s is not yet implemented'
% mname)
elif isinstance(obj, _AVMClass_Object):
func = self.extract_function(obj.avm_class, mname) func = self.extract_function(obj.avm_class, mname)
res = func(args) res = func(args)
stack.append(res) stack.append(res)
continue continue
elif isinstance(obj, _AVMClass):
func = self.extract_function(obj, mname)
res = func(args)
stack.append(res)
continue
elif isinstance(obj, _ScopeDict): elif isinstance(obj, _ScopeDict):
if mname in obj.avm_class.method_names: if mname in obj.avm_class.method_names:
func = self.extract_function(obj.avm_class, mname) func = self.extract_function(obj.avm_class, mname)
@@ -442,6 +571,13 @@ class SWFInterpreter(object):
res = obj.split(args[0]) res = obj.split(args[0])
stack.append(res) stack.append(res)
continue continue
elif mname == 'charCodeAt':
assert len(args) <= 1
idx = 0 if len(args) == 0 else args[0]
assert isinstance(idx, int)
res = ord(obj[idx])
stack.append(res)
continue
elif isinstance(obj, list): elif isinstance(obj, list):
if mname == 'slice': if mname == 'slice':
assert len(args) == 1 assert len(args) == 1
@@ -458,9 +594,18 @@ class SWFInterpreter(object):
raise NotImplementedError( raise NotImplementedError(
'Unsupported property %r on %r' 'Unsupported property %r on %r'
% (mname, obj)) % (mname, obj))
elif opcode == 71: # returnvoid
res = undefined
return res
elif opcode == 72: # returnvalue elif opcode == 72: # returnvalue
res = stack.pop() res = stack.pop()
return res return res
elif opcode == 73: # constructsuper
# Not yet implemented, just hope it works without it
arg_count = u30()
args = list(reversed(
[stack.pop() for _ in range(arg_count)]))
obj = stack.pop()
elif opcode == 74: # constructproperty elif opcode == 74: # constructproperty
index = u30() index = u30()
arg_count = u30() arg_count = u30()
@@ -481,6 +626,17 @@ class SWFInterpreter(object):
args = list(reversed( args = list(reversed(
[stack.pop() for _ in range(arg_count)])) [stack.pop() for _ in range(arg_count)]))
obj = stack.pop() obj = stack.pop()
if isinstance(obj, _AVMClass_Object):
func = self.extract_function(obj.avm_class, mname)
res = func(args)
assert res is undefined
continue
if isinstance(obj, _ScopeDict):
assert mname in obj.avm_class.method_names
func = self.extract_function(obj.avm_class, mname)
res = func(args)
assert res is undefined
continue
if mname == 'reverse': if mname == 'reverse':
assert isinstance(obj, list) assert isinstance(obj, list)
obj.reverse() obj.reverse()
@@ -504,6 +660,9 @@ class SWFInterpreter(object):
break break
else: else:
res = scopes[0] res = scopes[0]
if mname not in res and mname in _builtin_classes:
stack.append(_builtin_classes[mname])
else:
stack.append(res[mname]) stack.append(res[mname])
elif opcode == 94: # findproperty elif opcode == 94: # findproperty
index = u30() index = u30()
@@ -524,9 +683,15 @@ class SWFInterpreter(object):
break break
else: else:
scope = avm_class.variables scope = avm_class.variables
# I cannot find where static variables are initialized
# so let's just return None if mname in scope:
res = scope.get(mname) res = scope[mname]
elif mname in _builtin_classes:
res = _builtin_classes[mname]
else:
# Assume unitialized
# TODO warn here
res = undefined
stack.append(res) stack.append(res)
elif opcode == 97: # setproperty elif opcode == 97: # setproperty
index = u30() index = u30()
@@ -548,22 +713,57 @@ class SWFInterpreter(object):
pname = self.multinames[index] pname = self.multinames[index]
if pname == 'length': if pname == 'length':
obj = stack.pop() obj = stack.pop()
assert isinstance(obj, list) assert isinstance(obj, (compat_str, list))
stack.append(len(obj)) stack.append(len(obj))
elif isinstance(pname, compat_str): # Member access
obj = stack.pop()
if isinstance(obj, _AVMClass):
res = obj.static_properties[pname]
stack.append(res)
continue
assert isinstance(obj, (dict, _ScopeDict)),\
'Accessing member %r on %r' % (pname, obj)
res = obj.get(pname, undefined)
stack.append(res)
else: # Assume attribute access else: # Assume attribute access
idx = stack.pop() idx = stack.pop()
assert isinstance(idx, int) assert isinstance(idx, int)
obj = stack.pop() obj = stack.pop()
assert isinstance(obj, list) assert isinstance(obj, list)
stack.append(obj[idx]) stack.append(obj[idx])
elif opcode == 104: # initproperty
index = u30()
value = stack.pop()
idx = self.multinames[index]
if isinstance(idx, _Multiname):
idx = stack.pop()
obj = stack.pop()
obj[idx] = value
elif opcode == 115: # convert_ elif opcode == 115: # convert_
value = stack.pop() value = stack.pop()
intvalue = int(value) intvalue = int(value)
stack.append(intvalue) stack.append(intvalue)
elif opcode == 128: # coerce elif opcode == 128: # coerce
u30() u30()
elif opcode == 130: # coerce_a
value = stack.pop()
# um, yes, it's any value
stack.append(value)
elif opcode == 133: # coerce_s elif opcode == 133: # coerce_s
assert isinstance(stack[-1], (type(None), compat_str)) assert isinstance(stack[-1], (type(None), compat_str))
elif opcode == 147: # decrement
value = stack.pop()
assert isinstance(value, int)
stack.append(value - 1)
elif opcode == 149: # typeof
value = stack.pop()
return {
_Undefined: 'undefined',
compat_str: 'String',
int: 'Number',
float: 'Number',
}[type(value)]
elif opcode == 160: # add elif opcode == 160: # add
value2 = stack.pop() value2 = stack.pop()
value1 = stack.pop() value1 = stack.pop()
@@ -574,16 +774,37 @@ class SWFInterpreter(object):
value1 = stack.pop() value1 = stack.pop()
res = value1 - value2 res = value1 - value2
stack.append(res) stack.append(res)
elif opcode == 162: # multiply
value2 = stack.pop()
value1 = stack.pop()
res = value1 * value2
stack.append(res)
elif opcode == 164: # modulo elif opcode == 164: # modulo
value2 = stack.pop() value2 = stack.pop()
value1 = stack.pop() value1 = stack.pop()
res = value1 % value2 res = value1 % value2
stack.append(res) stack.append(res)
elif opcode == 168: # bitand
value2 = stack.pop()
value1 = stack.pop()
assert isinstance(value1, int)
assert isinstance(value2, int)
res = value1 & value2
stack.append(res)
elif opcode == 171: # equals
value2 = stack.pop()
value1 = stack.pop()
result = value1 == value2
stack.append(result)
elif opcode == 175: # greaterequals elif opcode == 175: # greaterequals
value2 = stack.pop() value2 = stack.pop()
value1 = stack.pop() value1 = stack.pop()
result = value1 >= value2 result = value1 >= value2
stack.append(result) stack.append(result)
elif opcode == 192: # increment_i
value = stack.pop()
assert isinstance(value, int)
stack.append(value + 1)
elif opcode == 208: # getlocal_0 elif opcode == 208: # getlocal_0
stack.append(registers[0]) stack.append(registers[0])
elif opcode == 209: # getlocal_1 elif opcode == 209: # getlocal_1

View File

@@ -63,7 +63,7 @@ def preferredencoding():
""" """
try: try:
pref = locale.getpreferredencoding() pref = locale.getpreferredencoding()
u'TEST'.encode(pref) 'TEST'.encode(pref)
except: except:
pref = 'UTF-8' pref = 'UTF-8'
@@ -71,12 +71,25 @@ def preferredencoding():
def write_json_file(obj, fn): def write_json_file(obj, fn):
""" Encode obj as JSON and write it to fn, atomically """ """ Encode obj as JSON and write it to fn, atomically if possible """
fn = encodeFilename(fn)
if sys.version_info < (3, 0) and sys.platform != 'win32':
encoding = get_filesystem_encoding()
# os.path.basename returns a bytes object, but NamedTemporaryFile
# will fail if the filename contains non ascii characters unless we
# use a unicode object
path_basename = lambda f: os.path.basename(fn).decode(encoding)
# the same for os.path.dirname
path_dirname = lambda f: os.path.dirname(fn).decode(encoding)
else:
path_basename = os.path.basename
path_dirname = os.path.dirname
args = { args = {
'suffix': '.tmp', 'suffix': '.tmp',
'prefix': os.path.basename(fn) + '.', 'prefix': path_basename(fn) + '.',
'dir': os.path.dirname(fn), 'dir': path_dirname(fn),
'delete': False, 'delete': False,
} }
@@ -95,6 +108,13 @@ def write_json_file(obj, fn):
try: try:
with tf: with tf:
json.dump(obj, tf) json.dump(obj, tf)
if sys.platform == 'win32':
# Need to remove existing file on Windows, else os.rename raises
# WindowsError or FileExistsError.
try:
os.unlink(fn)
except OSError:
pass
os.rename(tf.name, fn) os.rename(tf.name, fn)
except: except:
try: try:
@@ -203,7 +223,7 @@ def sanitize_open(filename, open_mode):
It returns the tuple (stream, definitive_file_name). It returns the tuple (stream, definitive_file_name).
""" """
try: try:
if filename == u'-': if filename == '-':
if sys.platform == 'win32': if sys.platform == 'win32':
import msvcrt import msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
@@ -216,7 +236,7 @@ def sanitize_open(filename, open_mode):
# In case of error, try to remove win32 forbidden chars # In case of error, try to remove win32 forbidden chars
alt_filename = os.path.join( alt_filename = os.path.join(
re.sub(u'[/<>:"\\|\\\\?\\*]', u'#', path_part) re.sub('[/<>:"\\|\\\\?\\*]', '#', path_part)
for path_part in os.path.split(filename) for path_part in os.path.split(filename)
) )
if alt_filename == filename: if alt_filename == filename:
@@ -255,7 +275,7 @@ def sanitize_filename(s, restricted=False, is_id=False):
return '_' return '_'
return char return char
result = u''.join(map(replace_insane, s)) result = ''.join(map(replace_insane, s))
if not is_id: if not is_id:
while '__' in result: while '__' in result:
result = result.replace('__', '_') result = result.replace('__', '_')
@@ -285,15 +305,15 @@ def _htmlentity_transform(entity):
mobj = re.match(r'#(x?[0-9]+)', entity) mobj = re.match(r'#(x?[0-9]+)', entity)
if mobj is not None: if mobj is not None:
numstr = mobj.group(1) numstr = mobj.group(1)
if numstr.startswith(u'x'): if numstr.startswith('x'):
base = 16 base = 16
numstr = u'0%s' % numstr numstr = '0%s' % numstr
else: else:
base = 10 base = 10
return compat_chr(int(numstr, base)) return compat_chr(int(numstr, base))
# Unknown entity in name, return its literal representation # Unknown entity in name, return its literal representation
return (u'&%s;' % entity) return ('&%s;' % entity)
def unescapeHTML(s): def unescapeHTML(s):
@@ -317,7 +337,7 @@ def encodeFilename(s, for_subprocess=False):
return s return s
if sys.platform == 'win32' and sys.getwindowsversion()[0] >= 5: if sys.platform == 'win32' and sys.getwindowsversion()[0] >= 5:
# Pass u'' directly to use Unicode APIs on Windows 2000 and up # Pass '' directly to use Unicode APIs on Windows 2000 and up
# (Detecting Windows NT 4 is tricky because 'major >= 4' would # (Detecting Windows NT 4 is tricky because 'major >= 4' would
# match Windows 9x series as well. Besides, NT 4 is obsolete.) # match Windows 9x series as well. Besides, NT 4 is obsolete.)
if not for_subprocess: if not for_subprocess:
@@ -412,9 +432,9 @@ class ExtractorError(Exception):
if video_id is not None: if video_id is not None:
msg = video_id + ': ' + msg msg = video_id + ': ' + msg
if cause: if cause:
msg += u' (caused by %r)' % cause msg += ' (caused by %r)' % cause
if not expected: if not expected:
msg = msg + u'; please report this issue on https://yt-dl.org/bug . Be sure to call youtube-dl with the --verbose flag and include its complete output. Make sure you are using the latest version; type youtube-dl -U to update.' msg = msg + '; please report this issue on https://yt-dl.org/bug . Be sure to call youtube-dl with the --verbose flag and include its complete output. Make sure you are using the latest version; type youtube-dl -U to update.'
super(ExtractorError, self).__init__(msg) super(ExtractorError, self).__init__(msg)
self.traceback = tb self.traceback = tb
@@ -425,7 +445,7 @@ class ExtractorError(Exception):
def format_traceback(self): def format_traceback(self):
if self.traceback is None: if self.traceback is None:
return None return None
return u''.join(traceback.format_tb(self.traceback)) return ''.join(traceback.format_tb(self.traceback))
class RegexNotFoundError(ExtractorError): class RegexNotFoundError(ExtractorError):
@@ -653,17 +673,17 @@ def unified_strdate(date_str):
upload_date = datetime.datetime(*timetuple[:6]).strftime('%Y%m%d') upload_date = datetime.datetime(*timetuple[:6]).strftime('%Y%m%d')
return upload_date return upload_date
def determine_ext(url, default_ext=u'unknown_video'): def determine_ext(url, default_ext='unknown_video'):
if url is None: if url is None:
return default_ext return default_ext
guess = url.partition(u'?')[0].rpartition(u'.')[2] guess = url.partition('?')[0].rpartition('.')[2]
if re.match(r'^[A-Za-z0-9]+$', guess): if re.match(r'^[A-Za-z0-9]+$', guess):
return guess return guess
else: else:
return default_ext return default_ext
def subtitles_filename(filename, sub_lang, sub_format): def subtitles_filename(filename, sub_lang, sub_format):
return filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format return filename.rsplit('.', 1)[0] + '.' + sub_lang + '.' + sub_format
def date_from_str(date_str): def date_from_str(date_str):
""" """
@@ -955,7 +975,7 @@ def shell_quote(args):
# We may get a filename encoded with 'encodeFilename' # We may get a filename encoded with 'encodeFilename'
a = a.decode(encoding) a = a.decode(encoding)
quoted_args.append(pipes.quote(a)) quoted_args.append(pipes.quote(a))
return u' '.join(quoted_args) return ' '.join(quoted_args)
def takewhile_inclusive(pred, seq): def takewhile_inclusive(pred, seq):
@@ -971,31 +991,31 @@ def smuggle_url(url, data):
""" Pass additional data in a URL for internal use. """ """ Pass additional data in a URL for internal use. """
sdata = compat_urllib_parse.urlencode( sdata = compat_urllib_parse.urlencode(
{u'__youtubedl_smuggle': json.dumps(data)}) {'__youtubedl_smuggle': json.dumps(data)})
return url + u'#' + sdata return url + '#' + sdata
def unsmuggle_url(smug_url, default=None): def unsmuggle_url(smug_url, default=None):
if not '#__youtubedl_smuggle' in smug_url: if not '#__youtubedl_smuggle' in smug_url:
return smug_url, default return smug_url, default
url, _, sdata = smug_url.rpartition(u'#') url, _, sdata = smug_url.rpartition('#')
jsond = compat_parse_qs(sdata)[u'__youtubedl_smuggle'][0] jsond = compat_parse_qs(sdata)['__youtubedl_smuggle'][0]
data = json.loads(jsond) data = json.loads(jsond)
return url, data return url, data
def format_bytes(bytes): def format_bytes(bytes):
if bytes is None: if bytes is None:
return u'N/A' return 'N/A'
if type(bytes) is str: if type(bytes) is str:
bytes = float(bytes) bytes = float(bytes)
if bytes == 0.0: if bytes == 0.0:
exponent = 0 exponent = 0
else: else:
exponent = int(math.log(bytes, 1024.0)) exponent = int(math.log(bytes, 1024.0))
suffix = [u'B', u'KiB', u'MiB', u'GiB', u'TiB', u'PiB', u'EiB', u'ZiB', u'YiB'][exponent] suffix = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'][exponent]
converted = float(bytes) / float(1024 ** exponent) converted = float(bytes) / float(1024 ** exponent)
return u'%.2f%s' % (converted, suffix) return '%.2f%s' % (converted, suffix)
def get_term_width(): def get_term_width():
@@ -1018,8 +1038,8 @@ def month_by_name(name):
""" Return the number of a month by (locale-independently) English name """ """ Return the number of a month by (locale-independently) English name """
ENGLISH_NAMES = [ ENGLISH_NAMES = [
u'January', u'February', u'March', u'April', u'May', u'June', 'January', 'February', 'March', 'April', 'May', 'June',
u'July', u'August', u'September', u'October', u'November', u'December'] 'July', 'August', 'September', 'October', 'November', 'December']
try: try:
return ENGLISH_NAMES.index(name) + 1 return ENGLISH_NAMES.index(name) + 1
except ValueError: except ValueError:
@@ -1030,7 +1050,7 @@ def fix_xml_ampersands(xml_str):
"""Replace all the '&' by '&amp;' in XML""" """Replace all the '&' by '&amp;' in XML"""
return re.sub( return re.sub(
r'&(?!amp;|lt;|gt;|apos;|quot;|#x[0-9a-fA-F]{,4};|#[0-9]{,4};)', r'&(?!amp;|lt;|gt;|apos;|quot;|#x[0-9a-fA-F]{,4};|#[0-9]{,4};)',
u'&amp;', '&amp;',
xml_str) xml_str)
@@ -1063,7 +1083,7 @@ def remove_end(s, end):
def url_basename(url): def url_basename(url):
path = compat_urlparse.urlparse(url).path path = compat_urlparse.urlparse(url).path
return path.strip(u'/').split(u'/')[-1] return path.strip('/').split('/')[-1]
class HEADRequest(compat_urllib_request.Request): class HEADRequest(compat_urllib_request.Request):
@@ -1088,7 +1108,7 @@ def str_to_int(int_str):
""" A more relaxed version of int_or_none """ """ A more relaxed version of int_or_none """
if int_str is None: if int_str is None:
return None return None
int_str = re.sub(r'[,\.\+]', u'', int_str) int_str = re.sub(r'[,\.\+]', '', int_str)
return int(int_str) return int(int_str)
@@ -1103,7 +1123,12 @@ def parse_duration(s):
s = s.strip() s = s.strip()
m = re.match( m = re.match(
r'(?i)(?:(?:(?P<hours>[0-9]+)\s*(?:[:h]|hours?)\s*)?(?P<mins>[0-9]+)\s*(?:[:m]|mins?|minutes?)\s*)?(?P<secs>[0-9]+)(?P<ms>\.[0-9]+)?\s*(?:s|secs?|seconds?)?$', s) r'''(?ix)T?
(?:
(?:(?P<hours>[0-9]+)\s*(?:[:h]|hours?)\s*)?
(?P<mins>[0-9]+)\s*(?:[:m]|mins?|minutes?)\s*
)?
(?P<secs>[0-9]+)(?P<ms>\.[0-9]+)?\s*(?:s|secs?|seconds?)?$''', s)
if not m: if not m:
return None return None
res = int(m.group('secs')) res = int(m.group('secs'))
@@ -1118,7 +1143,7 @@ def parse_duration(s):
def prepend_extension(filename, ext): def prepend_extension(filename, ext):
name, real_ext = os.path.splitext(filename) name, real_ext = os.path.splitext(filename)
return u'{0}.{1}{2}'.format(name, ext, real_ext) return '{0}.{1}{2}'.format(name, ext, real_ext)
def check_executable(exe, args=[]): def check_executable(exe, args=[]):
@@ -1133,7 +1158,7 @@ def check_executable(exe, args=[]):
def get_exe_version(exe, args=['--version'], def get_exe_version(exe, args=['--version'],
version_re=r'version\s+([0-9._-a-zA-Z]+)', version_re=r'version\s+([0-9._-a-zA-Z]+)',
unrecognized=u'present'): unrecognized='present'):
""" Returns the version of the specified executable, """ Returns the version of the specified executable,
or False if the executable is not present """ or False if the executable is not present """
try: try:
@@ -1254,7 +1279,7 @@ def escape_url(url):
).geturl() ).geturl()
try: try:
struct.pack(u'!I', 0) struct.pack('!I', 0)
except TypeError: except TypeError:
# In Python 2.6 (and some 2.7 versions), struct requires a bytes argument # In Python 2.6 (and some 2.7 versions), struct requires a bytes argument
def struct_pack(spec, *args): def struct_pack(spec, *args):
@@ -1275,7 +1300,7 @@ def read_batch_urls(batch_fd):
def fixup(url): def fixup(url):
if not isinstance(url, compat_str): if not isinstance(url, compat_str):
url = url.decode('utf-8', 'replace') url = url.decode('utf-8', 'replace')
BOM_UTF8 = u'\xef\xbb\xbf' BOM_UTF8 = '\xef\xbb\xbf'
if url.startswith(BOM_UTF8): if url.startswith(BOM_UTF8):
url = url[len(BOM_UTF8):] url = url[len(BOM_UTF8):]
url = url.strip() url = url.strip()

View File

@@ -1,2 +1,2 @@
__version__ = '2014.11.14' __version__ = '2014.11.20'