Compare commits
13 Commits
2014.12.13
...
2014.12.15
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e97a534f13 | ||
![]() |
8acb83d993 | ||
![]() |
71b640cc5b | ||
![]() |
4f026fafbc | ||
![]() |
39f594d660 | ||
![]() |
cae97f6521 | ||
![]() |
6cbf345f28 | ||
![]() |
a0ab29f8a1 | ||
![]() |
4a4fbfc967 | ||
![]() |
408b5839b1 | ||
![]() |
60620368d7 | ||
![]() |
4927de4f86 | ||
![]() |
bad5c1a303 |
16
Makefile
16
Makefile
@@ -35,13 +35,27 @@ install: youtube-dl youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtu
|
||||
install -d $(DESTDIR)$(SYSCONFDIR)/fish/completions
|
||||
install -m 644 youtube-dl.fish $(DESTDIR)$(SYSCONFDIR)/fish/completions/youtube-dl.fish
|
||||
|
||||
codetest:
|
||||
PYFLAKES_OUT=$$(pyflakes youtube_dl | grep -v youtube_dl/extractor/__init__.py); \
|
||||
if test -n "$$PYFLAKES_OUT"; then \
|
||||
echo "$$PYFLAKES_OUT"; \
|
||||
exit 1; \
|
||||
fi
|
||||
pep8 . --ignore E501
|
||||
|
||||
test:
|
||||
#nosetests --with-coverage --cover-package=youtube_dl --cover-html --verbose --processes 4 test
|
||||
nosetests --verbose test
|
||||
$(MAKE) codetest
|
||||
|
||||
ot: offlinetest
|
||||
|
||||
offlinetest: codetest
|
||||
nosetests --verbose test --exclude test_download --exclude test_age_restriction --exclude test_subtitles --exclude test_write_annotations
|
||||
|
||||
tar: youtube-dl.tar.gz
|
||||
|
||||
.PHONY: all clean install test tar bash-completion pypi-files zsh-completion fish-completion
|
||||
.PHONY: all clean install test tar bash-completion pypi-files zsh-completion fish-completion ot offlinetest codetest
|
||||
|
||||
pypi-files: youtube-dl.bash-completion README.txt youtube-dl.1 youtube-dl.fish
|
||||
|
||||
|
35
README.md
35
README.md
@@ -537,6 +537,41 @@ From a Python program, you can embed youtube-dl in a more powerful fashion, like
|
||||
|
||||
Most likely, you'll want to use various options. For a list of what can be done, have a look at [youtube_dl/YoutubeDL.py](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L69). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
|
||||
|
||||
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
||||
|
||||
|
||||
import youtube_dl
|
||||
|
||||
|
||||
class MyLogger(object):
|
||||
def debug(self, msg):
|
||||
pass
|
||||
|
||||
def warning(self, msg):
|
||||
pass
|
||||
|
||||
def error(self, msg):
|
||||
print(msg)
|
||||
|
||||
|
||||
def my_hook(d):
|
||||
if d['status'] == 'finished':
|
||||
print('Done downloading, now converting ...')
|
||||
|
||||
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
'postprocessors': [{
|
||||
'key': 'FFmpegExtractAudio',
|
||||
'preferredcodec': 'mp3',
|
||||
'preferredquality': '192',
|
||||
}],
|
||||
'logger': MyLogger(),
|
||||
'progress_hooks': [my_hook],
|
||||
}
|
||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
|
||||
|
||||
# BUGS
|
||||
|
||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
|
||||
|
@@ -16,39 +16,40 @@ import json
|
||||
import xml.etree.ElementTree
|
||||
|
||||
from youtube_dl.utils import (
|
||||
args_to_str,
|
||||
clean_html,
|
||||
DateRange,
|
||||
detect_exe_version,
|
||||
encodeFilename,
|
||||
escape_rfc3986,
|
||||
escape_url,
|
||||
find_xpath_attr,
|
||||
fix_xml_ampersands,
|
||||
orderedSet,
|
||||
OnDemandPagedList,
|
||||
InAdvancePagedList,
|
||||
intlist_to_bytes,
|
||||
js_to_json,
|
||||
limit_length,
|
||||
OnDemandPagedList,
|
||||
orderedSet,
|
||||
parse_duration,
|
||||
parse_filesize,
|
||||
parse_iso8601,
|
||||
read_batch_urls,
|
||||
sanitize_filename,
|
||||
shell_quote,
|
||||
smuggle_url,
|
||||
str_to_int,
|
||||
strip_jsonp,
|
||||
struct_unpack,
|
||||
timeconvert,
|
||||
unescapeHTML,
|
||||
unified_strdate,
|
||||
unsmuggle_url,
|
||||
uppercase_escape,
|
||||
url_basename,
|
||||
urlencode_postdata,
|
||||
xpath_with_ns,
|
||||
parse_iso8601,
|
||||
strip_jsonp,
|
||||
uppercase_escape,
|
||||
limit_length,
|
||||
escape_rfc3986,
|
||||
escape_url,
|
||||
js_to_json,
|
||||
intlist_to_bytes,
|
||||
args_to_str,
|
||||
parse_filesize,
|
||||
version_tuple,
|
||||
xpath_with_ns,
|
||||
)
|
||||
|
||||
|
||||
@@ -390,5 +391,16 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(version_tuple('10.23.344'), (10, 23, 344))
|
||||
self.assertEqual(version_tuple('10.1-6'), (10, 1, 6)) # avconv style
|
||||
|
||||
def test_detect_exe_version(self):
|
||||
self.assertEqual(detect_exe_version('''ffmpeg version 1.2.1
|
||||
built on May 27 2013 08:37:26 with gcc 4.7 (Debian 4.7.3-4)
|
||||
configuration: --prefix=/usr --extra-'''), '1.2.1')
|
||||
self.assertEqual(detect_exe_version('''ffmpeg version N-63176-g1fb4685
|
||||
built on May 15 2014 22:09:06 with gcc 4.8.2 (GCC)'''), 'N-63176-g1fb4685')
|
||||
self.assertEqual(detect_exe_version('''X server found. dri2 connection failed!
|
||||
Trying to open render node...
|
||||
Success at /dev/dri/renderD128.
|
||||
ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
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 test.helper import get_params
|
||||
|
||||
|
||||
import io
|
||||
import json
|
||||
|
||||
import youtube_dl.YoutubeDL
|
||||
import youtube_dl.extractor
|
||||
|
||||
|
||||
class YoutubeDL(youtube_dl.YoutubeDL):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(YoutubeDL, self).__init__(*args, **kwargs)
|
||||
self.to_stderr = self.to_screen
|
||||
|
||||
params = get_params({
|
||||
'writeinfojson': True,
|
||||
'skip_download': True,
|
||||
'writedescription': True,
|
||||
})
|
||||
|
||||
|
||||
TEST_ID = 'BaW_jenozKc'
|
||||
INFO_JSON_FILE = TEST_ID + '.info.json'
|
||||
DESCRIPTION_FILE = TEST_ID + '.mp4.description'
|
||||
EXPECTED_DESCRIPTION = '''test chars: "'/\ä↭𝕐
|
||||
test URL: https://github.com/rg3/youtube-dl/issues/1892
|
||||
|
||||
This is a test video for youtube-dl.
|
||||
|
||||
For more information, contact phihag@phihag.de .'''
|
||||
|
||||
|
||||
class TestInfoJSON(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Clear old files
|
||||
self.tearDown()
|
||||
|
||||
def test_info_json(self):
|
||||
ie = youtube_dl.extractor.YoutubeIE()
|
||||
ydl = YoutubeDL(params)
|
||||
ydl.add_info_extractor(ie)
|
||||
ydl.download([TEST_ID])
|
||||
self.assertTrue(os.path.exists(INFO_JSON_FILE))
|
||||
with io.open(INFO_JSON_FILE, 'r', encoding='utf-8') as jsonf:
|
||||
jd = json.load(jsonf)
|
||||
self.assertEqual(jd['upload_date'], '20121002')
|
||||
self.assertEqual(jd['description'], EXPECTED_DESCRIPTION)
|
||||
self.assertEqual(jd['id'], TEST_ID)
|
||||
self.assertEqual(jd['extractor'], 'youtube')
|
||||
self.assertEqual(jd['title'], '''youtube-dl test video "'/\ä↭𝕐''')
|
||||
self.assertEqual(jd['uploader'], 'Philipp Hagemeister')
|
||||
|
||||
self.assertTrue(os.path.exists(DESCRIPTION_FILE))
|
||||
with io.open(DESCRIPTION_FILE, 'r', encoding='utf-8') as descf:
|
||||
descr = descf.read()
|
||||
self.assertEqual(descr, EXPECTED_DESCRIPTION)
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists(INFO_JSON_FILE):
|
||||
os.remove(INFO_JSON_FILE)
|
||||
if os.path.exists(DESCRIPTION_FILE):
|
||||
os.remove(DESCRIPTION_FILE)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@@ -27,6 +27,7 @@ from .compat import (
|
||||
compat_cookiejar,
|
||||
compat_expanduser,
|
||||
compat_http_client,
|
||||
compat_kwargs,
|
||||
compat_str,
|
||||
compat_urllib_error,
|
||||
compat_urllib_request,
|
||||
@@ -67,7 +68,11 @@ from .cache import Cache
|
||||
from .extractor import get_info_extractor, gen_extractors
|
||||
from .downloader import get_suitable_downloader
|
||||
from .downloader.rtmp import rtmpdump_version
|
||||
from .postprocessor import FFmpegMergerPP, FFmpegPostProcessor
|
||||
from .postprocessor import (
|
||||
FFmpegMergerPP,
|
||||
FFmpegPostProcessor,
|
||||
get_postprocessor,
|
||||
)
|
||||
from .version import __version__
|
||||
|
||||
|
||||
@@ -176,6 +181,28 @@ class YoutubeDL(object):
|
||||
extract_flat: Do not resolve URLs, return the immediate result.
|
||||
Pass in 'in_playlist' to only show this behavior for
|
||||
playlist items.
|
||||
postprocessors: A list of dictionaries, each with an entry
|
||||
* key: The name of the postprocessor. See
|
||||
youtube_dl/postprocessor/__init__.py for a list.
|
||||
as well as any further keyword arguments for the
|
||||
postprocessor.
|
||||
progress_hooks: A list of functions that get called on download
|
||||
progress, with a dictionary with the entries
|
||||
* filename: The final filename
|
||||
* status: One of "downloading" and "finished"
|
||||
|
||||
The dict may also have some of the following entries:
|
||||
|
||||
* downloaded_bytes: Bytes on disk
|
||||
* total_bytes: Size of the whole file, None if unknown
|
||||
* tmpfilename: The filename we're currently writing to
|
||||
* eta: The estimated time in seconds, None if unknown
|
||||
* speed: The download speed in bytes/second, None if
|
||||
unknown
|
||||
|
||||
Progress hooks are guaranteed to be called at least once
|
||||
(with status "finished") if the download is successful.
|
||||
|
||||
|
||||
The following parameters are not used by YoutubeDL itself, they are used by
|
||||
the FileDownloader:
|
||||
@@ -256,6 +283,16 @@ class YoutubeDL(object):
|
||||
self.print_debug_header()
|
||||
self.add_default_info_extractors()
|
||||
|
||||
for pp_def_raw in self.params.get('postprocessors', []):
|
||||
pp_class = get_postprocessor(pp_def_raw['key'])
|
||||
pp_def = dict(pp_def_raw)
|
||||
del pp_def['key']
|
||||
pp = pp_class(self, **compat_kwargs(pp_def))
|
||||
self.add_post_processor(pp)
|
||||
|
||||
for ph in self.params.get('progress_hooks', []):
|
||||
self.add_progress_hook(ph)
|
||||
|
||||
def warn_if_short_id(self, argv):
|
||||
# short YouTube ID starting with dash?
|
||||
idxs = [
|
||||
|
@@ -40,16 +40,6 @@ from .downloader import (
|
||||
)
|
||||
from .extractor import gen_extractors
|
||||
from .YoutubeDL import YoutubeDL
|
||||
from .postprocessor import (
|
||||
AtomicParsleyPP,
|
||||
FFmpegAudioFixPP,
|
||||
FFmpegMetadataPP,
|
||||
FFmpegVideoConvertor,
|
||||
FFmpegExtractAudioPP,
|
||||
FFmpegEmbedSubtitlePP,
|
||||
XAttrMetadataPP,
|
||||
ExecAfterDownloadPP,
|
||||
)
|
||||
|
||||
|
||||
def _real_main(argv=None):
|
||||
@@ -212,6 +202,43 @@ def _real_main(argv=None):
|
||||
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
|
||||
|
||||
# PostProcessors
|
||||
postprocessors = []
|
||||
# Add the metadata pp first, the other pps will copy it
|
||||
if opts.addmetadata:
|
||||
postprocessors.append({'key': 'FFmpegMetadata'})
|
||||
if opts.extractaudio:
|
||||
postprocessors.append({
|
||||
'key': 'FFmpegExtractAudio',
|
||||
'preferredcodec': opts.audioformat,
|
||||
'preferredquality': opts.audioquality,
|
||||
'nopostoverwrites': opts.nopostoverwrites,
|
||||
})
|
||||
if opts.recodevideo:
|
||||
postprocessors.append({
|
||||
'key': 'FFmpegVideoConvertor',
|
||||
'preferedformat': opts.recodevideo,
|
||||
})
|
||||
if opts.embedsubtitles:
|
||||
postprocessors.append({
|
||||
'key': 'FFmpegEmbedSubtitle',
|
||||
'subtitlesformat': opts.subtitlesformat,
|
||||
})
|
||||
if opts.xattrs:
|
||||
postprocessors.append({'key': 'XAttrMetadata'})
|
||||
if opts.embedthumbnail:
|
||||
if not opts.addmetadata:
|
||||
postprocessors.append({'key': 'FFmpegAudioFix'})
|
||||
postprocessors.append({'key': 'AtomicParsley'})
|
||||
# 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:
|
||||
postprocessors.append({
|
||||
'key': 'ExecAfterDownload',
|
||||
'verboseOutput': opts.verbose,
|
||||
'exec_cmd': opts.exec_cmd,
|
||||
})
|
||||
|
||||
ydl_opts = {
|
||||
'usenetrc': opts.usenetrc,
|
||||
'username': opts.username,
|
||||
@@ -297,32 +324,10 @@ def _real_main(argv=None):
|
||||
'encoding': opts.encoding,
|
||||
'exec_cmd': opts.exec_cmd,
|
||||
'extract_flat': opts.extract_flat,
|
||||
'postprocessors': postprocessors,
|
||||
}
|
||||
|
||||
with YoutubeDL(ydl_opts) as ydl:
|
||||
# PostProcessors
|
||||
# Add the metadata pp first, the other pps will copy it
|
||||
if opts.addmetadata:
|
||||
ydl.add_post_processor(FFmpegMetadataPP())
|
||||
if opts.extractaudio:
|
||||
ydl.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
|
||||
if opts.recodevideo:
|
||||
ydl.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
|
||||
if opts.embedsubtitles:
|
||||
ydl.add_post_processor(FFmpegEmbedSubtitlePP(subtitlesformat=opts.subtitlesformat))
|
||||
if opts.xattrs:
|
||||
ydl.add_post_processor(XAttrMetadataPP())
|
||||
if opts.embedthumbnail:
|
||||
if not opts.addmetadata:
|
||||
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)
|
||||
|
@@ -305,19 +305,6 @@ class FileDownloader(object):
|
||||
ph(status)
|
||||
|
||||
def add_progress_hook(self, ph):
|
||||
""" ph gets called on download progress, with a dictionary with the entries
|
||||
* filename: The final filename
|
||||
* status: One of "downloading" and "finished"
|
||||
|
||||
It can also have some of the following entries:
|
||||
|
||||
* downloaded_bytes: Bytes on disks
|
||||
* total_bytes: Total bytes, None if unknown
|
||||
* tmpfilename: The filename we're currently writing to
|
||||
* eta: The estimated time in seconds, None if unknown
|
||||
* speed: The download speed in bytes/second, None if unknown
|
||||
|
||||
Hooks are guaranteed to be called at least once (with status "finished")
|
||||
if the download is successful.
|
||||
"""
|
||||
# See YoutubeDl.py (search for progress_hooks) for a description of
|
||||
# this interface
|
||||
self._progress_hooks.append(ph)
|
||||
|
@@ -510,6 +510,7 @@ from .yahoo import (
|
||||
YahooIE,
|
||||
YahooSearchIE,
|
||||
)
|
||||
from .yesjapan import YesJapanIE
|
||||
from .ynet import YnetIE
|
||||
from .youjizz import YouJizzIE
|
||||
from .youku import YoukuIE
|
||||
|
@@ -8,8 +8,8 @@ from ..utils import js_to_json
|
||||
|
||||
|
||||
class RTPIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?rtp\.pt/play/p(?P<program_id>[0-9]+)/e(?P<id>[0-9]+)/?'
|
||||
_TEST = {
|
||||
_VALID_URL = r'https?://(?:www\.)?rtp\.pt/play/p(?P<program_id>[0-9]+)/(?P<id>[^/?#]+)/?'
|
||||
_TESTS = [{
|
||||
'url': 'http://www.rtp.pt/play/p405/e174042/paixoes-cruzadas',
|
||||
'info_dict': {
|
||||
'id': '174042',
|
||||
@@ -21,7 +21,10 @@ class RTPIE(InfoExtractor):
|
||||
'params': {
|
||||
'skip_download': True, # RTMP download
|
||||
},
|
||||
}
|
||||
}, {
|
||||
'url': 'http://www.rtp.pt/play/p831/a-quimica-das-coisas',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
62
youtube_dl/extractor/yesjapan.py
Normal file
62
youtube_dl/extractor/yesjapan.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
HEADRequest,
|
||||
get_element_by_attribute,
|
||||
parse_iso8601,
|
||||
)
|
||||
|
||||
|
||||
class YesJapanIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?yesjapan\.com/video/(?P<slug>[A-Za-z0-9\-]*)_(?P<id>[A-Za-z0-9]+)\.html'
|
||||
_TEST = {
|
||||
'url': 'http://www.yesjapan.com/video/japanese-in-5-20-wa-and-ga-particle-usages_726497834.html',
|
||||
'md5': 'f0be416314e5be21a12b499b330c21cf',
|
||||
'info_dict': {
|
||||
'id': '726497834',
|
||||
'title': 'Japanese in 5! #20 - WA And GA Particle Usages',
|
||||
'description': 'This should clear up some issues most students of Japanese encounter with WA and GA....',
|
||||
'ext': 'mp4',
|
||||
'timestamp': 1416391590,
|
||||
'upload_date': '20141119',
|
||||
'thumbnail': 're:^https?://.*\.jpg$',
|
||||
}
|
||||
}
|
||||
|
||||
def _real_extract(self, url):
|
||||
video_id = self._match_id(url)
|
||||
|
||||
webpage = self._download_webpage(url, video_id)
|
||||
title = self._og_search_title(webpage)
|
||||
video_url = self._og_search_video_url(webpage)
|
||||
description = self._og_search_description(webpage)
|
||||
thumbnail = self._og_search_thumbnail(webpage)
|
||||
|
||||
timestamp = None
|
||||
submit_info = get_element_by_attribute('class', 'pm-submit-data', webpage)
|
||||
if submit_info:
|
||||
timestamp = parse_iso8601(self._search_regex(
|
||||
r'datetime="([^"]+)"', submit_info, 'upload date', fatal=False, default=None))
|
||||
|
||||
# attempt to resolve the final URL in order to get a proper extension
|
||||
redirect_req = HEADRequest(video_url)
|
||||
req = self._request_webpage(
|
||||
redirect_req, video_id, note='Resolving final URL', errnote='Could not resolve final URL', fatal=False)
|
||||
if req:
|
||||
video_url = req.geturl()
|
||||
|
||||
formats = [{
|
||||
'format_id': 'sd',
|
||||
'url': video_url,
|
||||
}]
|
||||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'formats': formats,
|
||||
'description': description,
|
||||
'timestamp': timestamp,
|
||||
'thumbnail': thumbnail,
|
||||
}
|
@@ -478,7 +478,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
||||
|
||||
def _extract_signature_function(self, video_id, player_url, example_sig):
|
||||
id_m = re.match(
|
||||
r'.*-(?P<id>[a-zA-Z0-9_-]+)(?:/watch_as3|/html5player)?\.(?P<ext>[a-z]+)$',
|
||||
r'.*?-(?P<id>[a-zA-Z0-9_-]+)(?:/watch_as3|/html5player)?\.(?P<ext>[a-z]+)$',
|
||||
player_url)
|
||||
if not id_m:
|
||||
raise ExtractorError('Cannot identify player %r' % player_url)
|
||||
|
@@ -8,11 +8,16 @@ from .ffmpeg import (
|
||||
FFmpegExtractAudioPP,
|
||||
FFmpegMergerPP,
|
||||
FFmpegMetadataPP,
|
||||
FFmpegVideoConvertor,
|
||||
FFmpegVideoConvertorPP,
|
||||
)
|
||||
from .xattrpp import XAttrMetadataPP
|
||||
from .execafterdownload import ExecAfterDownloadPP
|
||||
|
||||
|
||||
def get_postprocessor(key):
|
||||
return globals()[key + 'PP']
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AtomicParsleyPP',
|
||||
'ExecAfterDownloadPP',
|
||||
@@ -22,6 +27,6 @@ __all__ = [
|
||||
'FFmpegMergerPP',
|
||||
'FFmpegMetadataPP',
|
||||
'FFmpegPostProcessor',
|
||||
'FFmpegVideoConvertor',
|
||||
'FFmpegVideoConvertorPP',
|
||||
'XAttrMetadataPP',
|
||||
]
|
||||
|
@@ -236,9 +236,9 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
||||
return self._nopostoverwrites, information
|
||||
|
||||
|
||||
class FFmpegVideoConvertor(FFmpegPostProcessor):
|
||||
class FFmpegVideoConvertorPP(FFmpegPostProcessor):
|
||||
def __init__(self, downloader=None, preferedformat=None):
|
||||
super(FFmpegVideoConvertor, self).__init__(downloader)
|
||||
super(FFmpegVideoConvertorPP, self).__init__(downloader)
|
||||
self._preferedformat = preferedformat
|
||||
|
||||
def run(self, information):
|
||||
|
@@ -1262,18 +1262,25 @@ def check_executable(exe, args=[]):
|
||||
|
||||
|
||||
def get_exe_version(exe, args=['--version'],
|
||||
version_re=r'version\s+([0-9._-a-zA-Z]+)',
|
||||
unrecognized='present'):
|
||||
version_re=None, unrecognized='present'):
|
||||
""" Returns the version of the specified executable,
|
||||
or False if the executable is not present """
|
||||
try:
|
||||
out, err = subprocess.Popen(
|
||||
out, _ = subprocess.Popen(
|
||||
[exe] + args,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()
|
||||
except OSError:
|
||||
return False
|
||||
firstline = out.partition(b'\n')[0].decode('ascii', 'ignore')
|
||||
m = re.search(version_re, firstline)
|
||||
if isinstance(out, bytes): # Python 2.x
|
||||
out = out.decode('ascii', 'ignore')
|
||||
return detect_exe_version(out, version_re, unrecognized)
|
||||
|
||||
|
||||
def detect_exe_version(output, version_re=None, unrecognized='present'):
|
||||
assert isinstance(output, compat_str)
|
||||
if version_re is None:
|
||||
version_re = r'version\s+([-0-9._a-zA-Z]+)'
|
||||
m = re.search(version_re, output)
|
||||
if m:
|
||||
return m.group(1)
|
||||
else:
|
||||
|
@@ -1,3 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '2014.12.13.1'
|
||||
__version__ = '2014.12.15'
|
||||
|
Reference in New Issue
Block a user