Merge remote-tracking branch 'JGjorgji/fix-leading-zeroes'
authorPhilipp Hagemeister <phihag@phihag.de>
Mon, 25 Aug 2014 11:59:19 +0000 (13:59 +0200)
committerPhilipp Hagemeister <phihag@phihag.de>
Mon, 25 Aug 2014 11:59:19 +0000 (13:59 +0200)
12 files changed:
.gitignore
test/test_utils.py
youtube_dl/YoutubeDL.py
youtube_dl/__init__.py
youtube_dl/extractor/common.py
youtube_dl/extractor/rtlnl.py
youtube_dl/extractor/vimeo.py
youtube_dl/extractor/wat.py
youtube_dl/postprocessor/__init__.py
youtube_dl/postprocessor/execafterdownload.py [new file with mode: 0644]
youtube_dl/utils.py
youtube_dl/version.py

index 37b2fa8d3b3a914a55592af8dace119ecc23a824..b8128fab17f0599c5aac3fd1313d8caf32cf535b 100644 (file)
@@ -26,5 +26,6 @@ updates_key.pem
 *.m4a
 *.m4v
 *.part
+*.swp
 test/testdata
 .tox
index e26cc5b0cc0e9df46fc00ae9084930ed384aff36..0953db3719b004de0751427e9b83962fd2fc8f84 100644 (file)
@@ -219,6 +219,7 @@ class TestUtil(unittest.TestCase):
         self.assertEqual(parse_duration('0h0m0s'), 0)
         self.assertEqual(parse_duration('0m0s'), 0)
         self.assertEqual(parse_duration('0s'), 0)
+        self.assertEqual(parse_duration('01:02:03.05'), 3723.05)
 
     def test_fix_xml_ampersands(self):
         self.assertEqual(
index 5776423775392a09dc6376b48dca1ab04a5f5ceb..98639e004c4502bef367eb23b6180269f29e54f7 100755 (executable)
@@ -172,6 +172,7 @@ class YoutubeDL(object):
     The following options are used by the post processors:
     prefer_ffmpeg:     If True, use ffmpeg instead of avconv if both are available,
                        otherwise prefer avconv.
+    exec_cmd:          Arbitrary command to run after downloading
     """
 
     params = None
index a96bf9b5cd978cdc09e8cec4596cad473adc809a..b1569505369a3669f46040d29ea1cd0277130460 100644 (file)
@@ -73,6 +73,7 @@ __authors__  = (
     'Erik Johnson',
     'Keith Beckman',
     'Ole Ernst',
+    'Aaron McDaniel (mcd1992)',
 )
 
 __license__ = 'Public Domain'
@@ -119,6 +120,7 @@ from .postprocessor import (
     FFmpegExtractAudioPP,
     FFmpegEmbedSubtitlePP,
     XAttrMetadataPP,
+    ExecAfterDownloadPP,
 )
 
 
@@ -550,7 +552,9 @@ def parseOpts(overrideArguments=None):
         help='Prefer avconv over ffmpeg for running the postprocessors (default)')
     postproc.add_option('--prefer-ffmpeg', action='store_true', dest='prefer_ffmpeg',
         help='Prefer ffmpeg over avconv for running the postprocessors')
-
+    postproc.add_option(
+        '--exec', metavar='CMD', dest='exec_cmd',
+        help='Execute a command on the file after downloading, similar to find\'s -exec syntax. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'' )
 
     parser.add_option_group(general)
     parser.add_option_group(selection)
@@ -831,6 +835,7 @@ def _real_main(argv=None):
         'default_search': opts.default_search,
         'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
         'encoding': opts.encoding,
+        'exec_cmd': opts.exec_cmd,
     }
 
     with YoutubeDL(ydl_opts) as ydl:
@@ -854,6 +859,13 @@ def _real_main(argv=None):
                 ydl.add_post_processor(FFmpegAudioFixPP())
             ydl.add_post_processor(AtomicParsleyPP())
 
+
+        # Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
+        # So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
+        if opts.exec_cmd:
+            ydl.add_post_processor(ExecAfterDownloadPP(
+                verboseOutput=opts.verbose, exec_cmd=opts.exec_cmd))
+
         # Update version
         if opts.update_self:
             update_self(ydl.to_screen, opts.verbose)
index 4d5b48167cb604b6679b6e524b5420efb1b3b9c5..69d5f687cbcfc913c1ee8ae3d8bc0a530b7202f0 100644 (file)
@@ -620,11 +620,15 @@ class InfoExtractor(object):
             'Unable to download f4m manifest')
 
         formats = []
-        for media_el in manifest.findall('{http://ns.adobe.com/f4m/1.0}media'):
+        media_nodes = manifest.findall('{http://ns.adobe.com/f4m/1.0}media')
+        for i, media_el in enumerate(media_nodes):
+            tbr = int_or_none(media_el.attrib.get('bitrate'))
+            format_id = 'f4m-%d' % (i if tbr is None else tbr)
             formats.append({
+                'format_id': format_id,
                 'url': manifest_url,
                 'ext': 'flv',
-                'tbr': int_or_none(media_el.attrib.get('bitrate')),
+                'tbr': tbr,
                 'width': int_or_none(media_el.attrib.get('width')),
                 'height': int_or_none(media_el.attrib.get('height')),
             })
index 190c8f226096319f5f1634c4595ef86f36e6e611..2d9511d5ea21a78605503abb2d01dc2df913f8d7 100644 (file)
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
 import re
 
 from .common import InfoExtractor
+from ..utils import parse_duration
 
 
 class RtlXlIE(InfoExtractor):
@@ -20,6 +21,7 @@ class RtlXlIE(InfoExtractor):
                 'onze mobiele apps.',
             'timestamp': 1408051800,
             'upload_date': '20140814',
+            'duration': 576.880,
         },
         'params': {
             # We download the first bytes of the first fragment, it can't be
@@ -35,6 +37,7 @@ class RtlXlIE(InfoExtractor):
         info = self._download_json(
             'http://www.rtl.nl/system/s4m/vfd/version=2/uuid=%s/fmt=flash/' % uuid,
             uuid)
+
         material = info['material'][0]
         episode_info = info['episodes'][0]
 
@@ -44,8 +47,9 @@ class RtlXlIE(InfoExtractor):
 
         return {
             'id': uuid,
-            'title': '%s - %s' % (progname, subtitle), 
+            'title': '%s - %s' % (progname, subtitle),
             'formats': self._extract_f4m_formats(f4m_url, uuid),
             'timestamp': material['original_date'],
             'description': episode_info['synopsis'],
+            'duration': parse_duration(material.get('duration')),
         }
index 11c7d7e817f1f0839604311534dd918b5b5e4fee..55f6cd0d8e1d9e7de2fd49f0e123b23e47496f13 100644 (file)
@@ -151,6 +151,19 @@ class VimeoIE(VimeoBaseInfoExtractor, SubtitlesInfoExtractor):
                 'duration': 62,
             }
         },
+        {
+            'note': 'video player needs Referer',
+            'url': 'http://vimeo.com/user22258446/review/91613211/13f927e053',
+            'md5': '6295fdab8f4bf6a002d058b2c6dce276',
+            'info_dict': {
+                'id': '91613211',
+                'ext': 'mp4',
+                'title': 'Death by dogma versus assembling agile - Sander Hoogendoorn',
+                'uploader': 'DevWeek Events',
+                'duration': 2773,
+                'thumbnail': 're:^https?://.*\.jpg$',
+            }
+        }
     ]
 
     @classmethod
@@ -205,6 +218,8 @@ class VimeoIE(VimeoBaseInfoExtractor, SubtitlesInfoExtractor):
         if data is not None:
             headers = headers.copy()
             headers.update(data)
+        if 'Referer' not in headers:
+            headers['Referer'] = url
 
         # Extract ID from URL
         mobj = re.match(self._VALID_URL, url)
index 76744215f2cf2e86316393e45d4a9b31fb68e8ee..6462d2e8148cce91fd4f3a9cab7b5d4d17c429f5 100644 (file)
@@ -7,7 +7,6 @@ import hashlib
 
 from .common import InfoExtractor
 from ..utils import (
-    ExtractorError,
     unified_strdate,
 )
 
index 08e6ddd00cbfe5691fb14943d6b2217748e96398..15aa0daa9b7b69b5710096ecb099f62e6ab51f3d 100644 (file)
@@ -9,6 +9,7 @@ from .ffmpeg import (
     FFmpegEmbedSubtitlePP,
 )
 from .xattrpp import XAttrMetadataPP
+from .execafterdownload import ExecAfterDownloadPP
 
 __all__ = [
     'AtomicParsleyPP',
@@ -19,4 +20,5 @@ __all__ = [
     'FFmpegExtractAudioPP',
     'FFmpegEmbedSubtitlePP',
     'XAttrMetadataPP',
+    'ExecAfterDownloadPP',
 ]
diff --git a/youtube_dl/postprocessor/execafterdownload.py b/youtube_dl/postprocessor/execafterdownload.py
new file mode 100644 (file)
index 0000000..08419a3
--- /dev/null
@@ -0,0 +1,31 @@
+from __future__ import unicode_literals
+
+import subprocess
+
+from .common import PostProcessor
+from ..utils import (
+    shlex_quote,
+    PostProcessingError,
+)
+
+
+class ExecAfterDownloadPP(PostProcessor):
+    def __init__(self, downloader=None, verboseOutput=None, exec_cmd=None):
+        self.verboseOutput = verboseOutput
+        self.exec_cmd = exec_cmd
+
+    def run(self, information):
+        cmd = self.exec_cmd
+        if not '{}' in cmd:
+            cmd += ' {}'
+
+        cmd = cmd.replace('{}', shlex_quote(information['filepath']))
+
+        self._downloader.to_screen("[exec] Executing command: %s" % cmd)
+        retCode = subprocess.call(cmd, shell=True)
+        if retCode != 0:
+            raise PostProcessingError(
+                'Command returned error code %d' % retCode)
+
+        return None, information  # by default, keep file and do nothing
+
index 8095400d03dee6731b2bd24951597b74a90f1991..53977cd2a35c0c829b40cb12e3d61b0efac091f3 100644 (file)
@@ -192,6 +192,13 @@ try:
 except ImportError:  # Python 2.6
     from xml.parsers.expat import ExpatError as compat_xml_parse_error
 
+try:
+    from shlex import quote as shlex_quote
+except ImportError:  # Python < 3.3
+    def shlex_quote(s):
+        return "'" + s.replace("'", "'\"'\"'") + "'"
+
+
 def compat_ord(c):
     if type(c) is int: return c
     else: return ord(c)
@@ -1331,7 +1338,7 @@ def parse_duration(s):
         return None
 
     m = re.match(
-        r'(?:(?:(?P<hours>[0-9]+)[:h])?(?P<mins>[0-9]+)[:m])?(?P<secs>[0-9]+)s?(?::[0-9]+)?$', s)
+        r'(?:(?:(?P<hours>[0-9]+)[:h])?(?P<mins>[0-9]+)[:m])?(?P<secs>[0-9]+)s?(?::[0-9]+)?(?P<ms>\.[0-9]+)?$', s)
     if not m:
         return None
     res = int(m.group('secs'))
@@ -1339,6 +1346,8 @@ def parse_duration(s):
         res += int(m.group('mins')) * 60
         if m.group('hours'):
             res += int(m.group('hours')) * 60 * 60
+    if m.group('ms'):
+        res += float(m.group('ms'))
     return res
 
 
index 5d892c2ff9e15276652b4dea80b197c81c5f8ce3..68205af0fcb1f12c2ee8ea99977c263cfef6c91b 100644 (file)
@@ -1,2 +1,2 @@
 
-__version__ = '2014.08.24.6'
+__version__ = '2014.08.25'