diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | test/test_utils.py | 1 | ||||
| -rwxr-xr-x | youtube_dl/YoutubeDL.py | 1 | ||||
| -rw-r--r-- | youtube_dl/__init__.py | 14 | ||||
| -rw-r--r-- | youtube_dl/extractor/common.py | 8 | ||||
| -rw-r--r-- | youtube_dl/extractor/rtlnl.py | 6 | ||||
| -rw-r--r-- | youtube_dl/extractor/vimeo.py | 15 | ||||
| -rw-r--r-- | youtube_dl/extractor/wat.py | 1 | ||||
| -rw-r--r-- | youtube_dl/postprocessor/__init__.py | 2 | ||||
| -rw-r--r-- | youtube_dl/postprocessor/execafterdownload.py | 31 | ||||
| -rw-r--r-- | youtube_dl/utils.py | 11 | ||||
| -rw-r--r-- | youtube_dl/version.py | 2 | 
12 files changed, 86 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore index 37b2fa8d3..b8128fab1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,6 @@ updates_key.pem  *.m4a  *.m4v  *.part +*.swp  test/testdata  .tox diff --git a/test/test_utils.py b/test/test_utils.py index e26cc5b0c..0953db371 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -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( diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 577642377..98639e004 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -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 diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index a96bf9b5c..b15695053 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -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) diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index 4d5b48167..69d5f687c 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -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')),              }) diff --git a/youtube_dl/extractor/rtlnl.py b/youtube_dl/extractor/rtlnl.py index 190c8f226..2d9511d5e 100644 --- a/youtube_dl/extractor/rtlnl.py +++ b/youtube_dl/extractor/rtlnl.py @@ -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')),          } diff --git a/youtube_dl/extractor/vimeo.py b/youtube_dl/extractor/vimeo.py index 11c7d7e81..55f6cd0d8 100644 --- a/youtube_dl/extractor/vimeo.py +++ b/youtube_dl/extractor/vimeo.py @@ -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) diff --git a/youtube_dl/extractor/wat.py b/youtube_dl/extractor/wat.py index 76744215f..6462d2e81 100644 --- a/youtube_dl/extractor/wat.py +++ b/youtube_dl/extractor/wat.py @@ -7,7 +7,6 @@ import hashlib  from .common import InfoExtractor  from ..utils import ( -    ExtractorError,      unified_strdate,  ) diff --git a/youtube_dl/postprocessor/__init__.py b/youtube_dl/postprocessor/__init__.py index 08e6ddd00..15aa0daa9 100644 --- a/youtube_dl/postprocessor/__init__.py +++ b/youtube_dl/postprocessor/__init__.py @@ -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 index 000000000..08419a3d4 --- /dev/null +++ b/youtube_dl/postprocessor/execafterdownload.py @@ -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 + diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 8095400d0..53977cd2a 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -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 diff --git a/youtube_dl/version.py b/youtube_dl/version.py index 5d892c2ff..68205af0f 100644 --- a/youtube_dl/version.py +++ b/youtube_dl/version.py @@ -1,2 +1,2 @@ -__version__ = '2014.08.24.6' +__version__ = '2014.08.25'  | 
