Compare commits
52 Commits
2012.10.09
...
2012.11.17
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5002aea371 | ||
![]() |
74033a662d | ||
![]() |
0526e4f55a | ||
![]() |
39973a0236 | ||
![]() |
5d40a470a2 | ||
![]() |
4cc391461a | ||
![]() |
bf95333e5e | ||
![]() |
b7a34316d2 | ||
![]() |
74e453bdea | ||
![]() |
156a59e7a9 | ||
![]() |
aeca861f22 | ||
![]() |
42cb53fcfa | ||
![]() |
fe4d68e196 | ||
![]() |
25b7fd9c01 | ||
![]() |
e79e8b7dc4 | ||
![]() |
965a8b2bc4 | ||
![]() |
f06eaa873e | ||
![]() |
ece34e8951 | ||
![]() |
2262a32dd7 | ||
![]() |
c6c0e23a32 | ||
![]() |
02b324a23d | ||
![]() |
b8005afc20 | ||
![]() |
073522bc6c | ||
![]() |
9248cb0549 | ||
![]() |
6b41b61119 | ||
![]() |
591bbe9c90 | ||
![]() |
fc7376016c | ||
![]() |
97a37c2319 | ||
![]() |
3afed78a6a | ||
![]() |
4279a0ca98 | ||
![]() |
edcc7d2dd3 | ||
![]() |
7f60b5aa40 | ||
![]() |
aeeb29a356 | ||
![]() |
902b2a0a45 | ||
![]() |
6d9c22cd26 | ||
![]() |
729baf58b2 | ||
![]() |
4c9afeca34 | ||
![]() |
6da7877bf5 | ||
![]() |
b4e5de51ec | ||
![]() |
a4b5f22554 | ||
![]() |
ff08984246 | ||
![]() |
137c5803c3 | ||
![]() |
3eec021a1f | ||
![]() |
5a33b73309 | ||
![]() |
0b4e98490b | ||
![]() |
80a846e119 | ||
![]() |
434d60cd95 | ||
![]() |
efe8902f0b | ||
![]() |
44fb345437 | ||
![]() |
9993976ae4 | ||
![]() |
b387fb0385 | ||
![]() |
10daa766a1 |
9
.travis.yml
Normal file
9
.travis.yml
Normal 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
|
@@ -1 +1 @@
|
||||
2012.10.09
|
||||
2012.11.17
|
||||
|
18
Makefile
18
Makefile
@@ -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
|
||||
|
@@ -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
1
test/parameters.json
Normal 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}
|
@@ -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
93
test/test_download.py
Normal 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
47
test/test_utils.py
Normal 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.
BIN
youtube-dl
BIN
youtube-dl
Binary file not shown.
@@ -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
|
||||
|
@@ -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}) )
|
||||
|
BIN
youtube-dl.exe
BIN
youtube-dl.exe
Binary file not shown.
@@ -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)
|
||||
|
@@ -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=(.+?)&list=.*?%s'
|
||||
_VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&([^&"]+&)*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."""
|
||||
|
||||
|
@@ -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':
|
||||
|
@@ -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,
|
||||
|
@@ -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 """
|
||||
|
Reference in New Issue
Block a user