diff options
| -rw-r--r-- | test/test_YoutubeDL.py | 46 | ||||
| -rw-r--r-- | test/test_postprocessors.py | 17 | ||||
| -rw-r--r-- | test/test_unicode_literals.py | 11 | ||||
| -rw-r--r-- | tox.ini | 7 | ||||
| -rw-r--r-- | youtube_dl/__init__.py | 5 | ||||
| -rw-r--r-- | youtube_dl/extractor/aftenposten.py | 11 | ||||
| -rw-r--r-- | youtube_dl/options.py | 9 | ||||
| -rw-r--r-- | youtube_dl/postprocessor/__init__.py | 2 | ||||
| -rw-r--r-- | youtube_dl/postprocessor/ffmpeg.py | 6 | ||||
| -rw-r--r-- | youtube_dl/postprocessor/metadatafromtitle.py | 47 | 
10 files changed, 127 insertions, 34 deletions
| diff --git a/test/test_YoutubeDL.py b/test/test_YoutubeDL.py index 055e42555..db8a47d2d 100644 --- a/test/test_YoutubeDL.py +++ b/test/test_YoutubeDL.py @@ -15,6 +15,8 @@ from youtube_dl import YoutubeDL  from youtube_dl.extractor import YoutubeIE  from youtube_dl.postprocessor.common import PostProcessor +TEST_URL = 'http://localhost/sample.mp4' +  class YDL(FakeYDL):      def __init__(self, *args, **kwargs): @@ -46,8 +48,8 @@ class TestFormatSelection(unittest.TestCase):          ydl = YDL()          ydl.params['prefer_free_formats'] = True          formats = [ -            {'ext': 'webm', 'height': 460, 'url': 'x'}, -            {'ext': 'mp4', 'height': 460, 'url': 'y'}, +            {'ext': 'webm', 'height': 460, 'url': TEST_URL}, +            {'ext': 'mp4', 'height': 460, 'url': TEST_URL},          ]          info_dict = _make_result(formats)          yie = YoutubeIE(ydl) @@ -60,8 +62,8 @@ class TestFormatSelection(unittest.TestCase):          ydl = YDL()          ydl.params['prefer_free_formats'] = True          formats = [ -            {'ext': 'webm', 'height': 720, 'url': 'a'}, -            {'ext': 'mp4', 'height': 1080, 'url': 'b'}, +            {'ext': 'webm', 'height': 720, 'url': TEST_URL}, +            {'ext': 'mp4', 'height': 1080, 'url': TEST_URL},          ]          info_dict['formats'] = formats          yie = YoutubeIE(ydl) @@ -74,9 +76,9 @@ class TestFormatSelection(unittest.TestCase):          ydl = YDL()          ydl.params['prefer_free_formats'] = False          formats = [ -            {'ext': 'webm', 'height': 720, 'url': '_'}, -            {'ext': 'mp4', 'height': 720, 'url': '_'}, -            {'ext': 'flv', 'height': 720, 'url': '_'}, +            {'ext': 'webm', 'height': 720, 'url': TEST_URL}, +            {'ext': 'mp4', 'height': 720, 'url': TEST_URL}, +            {'ext': 'flv', 'height': 720, 'url': TEST_URL},          ]          info_dict['formats'] = formats          yie = YoutubeIE(ydl) @@ -88,8 +90,8 @@ class TestFormatSelection(unittest.TestCase):          ydl = YDL()          ydl.params['prefer_free_formats'] = False          formats = [ -            {'ext': 'flv', 'height': 720, 'url': '_'}, -            {'ext': 'webm', 'height': 720, 'url': '_'}, +            {'ext': 'flv', 'height': 720, 'url': TEST_URL}, +            {'ext': 'webm', 'height': 720, 'url': TEST_URL},          ]          info_dict['formats'] = formats          yie = YoutubeIE(ydl) @@ -133,10 +135,10 @@ class TestFormatSelection(unittest.TestCase):      def test_format_selection(self):          formats = [ -            {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': '_'}, -            {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': '_'}, -            {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': '_'}, -            {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': '_'}, +            {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}, +            {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}, +            {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}, +            {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},          ]          info_dict = _make_result(formats) @@ -167,10 +169,10 @@ class TestFormatSelection(unittest.TestCase):      def test_format_selection_audio(self):          formats = [ -            {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': '_'}, -            {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': '_'}, -            {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': '_'}, -            {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': '_'}, +            {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}, +            {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}, +            {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}, +            {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},          ]          info_dict = _make_result(formats) @@ -185,8 +187,8 @@ class TestFormatSelection(unittest.TestCase):          self.assertEqual(downloaded['format_id'], 'audio-low')          formats = [ -            {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': '_'}, -            {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': '_'}, +            {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}, +            {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},          ]          info_dict = _make_result(formats) @@ -228,9 +230,9 @@ class TestFormatSelection(unittest.TestCase):      def test_format_selection_video(self):          formats = [ -            {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': '_'}, -            {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': '_'}, -            {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': '_'}, +            {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}, +            {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}, +            {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},          ]          info_dict = _make_result(formats) diff --git a/test/test_postprocessors.py b/test/test_postprocessors.py new file mode 100644 index 000000000..addb69d6f --- /dev/null +++ b/test/test_postprocessors.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +from __future__ import unicode_literals + +# Allow direct execution +import os +import sys +import unittest +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from youtube_dl.postprocessor import MetadataFromTitlePP + + +class TestMetadataFromTitle(unittest.TestCase): +    def test_format_to_regex(self): +        pp = MetadataFromTitlePP(None, '%(title)s - %(artist)s') +        self.assertEqual(pp._titleregex, '(?P<title>.+)\ \-\ (?P<artist>.+)') diff --git a/test/test_unicode_literals.py b/test/test_unicode_literals.py index 7f816698e..6c1b7ec91 100644 --- a/test/test_unicode_literals.py +++ b/test/test_unicode_literals.py @@ -17,13 +17,22 @@ IGNORED_FILES = [      'buildserver.py',  ] +IGNORED_DIRS = [ +    '.git', +    '.tox', +]  from test.helper import assertRegexpMatches  class TestUnicodeLiterals(unittest.TestCase):      def test_all_files(self): -        for dirpath, _, filenames in os.walk(rootDir): +        for dirpath, dirnames, filenames in os.walk(rootDir): +            for ignore_dir in IGNORED_DIRS: +                if ignore_dir in dirnames: +                    # If we remove the directory from dirnames os.walk won't +                    # recurse into it +                    dirnames.remove(ignore_dir)              for basename in filenames:                  if not basename.endswith('.py'):                      continue @@ -1,8 +1,11 @@  [tox] -envlist = py26,py27,py33 +envlist = py26,py27,py33,py34  [testenv]  deps =     nose     coverage -commands = nosetests --verbose {posargs:test}  # --with-coverage --cover-package=youtube_dl --cover-html +defaultargs = test --exclude test_download.py --exclude test_age_restriction.py +    --exclude test_subtitles.py --exclude test_write_annotations.py +    --exclude test_youtube_lists.py +commands = nosetests --verbose {posargs:{[testenv]defaultargs}}  # --with-coverage --cover-package=youtube_dl --cover-html                                                 # test.test_download:TestDownload.test_NowVideo diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index a08ddd670..852b2fc3d 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -213,6 +213,11 @@ def _real_main(argv=None):      # PostProcessors      postprocessors = []      # Add the metadata pp first, the other pps will copy it +    if opts.metafromtitle: +        postprocessors.append({ +            'key': 'MetadataFromTitle', +            'titleformat': opts.metafromtitle +        })      if opts.addmetadata:          postprocessors.append({'key': 'FFmpegMetadata'})      if opts.extractaudio: diff --git a/youtube_dl/extractor/aftenposten.py b/youtube_dl/extractor/aftenposten.py index 2b257ede7..e15c015fb 100644 --- a/youtube_dl/extractor/aftenposten.py +++ b/youtube_dl/extractor/aftenposten.py @@ -14,10 +14,10 @@ from ..utils import (  class AftenpostenIE(InfoExtractor): -    _VALID_URL = r'https?://(?:www\.)?aftenposten\.no/webtv/([^/]+/)*(?P<id>[^/]+)-\d+\.html' +    _VALID_URL = r'https?://(?:www\.)?aftenposten\.no/webtv/(?:#!/)?video/(?P<id>\d+)'      _TEST = { -        'url': 'http://www.aftenposten.no/webtv/serier-og-programmer/sweatshopenglish/TRAILER-SWEATSHOP---I-cant-take-any-more-7800835.html?paging=§ion=webtv_serierogprogrammer_sweatshop_sweatshopenglish', +        'url': 'http://www.aftenposten.no/webtv/#!/video/21039/trailer-sweatshop-i-can-t-take-any-more',          'md5': 'fd828cd29774a729bf4d4425fe192972',          'info_dict': {              'id': '21039', @@ -30,12 +30,7 @@ class AftenpostenIE(InfoExtractor):      }      def _real_extract(self, url): -        display_id = self._match_id(url) - -        webpage = self._download_webpage(url, display_id) - -        video_id = self._html_search_regex( -            r'data-xs-id="(\d+)"', webpage, 'video id') +        video_id = self._match_id(url)          data = self._download_xml(              'http://frontend.xstream.dk/ap/feed/video/?platform=web&id=%s' % video_id, video_id) diff --git a/youtube_dl/options.py b/youtube_dl/options.py index eefe008d5..4e6e47d6f 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -736,6 +736,15 @@ def parseOpts(overrideArguments=None):          action='store_true', dest='addmetadata', default=False,          help='write metadata to the video file')      postproc.add_option( +        '--metadata-from-title', +        metavar='FORMAT', dest='metafromtitle', +        help='parse additional metadata like song title / artist from the video title. ' +             'The format syntax is the same as --output, ' +             'the parsed parameters replace existing values. ' +             'Additional templates: %(album), %(artist). ' +             'Example: --metadata-from-title "%(artist)s - %(title)s" matches a title like ' +             '"Coldplay - Paradise"') +    postproc.add_option(          '--xattrs',          action='store_true', dest='xattrs', default=False,          help='write metadata to the video file\'s xattrs (using dublin core and xdg standards)') diff --git a/youtube_dl/postprocessor/__init__.py b/youtube_dl/postprocessor/__init__.py index 708df3dd4..f39acadce 100644 --- a/youtube_dl/postprocessor/__init__.py +++ b/youtube_dl/postprocessor/__init__.py @@ -15,6 +15,7 @@ from .ffmpeg import (  )  from .xattrpp import XAttrMetadataPP  from .execafterdownload import ExecAfterDownloadPP +from .metadatafromtitle import MetadataFromTitlePP  def get_postprocessor(key): @@ -34,5 +35,6 @@ __all__ = [      'FFmpegPostProcessor',      'FFmpegSubtitlesConvertorPP',      'FFmpegVideoConvertorPP', +    'MetadataFromTitlePP',      'XAttrMetadataPP',  ] diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py index 30094c2f3..b6f51cfd5 100644 --- a/youtube_dl/postprocessor/ffmpeg.py +++ b/youtube_dl/postprocessor/ffmpeg.py @@ -545,7 +545,9 @@ class FFmpegMetadataPP(FFmpegPostProcessor):              metadata['title'] = info['title']          if info.get('upload_date') is not None:              metadata['date'] = info['upload_date'] -        if info.get('uploader') is not None: +        if info.get('artist') is not None: +            metadata['artist'] = info['artist'] +        elif info.get('uploader') is not None:              metadata['artist'] = info['uploader']          elif info.get('uploader_id') is not None:              metadata['artist'] = info['uploader_id'] @@ -554,6 +556,8 @@ class FFmpegMetadataPP(FFmpegPostProcessor):              metadata['comment'] = info['description']          if info.get('webpage_url') is not None:              metadata['purl'] = info['webpage_url'] +        if info.get('album') is not None: +            metadata['album'] = info['album']          if not metadata:              self._downloader.to_screen('[ffmpeg] There isn\'t any metadata to add') diff --git a/youtube_dl/postprocessor/metadatafromtitle.py b/youtube_dl/postprocessor/metadatafromtitle.py new file mode 100644 index 000000000..5019433d3 --- /dev/null +++ b/youtube_dl/postprocessor/metadatafromtitle.py @@ -0,0 +1,47 @@ +from __future__ import unicode_literals + +import re + +from .common import PostProcessor +from ..utils import PostProcessingError + + +class MetadataFromTitlePPError(PostProcessingError): +    pass + + +class MetadataFromTitlePP(PostProcessor): +    def __init__(self, downloader, titleformat): +        super(MetadataFromTitlePP, self).__init__(downloader) +        self._titleformat = titleformat +        self._titleregex = self.format_to_regex(titleformat) + +    def format_to_regex(self, fmt): +        """ +        Converts a string like +           '%(title)s - %(artist)s' +        to a regex like +           '(?P<title>.+)\ \-\ (?P<artist>.+)' +        """ +        lastpos = 0 +        regex = "" +        # replace %(..)s with regex group and escape other string parts +        for match in re.finditer(r'%\((\w+)\)s', fmt): +            regex += re.escape(fmt[lastpos:match.start()]) +            regex += r'(?P<' + match.group(1) + '>.+)' +            lastpos = match.end() +        if lastpos < len(fmt): +            regex += re.escape(fmt[lastpos:len(fmt)]) +        return regex + +    def run(self, info): +        title = info['title'] +        match = re.match(self._titleregex, title) +        if match is None: +            raise MetadataFromTitlePPError('Could not interpret title of video as "%s"' % self._titleformat) +        for attribute, value in match.groupdict().items(): +            value = match.group(attribute) +            info[attribute] = value +            self._downloader.to_screen('[fromtitle] parsed ' + attribute + ': ' + value) + +        return True, info | 
