+++ /dev/null
-updates_key.pem
-*.pyc
-*.pyo
-youtube-dl.exe
-wine-py2exe/
-py2exe.log
-*.kate-swp
-build/
-dist/
-MANIFEST
-*.DS_Store
-youtube-dl.tar.gz
-.coverage
-cover/
-__pycache__/
-.git/
-*~
email:
- filippo.valsorda@gmail.com
- phihag@phihag.de
- irc:
- channels:
- - "irc.freenode.org#youtube-dl"
- skip_join: true
+# irc:
+# channels:
+# - "irc.freenode.org#youtube-dl"
+# skip_join: true
-9999.99.99
\ No newline at end of file
+2012.12.99
all: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
clean:
- rm -rf youtube-dl youtube-dl.exe youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/
+ rm -rf youtube-dl youtube-dl.exe youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz
PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
#nosetests --with-coverage --cover-package=youtube_dl --cover-html --verbose --processes 4 test
nosetests --verbose test
-.PHONY: all clean install test
+tar: youtube-dl.tar.gz
+
+.PHONY: all clean install test tar
youtube-dl: youtube_dl/*.py
zip --quiet youtube-dl youtube_dl/*.py
youtube-dl.bash-completion: youtube_dl/*.py devscripts/bash-completion.in
python devscripts/bash-completion.py
-youtube-dl.tar.gz: all
- tar -cvzf youtube-dl.tar.gz -s "|^./|./youtube-dl/|" \
- --exclude-from=".tarignore" -- .
+youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion
+ @tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
+ --exclude '*.DS_Store' \
+ --exclude '*.kate-swp' \
+ --exclude '*.pyc' \
+ --exclude '*.pyo' \
+ --exclude '*~' \
+ --exclude '__pycache' \
+ --exclude '.git' \
+ -- \
+ bin devscripts test youtube_dl \
+ CHANGELOG LICENSE README.md README.txt \
+ Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion setup.py \
+ youtube-dl
# DESCRIPTION
**youtube-dl** is a small command-line program to download videos from
YouTube.com and a few more sites. It requires the Python interpreter, version
-2.x (x being at least 6), and it is not platform specific. It should work in
-your Unix box, in Windows or in Mac OS X. It is released to the public domain,
+2.6, 2.7, or 3.3+, and it is not platform specific. It should work on
+your Unix box, on Windows or on Mac OS X. It is released to the public domain,
which means you can modify it, redistribute it or use it however you like.
# OPTIONS
## Post-processing Options:
-x, --extract-audio convert video files to audio-only files (requires
ffmpeg or avconv and ffprobe or avprobe)
- --audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", or "wav";
- best by default
+ --audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
+ "wav"; best by default
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert a
value between 0 (better) and 9 (worse) for VBR or a
specific bitrate like 128K (default 5)
# CONFIGURATION
-You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.local/config/youtube-dl.conf`.
+You can configure youtube-dl by placing default arguments (such as `--extract-audio --no-mtime` to always extract the audio and not copy the mtime) into `/etc/youtube-dl.conf` and/or `~/.config/youtube-dl.conf`.
# OUTPUT TEMPLATE
-#!/bin/sh
+#!/bin/bash
# IMPORTANT: the following assumptions are made
# * the GH repo is on the origin remote
make youtube-dl youtube-dl.tar.gz
wget "http://jeromelaheurte.net:8142/download/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe || \
wget "http://jeromelaheurte.net:8142/build/rg3/youtube-dl/youtube-dl.exe?rev=$REV" -O youtube-dl.exe
-mkdir -p "update_staging/$version"
-mv youtube-dl youtube-dl.exe "update_staging/$version"
-mv youtube-dl.tar.gz "update_staging/$version/youtube-dl-$version.tar.gz"
-RELEASE_FILES=youtube-dl youtube-dl.exe youtube-dl-$version.tar.gz
-(cd update_staging/$version/ && md5sum $RELEASE_FILES > MD5SUMS)
-(cd update_staging/$version/ && sha1sum $RELEASE_FILES > SHA1SUMS)
-(cd update_staging/$version/ && sha256sum $RELEASE_FILES > SHA2-256SUMS)
-(cd update_staging/$version/ && sha512sum $RELEASE_FILES > SHA2-512SUMS)
+mkdir -p "build/$version"
+mv youtube-dl youtube-dl.exe "build/$version"
+mv youtube-dl.tar.gz "build/$version/youtube-dl-$version.tar.gz"
+RELEASE_FILES="youtube-dl youtube-dl.exe youtube-dl-$version.tar.gz"
+(cd build/$version/ && md5sum $RELEASE_FILES > MD5SUMS)
+(cd build/$version/ && sha1sum $RELEASE_FILES > SHA1SUMS)
+(cd build/$version/ && sha256sum $RELEASE_FILES > SHA2-256SUMS)
+(cd build/$version/ && sha512sum $RELEASE_FILES > SHA2-512SUMS)
git checkout HEAD -- youtube-dl youtube-dl.exe
echo "\n### Signing and uploading the new binaries to youtube-dl.org..."
-for f in $RELEASE_FILES; do gpg --detach-sig "update_staging/$version/$f"; done
-scp -r "update_staging/$version" ytdl@youtube-dl.org:html/downloads/
-rm -r update_staging
+for f in $RELEASE_FILES; do gpg --detach-sig "build/$version/$f"; done
+scp -r "build/$version" ytdl@youtube-dl.org:html/downloads/
echo "\n### Now switching to gh-pages..."
-git checkout gh-pages
-git checkout "$MASTER" -- devscripts/gh-pages/
-git reset devscripts/gh-pages/
-devscripts/gh-pages/add-version.py $version
-devscripts/gh-pages/sign-versions.py < updates_key.pem
-devscripts/gh-pages/generate-download.py
-devscripts/gh-pages/update-copyright.py
-rm -r test_coverage
-mv cover test_coverage
-git add *.html *.html.in update test_coverage
-git commit -m "release $version"
-git show HEAD
-read -p "Is it good, can I push? (y/n) " -n 1
-if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi
-echo
-git push origin gh-pages
+git clone --branch gh-pages --single-branch . build/gh-pages
+ROOT=$(pwd)
+(
+ set -e
+ cd build/gh-pages
+ ORIGIN_URL=$(git config --get remote.origin.url)
+ "$ROOT/devscripts/gh-pages/add-version.py" $version
+ "$ROOT/devscripts/gh-pages/sign-versions.py" < updates_key.pem
+ "$ROOT/devscripts/gh-pages/generate-download.py"
+ "$ROOT/devscripts/gh-pages/update-copyright.py"
+ git add *.html *.html.in update
+ git commit -m "release $version"
+ git show HEAD
+ read -p "Is it good, can I push? (y/n) " -n 1
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi
+ echo
+ git push $ORIGIN_URL gh-pages
+)
+rm -r build
echo "\n### DONE!"
-rm -r devscripts
-git checkout $MASTER
"params": {
"skip_download": true
}
+ },
+ {
+ "name": "ComedyCentral",
+ "url": "http://www.thedailyshow.com/full-episodes/thu-december-13-2012-kristen-stewart",
+ "playlist": [
+ {
+ "file": "422204.mp4",
+ "md5": "7a7abe068b31ff03e7b8a37596e72380",
+ "info_dict": {
+ "title": "thedailyshow-thu-december-13-2012-kristen-stewart part 1"
+ }
+ },
+ {
+ "file": "422205.mp4",
+ "md5": "30552b7274c94dbb933f64600eadddd2",
+ "info_dict": {
+ "title": "thedailyshow-thu-december-13-2012-kristen-stewart part 2"
+ }
+ },
+ {
+ "file": "422206.mp4",
+ "md5": "1f4c0664b352cb8e8fe85d5da4fbee91",
+ "info_dict": {
+ "title": "thedailyshow-thu-december-13-2012-kristen-stewart part 3"
+ }
+ },
+ {
+ "file": "422207.mp4",
+ "md5": "f61ee8a4e6bd1308438e03badad78554",
+ "info_dict": {
+ "title": "thedailyshow-thu-december-13-2012-kristen-stewart part 4"
+ }
+ }
+ ]
}
]
writesubtitles: Write the video subtitles to a .srt file
subtitleslang: Language of the subtitles to download
test: Download only first bytes to test the downloader.
+ keepvideo: Keep the video file after post-processing
"""
params = None
return self._download_retcode
def post_process(self, filename, ie_info):
- """Run the postprocessing chain on the given file."""
+ """Run all the postprocessors on the given file."""
info = dict(ie_info)
info['filepath'] = filename
+ keep_video = None
for pp in self._pps:
- info = pp.run(info)
- if info is None:
- break
+ try:
+ keep_video_wish,new_info = pp.run(info)
+ if keep_video_wish is not None:
+ if keep_video_wish:
+ keep_video = keep_video_wish
+ elif keep_video is None:
+ # No clear decision yet, let IE decide
+ keep_video = keep_video_wish
+ except PostProcessingError as e:
+ self.to_stderr(u'ERROR: ' + e.msg)
+ if keep_video is False and not self.params.get('keepvideo', False):
+ try:
+ self.to_stderr(u'Deleting original file %s (pass -k to keep)' % filename)
+ os.remove(encodeFilename(filename))
+ except (IOError, OSError):
+ self.to_stderr(u'WARNING: Unable to remove downloaded video file')
def _download_with_rtmpdump(self, filename, url, player_url, page_url):
self.report_destination(filename)
(the-colbert-report-(videos|collections)/(?P<clipID>[0-9]+)/[^/]*/(?P<cntitle>.*?))
|(watch/(?P<date>[^/]*)/(?P<tdstitle>.*)))))
$"""
- IE_NAME = u'comedycentral'
_available_formats = ['3500', '2200', '1700', '1200', '750', '400']
def report_extraction(self, episode_id):
self._downloader.to_screen(u'[comedycentral] %s: Extracting information' % episode_id)
- def report_config_download(self, episode_id):
- self._downloader.to_screen(u'[comedycentral] %s: Downloading configuration' % episode_id)
+ def report_config_download(self, episode_id, media_id):
+ self._downloader.to_screen(u'[comedycentral] %s: Downloading configuration for %s' % (episode_id, media_id))
def report_index_download(self, episode_id):
self._downloader.to_screen(u'[comedycentral] %s: Downloading show index' % episode_id)
- def report_player_url(self, episode_id):
- self._downloader.to_screen(u'[comedycentral] %s: Determining player URL' % episode_id)
-
-
def _print_formats(self, formats):
print('Available formats:')
for x in formats:
try:
htmlHandle = compat_urllib_request.urlopen(req)
html = htmlHandle.read()
+ webpage = html.decode('utf-8')
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
self._downloader.trouble(u'ERROR: unable to download webpage: %s' % compat_str(err))
return
return
epTitle = mobj.group('episode')
- mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', html)
+ mMovieParams = re.findall('(?:<param name="movie" value="|var url = ")(http://media.mtvnservices.com/([^"]*(?:episode|video).*?:.*?))"', webpage)
if len(mMovieParams) == 0:
# The Colbert Report embeds the information in a without
# a URL prefix; so extract the alternate reference
# and then add the URL prefix manually.
- altMovieParams = re.findall('data-mgid="([^"]*(?:episode|video).*?:.*?)"', html)
+ altMovieParams = re.findall('data-mgid="([^"]*(?:episode|video).*?:.*?)"', webpage)
if len(altMovieParams) == 0:
self._downloader.trouble(u'ERROR: unable to find Flash URL in webpage ' + url)
return
else:
mMovieParams = [("http://media.mtvnservices.com/" + altMovieParams[0], altMovieParams[0])]
- playerUrl_raw = mMovieParams[0][0]
- self.report_player_url(epTitle)
- try:
- urlHandle = compat_urllib_request.urlopen(playerUrl_raw)
- playerUrl = urlHandle.geturl()
- except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
- self._downloader.trouble(u'ERROR: unable to find out player URL: ' + compat_str(err))
- return
-
uri = mMovieParams[0][1]
indexUrl = 'http://shadow.comedycentral.com/feeds/video_player/mrss/?' + compat_urllib_parse.urlencode({'uri': uri})
self.report_index_download(epTitle)
idoc = xml.etree.ElementTree.fromstring(indexXml)
itemEls = idoc.findall('.//item')
- for itemEl in itemEls:
+ for partNum,itemEl in enumerate(itemEls):
mediaId = itemEl.findall('./guid')[0].text
shortMediaId = mediaId.split(':')[-1]
showId = mediaId.split(':')[-2].replace('.com', '')
configUrl = ('http://www.comedycentral.com/global/feeds/entertainment/media/mediaGenEntertainment.jhtml?' +
compat_urllib_parse.urlencode({'uri': mediaId}))
configReq = compat_urllib_request.Request(configUrl)
- self.report_config_download(epTitle)
+ self.report_config_download(epTitle, shortMediaId)
try:
configXml = compat_urllib_request.urlopen(configReq).read()
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
return
# For now, just pick the highest bitrate
- format,video_url = turls[-1]
+ format,rtmp_video_url = turls[-1]
# Get the format arg from the arg stream
req_format = self._downloader.params.get('format', None)
# Select format if we can find one
for f,v in turls:
if f == req_format:
- format, video_url = f, v
+ format, rtmp_video_url = f, v
break
- # Patch to download from alternative CDN, which does not
- # break on current RTMPDump builds
- broken_cdn = "rtmpe://viacomccstrmfs.fplive.net/viacomccstrm/gsp.comedystor/"
- better_cdn = "rtmpe://cp10740.edgefcs.net/ondemand/mtvnorigin/gsp.comedystor/"
-
- if video_url.startswith(broken_cdn):
- video_url = video_url.replace(broken_cdn, better_cdn)
+ m = re.match(r'^rtmpe?://.*?/(?P<finalid>gsp.comedystor/.*)$', rtmp_video_url)
+ if not m:
+ raise ExtractorError(u'Cannot transform RTMP url')
+ base = 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=1+_pxI0=Ripod-h264+_pxL0=undefined+_pxM0=+_pxK=18639+_pxE=mp4/44620/mtvnorigin/'
+ video_url = base + m.group('finalid')
- effTitle = showId + u'-' + epTitle
+ effTitle = showId + u'-' + epTitle + u' part ' + compat_str(partNum+1)
info = {
'id': shortMediaId,
'url': video_url,
'format': format,
'thumbnail': None,
'description': officialTitle,
- 'player_url': None #playerUrl
}
-
results.append(info)
return results
return [info]
-
class CollegeHumorIE(InfoExtractor):
"""Information extractor for collegehumor.com"""
return
response = json.loads(webpage)
+ if type(response) != list:
+ error_text = response.get('error', 'unknown error')
+ self._downloader.trouble(u'ERROR: Justin.tv API: %s' % error_text)
+ return
info = []
for clip in response:
video_url = clip['video_file_url']
if video_url:
video_extension = os.path.splitext(video_url)[1][1:]
- video_date = re.sub('-', '', clip['created_on'][:10])
+ video_date = re.sub('-', '', clip['start_time'][:10])
+ video_uploader_id = clip.get('user_id', clip.get('channel_id'))
info.append({
'id': clip['id'],
'url': video_url,
'title': clip['title'],
- 'uploader': clip.get('user_id', clip.get('channel_id')),
+ 'uploader': clip.get('channel_name', video_uploader_id),
+ 'uploader_id': video_uploader_id,
'upload_date': video_date,
'ext': video_extension,
})
paged = True
api += '/channel/archives/%s.json'
else:
- api += '/clip/show/%s.json'
+ api += '/broadcast/by_archive/%s.json'
api = api % (video_id,)
self.report_extraction(video_id)
}
videos.append(info)
return videos
-
+
class UstreamIE(InfoExtractor):
- _VALID_URL = r'http://www.ustream.tv/recorded/(?P<videoID>\d+)'
+ _VALID_URL = r'https?://www\.ustream\.tv/recorded/(?P<videoID>\d+)'
IE_NAME = u'ustream'
-
+
def _real_extract(self, url):
m = re.match(self._VALID_URL, url)
video_id = m.group('videoID')
one has an extra field called "filepath" that points to the
downloaded file.
- When this method returns None, the postprocessing chain is
- stopped. However, this method may return an information
- dictionary that will be passed to the next postprocessing
- object in the chain. It can be the one it received after
- changing some fields.
+ This method returns a tuple, the first element of which describes
+ whether the original file should be kept (i.e. not deleted - None for
+ no preference), and the second of which is the updated information.
In addition, this method may raise a PostProcessingError
- exception that will be taken into account by the downloader
- it was called from.
+ exception if post processing fails.
"""
- return information # by default, do nothing
+ return None, information # by default, keep file and do nothing
-class AudioConversionError(BaseException):
- def __init__(self, message):
- self.message = message
+class FFmpegPostProcessorError(PostProcessingError):
+ pass
-class FFmpegExtractAudioPP(PostProcessor):
- def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, keepvideo=False, nopostoverwrites=False):
+class AudioConversionError(PostProcessingError):
+ pass
+
+class FFmpegPostProcessor(PostProcessor):
+ def __init__(self,downloader=None):
PostProcessor.__init__(self, downloader)
- if preferredcodec is None:
- preferredcodec = 'best'
- self._preferredcodec = preferredcodec
- self._preferredquality = preferredquality
- self._keepvideo = keepvideo
- self._nopostoverwrites = nopostoverwrites
self._exes = self.detect_executables()
@staticmethod
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
return dict((program, executable(program)) for program in programs)
+ def run_ffmpeg(self, path, out_path, opts):
+ if not self._exes['ffmpeg'] and not self._exes['avconv']:
+ raise FFmpegPostProcessorError(u'ffmpeg or avconv not found. Please install one.')
+ cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y', '-i', encodeFilename(path)]
+ + opts +
+ [encodeFilename(self._ffmpeg_filename_argument(out_path))])
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout,stderr = p.communicate()
+ if p.returncode != 0:
+ msg = stderr.strip().split('\n')[-1]
+ raise FFmpegPostProcessorError(msg.decode('utf-8', 'replace'))
+
+ def _ffmpeg_filename_argument(self, fn):
+ # ffmpeg broke --, see https://ffmpeg.org/trac/ffmpeg/ticket/2127 for details
+ if fn.startswith(u'-'):
+ return u'./' + fn
+ return fn
+
+class FFmpegExtractAudioPP(FFmpegPostProcessor):
+ def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False):
+ FFmpegPostProcessor.__init__(self, downloader)
+ if preferredcodec is None:
+ preferredcodec = 'best'
+ self._preferredcodec = preferredcodec
+ self._preferredquality = preferredquality
+ self._nopostoverwrites = nopostoverwrites
+
def get_audio_codec(self, path):
if not self._exes['ffprobe'] and not self._exes['avprobe']: return None
try:
- cmd = [self._exes['avprobe'] or self._exes['ffprobe'], '-show_streams', '--', encodeFilename(path)]
+ cmd = [self._exes['avprobe'] or self._exes['ffprobe'], '-show_streams', encodeFilename(self._ffmpeg_filename_argument(path))]
handle = subprocess.Popen(cmd, stderr=compat_subprocess_get_DEVNULL(), stdout=subprocess.PIPE)
output = handle.communicate()[0]
if handle.wait() != 0:
acodec_opts = []
else:
acodec_opts = ['-acodec', codec]
- cmd = ([self._exes['avconv'] or self._exes['ffmpeg'], '-y', '-i', encodeFilename(path), '-vn']
- + acodec_opts + more_opts +
- ['--', encodeFilename(out_path)])
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- stdout,stderr = p.communicate()
- if p.returncode != 0:
- msg = stderr.strip().split('\n')[-1]
- raise AudioConversionError(msg)
+ opts = ['-vn'] + acodec_opts + more_opts
+ try:
+ FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts)
+ except FFmpegPostProcessorError as err:
+ raise AudioConversionError(err.message)
def run(self, information):
path = information['filepath']
filecodec = self.get_audio_codec(path)
if filecodec is None:
- self._downloader.to_stderr(u'WARNING: unable to obtain file audio codec with ffprobe')
- return None
+ raise PostProcessingError(u'WARNING: unable to obtain file audio codec with ffprobe')
more_opts = []
if self._preferredcodec == 'best' or self._preferredcodec == filecodec or (self._preferredcodec == 'm4a' and filecodec == 'aac'):
acodec = 'copy'
extension = self._preferredcodec
more_opts = [self._exes['avconv'] and '-bsf:a' or '-absf', 'aac_adtstoasc']
- elif filecodec in ['aac', 'mp3', 'vorbis']:
+ elif filecodec in ['aac', 'mp3', 'vorbis', 'opus']:
# Lossless if possible
acodec = 'copy'
extension = filecodec
more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality + 'k']
else:
# We convert the audio (lossy)
- acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'vorbis': 'libvorbis', 'wav': None}[self._preferredcodec]
+ acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'opus': 'opus', 'vorbis': 'libvorbis', 'wav': None}[self._preferredcodec]
extension = self._preferredcodec
more_opts = []
if self._preferredquality is not None:
except:
etype,e,tb = sys.exc_info()
if isinstance(e, AudioConversionError):
- self._downloader.to_stderr(u'ERROR: audio conversion failed: ' + e.message)
+ msg = u'audio conversion failed: ' + e.message
else:
- self._downloader.to_stderr(u'ERROR: error running ' + (self._exes['avconv'] and 'avconv' or 'ffmpeg'))
- return None
+ msg = u'error running ' + (self._exes['avconv'] and 'avconv' or 'ffmpeg')
+ raise PostProcessingError(msg)
# Try to update the date time for extracted audio file.
if information.get('filetime') is not None:
except:
self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file')
- if not self._keepvideo:
- try:
- os.remove(encodeFilename(path))
- except (IOError, OSError):
- self._downloader.to_stderr(u'WARNING: Unable to remove downloaded video file')
- return None
-
information['filepath'] = new_path
- return information
+ return False,information
+
+class FFmpegVideoConvertor(FFmpegPostProcessor):
+ def __init__(self, downloader=None,preferedformat=None):
+ super(FFmpegVideoConvertor, self).__init__(downloader)
+ self._preferedformat=preferedformat
+
+ def run(self, information):
+ path = information['filepath']
+ prefix, sep, ext = path.rpartition(u'.')
+ outpath = prefix + sep + self._preferedformat
+ if information['ext'] == self._preferedformat:
+ self._downloader.to_screen(u'[ffmpeg] Not converting video file %s - already is in target format %s' % (path, self._preferedformat))
+ return True,information
+ self._downloader.to_screen(u'['+'ffmpeg'+'] Converting video from %s to %s, Destination: ' % (information['ext'], self._preferedformat) +outpath)
+ self.run_ffmpeg(path, outpath, [])
+ information['filepath'] = outpath
+ information['format'] = self._preferedformat
+ information['ext'] = self._preferedformat
+ return False,information
action='store', dest='subtitleslang', metavar='LANG',
help='language of the closed captions to download (optional) use IETF language tags like \'en\'')
-
verbosity.add_option('-q', '--quiet',
action='store_true', dest='quiet', help='activates quiet mode', default=False)
verbosity.add_option('-s', '--simulate',
postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
- help='"best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default')
+ help='"best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; best by default')
postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
+ postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
+ help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
help='keeps the video file on disk after the post-processing; the video is erased by default')
postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
def _real_main():
parser, opts, args = parseOpts()
+ # Update version
+ if opts.update_self:
+ update_self(fd.to_screen, opts.verbose, sys.argv[0])
+
# Open appropriate CookieJar
if opts.cookiefile is None:
jar = compat_cookiejar.CookieJar()
except (TypeError, ValueError) as err:
parser.error(u'invalid playlist end number specified')
if opts.extractaudio:
- if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis', 'm4a', 'wav']:
+ if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
parser.error(u'invalid audio format specified')
if opts.audioquality:
opts.audioquality = opts.audioquality.strip('k').strip('K')
if not opts.audioquality.isdigit():
parser.error(u'invalid audio quality specified')
+ if opts.recodevideo is not None:
+ if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
+ parser.error(u'invalid video recode format specified')
if sys.version_info < (3,):
# In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
'prefer_free_formats': opts.prefer_free_formats,
'verbose': opts.verbose,
'test': opts.test,
+ 'keepvideo': opts.keepvideo,
})
if opts.verbose:
# PostProcessors
if opts.extractaudio:
- fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, keepvideo=opts.keepvideo, nopostoverwrites=opts.nopostoverwrites))
-
- # Update version
- if opts.update_self:
- update_self(fd.to_screen, opts.verbose, sys.argv[0])
+ fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
+ if opts.recodevideo:
+ fd.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
# Maybe do nothing
if len(all_urls) < 1:
This exception may be raised by PostProcessor's .run() method to
indicate an error in the postprocessing task.
"""
- pass
+ def __init__(self, msg):
+ self.msg = msg
class MaxDownloadsReached(Exception):
""" --max-downloads limit has been reached. """
-__version__ = '2013.01.02'
+__version__ = '2013.01.11'