Compare commits

...

52 Commits

Author SHA1 Message Date
Filippo Valsorda
5002aea371 release 2012.11.17 2012-11-17 14:02:31 +01:00
Filippo Valsorda
74033a662d Reworked Vimeo file selection logic (quality, codec) - closes #530 2012-11-13 21:53:18 +01:00
Filippo Valsorda
0526e4f55a Merge pull request #522 from art-zhitnik/master
--(match|reject)-title utf8 fix
2012-11-11 06:22:10 -08:00
Art Zhitnik
39973a0236 Solve the bug of parsing titles with unicode (cyrillic) 2012-11-11 14:09:12 +10:00
Filippo Valsorda
5d40a470a2 quiet the HTMLParser debug info - closes #517 2012-11-09 12:32:07 +01:00
Filippo Valsorda
4cc391461a fix DailyMotion official users videos - closes #281 - by @yvestan 2012-11-07 14:44:10 +01:00
Filippo Valsorda
bf95333e5e fixed MetacafeIE (uploader nickname regex) - closes #515 2012-11-06 23:08:10 +01:00
Philipp Hagemeister
b7a34316d2 -x for --extract-audio, one of the most popular options 2012-10-30 17:41:38 +01:00
Philipp Hagemeister
74e453bdea New --id option for the old default filename pattern 2012-10-30 17:37:53 +01:00
Philipp Hagemeister
156a59e7a9 Additional tests in file name sanitation 2012-10-29 08:19:54 +01:00
Philipp Hagemeister
aeca861f22 Merge pull request #502 from FiloSottile/new_sanitize_filename
My sanitize_filename proposal
2012-10-28 15:33:59 -07:00
Filippo Valsorda
42cb53fcfa modified filename escaping to a "smarter" one 2012-10-28 22:47:02 +01:00
Filippo Valsorda
fe4d68e196 slight change to Dailymotion uploader regex (fix) 2012-10-28 21:43:43 +01:00
Philipp Hagemeister
25b7fd9c01 Merge pull request #491 from tyll/master
Update install target
2012-10-26 01:10:25 -07:00
Till Maas
e79e8b7dc4 Update install target
- Allow to configure destination directories to fulfill the needs of
  different distributions
- Support DESTDIR variable for staging installation when packaging
- Do not set user/group to root. It requires 'make install' to run as
  root, but then this is the default behaviour anyways.
2012-10-25 21:19:13 +02:00
Filippo Valsorda
965a8b2bc4 Merge pull request #488 from Tailszefox/local
Fix audio bitrate quality for ffmpeg/avconv (closes #487)
2012-10-24 11:42:31 -07:00
Tailszefox
f06eaa873e Fix audio bitrate quality for ffmpeg/avconv 2012-10-23 16:37:12 +02:00
Philipp Hagemeister
ece34e8951 Merge pull request #486 from Tailszefox/local
Added duration for YouTube videos
2012-10-23 05:53:28 -07:00
Tailszefox
2262a32dd7 Added duration for YouTube videos 2012-10-22 18:32:42 +02:00
Philipp Hagemeister
c6c0e23a32 Support raw playlist parameters (Closes #482) 2012-10-22 13:01:36 +02:00
Philipp Hagemeister
02b324a23d Restore 2.5 compat by activating with_statement future 2012-10-22 12:51:20 +02:00
Filippo Valsorda
b8005afc20 handle YT urls with #/ redirects (closes #484) 2012-10-22 09:15:27 +02:00
Philipp Hagemeister
073522bc6c Don't use 2.7+ check_output 2012-10-19 23:28:37 +02:00
Philipp Hagemeister
9248cb0549 Merge pull request #472 from gcmalloc/master
Test proposal
2012-10-19 05:48:12 -07:00
gcmalloc
6b41b61119 correcting travis 2012-10-19 12:53:20 +02:00
gcmalloc
591bbe9c90 changing test from md5 to filesize, the file changed between download 2012-10-19 12:53:20 +02:00
gcmalloc
fc7376016c cleaning the test that doesn't work with the api for the moment 2012-10-19 12:53:20 +02:00
gcmalloc
97a37c2319 some assertion on the file downloaded 2012-10-19 12:53:20 +02:00
gcmalloc
3afed78a6a removing testing video 2012-10-19 12:53:20 +02:00
gcmalloc
4279a0ca98 correcting test to be compatible with python2.6 2012-10-19 12:53:20 +02:00
gcmalloc
edcc7d2dd3 StringIO used by nosetests do not merge with the way youtube-dl handle sys.stdout and sys.stderr 2012-10-19 12:53:19 +02:00
gcmalloc
7f60b5aa40 correction on the test 2012-10-19 12:53:19 +02:00
gcmalloc
aeeb29a356 adding travis support 2012-10-15 10:58:35 +02:00
Filippo Valsorda
902b2a0a45 New IE: YouTube channels (closes #396) 2012-10-14 13:48:18 +02:00
gcmalloc
6d9c22cd26 correcting the makefile according to the new one 2012-10-12 20:30:01 +02:00
gcmalloc
729baf58b2 removing extended globbing for the find utility 2012-10-12 20:25:22 +02:00
gcmalloc
4c9afeca34 adding xvideo 2012-10-12 20:25:22 +02:00
gcmalloc
6da7877bf5 adding facebook test 2012-10-12 20:25:22 +02:00
gcmalloc
b4e5de51ec adding photobucket test 2012-10-12 20:25:22 +02:00
gcmalloc
a4b5f22554 adding metacafe test 2012-10-12 20:25:22 +02:00
gcmalloc
ff08984246 adding dailymotion test 2012-10-12 20:25:22 +02:00
gcmalloc
137c5803c3 some changes to keep the same standard 2012-10-12 20:25:22 +02:00
gcmalloc
3eec021a1f removing unused global modifier 2012-10-12 20:25:22 +02:00
gcmalloc
5a33b73309 correcting the makefile 2012-10-12 20:25:22 +02:00
gcmalloc
0b4e98490b changing test video 2012-10-12 20:24:58 +02:00
gcmalloc
80a846e119 correction on the test for the utils.py 2012-10-12 20:24:58 +02:00
gcmalloc
434d60cd95 adding clean rule in the makefile 2012-10-12 20:24:58 +02:00
gcmalloc
efe8902f0b adding download test with md5 check 2012-10-12 20:24:58 +02:00
gcmalloc
44fb345437 adding TestCase class and corresponding test 2012-10-12 20:24:58 +02:00
gcmalloc
9993976ae4 correction on the sanitize title method, change in title resulting 2012-10-12 20:24:58 +02:00
gcmalloc
b387fb0385 adding test rule in the Makefile 2012-10-12 20:24:58 +02:00
Filippo Valsorda
10daa766a1 support EDU YouTube playlists (closes #407) 2012-10-11 08:27:19 +02:00
18 changed files with 302 additions and 70 deletions

9
.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
language: python
#specify the python version
python:
- "2.6"
- "2.7"
#command to install the setup
install:
# command to run tests
script: nosetests test --nocapture

View File

@@ -1 +1 @@
2012.10.09
2012.11.17

View File

@@ -5,10 +5,17 @@ clean:
rm -f youtube-dl youtube-dl.exe youtube-dl.1 LATEST_VERSION
PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
MANDIR=$(PREFIX)/man
SYSCONFDIR=/etc
install: youtube-dl youtube-dl.1 youtube-dl.bash-completion
install -m 755 --owner root --group root youtube-dl $(PREFIX)/bin/
install -m 644 --owner root --group root youtube-dl.1 $(PREFIX)/man/man1
install -m 644 --owner root --group root youtube-dl.bash-completion /etc/bash_completion.d/youtube-dl
install -d $(DESTDIR)$(BINDIR)
install -m 755 youtube-dl $(DESTDIR)$(BINDIR)
install -d $(DESTDIR)$(MANDIR)/man1
install -m 644 youtube-dl.1 $(DESTDIR)$(MANDIR)/man1
install -d $(DESTDIR)$(SYSCONFDIR)/bash_completion.d
install -m 644 youtube-dl.bash-completion $(DESTDIR)$(SYSCONFDIR)/bash_completion.d/youtube-dl
.PHONY: all clean install README.md youtube-dl.bash-completion
# TODO un-phony README.md and youtube-dl.bash_completion by reading from .in files and generating from them
@@ -45,3 +52,8 @@ youtube-dl.bash-completion: README.md
LATEST_VERSION: youtube_dl/__init__.py
python -m youtube_dl --version > LATEST_VERSION
test:
nosetests2 --nocapture test
.PHONY: default compile update update-latest update-readme test clean

View File

@@ -36,6 +36,7 @@ which means you can modify it, redistribute it or use it however you like.
## Filesystem Options:
-t, --title use title in file name
--id use video ID in file name
-l, --literal use literal title in file name
-A, --auto-number number downloaded files starting from 00000
-o, --output TEMPLATE output filename template. Use %(stitle)s to get the
@@ -91,7 +92,7 @@ which means you can modify it, redistribute it or use it however you like.
-n, --netrc use .netrc authentication data
## Post-processing Options:
--extract-audio convert video files to audio-only files (requires
-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

1
test/parameters.json Normal file
View File

@@ -0,0 +1 @@
{"username": null, "listformats": null, "skip_download": false, "usenetrc": false, "max_downloads": null, "noprogress": false, "forcethumbnail": false, "forceformat": false, "format_limit": null, "ratelimit": null, "nooverwrites": false, "forceurl": false, "writeinfojson": false, "simulate": false, "playliststart": 1, "continuedl": true, "password": null, "prefer_free_formats": false, "nopart": false, "retries": 10, "updatetime": true, "consoletitle": false, "verbose": true, "forcefilename": false, "ignoreerrors": false, "logtostderr": false, "format": null, "subtitleslang": null, "quiet": false, "outtmpl": "%(id)s.%(ext)s", "rejecttitle": null, "playlistend": -1, "writedescription": false, "forcetitle": false, "forcedescription": false, "writesubtitles": false, "matchtitle": null}

View File

@@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Various small unit tests
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import youtube_dl
def test_simplify_title():
assert youtube_dl._simplify_title(u'abc') == u'abc'
assert youtube_dl._simplify_title(u'abc_d-e') == u'abc_d-e'
assert youtube_dl._simplify_title(u'123') == u'123'
assert u'/' not in youtube_dl._simplify_title(u'abc/de')
assert u'abc' in youtube_dl._simplify_title(u'abc/de')
assert u'de' in youtube_dl._simplify_title(u'abc/de')
assert u'/' not in youtube_dl._simplify_title(u'abc/de///')
assert u'\\' not in youtube_dl._simplify_title(u'abc\\de')
assert u'abc' in youtube_dl._simplify_title(u'abc\\de')
assert u'de' in youtube_dl._simplify_title(u'abc\\de')
assert youtube_dl._simplify_title(u'ä') == u'ä'
assert youtube_dl._simplify_title(u'кириллица') == u'кириллица'
# Strip underlines
assert youtube_dl._simplify_title(u'\'a_') == u'a'

93
test/test_download.py Normal file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python2
import unittest
import hashlib
import os
import json
from youtube_dl.FileDownloader import FileDownloader
from youtube_dl.InfoExtractors import YoutubeIE, DailymotionIE
from youtube_dl.InfoExtractors import MetacafeIE, BlipTVIE
class DownloadTest(unittest.TestCase):
PARAMETERS_FILE = "test/parameters.json"
#calculated with md5sum:
#md5sum (GNU coreutils) 8.19
YOUTUBE_SIZE = 1993883
YOUTUBE_URL = "http://www.youtube.com/watch?v=BaW_jenozKc"
YOUTUBE_FILE = "BaW_jenozKc.mp4"
DAILYMOTION_MD5 = "d363a50e9eb4f22ce90d08d15695bb47"
DAILYMOTION_URL = "http://www.dailymotion.com/video/x33vw9_tutoriel-de-youtubeur-dl-des-video_tech"
DAILYMOTION_FILE = "x33vw9.mp4"
METACAFE_SIZE = 5754305
METACAFE_URL = "http://www.metacafe.com/watch/yt-_aUehQsCQtM/the_electric_company_short_i_pbs_kids_go/"
METACAFE_FILE = "_aUehQsCQtM.flv"
BLIP_MD5 = "93c24d2f4e0782af13b8a7606ea97ba7"
BLIP_URL = "http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352"
BLIP_FILE = "5779306.m4v"
XVIDEO_MD5 = ""
XVIDEO_URL = ""
XVIDEO_FILE = ""
def test_youtube(self):
#let's download a file from youtube
with open(DownloadTest.PARAMETERS_FILE) as f:
fd = FileDownloader(json.load(f))
fd.add_info_extractor(YoutubeIE())
fd.download([DownloadTest.YOUTUBE_URL])
self.assertTrue(os.path.exists(DownloadTest.YOUTUBE_FILE))
self.assertEqual(os.path.getsize(DownloadTest.YOUTUBE_FILE), DownloadTest.YOUTUBE_SIZE)
def test_dailymotion(self):
with open(DownloadTest.PARAMETERS_FILE) as f:
fd = FileDownloader(json.load(f))
fd.add_info_extractor(DailymotionIE())
fd.download([DownloadTest.DAILYMOTION_URL])
self.assertTrue(os.path.exists(DownloadTest.DAILYMOTION_FILE))
md5_down_file = md5_for_file(DownloadTest.DAILYMOTION_FILE)
self.assertEqual(md5_down_file, DownloadTest.DAILYMOTION_MD5)
def test_metacafe(self):
#this emulate a skip,to be 2.6 compatible
with open(DownloadTest.PARAMETERS_FILE) as f:
fd = FileDownloader(json.load(f))
fd.add_info_extractor(MetacafeIE())
fd.add_info_extractor(YoutubeIE())
fd.download([DownloadTest.METACAFE_URL])
self.assertTrue(os.path.exists(DownloadTest.METACAFE_FILE))
self.assertEqual(os.path.getsize(DownloadTest.METACAFE_FILE), DownloadTest.METACAFE_SIZE)
def test_blip(self):
with open(DownloadTest.PARAMETERS_FILE) as f:
fd = FileDownloader(json.load(f))
fd.add_info_extractor(BlipTVIE())
fd.download([DownloadTest.BLIP_URL])
self.assertTrue(os.path.exists(DownloadTest.BLIP_FILE))
md5_down_file = md5_for_file(DownloadTest.BLIP_FILE)
self.assertEqual(md5_down_file, DownloadTest.BLIP_MD5)
def tearDown(self):
if os.path.exists(DownloadTest.YOUTUBE_FILE):
os.remove(DownloadTest.YOUTUBE_FILE)
if os.path.exists(DownloadTest.DAILYMOTION_FILE):
os.remove(DownloadTest.DAILYMOTION_FILE)
if os.path.exists(DownloadTest.METACAFE_FILE):
os.remove(DownloadTest.METACAFE_FILE)
if os.path.exists(DownloadTest.BLIP_FILE):
os.remove(DownloadTest.BLIP_FILE)
def md5_for_file(filename, block_size=2**20):
with open(filename) as f:
md5 = hashlib.md5()
while True:
data = f.read(block_size)
if not data:
break
md5.update(data)
return md5.hexdigest()

47
test/test_utils.py Normal file
View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Various small unit tests
import unittest
#from youtube_dl.utils import htmlentity_transform
from youtube_dl.utils import timeconvert
from youtube_dl.utils import sanitize_filename
from youtube_dl.utils import unescapeHTML
from youtube_dl.utils import orderedSet
class TestUtil(unittest.TestCase):
def test_timeconvert(self):
self.assertTrue(timeconvert('') is None)
self.assertTrue(timeconvert('bougrg') is None)
def test_sanitize_filename(self):
self.assertEqual(sanitize_filename(u'abc'), u'abc')
self.assertEqual(sanitize_filename(u'abc_d-e'), u'abc_d-e')
self.assertEqual(sanitize_filename(u'123'), u'123')
self.assertEqual(u'abc-de', sanitize_filename(u'abc/de'))
self.assertFalse(u'/' in sanitize_filename(u'abc/de///'))
self.assertEqual(u'abc-de', sanitize_filename(u'abc/<>\\*|de'))
self.assertEqual(u'xxx', sanitize_filename(u'xxx/<>\\*|'))
self.assertEqual(u'yes no', sanitize_filename(u'yes? no'))
self.assertEqual(u'this - that', sanitize_filename(u'this: that'))
self.assertEqual(sanitize_filename(u'ä'), u'ä')
self.assertEqual(sanitize_filename(u'кириллица'), u'кириллица')
for forbidden in u'"\0\\/':
self.assertTrue(forbidden not in sanitize_filename(forbidden))
def test_ordered_set(self):
self.assertEqual(orderedSet([1,1,2,3,4,4,5,6,7,3,5]), [1,2,3,4,5,6,7])
self.assertEqual(orderedSet([]), [])
self.assertEqual(orderedSet([1]), [1])
#keep the list ordered
self.assertEqual(orderedSet([135,1,1,1]), [135,1])
def test_unescape_html(self):
self.assertEqual(unescapeHTML(u"%20;"), u"%20;")

Binary file not shown.

Binary file not shown.

View File

@@ -48,6 +48,7 @@ redistribute it or use it however you like.
.nf
\f[C]
-t,\ --title\ \ \ \ \ \ \ \ \ \ \ \ \ \ use\ title\ in\ file\ name
--id\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ use\ video\ ID\ in\ file\ name
-l,\ --literal\ \ \ \ \ \ \ \ \ \ \ \ use\ literal\ title\ in\ file\ name
-A,\ --auto-number\ \ \ \ \ \ \ \ number\ downloaded\ files\ starting\ from\ 00000
-o,\ --output\ TEMPLATE\ \ \ \ output\ filename\ template.\ Use\ %(stitle)s\ to\ get\ the
@@ -119,7 +120,7 @@ redistribute it or use it however you like.
.IP
.nf
\f[C]
--extract-audio\ \ \ \ \ \ \ \ \ \ convert\ video\ files\ to\ audio-only\ files\ (requires
-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

View File

@@ -3,7 +3,7 @@ __youtube-dl()
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
opts="--all-formats --audio-format --audio-quality --auto-number --batch-file --console-title --continue --cookies --dump-user-agent --extract-audio --format --get-description --get-filename --get-format --get-thumbnail --get-title --get-url --help --ignore-errors --keep-video --list-extractors --list-formats --literal --match-title --max-downloads --max-quality --netrc --no-continue --no-mtime --no-overwrites --no-part --no-progress --output --password --playlist-end --playlist-start --prefer-free-formats --quiet --rate-limit --reject-title --retries --simulate --skip-download --srt-lang --title --update --user-agent --username --verbose --version --write-description --write-info-json --write-srt"
opts="--all-formats --audio-format --audio-quality --auto-number --batch-file --console-title --continue --cookies --dump-user-agent --extract-audio --format --get-description --get-filename --get-format --get-thumbnail --get-title --get-url --help --id --ignore-errors --keep-video --list-extractors --list-formats --literal --match-title --max-downloads --max-quality --netrc --no-continue --no-mtime --no-overwrites --no-part --no-progress --output --password --playlist-end --playlist-start --prefer-free-formats --quiet --rate-limit --reject-title --retries --simulate --skip-download --srt-lang --title --update --user-agent --username --verbose --version --write-description --write-info-json --write-srt"
if [[ ${cur} == * ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )

Binary file not shown.

View File

@@ -13,7 +13,7 @@ import urllib2
if os.name == 'nt':
import ctypes
from utils import *
@@ -173,7 +173,6 @@ class FileDownloader(object):
if not self.params.get('quiet', False):
terminator = [u'\n', u''][skip_eol]
output = message + terminator
if 'b' not in self._screen_file.mode or sys.version_info[0] < 3: # Python 2 lies about the mode of sys.stdout/sys.stderr
output = output.encode(preferredencoding(), 'ignore')
self._screen_file.write(output)
@@ -334,11 +333,15 @@ class FileDownloader(object):
title = info_dict['title']
matchtitle = self.params.get('matchtitle', False)
if matchtitle and not re.search(matchtitle, title, re.IGNORECASE):
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
if matchtitle:
matchtitle = matchtitle.decode('utf8')
if not re.search(matchtitle, title, re.IGNORECASE):
return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
rejecttitle = self.params.get('rejecttitle', False)
if rejecttitle and re.search(rejecttitle, title, re.IGNORECASE):
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
if rejecttitle:
rejecttitle = rejecttitle.decode('utf8')
if re.search(rejecttitle, title, re.IGNORECASE):
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
return None
def process_info(self, info_dict):
@@ -357,7 +360,7 @@ class FileDownloader(object):
raise MaxDownloadsReached()
filename = self.prepare_filename(info_dict)
# Forced printings
if self.params.get('forcetitle', False):
print info_dict['title'].encode(preferredencoding(), 'xmlcharrefreplace')
@@ -399,10 +402,10 @@ class FileDownloader(object):
except (OSError, IOError):
self.trouble(u'ERROR: Cannot write description file ' + descfn)
return
if self.params.get('writesubtitles', False) and 'subtitles' in info_dict and info_dict['subtitles']:
# subtitles download errors are already managed as troubles in relevant IE
# that way it will silently go on when used with unsupporting IE
# that way it will silently go on when used with unsupporting IE
try:
srtfn = filename.rsplit('.', 1)[0] + u'.srt'
self.report_writesubtitles(srtfn)
@@ -448,7 +451,7 @@ class FileDownloader(object):
except (ContentTooShortError, ), err:
self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
return
if success:
try:
self.post_process(filename, info_dict)

View File

@@ -102,6 +102,7 @@ class YoutubeIE(InfoExtractor):
(?:https?://)? # http(s):// (optional)
(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|
tube\.majestyc\.net/) # the various hostnames, with wildcard subdomains
(?:.*?\#/)? # handle anchor (#/) redirect urls
(?!view_play_list|my_playlists|artist|playlist) # ignore playlist URLs
(?: # the various things that can precede the ID:
(?:(?:v|embed|e)/) # v/ or embed/ or e/
@@ -413,6 +414,12 @@ class YoutubeIE(InfoExtractor):
except Trouble as trouble:
self._downloader.trouble(trouble[0])
if 'length_seconds' not in video_info:
self._downloader.trouble(u'WARNING: unable to extract video duration')
video_duration = ''
else:
video_duration = urllib.unquote_plus(video_info['length_seconds'][0])
# token
video_token = urllib.unquote_plus(video_info['token'][0])
@@ -479,7 +486,8 @@ class YoutubeIE(InfoExtractor):
'thumbnail': video_thumbnail.decode('utf-8'),
'description': video_description,
'player_url': player_url,
'subtitles': video_subtitles
'subtitles': video_subtitles,
'duration': video_duration
})
return results
@@ -595,7 +603,7 @@ class MetacafeIE(InfoExtractor):
return
video_title = mobj.group(1).decode('utf-8')
mobj = re.search(r'(?ms)By:\s*<a .*?>(.+?)<', webpage)
mobj = re.search(r'submitter=(.*?);', webpage)
if mobj is None:
self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
return
@@ -684,9 +692,14 @@ class DailymotionIE(InfoExtractor):
video_title = unescapeHTML(mobj.group('title').decode('utf-8'))
video_uploader = u'NA'
mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a></span>', webpage)
mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a>', webpage)
if mobj is None:
self._downloader.trouble(u'WARNING: unable to extract uploader nickname')
# lookin for official user
mobj_official = re.search(r'<span rel="author"[^>]+?>([^<]+?)</span>', webpage)
if mobj_official is None:
self._downloader.trouble(u'WARNING: unable to extract uploader nickname')
else:
video_uploader = mobj_official.group(1)
else:
video_uploader = mobj.group(1)
@@ -1087,21 +1100,32 @@ class VimeoIE(InfoExtractor):
timestamp = config['request']['timestamp']
# Vimeo specific: extract video codec and quality information
# First consider quality, then codecs, then take everything
# TODO bind to format param
codecs = [('h264', 'mp4'), ('vp8', 'flv'), ('vp6', 'flv')]
for codec in codecs:
if codec[0] in config["video"]["files"]:
video_codec = codec[0]
video_extension = codec[1]
if 'hd' in config["video"]["files"][codec[0]]: quality = 'hd'
else: quality = 'sd'
files = { 'hd': [], 'sd': [], 'other': []}
for codec_name, codec_extension in codecs:
if codec_name in config["video"]["files"]:
if 'hd' in config["video"]["files"][codec_name]:
files['hd'].append((codec_name, codec_extension, 'hd'))
elif 'sd' in config["video"]["files"][codec_name]:
files['sd'].append((codec_name, codec_extension, 'sd'))
else:
files['other'].append((codec_name, codec_extension, config["video"]["files"][codec_name][0]))
for quality in ('hd', 'sd', 'other'):
if len(files[quality]) > 0:
video_quality = files[quality][0][2]
video_codec = files[quality][0][0]
video_extension = files[quality][0][1]
self._downloader.to_screen(u'[vimeo] %s: Downloading %s file at %s quality' % (video_id, video_codec.upper(), video_quality))
break
else:
self._downloader.trouble(u'ERROR: no known codec found')
return
video_url = "http://player.vimeo.com/play_redirect?clip_id=%s&sig=%s&time=%s&quality=%s&codecs=%s&type=moogaloop_local&embed_location=" \
%(video_id, sig, timestamp, quality, video_codec.upper())
%(video_id, sig, timestamp, video_quality, video_codec.upper())
return [{
'id': video_id,
@@ -1508,9 +1532,9 @@ class YahooSearchIE(InfoExtractor):
class YoutubePlaylistIE(InfoExtractor):
"""Information Extractor for YouTube playlists."""
_VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL)?([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
_VALID_URL = r'(?:(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL|EC)?|PL|EC)([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
_TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
_VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;list=.*?%s'
_VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;([^&"]+&amp;)*list=.*?%s'
_MORE_PAGES_INDICATOR = r'yt-uix-pager-next'
IE_NAME = u'youtube:playlist'
@@ -1578,6 +1602,56 @@ class YoutubePlaylistIE(InfoExtractor):
return
class YoutubeChannelIE(InfoExtractor):
"""Information Extractor for YouTube channels."""
_VALID_URL = r"^(?:https?://)?(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/([0-9A-Za-z_-]+)(?:/.*)?$"
_TEMPLATE_URL = 'http://www.youtube.com/channel/%s/videos?sort=da&flow=list&view=0&page=%s&gl=US&hl=en'
_MORE_PAGES_INDICATOR = r'yt-uix-button-content">Next' # TODO
IE_NAME = u'youtube:channel'
def report_download_page(self, channel_id, pagenum):
"""Report attempt to download channel page with given number."""
self._downloader.to_screen(u'[youtube] Channel %s: Downloading page #%s' % (channel_id, pagenum))
def _real_extract(self, url):
# Extract channel id
mobj = re.match(self._VALID_URL, url)
if mobj is None:
self._downloader.trouble(u'ERROR: invalid url: %s' % url)
return
# Download channel pages
channel_id = mobj.group(1)
video_ids = []
pagenum = 1
while True:
self.report_download_page(channel_id, pagenum)
url = self._TEMPLATE_URL % (channel_id, pagenum)
request = urllib2.Request(url)
try:
page = urllib2.urlopen(request).read()
except (urllib2.URLError, httplib.HTTPException, socket.error), err:
self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
return
# Extract video identifiers
ids_in_page = []
for mobj in re.finditer(r'href="/watch\?v=([0-9A-Za-z_-]+)&', page):
if mobj.group(1) not in ids_in_page:
ids_in_page.append(mobj.group(1))
video_ids.extend(ids_in_page)
if re.search(self._MORE_PAGES_INDICATOR, page) is None:
break
pagenum = pagenum + 1
for id in video_ids:
self._downloader.download(['http://www.youtube.com/watch?v=%s' % id])
return
class YoutubeUserIE(InfoExtractor):
"""Information Extractor for YouTube users."""

View File

@@ -73,7 +73,7 @@ class FFmpegExtractAudioPP(PostProcessor):
def detect_executables():
def executable(exe):
try:
subprocess.check_output([exe, '-version'])
subprocess.Popen([exe, '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
except OSError:
return False
return exe
@@ -146,7 +146,7 @@ class FFmpegExtractAudioPP(PostProcessor):
if int(self._preferredquality) < 10:
more_opts += [self._exes['avconv'] and '-q:a' or '-aq', self._preferredquality]
else:
more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality]
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]
@@ -156,7 +156,7 @@ class FFmpegExtractAudioPP(PostProcessor):
if int(self._preferredquality) < 10:
more_opts += [self._exes['avconv'] and '-q:a' or '-aq', self._preferredquality]
else:
more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality]
more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality + 'k']
if self._preferredcodec == 'aac':
more_opts += ['-f', 'adts']
if self._preferredcodec == 'm4a':

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
__authors__ = (
'Ricardo Garcia Gonzalez',
'Danny Colligan',
@@ -19,7 +21,7 @@ __authors__ = (
)
__license__ = 'Public Domain'
__version__ = '2012.10.09'
__version__ = '2012.11.17'
UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
UPDATE_URL_VERSION = 'https://raw.github.com/rg3/youtube-dl/master/LATEST_VERSION'
@@ -263,6 +265,8 @@ def parseOpts():
filesystem.add_option('-t', '--title',
action='store_true', dest='usetitle', help='use title in file name', default=False)
filesystem.add_option('--id',
action='store_true', dest='useid', help='use video ID in file name', default=False)
filesystem.add_option('-l', '--literal',
action='store_true', dest='useliteral', help='use literal title in file name', default=False)
filesystem.add_option('-A', '--auto-number',
@@ -294,7 +298,7 @@ def parseOpts():
help='write video metadata to a .info.json file', default=False)
postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False,
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')
@@ -328,6 +332,7 @@ def gen_extractors():
"""
return [
YoutubePlaylistIE(),
YoutubeChannelIE(),
YoutubeUserIE(),
YoutubeSearchIE(),
YoutubeIE(),
@@ -421,10 +426,14 @@ def _real_main():
parser.error(u'using .netrc conflicts with giving username/password')
if opts.password is not None and opts.username is None:
parser.error(u'account username missing')
if opts.outtmpl is not None and (opts.useliteral or opts.usetitle or opts.autonumber):
parser.error(u'using output template conflicts with using title, literal title or auto number')
if opts.outtmpl is not None and (opts.useliteral or opts.usetitle or opts.autonumber or opts.useid):
parser.error(u'using output template conflicts with using title, literal title, video ID or auto number')
if opts.usetitle and opts.useliteral:
parser.error(u'using title conflicts with using literal title')
if opts.usetitle and opts.useid:
parser.error(u'using title conflicts with using video ID')
if opts.useliteral and opts.useid:
parser.error(u'using literal title conflicts with using video ID')
if opts.username is not None and opts.password is None:
opts.password = getpass.getpass(u'Type account password and press return:')
if opts.ratelimit is not None:
@@ -482,6 +491,7 @@ def _real_main():
or (opts.useliteral and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
or (opts.usetitle and u'%(stitle)s-%(id)s.%(ext)s')
or (opts.useliteral and u'%(title)s-%(id)s.%(ext)s')
or (opts.useid and u'%(id)s.%(ext)s')
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
or u'%(id)s.%(ext)s'),
'ignoreerrors': opts.ignoreerrors,

View File

@@ -83,7 +83,7 @@ class IDParser(HTMLParser.HTMLParser):
HTMLParser.HTMLParser.__init__(self)
def error(self, message):
print >> sys.stderr, self.getpos()
#print >> sys.stderr, self.getpos()
if self.error_count > 10 or self.started:
raise HTMLParser.HTMLParseError(message, self.getpos())
self.rawdata = '\n'.join(self.html.split('\n')[self.getpos()[0]:]) # skip one line
@@ -194,10 +194,20 @@ def timeconvert(timestr):
def sanitize_filename(s):
"""Sanitizes a string so it could be used as part of a filename."""
def replace_insane(char):
if char in u' .\\/|?*<>:"' or ord(char) < 32:
return '_'
if char == '?' or ord(char) < 32 or ord(char) == 127:
return ''
elif char == '"':
return '\''
elif char == ':':
return ' -'
elif char in '\\/|*<>':
return '-'
return char
return u''.join(map(replace_insane, s)).strip('_')
result = u''.join(map(replace_insane, s))
while '--' in result:
result = result.replace('--', '-')
return result.strip('-')
def orderedSet(iterable):
""" Remove all duplicates from the input iterable """