From 6f56696ccf1a493458992141eb0d58ef20ad7100 Mon Sep 17 00:00:00 2001 From: Steven Almeroth Date: Mon, 21 Jul 2014 12:26:21 -0500 Subject: [PATCH 1/8] Allow cancellation of active download --- pafy/pafy.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pafy/pafy.py b/pafy/pafy.py index a86b9a2b..9ff30f37 100644 --- a/pafy/pafy.py +++ b/pafy/pafy.py @@ -588,6 +588,7 @@ def safeint(x): self._url = None self._rawurl = sm['url'] self._sig = sm['s'] if self.encrypted else sm.get("sig") + self._active = False if self.mediatype == "audio": self._dimensions = (0, 0) @@ -748,6 +749,12 @@ def get_filesize(self): return self._fsize + def cancel(self): + """ Cancel an active download. """ + if self._active: + self._active = False + return True + def download(self, filepath="", quiet=False, callback=lambda *x: None, meta=False): """ Download. Use quiet=True to supress output. Return filename. @@ -800,7 +807,9 @@ def download(self, filepath="", quiet=False, callback=lambda *x: None, response = resuming_opener.open(self.url) bytesdone = offset - while True: + self._active = True + + while self._active: chunk = response.read(chunksize) outfh.write(chunk) elapsed = time.time() - t0 @@ -821,8 +830,9 @@ def download(self, filepath="", quiet=False, callback=lambda *x: None, if callback: callback(total, *progress_stats) - os.rename(temp_filepath, filepath) - return filepath + if self._active: + os.rename(temp_filepath, filepath) + return filepath class Pafy(object): From 87011e1df410f6a6b086361aa23393da321122e1 Mon Sep 17 00:00:00 2001 From: np1 Date: Mon, 21 Jul 2014 23:55:15 +0100 Subject: [PATCH 2/8] Update docstring --- pafy/pafy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pafy/pafy.py b/pafy/pafy.py index a86b9a2b..7befd6d3 100644 --- a/pafy/pafy.py +++ b/pafy/pafy.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- """ -Python API for YouTube. +pafy.py + +Python library to retrieve YouTube content and metadata https://github.com/np1/pafy From e5ccead82551ddfa234f7a48eff5b573ade2df0b Mon Sep 17 00:00:00 2001 From: np1 Date: Mon, 21 Jul 2014 23:56:27 +0100 Subject: [PATCH 3/8] use os.path.abspath --- docs-sphinx/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-sphinx/conf.py b/docs-sphinx/conf.py index e88683ca..1198b1d9 100644 --- a/docs-sphinx/conf.py +++ b/docs-sphinx/conf.py @@ -19,7 +19,7 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) -sys.path.insert(0, "../pafy") +sys.path.insert(0, os.path.abspath("../pafy")) # -- General configuration ------------------------------------------------ From a2e9648bcbd8b6df5ff566c6994410c92c02846e Mon Sep 17 00:00:00 2001 From: np1 Date: Tue, 22 Jul 2014 00:11:17 +0100 Subject: [PATCH 4/8] Return temp filename for cancelled download --- pafy/pafy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pafy/pafy.py b/pafy/pafy.py index c163738a..367ec726 100644 --- a/pafy/pafy.py +++ b/pafy/pafy.py @@ -836,6 +836,10 @@ def download(self, filepath="", quiet=False, callback=lambda *x: None, os.rename(temp_filepath, filepath) return filepath + else: + outfh.close() + return temp_filepath + class Pafy(object): From a71708c145bc6c45b1ba3e315638493081fe7ac1 Mon Sep 17 00:00:00 2001 From: np1 Date: Thu, 24 Jul 2014 19:42:13 +0100 Subject: [PATCH 5/8] remove thumbnail property test --- tests/test.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test.py b/tests/test.py index 6ded9df4..085e3b05 100644 --- a/tests/test.py +++ b/tests/test.py @@ -165,9 +165,10 @@ def test_video_properties(self): video['description']) for prop in self.properties: - paf_prop = getattr(video['pafy'], prop) - exp_prop = video[prop] - self.assertEqual(paf_prop, exp_prop) + if prop != "thumb": + paf_prop = getattr(video['pafy'], prop) + exp_prop = video[prop] + self.assertEqual(paf_prop, exp_prop) self.assertNotEqual(video.__repr__(), None) From 4ea7a285ceaeadbd995cd111acf715fc3f191a1c Mon Sep 17 00:00:00 2001 From: np1 Date: Thu, 24 Jul 2014 19:42:51 +0100 Subject: [PATCH 6/8] Updated signature functions --- pafy/pafy.py | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/pafy/pafy.py b/pafy/pafy.py index 367ec726..7f628f38 100644 --- a/pafy/pafy.py +++ b/pafy/pafy.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -pafy.py +pafy.py. Python library to retrieve YouTube content and metadata @@ -309,7 +309,7 @@ def _get_other_funcs(primary_func, js): call = re.compile(r'(?:[$\w+])=([$\w]+)\(((?:\w+,?)+)\)$') # dot notation function call; X=O.F(A,B,C..) - dotcall = re.compile(r'(?:[$\w+])=([$\w]+)\.([$\w]+)\(((?:\w+,?)+)\)$') + dotcall = re.compile(r'(?:[$\w+]=)?([$\w]+)\.([$\w]+)\(((?:\w+,?)+)\)$') functions = {} @@ -382,6 +382,7 @@ def _get_func_from_call(caller, name, arguments, js_url): def _solve(f, js_url): """Solve basic javascript function. Return solution value (str). """ # pylint: disable=R0914,R0912 + resv = "slice|splice|reverse" patterns = { 'split_or_join': r'(\w+)=\1\.(?:split|join)\(""\)$', 'func_call': r'(\w+)=([$\w]+)\(((?:\w+,?)+)\)$', @@ -390,10 +391,15 @@ def _solve(f, js_url): 'x3': r'(\w+)\[(\w+)\]=(\w+)$', 'return': r'return (\w+)(\.join\(""\))?$', 'reverse': r'(\w+)=(\w+)\.reverse\(\)$', - 'return_reverse': r'return (\w+)\.reverse()', + 'reverse_noass': r'(\w+)\.reverse\(\)$', + 'return_reverse': r'return (\w+)\.reverse()$', 'slice': r'(\w+)=(\w+)\.slice\((\w+)\)$', + 'splice_noass': r'([$\w]+)\.splice\(([$\w]+)\,([$\w]+)\)$', 'return_slice': r'return (\w+)\.slice\((\w+)\)$', - 'func_call_dict': r'(\w)=([$\w]+)\.(?!slice)([$\w]+)\(((?:\w+,?)+)\)$' + 'func_call_dict': r'(\w)=([$\w]+)\.(?!%s)([$\w]+)\(((?:\w+,?)+)\)$' + % resv, + 'func_call_dict_noret': r'([$\w]+)\.(?!%s)([$\w]+)\(((?:\w+,?)+)\)$' + % resv } parts = f['body'].split(";") @@ -420,6 +426,19 @@ def _solve(f, js_url): newfunc = _get_func_from_call(f, funcname, args.split(","), js_url) f['args'][lhs] = _solve(newfunc, js_url) + elif name == "func_call_dict_noret": + dic, key, args = m.group(1, 2, 3) + funcname = "%s.%s" % (dic, key) + newfunc = _get_func_from_call(f, funcname, args.split(","), js_url) + _solve.expect_noret = True + changed_args = _solve(newfunc, js_url) + _solve.expect_noret = False + + for arg in f['args']: + + if arg in changed_args: + f['args'][arg] = changed_args[arg] + elif name == "func_call": lhs, funcname, args = m.group(1, 2, 3) newfunc = _get_func_from_call(f, funcname, args.split(","), js_url) @@ -447,6 +466,13 @@ def _solve(f, js_url): elif name == "reverse": f['args'][m.group(1)] = _getval(m.group(2), f['args'])[::-1] + elif name == "reverse_noass": + f['args'][m.group(1)] = _getval(m.group(1), f['args'])[::-1] + + elif name == "splice_noass": + a, b, c = [_getval(x, f['args']) for x in m.group(1, 2, 3)] + f['args'][m.group(1)] = a[:b] + a[b + c:] + elif name == "return_reverse": return f['args'][m.group(1)][::-1] @@ -458,7 +484,12 @@ def _solve(f, js_url): a, b, c = [_getval(x, f['args']) for x in m.group(1, 2, 3)] f['args'][m.group(1)] = b[c:] - raise IOError("Processed js funtion parts without finding return") + if _solve.expect_noret: + return f['args'] + + else: + raise IOError("Processed js funtion parts without finding return") + def _decodesig(sig, js_url): @@ -764,8 +795,8 @@ def download(self, filepath="", quiet=False, callback=lambda *x: None, Use meta=True to append video id and itag to generated filename """ - # pylint: disable=R0914 - # Too many local variables - who cares? + # pylint: disable=R0912,R0914 + # Too many branches, too many local vars savedir = filename = "" if filepath and os.path.isdir(filepath): From 9cb07db0d33b77e9a7456f7a0e19f122d1152fe3 Mon Sep 17 00:00:00 2001 From: np1 Date: Thu, 24 Jul 2014 19:46:53 +0100 Subject: [PATCH 7/8] Added changes --- CHANGELOG | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d5f6a088..25b7349b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +24 July 2014 +Version 0.3.58 + +[Feature] - Added cancel download function (stav) (#47) +[Bugfix] - Update signature decryption to work with YouTube changes (#48) + +------------------------------------------------------------------------------- + 16 July 2014 Version 0.3.56 From a024107fa0783bc9bbf5063f7a1fb1dc9c54fd53 Mon Sep 17 00:00:00 2001 From: np1 Date: Thu, 24 Jul 2014 19:48:11 +0100 Subject: [PATCH 8/8] Update version number to 0.3.58 --- docs-sphinx/conf.py | 4 ++-- pafy/pafy.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs-sphinx/conf.py b/docs-sphinx/conf.py index 1198b1d9..97444a71 100644 --- a/docs-sphinx/conf.py +++ b/docs-sphinx/conf.py @@ -55,9 +55,9 @@ # built documents. # # The short X.Y version. -version = '0.3.56' +version = '0.3.58' # The full version, including alpha/beta/rc tags. -release = '0.3.56' +release = '0.3.58' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pafy/pafy.py b/pafy/pafy.py index 7f628f38..5db8fa8a 100644 --- a/pafy/pafy.py +++ b/pafy/pafy.py @@ -27,7 +27,7 @@ from __future__ import unicode_literals -__version__ = "0.3.56" +__version__ = "0.3.58" __author__ = "nagev" __license__ = "GPLv3" diff --git a/setup.py b/setup.py index acd3b95e..239be791 100755 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ name='Pafy', packages=['pafy'], scripts=['scripts/ytdl'], - version='0.3.56', + version='0.3.58', description="Retrieve YouTube content and metadata", keywords=["Pafy", "API", "YouTube", "youtube", "download", "video"], author="nagev",