]> Raphaël G. Git Repositories - youtubedl/blob - youtube-dl
Imported Upstream version 2011.09.27
[youtubedl] / youtube-dl
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 __author__ = (
5 'Ricardo Garcia Gonzalez',
6 'Danny Colligan',
7 'Benjamin Johnson',
8 'Vasyl\' Vavrychuk',
9 'Witold Baryluk',
10 'Paweł Paprota',
11 'Gergely Imreh',
12 'Rogério Brito',
13 'Philipp Hagemeister',
14 'Sören Schulze',
15 )
16
17 __license__ = 'Public Domain'
18 __version__ = '2011.09.27'
19
20 UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
21
22 import cookielib
23 import datetime
24 import gzip
25 import htmlentitydefs
26 import HTMLParser
27 import httplib
28 import locale
29 import math
30 import netrc
31 import os
32 import os.path
33 import re
34 import socket
35 import string
36 import subprocess
37 import sys
38 import time
39 import urllib
40 import urllib2
41 import warnings
42 import zlib
43
44 if os.name == 'nt':
45 import ctypes
46
47 try:
48 import email.utils
49 except ImportError: # Python 2.4
50 import email.Utils
51 try:
52 import cStringIO as StringIO
53 except ImportError:
54 import StringIO
55
56 # parse_qs was moved from the cgi module to the urlparse module recently.
57 try:
58 from urlparse import parse_qs
59 except ImportError:
60 from cgi import parse_qs
61
62 try:
63 import lxml.etree
64 except ImportError:
65 pass # Handled below
66
67 try:
68 import xml.etree.ElementTree
69 except ImportError: # Python<2.5: Not officially supported, but let it slip
70 warnings.warn('xml.etree.ElementTree support is missing. Consider upgrading to Python >= 2.5 if you get related errors.')
71
72 std_headers = {
73 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:5.0.1) Gecko/20100101 Firefox/5.0.1',
74 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
75 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
76 'Accept-Encoding': 'gzip, deflate',
77 'Accept-Language': 'en-us,en;q=0.5',
78 }
79
80 simple_title_chars = string.ascii_letters.decode('ascii') + string.digits.decode('ascii')
81
82 try:
83 import json
84 except ImportError: # Python <2.6, use trivialjson (https://github.com/phihag/trivialjson):
85 import re
86 class json(object):
87 @staticmethod
88 def loads(s):
89 s = s.decode('UTF-8')
90 def raiseError(msg, i):
91 raise ValueError(msg + ' at position ' + str(i) + ' of ' + repr(s) + ': ' + repr(s[i:]))
92 def skipSpace(i, expectMore=True):
93 while i < len(s) and s[i] in ' \t\r\n':
94 i += 1
95 if expectMore:
96 if i >= len(s):
97 raiseError('Premature end', i)
98 return i
99 def decodeEscape(match):
100 esc = match.group(1)
101 _STATIC = {
102 '"': '"',
103 '\\': '\\',
104 '/': '/',
105 'b': unichr(0x8),
106 'f': unichr(0xc),
107 'n': '\n',
108 'r': '\r',
109 't': '\t',
110 }
111 if esc in _STATIC:
112 return _STATIC[esc]
113 if esc[0] == 'u':
114 if len(esc) == 1+4:
115 return unichr(int(esc[1:5], 16))
116 if len(esc) == 5+6 and esc[5:7] == '\\u':
117 hi = int(esc[1:5], 16)
118 low = int(esc[7:11], 16)
119 return unichr((hi - 0xd800) * 0x400 + low - 0xdc00 + 0x10000)
120 raise ValueError('Unknown escape ' + str(esc))
121 def parseString(i):
122 i += 1
123 e = i
124 while True:
125 e = s.index('"', e)
126 bslashes = 0
127 while s[e-bslashes-1] == '\\':
128 bslashes += 1
129 if bslashes % 2 == 1:
130 e += 1
131 continue
132 break
133 rexp = re.compile(r'\\(u[dD][89aAbB][0-9a-fA-F]{2}\\u[0-9a-fA-F]{4}|u[0-9a-fA-F]{4}|.|$)')
134 stri = rexp.sub(decodeEscape, s[i:e])
135 return (e+1,stri)
136 def parseObj(i):
137 i += 1
138 res = {}
139 i = skipSpace(i)
140 if s[i] == '}': # Empty dictionary
141 return (i+1,res)
142 while True:
143 if s[i] != '"':
144 raiseError('Expected a string object key', i)
145 i,key = parseString(i)
146 i = skipSpace(i)
147 if i >= len(s) or s[i] != ':':
148 raiseError('Expected a colon', i)
149 i,val = parse(i+1)
150 res[key] = val
151 i = skipSpace(i)
152 if s[i] == '}':
153 return (i+1, res)
154 if s[i] != ',':
155 raiseError('Expected comma or closing curly brace', i)
156 i = skipSpace(i+1)
157 def parseArray(i):
158 res = []
159 i = skipSpace(i+1)
160 if s[i] == ']': # Empty array
161 return (i+1,res)
162 while True:
163 i,val = parse(i)
164 res.append(val)
165 i = skipSpace(i) # Raise exception if premature end
166 if s[i] == ']':
167 return (i+1, res)
168 if s[i] != ',':
169 raiseError('Expected a comma or closing bracket', i)
170 i = skipSpace(i+1)
171 def parseDiscrete(i):
172 for k,v in {'true': True, 'false': False, 'null': None}.items():
173 if s.startswith(k, i):
174 return (i+len(k), v)
175 raiseError('Not a boolean (or null)', i)
176 def parseNumber(i):
177 mobj = re.match('^(-?(0|[1-9][0-9]*)(\.[0-9]*)?([eE][+-]?[0-9]+)?)', s[i:])
178 if mobj is None:
179 raiseError('Not a number', i)
180 nums = mobj.group(1)
181 if '.' in nums or 'e' in nums or 'E' in nums:
182 return (i+len(nums), float(nums))
183 return (i+len(nums), int(nums))
184 CHARMAP = {'{': parseObj, '[': parseArray, '"': parseString, 't': parseDiscrete, 'f': parseDiscrete, 'n': parseDiscrete}
185 def parse(i):
186 i = skipSpace(i)
187 i,res = CHARMAP.get(s[i], parseNumber)(i)
188 i = skipSpace(i, False)
189 return (i,res)
190 i,res = parse(0)
191 if i < len(s):
192 raise ValueError('Extra data at end of input (index ' + str(i) + ' of ' + repr(s) + ': ' + repr(s[i:]) + ')')
193 return res
194
195 def preferredencoding():
196 """Get preferred encoding.
197
198 Returns the best encoding scheme for the system, based on
199 locale.getpreferredencoding() and some further tweaks.
200 """
201 def yield_preferredencoding():
202 try:
203 pref = locale.getpreferredencoding()
204 u'TEST'.encode(pref)
205 except:
206 pref = 'UTF-8'
207 while True:
208 yield pref
209 return yield_preferredencoding().next()
210
211
212 def htmlentity_transform(matchobj):
213 """Transforms an HTML entity to a Unicode character.
214
215 This function receives a match object and is intended to be used with
216 the re.sub() function.
217 """
218 entity = matchobj.group(1)
219
220 # Known non-numeric HTML entity
221 if entity in htmlentitydefs.name2codepoint:
222 return unichr(htmlentitydefs.name2codepoint[entity])
223
224 # Unicode character
225 mobj = re.match(ur'(?u)#(x?\d+)', entity)
226 if mobj is not None:
227 numstr = mobj.group(1)
228 if numstr.startswith(u'x'):
229 base = 16
230 numstr = u'0%s' % numstr
231 else:
232 base = 10
233 return unichr(long(numstr, base))
234
235 # Unknown entity in name, return its literal representation
236 return (u'&%s;' % entity)
237
238
239 def sanitize_title(utitle):
240 """Sanitizes a video title so it could be used as part of a filename."""
241 utitle = re.sub(ur'(?u)&(.+?);', htmlentity_transform, utitle)
242 return utitle.replace(unicode(os.sep), u'%')
243
244
245 def sanitize_open(filename, open_mode):
246 """Try to open the given filename, and slightly tweak it if this fails.
247
248 Attempts to open the given filename. If this fails, it tries to change
249 the filename slightly, step by step, until it's either able to open it
250 or it fails and raises a final exception, like the standard open()
251 function.
252
253 It returns the tuple (stream, definitive_file_name).
254 """
255 try:
256 if filename == u'-':
257 if sys.platform == 'win32':
258 import msvcrt
259 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
260 return (sys.stdout, filename)
261 stream = open(filename, open_mode)
262 return (stream, filename)
263 except (IOError, OSError), err:
264 # In case of error, try to remove win32 forbidden chars
265 filename = re.sub(ur'[/<>:"\|\?\*]', u'#', filename)
266
267 # An exception here should be caught in the caller
268 stream = open(filename, open_mode)
269 return (stream, filename)
270
271
272 def timeconvert(timestr):
273 """Convert RFC 2822 defined time string into system timestamp"""
274 timestamp = None
275 timetuple = email.utils.parsedate_tz(timestr)
276 if timetuple is not None:
277 timestamp = email.utils.mktime_tz(timetuple)
278 return timestamp
279
280
281 class DownloadError(Exception):
282 """Download Error exception.
283
284 This exception may be thrown by FileDownloader objects if they are not
285 configured to continue on errors. They will contain the appropriate
286 error message.
287 """
288 pass
289
290
291 class SameFileError(Exception):
292 """Same File exception.
293
294 This exception will be thrown by FileDownloader objects if they detect
295 multiple files would have to be downloaded to the same file on disk.
296 """
297 pass
298
299
300 class PostProcessingError(Exception):
301 """Post Processing exception.
302
303 This exception may be raised by PostProcessor's .run() method to
304 indicate an error in the postprocessing task.
305 """
306 pass
307
308
309 class UnavailableVideoError(Exception):
310 """Unavailable Format exception.
311
312 This exception will be thrown when a video is requested
313 in a format that is not available for that video.
314 """
315 pass
316
317
318 class ContentTooShortError(Exception):
319 """Content Too Short exception.
320
321 This exception may be raised by FileDownloader objects when a file they
322 download is too small for what the server announced first, indicating
323 the connection was probably interrupted.
324 """
325 # Both in bytes
326 downloaded = None
327 expected = None
328
329 def __init__(self, downloaded, expected):
330 self.downloaded = downloaded
331 self.expected = expected
332
333
334 class YoutubeDLHandler(urllib2.HTTPHandler):
335 """Handler for HTTP requests and responses.
336
337 This class, when installed with an OpenerDirector, automatically adds
338 the standard headers to every HTTP request and handles gzipped and
339 deflated responses from web servers. If compression is to be avoided in
340 a particular request, the original request in the program code only has
341 to include the HTTP header "Youtubedl-No-Compression", which will be
342 removed before making the real request.
343
344 Part of this code was copied from:
345
346 http://techknack.net/python-urllib2-handlers/
347
348 Andrew Rowls, the author of that code, agreed to release it to the
349 public domain.
350 """
351
352 @staticmethod
353 def deflate(data):
354 try:
355 return zlib.decompress(data, -zlib.MAX_WBITS)
356 except zlib.error:
357 return zlib.decompress(data)
358
359 @staticmethod
360 def addinfourl_wrapper(stream, headers, url, code):
361 if hasattr(urllib2.addinfourl, 'getcode'):
362 return urllib2.addinfourl(stream, headers, url, code)
363 ret = urllib2.addinfourl(stream, headers, url)
364 ret.code = code
365 return ret
366
367 def http_request(self, req):
368 for h in std_headers:
369 if h in req.headers:
370 del req.headers[h]
371 req.add_header(h, std_headers[h])
372 if 'Youtubedl-no-compression' in req.headers:
373 if 'Accept-encoding' in req.headers:
374 del req.headers['Accept-encoding']
375 del req.headers['Youtubedl-no-compression']
376 return req
377
378 def http_response(self, req, resp):
379 old_resp = resp
380 # gzip
381 if resp.headers.get('Content-encoding', '') == 'gzip':
382 gz = gzip.GzipFile(fileobj=StringIO.StringIO(resp.read()), mode='r')
383 resp = self.addinfourl_wrapper(gz, old_resp.headers, old_resp.url, old_resp.code)
384 resp.msg = old_resp.msg
385 # deflate
386 if resp.headers.get('Content-encoding', '') == 'deflate':
387 gz = StringIO.StringIO(self.deflate(resp.read()))
388 resp = self.addinfourl_wrapper(gz, old_resp.headers, old_resp.url, old_resp.code)
389 resp.msg = old_resp.msg
390 return resp
391
392
393 class FileDownloader(object):
394 """File Downloader class.
395
396 File downloader objects are the ones responsible of downloading the
397 actual video file and writing it to disk if the user has requested
398 it, among some other tasks. In most cases there should be one per
399 program. As, given a video URL, the downloader doesn't know how to
400 extract all the needed information, task that InfoExtractors do, it
401 has to pass the URL to one of them.
402
403 For this, file downloader objects have a method that allows
404 InfoExtractors to be registered in a given order. When it is passed
405 a URL, the file downloader handles it to the first InfoExtractor it
406 finds that reports being able to handle it. The InfoExtractor extracts
407 all the information about the video or videos the URL refers to, and
408 asks the FileDownloader to process the video information, possibly
409 downloading the video.
410
411 File downloaders accept a lot of parameters. In order not to saturate
412 the object constructor with arguments, it receives a dictionary of
413 options instead. These options are available through the params
414 attribute for the InfoExtractors to use. The FileDownloader also
415 registers itself as the downloader in charge for the InfoExtractors
416 that are added to it, so this is a "mutual registration".
417
418 Available options:
419
420 username: Username for authentication purposes.
421 password: Password for authentication purposes.
422 usenetrc: Use netrc for authentication instead.
423 quiet: Do not print messages to stdout.
424 forceurl: Force printing final URL.
425 forcetitle: Force printing title.
426 forcethumbnail: Force printing thumbnail URL.
427 forcedescription: Force printing description.
428 forcefilename: Force printing final filename.
429 simulate: Do not download the video files.
430 format: Video format code.
431 format_limit: Highest quality format to try.
432 outtmpl: Template for output names.
433 ignoreerrors: Do not stop on download errors.
434 ratelimit: Download speed limit, in bytes/sec.
435 nooverwrites: Prevent overwriting files.
436 retries: Number of times to retry for HTTP error 5xx
437 continuedl: Try to continue downloads if possible.
438 noprogress: Do not print the progress bar.
439 playliststart: Playlist item to start at.
440 playlistend: Playlist item to end at.
441 matchtitle: Download only matching titles.
442 rejecttitle: Reject downloads for matching titles.
443 logtostderr: Log messages to stderr instead of stdout.
444 consoletitle: Display progress in console window's titlebar.
445 nopart: Do not use temporary .part files.
446 updatetime: Use the Last-modified header to set output file timestamps.
447 writedescription: Write the video description to a .description file
448 writeinfojson: Write the video description to a .info.json file
449 """
450
451 params = None
452 _ies = []
453 _pps = []
454 _download_retcode = None
455 _num_downloads = None
456 _screen_file = None
457
458 def __init__(self, params):
459 """Create a FileDownloader object with the given options."""
460 self._ies = []
461 self._pps = []
462 self._download_retcode = 0
463 self._num_downloads = 0
464 self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
465 self.params = params
466
467 @staticmethod
468 def format_bytes(bytes):
469 if bytes is None:
470 return 'N/A'
471 if type(bytes) is str:
472 bytes = float(bytes)
473 if bytes == 0.0:
474 exponent = 0
475 else:
476 exponent = long(math.log(bytes, 1024.0))
477 suffix = 'bkMGTPEZY'[exponent]
478 converted = float(bytes) / float(1024 ** exponent)
479 return '%.2f%s' % (converted, suffix)
480
481 @staticmethod
482 def calc_percent(byte_counter, data_len):
483 if data_len is None:
484 return '---.-%'
485 return '%6s' % ('%3.1f%%' % (float(byte_counter) / float(data_len) * 100.0))
486
487 @staticmethod
488 def calc_eta(start, now, total, current):
489 if total is None:
490 return '--:--'
491 dif = now - start
492 if current == 0 or dif < 0.001: # One millisecond
493 return '--:--'
494 rate = float(current) / dif
495 eta = long((float(total) - float(current)) / rate)
496 (eta_mins, eta_secs) = divmod(eta, 60)
497 if eta_mins > 99:
498 return '--:--'
499 return '%02d:%02d' % (eta_mins, eta_secs)
500
501 @staticmethod
502 def calc_speed(start, now, bytes):
503 dif = now - start
504 if bytes == 0 or dif < 0.001: # One millisecond
505 return '%10s' % '---b/s'
506 return '%10s' % ('%s/s' % FileDownloader.format_bytes(float(bytes) / dif))
507
508 @staticmethod
509 def best_block_size(elapsed_time, bytes):
510 new_min = max(bytes / 2.0, 1.0)
511 new_max = min(max(bytes * 2.0, 1.0), 4194304) # Do not surpass 4 MB
512 if elapsed_time < 0.001:
513 return long(new_max)
514 rate = bytes / elapsed_time
515 if rate > new_max:
516 return long(new_max)
517 if rate < new_min:
518 return long(new_min)
519 return long(rate)
520
521 @staticmethod
522 def parse_bytes(bytestr):
523 """Parse a string indicating a byte quantity into a long integer."""
524 matchobj = re.match(r'(?i)^(\d+(?:\.\d+)?)([kMGTPEZY]?)$', bytestr)
525 if matchobj is None:
526 return None
527 number = float(matchobj.group(1))
528 multiplier = 1024.0 ** 'bkmgtpezy'.index(matchobj.group(2).lower())
529 return long(round(number * multiplier))
530
531 def add_info_extractor(self, ie):
532 """Add an InfoExtractor object to the end of the list."""
533 self._ies.append(ie)
534 ie.set_downloader(self)
535
536 def add_post_processor(self, pp):
537 """Add a PostProcessor object to the end of the chain."""
538 self._pps.append(pp)
539 pp.set_downloader(self)
540
541 def to_screen(self, message, skip_eol=False, ignore_encoding_errors=False):
542 """Print message to stdout if not in quiet mode."""
543 try:
544 if not self.params.get('quiet', False):
545 terminator = [u'\n', u''][skip_eol]
546 print >>self._screen_file, (u'%s%s' % (message, terminator)).encode(preferredencoding()),
547 self._screen_file.flush()
548 except (UnicodeEncodeError), err:
549 if not ignore_encoding_errors:
550 raise
551
552 def to_stderr(self, message):
553 """Print message to stderr."""
554 print >>sys.stderr, message.encode(preferredencoding())
555
556 def to_cons_title(self, message):
557 """Set console/terminal window title to message."""
558 if not self.params.get('consoletitle', False):
559 return
560 if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
561 # c_wchar_p() might not be necessary if `message` is
562 # already of type unicode()
563 ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
564 elif 'TERM' in os.environ:
565 sys.stderr.write('\033]0;%s\007' % message.encode(preferredencoding()))
566
567 def fixed_template(self):
568 """Checks if the output template is fixed."""
569 return (re.search(ur'(?u)%\(.+?\)s', self.params['outtmpl']) is None)
570
571 def trouble(self, message=None):
572 """Determine action to take when a download problem appears.
573
574 Depending on if the downloader has been configured to ignore
575 download errors or not, this method may throw an exception or
576 not when errors are found, after printing the message.
577 """
578 if message is not None:
579 self.to_stderr(message)
580 if not self.params.get('ignoreerrors', False):
581 raise DownloadError(message)
582 self._download_retcode = 1
583
584 def slow_down(self, start_time, byte_counter):
585 """Sleep if the download speed is over the rate limit."""
586 rate_limit = self.params.get('ratelimit', None)
587 if rate_limit is None or byte_counter == 0:
588 return
589 now = time.time()
590 elapsed = now - start_time
591 if elapsed <= 0.0:
592 return
593 speed = float(byte_counter) / elapsed
594 if speed > rate_limit:
595 time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit)
596
597 def temp_name(self, filename):
598 """Returns a temporary filename for the given filename."""
599 if self.params.get('nopart', False) or filename == u'-' or \
600 (os.path.exists(filename) and not os.path.isfile(filename)):
601 return filename
602 return filename + u'.part'
603
604 def undo_temp_name(self, filename):
605 if filename.endswith(u'.part'):
606 return filename[:-len(u'.part')]
607 return filename
608
609 def try_rename(self, old_filename, new_filename):
610 try:
611 if old_filename == new_filename:
612 return
613 os.rename(old_filename, new_filename)
614 except (IOError, OSError), err:
615 self.trouble(u'ERROR: unable to rename file')
616
617 def try_utime(self, filename, last_modified_hdr):
618 """Try to set the last-modified time of the given file."""
619 if last_modified_hdr is None:
620 return
621 if not os.path.isfile(filename):
622 return
623 timestr = last_modified_hdr
624 if timestr is None:
625 return
626 filetime = timeconvert(timestr)
627 if filetime is None:
628 return filetime
629 try:
630 os.utime(filename, (time.time(), filetime))
631 except:
632 pass
633 return filetime
634
635 def report_writedescription(self, descfn):
636 """ Report that the description file is being written """
637 self.to_screen(u'[info] Writing video description to: %s' % descfn, ignore_encoding_errors=True)
638
639 def report_writeinfojson(self, infofn):
640 """ Report that the metadata file has been written """
641 self.to_screen(u'[info] Video description metadata as JSON to: %s' % infofn, ignore_encoding_errors=True)
642
643 def report_destination(self, filename):
644 """Report destination filename."""
645 self.to_screen(u'[download] Destination: %s' % filename, ignore_encoding_errors=True)
646
647 def report_progress(self, percent_str, data_len_str, speed_str, eta_str):
648 """Report download progress."""
649 if self.params.get('noprogress', False):
650 return
651 self.to_screen(u'\r[download] %s of %s at %s ETA %s' %
652 (percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
653 self.to_cons_title(u'youtube-dl - %s of %s at %s ETA %s' %
654 (percent_str.strip(), data_len_str.strip(), speed_str.strip(), eta_str.strip()))
655
656 def report_resuming_byte(self, resume_len):
657 """Report attempt to resume at given byte."""
658 self.to_screen(u'[download] Resuming download at byte %s' % resume_len)
659
660 def report_retry(self, count, retries):
661 """Report retry in case of HTTP error 5xx"""
662 self.to_screen(u'[download] Got server HTTP error. Retrying (attempt %d of %d)...' % (count, retries))
663
664 def report_file_already_downloaded(self, file_name):
665 """Report file has already been fully downloaded."""
666 try:
667 self.to_screen(u'[download] %s has already been downloaded' % file_name)
668 except (UnicodeEncodeError), err:
669 self.to_screen(u'[download] The file has already been downloaded')
670
671 def report_unable_to_resume(self):
672 """Report it was impossible to resume download."""
673 self.to_screen(u'[download] Unable to resume')
674
675 def report_finish(self):
676 """Report download finished."""
677 if self.params.get('noprogress', False):
678 self.to_screen(u'[download] Download completed')
679 else:
680 self.to_screen(u'')
681
682 def increment_downloads(self):
683 """Increment the ordinal that assigns a number to each file."""
684 self._num_downloads += 1
685
686 def prepare_filename(self, info_dict):
687 """Generate the output filename."""
688 try:
689 template_dict = dict(info_dict)
690 template_dict['epoch'] = unicode(long(time.time()))
691 template_dict['autonumber'] = unicode('%05d' % self._num_downloads)
692 filename = self.params['outtmpl'] % template_dict
693 return filename
694 except (ValueError, KeyError), err:
695 self.trouble(u'ERROR: invalid system charset or erroneous output template')
696 return None
697
698 def process_info(self, info_dict):
699 """Process a single dictionary returned by an InfoExtractor."""
700 filename = self.prepare_filename(info_dict)
701
702 # Forced printings
703 if self.params.get('forcetitle', False):
704 print info_dict['title'].encode(preferredencoding(), 'xmlcharrefreplace')
705 if self.params.get('forceurl', False):
706 print info_dict['url'].encode(preferredencoding(), 'xmlcharrefreplace')
707 if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
708 print info_dict['thumbnail'].encode(preferredencoding(), 'xmlcharrefreplace')
709 if self.params.get('forcedescription', False) and 'description' in info_dict:
710 print info_dict['description'].encode(preferredencoding(), 'xmlcharrefreplace')
711 if self.params.get('forcefilename', False) and filename is not None:
712 print filename.encode(preferredencoding(), 'xmlcharrefreplace')
713 if self.params.get('forceformat', False):
714 print info_dict['format'].encode(preferredencoding(), 'xmlcharrefreplace')
715
716 # Do nothing else if in simulate mode
717 if self.params.get('simulate', False):
718 return
719
720 if filename is None:
721 return
722
723 matchtitle=self.params.get('matchtitle',False)
724 rejecttitle=self.params.get('rejecttitle',False)
725 title=info_dict['title'].encode(preferredencoding(), 'xmlcharrefreplace')
726 if matchtitle and not re.search(matchtitle, title, re.IGNORECASE):
727 self.to_screen(u'[download] "%s" title did not match pattern "%s"' % (title, matchtitle))
728 return
729 if rejecttitle and re.search(rejecttitle, title, re.IGNORECASE):
730 self.to_screen(u'[download] "%s" title matched reject pattern "%s"' % (title, rejecttitle))
731 return
732
733 if self.params.get('nooverwrites', False) and os.path.exists(filename):
734 self.to_stderr(u'WARNING: file exists and will be skipped')
735 return
736
737 try:
738 dn = os.path.dirname(filename)
739 if dn != '' and not os.path.exists(dn):
740 os.makedirs(dn)
741 except (OSError, IOError), err:
742 self.trouble(u'ERROR: unable to create directory ' + unicode(err))
743 return
744
745 if self.params.get('writedescription', False):
746 try:
747 descfn = filename + '.description'
748 self.report_writedescription(descfn)
749 descfile = open(descfn, 'wb')
750 try:
751 descfile.write(info_dict['description'].encode('utf-8'))
752 finally:
753 descfile.close()
754 except (OSError, IOError):
755 self.trouble(u'ERROR: Cannot write description file ' + descfn)
756 return
757
758 if self.params.get('writeinfojson', False):
759 infofn = filename + '.info.json'
760 self.report_writeinfojson(infofn)
761 try:
762 json.dump
763 except (NameError,AttributeError):
764 self.trouble(u'ERROR: No JSON encoder found. Update to Python 2.6+, setup a json module, or leave out --write-info-json.')
765 return
766 try:
767 infof = open(infofn, 'wb')
768 try:
769 json_info_dict = dict((k,v) for k,v in info_dict.iteritems() if not k in ('urlhandle',))
770 json.dump(json_info_dict, infof)
771 finally:
772 infof.close()
773 except (OSError, IOError):
774 self.trouble(u'ERROR: Cannot write metadata to JSON file ' + infofn)
775 return
776
777 if not self.params.get('skip_download', False):
778 try:
779 success = self._do_download(filename, info_dict)
780 except (OSError, IOError), err:
781 raise UnavailableVideoError
782 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
783 self.trouble(u'ERROR: unable to download video data: %s' % str(err))
784 return
785 except (ContentTooShortError, ), err:
786 self.trouble(u'ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
787 return
788
789 if success:
790 try:
791 self.post_process(filename, info_dict)
792 except (PostProcessingError), err:
793 self.trouble(u'ERROR: postprocessing: %s' % str(err))
794 return
795
796 def download(self, url_list):
797 """Download a given list of URLs."""
798 if len(url_list) > 1 and self.fixed_template():
799 raise SameFileError(self.params['outtmpl'])
800
801 for url in url_list:
802 suitable_found = False
803 for ie in self._ies:
804 # Go to next InfoExtractor if not suitable
805 if not ie.suitable(url):
806 continue
807
808 # Suitable InfoExtractor found
809 suitable_found = True
810
811 # Extract information from URL and process it
812 ie.extract(url)
813
814 # Suitable InfoExtractor had been found; go to next URL
815 break
816
817 if not suitable_found:
818 self.trouble(u'ERROR: no suitable InfoExtractor: %s' % url)
819
820 return self._download_retcode
821
822 def post_process(self, filename, ie_info):
823 """Run the postprocessing chain on the given file."""
824 info = dict(ie_info)
825 info['filepath'] = filename
826 for pp in self._pps:
827 info = pp.run(info)
828 if info is None:
829 break
830
831 def _download_with_rtmpdump(self, filename, url, player_url):
832 self.report_destination(filename)
833 tmpfilename = self.temp_name(filename)
834
835 # Check for rtmpdump first
836 try:
837 subprocess.call(['rtmpdump', '-h'], stdout=(file(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
838 except (OSError, IOError):
839 self.trouble(u'ERROR: RTMP download detected but "rtmpdump" could not be run')
840 return False
841
842 # Download using rtmpdump. rtmpdump returns exit code 2 when
843 # the connection was interrumpted and resuming appears to be
844 # possible. This is part of rtmpdump's normal usage, AFAIK.
845 basic_args = ['rtmpdump', '-q'] + [[], ['-W', player_url]][player_url is not None] + ['-r', url, '-o', tmpfilename]
846 retval = subprocess.call(basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)])
847 while retval == 2 or retval == 1:
848 prevsize = os.path.getsize(tmpfilename)
849 self.to_screen(u'\r[rtmpdump] %s bytes' % prevsize, skip_eol=True)
850 time.sleep(5.0) # This seems to be needed
851 retval = subprocess.call(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1])
852 cursize = os.path.getsize(tmpfilename)
853 if prevsize == cursize and retval == 1:
854 break
855 # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
856 if prevsize == cursize and retval == 2 and cursize > 1024:
857 self.to_screen(u'\r[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
858 retval = 0
859 break
860 if retval == 0:
861 self.to_screen(u'\r[rtmpdump] %s bytes' % os.path.getsize(tmpfilename))
862 self.try_rename(tmpfilename, filename)
863 return True
864 else:
865 self.trouble(u'\nERROR: rtmpdump exited with code %d' % retval)
866 return False
867
868 def _do_download(self, filename, info_dict):
869 url = info_dict['url']
870 player_url = info_dict.get('player_url', None)
871
872 # Check file already present
873 if self.params.get('continuedl', False) and os.path.isfile(filename) and not self.params.get('nopart', False):
874 self.report_file_already_downloaded(filename)
875 return True
876
877 # Attempt to download using rtmpdump
878 if url.startswith('rtmp'):
879 return self._download_with_rtmpdump(filename, url, player_url)
880
881 tmpfilename = self.temp_name(filename)
882 stream = None
883
884 # Do not include the Accept-Encoding header
885 headers = {'Youtubedl-no-compression': 'True'}
886 basic_request = urllib2.Request(url, None, headers)
887 request = urllib2.Request(url, None, headers)
888
889 # Establish possible resume length
890 if os.path.isfile(tmpfilename):
891 resume_len = os.path.getsize(tmpfilename)
892 else:
893 resume_len = 0
894
895 open_mode = 'wb'
896 if resume_len != 0:
897 if self.params.get('continuedl', False):
898 self.report_resuming_byte(resume_len)
899 request.add_header('Range','bytes=%d-' % resume_len)
900 open_mode = 'ab'
901 else:
902 resume_len = 0
903
904 count = 0
905 retries = self.params.get('retries', 0)
906 while count <= retries:
907 # Establish connection
908 try:
909 if count == 0 and 'urlhandle' in info_dict:
910 data = info_dict['urlhandle']
911 data = urllib2.urlopen(request)
912 break
913 except (urllib2.HTTPError, ), err:
914 if (err.code < 500 or err.code >= 600) and err.code != 416:
915 # Unexpected HTTP error
916 raise
917 elif err.code == 416:
918 # Unable to resume (requested range not satisfiable)
919 try:
920 # Open the connection again without the range header
921 data = urllib2.urlopen(basic_request)
922 content_length = data.info()['Content-Length']
923 except (urllib2.HTTPError, ), err:
924 if err.code < 500 or err.code >= 600:
925 raise
926 else:
927 # Examine the reported length
928 if (content_length is not None and
929 (resume_len - 100 < long(content_length) < resume_len + 100)):
930 # The file had already been fully downloaded.
931 # Explanation to the above condition: in issue #175 it was revealed that
932 # YouTube sometimes adds or removes a few bytes from the end of the file,
933 # changing the file size slightly and causing problems for some users. So
934 # I decided to implement a suggested change and consider the file
935 # completely downloaded if the file size differs less than 100 bytes from
936 # the one in the hard drive.
937 self.report_file_already_downloaded(filename)
938 self.try_rename(tmpfilename, filename)
939 return True
940 else:
941 # The length does not match, we start the download over
942 self.report_unable_to_resume()
943 open_mode = 'wb'
944 break
945 # Retry
946 count += 1
947 if count <= retries:
948 self.report_retry(count, retries)
949
950 if count > retries:
951 self.trouble(u'ERROR: giving up after %s retries' % retries)
952 return False
953
954 data_len = data.info().get('Content-length', None)
955 if data_len is not None:
956 data_len = long(data_len) + resume_len
957 data_len_str = self.format_bytes(data_len)
958 byte_counter = 0 + resume_len
959 block_size = 1024
960 start = time.time()
961 while True:
962 # Download and write
963 before = time.time()
964 data_block = data.read(block_size)
965 after = time.time()
966 if len(data_block) == 0:
967 break
968 byte_counter += len(data_block)
969
970 # Open file just in time
971 if stream is None:
972 try:
973 (stream, tmpfilename) = sanitize_open(tmpfilename, open_mode)
974 assert stream is not None
975 filename = self.undo_temp_name(tmpfilename)
976 self.report_destination(filename)
977 except (OSError, IOError), err:
978 self.trouble(u'ERROR: unable to open for writing: %s' % str(err))
979 return False
980 try:
981 stream.write(data_block)
982 except (IOError, OSError), err:
983 self.trouble(u'\nERROR: unable to write data: %s' % str(err))
984 return False
985 block_size = self.best_block_size(after - before, len(data_block))
986
987 # Progress message
988 speed_str = self.calc_speed(start, time.time(), byte_counter - resume_len)
989 if data_len is None:
990 self.report_progress('Unknown %', data_len_str, speed_str, 'Unknown ETA')
991 else:
992 percent_str = self.calc_percent(byte_counter, data_len)
993 eta_str = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len)
994 self.report_progress(percent_str, data_len_str, speed_str, eta_str)
995
996 # Apply rate limit
997 self.slow_down(start, byte_counter - resume_len)
998
999 if stream is None:
1000 self.trouble(u'\nERROR: Did not get any data blocks')
1001 return False
1002 stream.close()
1003 self.report_finish()
1004 if data_len is not None and byte_counter != data_len:
1005 raise ContentTooShortError(byte_counter, long(data_len))
1006 self.try_rename(tmpfilename, filename)
1007
1008 # Update file modification time
1009 if self.params.get('updatetime', True):
1010 info_dict['filetime'] = self.try_utime(filename, data.info().get('last-modified', None))
1011
1012 return True
1013
1014
1015 class InfoExtractor(object):
1016 """Information Extractor class.
1017
1018 Information extractors are the classes that, given a URL, extract
1019 information from the video (or videos) the URL refers to. This
1020 information includes the real video URL, the video title and simplified
1021 title, author and others. The information is stored in a dictionary
1022 which is then passed to the FileDownloader. The FileDownloader
1023 processes this information possibly downloading the video to the file
1024 system, among other possible outcomes. The dictionaries must include
1025 the following fields:
1026
1027 id: Video identifier.
1028 url: Final video URL.
1029 uploader: Nickname of the video uploader.
1030 title: Literal title.
1031 stitle: Simplified title.
1032 ext: Video filename extension.
1033 format: Video format.
1034 player_url: SWF Player URL (may be None).
1035
1036 The following fields are optional. Their primary purpose is to allow
1037 youtube-dl to serve as the backend for a video search function, such
1038 as the one in youtube2mp3. They are only used when their respective
1039 forced printing functions are called:
1040
1041 thumbnail: Full URL to a video thumbnail image.
1042 description: One-line video description.
1043
1044 Subclasses of this one should re-define the _real_initialize() and
1045 _real_extract() methods and define a _VALID_URL regexp.
1046 Probably, they should also be added to the list of extractors.
1047 """
1048
1049 _ready = False
1050 _downloader = None
1051
1052 def __init__(self, downloader=None):
1053 """Constructor. Receives an optional downloader."""
1054 self._ready = False
1055 self.set_downloader(downloader)
1056
1057 def suitable(self, url):
1058 """Receives a URL and returns True if suitable for this IE."""
1059 return re.match(self._VALID_URL, url) is not None
1060
1061 def initialize(self):
1062 """Initializes an instance (authentication, etc)."""
1063 if not self._ready:
1064 self._real_initialize()
1065 self._ready = True
1066
1067 def extract(self, url):
1068 """Extracts URL information and returns it in list of dicts."""
1069 self.initialize()
1070 return self._real_extract(url)
1071
1072 def set_downloader(self, downloader):
1073 """Sets the downloader for this IE."""
1074 self._downloader = downloader
1075
1076 def _real_initialize(self):
1077 """Real initialization process. Redefine in subclasses."""
1078 pass
1079
1080 def _real_extract(self, url):
1081 """Real extraction process. Redefine in subclasses."""
1082 pass
1083
1084
1085 class YoutubeIE(InfoExtractor):
1086 """Information extractor for youtube.com."""
1087
1088 _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))?)?([0-9A-Za-z_-]+)(?(1).+)?$'
1089 _LANG_URL = r'http://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'
1090 _LOGIN_URL = 'https://www.youtube.com/signup?next=/&gl=US&hl=en'
1091 _AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
1092 _NETRC_MACHINE = 'youtube'
1093 # Listed in order of quality
1094 _available_formats = ['38', '37', '22', '45', '35', '44', '34', '18', '43', '6', '5', '17', '13']
1095 _video_extensions = {
1096 '13': '3gp',
1097 '17': 'mp4',
1098 '18': 'mp4',
1099 '22': 'mp4',
1100 '37': 'mp4',
1101 '38': 'video', # You actually don't know if this will be MOV, AVI or whatever
1102 '43': 'webm',
1103 '44': 'webm',
1104 '45': 'webm',
1105 }
1106 IE_NAME = u'youtube'
1107
1108 def report_lang(self):
1109 """Report attempt to set language."""
1110 self._downloader.to_screen(u'[youtube] Setting language')
1111
1112 def report_login(self):
1113 """Report attempt to log in."""
1114 self._downloader.to_screen(u'[youtube] Logging in')
1115
1116 def report_age_confirmation(self):
1117 """Report attempt to confirm age."""
1118 self._downloader.to_screen(u'[youtube] Confirming age')
1119
1120 def report_video_webpage_download(self, video_id):
1121 """Report attempt to download video webpage."""
1122 self._downloader.to_screen(u'[youtube] %s: Downloading video webpage' % video_id)
1123
1124 def report_video_info_webpage_download(self, video_id):
1125 """Report attempt to download video info webpage."""
1126 self._downloader.to_screen(u'[youtube] %s: Downloading video info webpage' % video_id)
1127
1128 def report_information_extraction(self, video_id):
1129 """Report attempt to extract video information."""
1130 self._downloader.to_screen(u'[youtube] %s: Extracting video information' % video_id)
1131
1132 def report_unavailable_format(self, video_id, format):
1133 """Report extracted video URL."""
1134 self._downloader.to_screen(u'[youtube] %s: Format %s not available' % (video_id, format))
1135
1136 def report_rtmp_download(self):
1137 """Indicate the download will use the RTMP protocol."""
1138 self._downloader.to_screen(u'[youtube] RTMP download detected')
1139
1140 def _real_initialize(self):
1141 if self._downloader is None:
1142 return
1143
1144 username = None
1145 password = None
1146 downloader_params = self._downloader.params
1147
1148 # Attempt to use provided username and password or .netrc data
1149 if downloader_params.get('username', None) is not None:
1150 username = downloader_params['username']
1151 password = downloader_params['password']
1152 elif downloader_params.get('usenetrc', False):
1153 try:
1154 info = netrc.netrc().authenticators(self._NETRC_MACHINE)
1155 if info is not None:
1156 username = info[0]
1157 password = info[2]
1158 else:
1159 raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
1160 except (IOError, netrc.NetrcParseError), err:
1161 self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err))
1162 return
1163
1164 # Set language
1165 request = urllib2.Request(self._LANG_URL)
1166 try:
1167 self.report_lang()
1168 urllib2.urlopen(request).read()
1169 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1170 self._downloader.to_stderr(u'WARNING: unable to set language: %s' % str(err))
1171 return
1172
1173 # No authentication to be performed
1174 if username is None:
1175 return
1176
1177 # Log in
1178 login_form = {
1179 'current_form': 'loginForm',
1180 'next': '/',
1181 'action_login': 'Log In',
1182 'username': username,
1183 'password': password,
1184 }
1185 request = urllib2.Request(self._LOGIN_URL, urllib.urlencode(login_form))
1186 try:
1187 self.report_login()
1188 login_results = urllib2.urlopen(request).read()
1189 if re.search(r'(?i)<form[^>]* name="loginForm"', login_results) is not None:
1190 self._downloader.to_stderr(u'WARNING: unable to log in: bad username or password')
1191 return
1192 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1193 self._downloader.to_stderr(u'WARNING: unable to log in: %s' % str(err))
1194 return
1195
1196 # Confirm age
1197 age_form = {
1198 'next_url': '/',
1199 'action_confirm': 'Confirm',
1200 }
1201 request = urllib2.Request(self._AGE_URL, urllib.urlencode(age_form))
1202 try:
1203 self.report_age_confirmation()
1204 age_results = urllib2.urlopen(request).read()
1205 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1206 self._downloader.trouble(u'ERROR: unable to confirm age: %s' % str(err))
1207 return
1208
1209 def _real_extract(self, url):
1210 # Extract video id from URL
1211 mobj = re.match(self._VALID_URL, url)
1212 if mobj is None:
1213 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
1214 return
1215 video_id = mobj.group(2)
1216
1217 # Get video webpage
1218 self.report_video_webpage_download(video_id)
1219 request = urllib2.Request('http://www.youtube.com/watch?v=%s&gl=US&hl=en&amp;has_verified=1' % video_id)
1220 try:
1221 video_webpage = urllib2.urlopen(request).read()
1222 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1223 self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err))
1224 return
1225
1226 # Attempt to extract SWF player URL
1227 mobj = re.search(r'swfConfig.*?"(http:\\/\\/.*?watch.*?-.*?\.swf)"', video_webpage)
1228 if mobj is not None:
1229 player_url = re.sub(r'\\(.)', r'\1', mobj.group(1))
1230 else:
1231 player_url = None
1232
1233 # Get video info
1234 self.report_video_info_webpage_download(video_id)
1235 for el_type in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
1236 video_info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en'
1237 % (video_id, el_type))
1238 request = urllib2.Request(video_info_url)
1239 try:
1240 video_info_webpage = urllib2.urlopen(request).read()
1241 video_info = parse_qs(video_info_webpage)
1242 if 'token' in video_info:
1243 break
1244 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1245 self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % str(err))
1246 return
1247 if 'token' not in video_info:
1248 if 'reason' in video_info:
1249 self._downloader.trouble(u'ERROR: YouTube said: %s' % video_info['reason'][0].decode('utf-8'))
1250 else:
1251 self._downloader.trouble(u'ERROR: "token" parameter not in video info for unknown reason')
1252 return
1253
1254 # Start extracting information
1255 self.report_information_extraction(video_id)
1256
1257 # uploader
1258 if 'author' not in video_info:
1259 self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
1260 return
1261 video_uploader = urllib.unquote_plus(video_info['author'][0])
1262
1263 # title
1264 if 'title' not in video_info:
1265 self._downloader.trouble(u'ERROR: unable to extract video title')
1266 return
1267 video_title = urllib.unquote_plus(video_info['title'][0])
1268 video_title = video_title.decode('utf-8')
1269 video_title = sanitize_title(video_title)
1270
1271 # simplified title
1272 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
1273 simple_title = simple_title.strip(ur'_')
1274
1275 # thumbnail image
1276 if 'thumbnail_url' not in video_info:
1277 self._downloader.trouble(u'WARNING: unable to extract video thumbnail')
1278 video_thumbnail = ''
1279 else: # don't panic if we can't find it
1280 video_thumbnail = urllib.unquote_plus(video_info['thumbnail_url'][0])
1281
1282 # upload date
1283 upload_date = u'NA'
1284 mobj = re.search(r'id="eow-date.*?>(.*?)</span>', video_webpage, re.DOTALL)
1285 if mobj is not None:
1286 upload_date = ' '.join(re.sub(r'[/,-]', r' ', mobj.group(1)).split())
1287 format_expressions = ['%d %B %Y', '%B %d %Y', '%b %d %Y']
1288 for expression in format_expressions:
1289 try:
1290 upload_date = datetime.datetime.strptime(upload_date, expression).strftime('%Y%m%d')
1291 except:
1292 pass
1293
1294 # description
1295 try:
1296 lxml.etree
1297 except NameError:
1298 video_description = u'No description available.'
1299 if self._downloader.params.get('forcedescription', False) or self._downloader.params.get('writedescription', False):
1300 mobj = re.search(r'<meta name="description" content="(.*)"(?:\s*/)?>', video_webpage)
1301 if mobj is not None:
1302 video_description = mobj.group(1).decode('utf-8')
1303 else:
1304 html_parser = lxml.etree.HTMLParser(encoding='utf-8')
1305 vwebpage_doc = lxml.etree.parse(StringIO.StringIO(video_webpage), html_parser)
1306 video_description = u''.join(vwebpage_doc.xpath('id("eow-description")//text()'))
1307 # TODO use another parser
1308
1309 # token
1310 video_token = urllib.unquote_plus(video_info['token'][0])
1311
1312 # Decide which formats to download
1313 req_format = self._downloader.params.get('format', None)
1314
1315 if 'conn' in video_info and video_info['conn'][0].startswith('rtmp'):
1316 self.report_rtmp_download()
1317 video_url_list = [(None, video_info['conn'][0])]
1318 elif 'url_encoded_fmt_stream_map' in video_info and len(video_info['url_encoded_fmt_stream_map']) >= 1:
1319 url_data_strs = video_info['url_encoded_fmt_stream_map'][0].split(',')
1320 url_data = [parse_qs(uds) for uds in url_data_strs]
1321 url_data = filter(lambda ud: 'itag' in ud and 'url' in ud, url_data)
1322 url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data)
1323
1324 format_limit = self._downloader.params.get('format_limit', None)
1325 if format_limit is not None and format_limit in self._available_formats:
1326 format_list = self._available_formats[self._available_formats.index(format_limit):]
1327 else:
1328 format_list = self._available_formats
1329 existing_formats = [x for x in format_list if x in url_map]
1330 if len(existing_formats) == 0:
1331 self._downloader.trouble(u'ERROR: no known formats available for video')
1332 return
1333 if req_format is None or req_format == 'best':
1334 video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
1335 elif req_format == 'worst':
1336 video_url_list = [(existing_formats[len(existing_formats)-1], url_map[existing_formats[len(existing_formats)-1]])] # worst quality
1337 elif req_format in ('-1', 'all'):
1338 video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
1339 else:
1340 # Specific formats. We pick the first in a slash-delimeted sequence.
1341 # For example, if '1/2/3/4' is requested and '2' and '4' are available, we pick '2'.
1342 req_formats = req_format.split('/')
1343 video_url_list = None
1344 for rf in req_formats:
1345 if rf in url_map:
1346 video_url_list = [(rf, url_map[rf])]
1347 break
1348 if video_url_list is None:
1349 self._downloader.trouble(u'ERROR: requested format not available')
1350 return
1351 else:
1352 self._downloader.trouble(u'ERROR: no conn or url_encoded_fmt_stream_map information found in video info')
1353 return
1354
1355 for format_param, video_real_url in video_url_list:
1356 # At this point we have a new video
1357 self._downloader.increment_downloads()
1358
1359 # Extension
1360 video_extension = self._video_extensions.get(format_param, 'flv')
1361
1362 try:
1363 # Process video information
1364 self._downloader.process_info({
1365 'id': video_id.decode('utf-8'),
1366 'url': video_real_url.decode('utf-8'),
1367 'uploader': video_uploader.decode('utf-8'),
1368 'upload_date': upload_date,
1369 'title': video_title,
1370 'stitle': simple_title,
1371 'ext': video_extension.decode('utf-8'),
1372 'format': (format_param is None and u'NA' or format_param.decode('utf-8')),
1373 'thumbnail': video_thumbnail.decode('utf-8'),
1374 'description': video_description,
1375 'player_url': player_url,
1376 })
1377 except UnavailableVideoError, err:
1378 self._downloader.trouble(u'\nERROR: unable to download video')
1379
1380
1381 class MetacafeIE(InfoExtractor):
1382 """Information Extractor for metacafe.com."""
1383
1384 _VALID_URL = r'(?:http://)?(?:www\.)?metacafe\.com/watch/([^/]+)/([^/]+)/.*'
1385 _DISCLAIMER = 'http://www.metacafe.com/family_filter/'
1386 _FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user'
1387 _youtube_ie = None
1388 IE_NAME = u'metacafe'
1389
1390 def __init__(self, youtube_ie, downloader=None):
1391 InfoExtractor.__init__(self, downloader)
1392 self._youtube_ie = youtube_ie
1393
1394 def report_disclaimer(self):
1395 """Report disclaimer retrieval."""
1396 self._downloader.to_screen(u'[metacafe] Retrieving disclaimer')
1397
1398 def report_age_confirmation(self):
1399 """Report attempt to confirm age."""
1400 self._downloader.to_screen(u'[metacafe] Confirming age')
1401
1402 def report_download_webpage(self, video_id):
1403 """Report webpage download."""
1404 self._downloader.to_screen(u'[metacafe] %s: Downloading webpage' % video_id)
1405
1406 def report_extraction(self, video_id):
1407 """Report information extraction."""
1408 self._downloader.to_screen(u'[metacafe] %s: Extracting information' % video_id)
1409
1410 def _real_initialize(self):
1411 # Retrieve disclaimer
1412 request = urllib2.Request(self._DISCLAIMER)
1413 try:
1414 self.report_disclaimer()
1415 disclaimer = urllib2.urlopen(request).read()
1416 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1417 self._downloader.trouble(u'ERROR: unable to retrieve disclaimer: %s' % str(err))
1418 return
1419
1420 # Confirm age
1421 disclaimer_form = {
1422 'filters': '0',
1423 'submit': "Continue - I'm over 18",
1424 }
1425 request = urllib2.Request(self._FILTER_POST, urllib.urlencode(disclaimer_form))
1426 try:
1427 self.report_age_confirmation()
1428 disclaimer = urllib2.urlopen(request).read()
1429 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1430 self._downloader.trouble(u'ERROR: unable to confirm age: %s' % str(err))
1431 return
1432
1433 def _real_extract(self, url):
1434 # Extract id and simplified title from URL
1435 mobj = re.match(self._VALID_URL, url)
1436 if mobj is None:
1437 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
1438 return
1439
1440 video_id = mobj.group(1)
1441
1442 # Check if video comes from YouTube
1443 mobj2 = re.match(r'^yt-(.*)$', video_id)
1444 if mobj2 is not None:
1445 self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % mobj2.group(1))
1446 return
1447
1448 # At this point we have a new video
1449 self._downloader.increment_downloads()
1450
1451 simple_title = mobj.group(2).decode('utf-8')
1452
1453 # Retrieve video webpage to extract further information
1454 request = urllib2.Request('http://www.metacafe.com/watch/%s/' % video_id)
1455 try:
1456 self.report_download_webpage(video_id)
1457 webpage = urllib2.urlopen(request).read()
1458 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1459 self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % str(err))
1460 return
1461
1462 # Extract URL, uploader and title from webpage
1463 self.report_extraction(video_id)
1464 mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage)
1465 if mobj is not None:
1466 mediaURL = urllib.unquote(mobj.group(1))
1467 video_extension = mediaURL[-3:]
1468
1469 # Extract gdaKey if available
1470 mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
1471 if mobj is None:
1472 video_url = mediaURL
1473 else:
1474 gdaKey = mobj.group(1)
1475 video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
1476 else:
1477 mobj = re.search(r' name="flashvars" value="(.*?)"', webpage)
1478 if mobj is None:
1479 self._downloader.trouble(u'ERROR: unable to extract media URL')
1480 return
1481 vardict = parse_qs(mobj.group(1))
1482 if 'mediaData' not in vardict:
1483 self._downloader.trouble(u'ERROR: unable to extract media URL')
1484 return
1485 mobj = re.search(r'"mediaURL":"(http.*?)","key":"(.*?)"', vardict['mediaData'][0])
1486 if mobj is None:
1487 self._downloader.trouble(u'ERROR: unable to extract media URL')
1488 return
1489 mediaURL = mobj.group(1).replace('\\/', '/')
1490 video_extension = mediaURL[-3:]
1491 video_url = '%s?__gda__=%s' % (mediaURL, mobj.group(2))
1492
1493 mobj = re.search(r'(?im)<title>(.*) - Video</title>', webpage)
1494 if mobj is None:
1495 self._downloader.trouble(u'ERROR: unable to extract title')
1496 return
1497 video_title = mobj.group(1).decode('utf-8')
1498 video_title = sanitize_title(video_title)
1499
1500 mobj = re.search(r'(?ms)By:\s*<a .*?>(.+?)<', webpage)
1501 if mobj is None:
1502 self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
1503 return
1504 video_uploader = mobj.group(1)
1505
1506 try:
1507 # Process video information
1508 self._downloader.process_info({
1509 'id': video_id.decode('utf-8'),
1510 'url': video_url.decode('utf-8'),
1511 'uploader': video_uploader.decode('utf-8'),
1512 'upload_date': u'NA',
1513 'title': video_title,
1514 'stitle': simple_title,
1515 'ext': video_extension.decode('utf-8'),
1516 'format': u'NA',
1517 'player_url': None,
1518 })
1519 except UnavailableVideoError:
1520 self._downloader.trouble(u'\nERROR: unable to download video')
1521
1522
1523 class DailymotionIE(InfoExtractor):
1524 """Information Extractor for Dailymotion"""
1525
1526 _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^_/]+)_([^/]+)'
1527 IE_NAME = u'dailymotion'
1528
1529 def __init__(self, downloader=None):
1530 InfoExtractor.__init__(self, downloader)
1531
1532 def report_download_webpage(self, video_id):
1533 """Report webpage download."""
1534 self._downloader.to_screen(u'[dailymotion] %s: Downloading webpage' % video_id)
1535
1536 def report_extraction(self, video_id):
1537 """Report information extraction."""
1538 self._downloader.to_screen(u'[dailymotion] %s: Extracting information' % video_id)
1539
1540 def _real_initialize(self):
1541 return
1542
1543 def _real_extract(self, url):
1544 # Extract id and simplified title from URL
1545 mobj = re.match(self._VALID_URL, url)
1546 if mobj is None:
1547 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
1548 return
1549
1550 # At this point we have a new video
1551 self._downloader.increment_downloads()
1552 video_id = mobj.group(1)
1553
1554 simple_title = mobj.group(2).decode('utf-8')
1555 video_extension = 'flv'
1556
1557 # Retrieve video webpage to extract further information
1558 request = urllib2.Request(url)
1559 request.add_header('Cookie', 'family_filter=off')
1560 try:
1561 self.report_download_webpage(video_id)
1562 webpage = urllib2.urlopen(request).read()
1563 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1564 self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % str(err))
1565 return
1566
1567 # Extract URL, uploader and title from webpage
1568 self.report_extraction(video_id)
1569 mobj = re.search(r'(?i)addVariable\(\"sequence\"\s*,\s*\"([^\"]+?)\"\)', webpage)
1570 if mobj is None:
1571 self._downloader.trouble(u'ERROR: unable to extract media URL')
1572 return
1573 sequence = urllib.unquote(mobj.group(1))
1574 mobj = re.search(r',\"sdURL\"\:\"([^\"]+?)\",', sequence)
1575 if mobj is None:
1576 self._downloader.trouble(u'ERROR: unable to extract media URL')
1577 return
1578 mediaURL = urllib.unquote(mobj.group(1)).replace('\\', '')
1579
1580 # if needed add http://www.dailymotion.com/ if relative URL
1581
1582 video_url = mediaURL
1583
1584 mobj = re.search(r'(?im)<title>Dailymotion\s*-\s*(.+)\s*-\s*[^<]+?</title>', webpage)
1585 if mobj is None:
1586 self._downloader.trouble(u'ERROR: unable to extract title')
1587 return
1588 video_title = mobj.group(1).decode('utf-8')
1589 video_title = sanitize_title(video_title)
1590
1591 mobj = re.search(r'(?im)<span class="owner[^\"]+?">[^<]+?<a [^>]+?>([^<]+?)</a></span>', webpage)
1592 if mobj is None:
1593 self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
1594 return
1595 video_uploader = mobj.group(1)
1596
1597 try:
1598 # Process video information
1599 self._downloader.process_info({
1600 'id': video_id.decode('utf-8'),
1601 'url': video_url.decode('utf-8'),
1602 'uploader': video_uploader.decode('utf-8'),
1603 'upload_date': u'NA',
1604 'title': video_title,
1605 'stitle': simple_title,
1606 'ext': video_extension.decode('utf-8'),
1607 'format': u'NA',
1608 'player_url': None,
1609 })
1610 except UnavailableVideoError:
1611 self._downloader.trouble(u'\nERROR: unable to download video')
1612
1613
1614 class GoogleIE(InfoExtractor):
1615 """Information extractor for video.google.com."""
1616
1617 _VALID_URL = r'(?:http://)?video\.google\.(?:com(?:\.au)?|co\.(?:uk|jp|kr|cr)|ca|de|es|fr|it|nl|pl)/videoplay\?docid=([^\&]+).*'
1618 IE_NAME = u'video.google'
1619
1620 def __init__(self, downloader=None):
1621 InfoExtractor.__init__(self, downloader)
1622
1623 def report_download_webpage(self, video_id):
1624 """Report webpage download."""
1625 self._downloader.to_screen(u'[video.google] %s: Downloading webpage' % video_id)
1626
1627 def report_extraction(self, video_id):
1628 """Report information extraction."""
1629 self._downloader.to_screen(u'[video.google] %s: Extracting information' % video_id)
1630
1631 def _real_initialize(self):
1632 return
1633
1634 def _real_extract(self, url):
1635 # Extract id from URL
1636 mobj = re.match(self._VALID_URL, url)
1637 if mobj is None:
1638 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
1639 return
1640
1641 # At this point we have a new video
1642 self._downloader.increment_downloads()
1643 video_id = mobj.group(1)
1644
1645 video_extension = 'mp4'
1646
1647 # Retrieve video webpage to extract further information
1648 request = urllib2.Request('http://video.google.com/videoplay?docid=%s&hl=en&oe=utf-8' % video_id)
1649 try:
1650 self.report_download_webpage(video_id)
1651 webpage = urllib2.urlopen(request).read()
1652 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1653 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1654 return
1655
1656 # Extract URL, uploader, and title from webpage
1657 self.report_extraction(video_id)
1658 mobj = re.search(r"download_url:'([^']+)'", webpage)
1659 if mobj is None:
1660 video_extension = 'flv'
1661 mobj = re.search(r"(?i)videoUrl\\x3d(.+?)\\x26", webpage)
1662 if mobj is None:
1663 self._downloader.trouble(u'ERROR: unable to extract media URL')
1664 return
1665 mediaURL = urllib.unquote(mobj.group(1))
1666 mediaURL = mediaURL.replace('\\x3d', '\x3d')
1667 mediaURL = mediaURL.replace('\\x26', '\x26')
1668
1669 video_url = mediaURL
1670
1671 mobj = re.search(r'<title>(.*)</title>', webpage)
1672 if mobj is None:
1673 self._downloader.trouble(u'ERROR: unable to extract title')
1674 return
1675 video_title = mobj.group(1).decode('utf-8')
1676 video_title = sanitize_title(video_title)
1677 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
1678
1679 # Extract video description
1680 mobj = re.search(r'<span id=short-desc-content>([^<]*)</span>', webpage)
1681 if mobj is None:
1682 self._downloader.trouble(u'ERROR: unable to extract video description')
1683 return
1684 video_description = mobj.group(1).decode('utf-8')
1685 if not video_description:
1686 video_description = 'No description available.'
1687
1688 # Extract video thumbnail
1689 if self._downloader.params.get('forcethumbnail', False):
1690 request = urllib2.Request('http://video.google.com/videosearch?q=%s+site:video.google.com&hl=en' % abs(int(video_id)))
1691 try:
1692 webpage = urllib2.urlopen(request).read()
1693 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1694 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1695 return
1696 mobj = re.search(r'<img class=thumbnail-img (?:.* )?src=(http.*)>', webpage)
1697 if mobj is None:
1698 self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
1699 return
1700 video_thumbnail = mobj.group(1)
1701 else: # we need something to pass to process_info
1702 video_thumbnail = ''
1703
1704 try:
1705 # Process video information
1706 self._downloader.process_info({
1707 'id': video_id.decode('utf-8'),
1708 'url': video_url.decode('utf-8'),
1709 'uploader': u'NA',
1710 'upload_date': u'NA',
1711 'title': video_title,
1712 'stitle': simple_title,
1713 'ext': video_extension.decode('utf-8'),
1714 'format': u'NA',
1715 'player_url': None,
1716 })
1717 except UnavailableVideoError:
1718 self._downloader.trouble(u'\nERROR: unable to download video')
1719
1720
1721 class PhotobucketIE(InfoExtractor):
1722 """Information extractor for photobucket.com."""
1723
1724 _VALID_URL = r'(?:http://)?(?:[a-z0-9]+\.)?photobucket\.com/.*[\?\&]current=(.*\.flv)'
1725 IE_NAME = u'photobucket'
1726
1727 def __init__(self, downloader=None):
1728 InfoExtractor.__init__(self, downloader)
1729
1730 def report_download_webpage(self, video_id):
1731 """Report webpage download."""
1732 self._downloader.to_screen(u'[photobucket] %s: Downloading webpage' % video_id)
1733
1734 def report_extraction(self, video_id):
1735 """Report information extraction."""
1736 self._downloader.to_screen(u'[photobucket] %s: Extracting information' % video_id)
1737
1738 def _real_initialize(self):
1739 return
1740
1741 def _real_extract(self, url):
1742 # Extract id from URL
1743 mobj = re.match(self._VALID_URL, url)
1744 if mobj is None:
1745 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
1746 return
1747
1748 # At this point we have a new video
1749 self._downloader.increment_downloads()
1750 video_id = mobj.group(1)
1751
1752 video_extension = 'flv'
1753
1754 # Retrieve video webpage to extract further information
1755 request = urllib2.Request(url)
1756 try:
1757 self.report_download_webpage(video_id)
1758 webpage = urllib2.urlopen(request).read()
1759 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1760 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1761 return
1762
1763 # Extract URL, uploader, and title from webpage
1764 self.report_extraction(video_id)
1765 mobj = re.search(r'<link rel="video_src" href=".*\?file=([^"]+)" />', webpage)
1766 if mobj is None:
1767 self._downloader.trouble(u'ERROR: unable to extract media URL')
1768 return
1769 mediaURL = urllib.unquote(mobj.group(1))
1770
1771 video_url = mediaURL
1772
1773 mobj = re.search(r'<title>(.*) video by (.*) - Photobucket</title>', webpage)
1774 if mobj is None:
1775 self._downloader.trouble(u'ERROR: unable to extract title')
1776 return
1777 video_title = mobj.group(1).decode('utf-8')
1778 video_title = sanitize_title(video_title)
1779 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
1780
1781 video_uploader = mobj.group(2).decode('utf-8')
1782
1783 try:
1784 # Process video information
1785 self._downloader.process_info({
1786 'id': video_id.decode('utf-8'),
1787 'url': video_url.decode('utf-8'),
1788 'uploader': video_uploader,
1789 'upload_date': u'NA',
1790 'title': video_title,
1791 'stitle': simple_title,
1792 'ext': video_extension.decode('utf-8'),
1793 'format': u'NA',
1794 'player_url': None,
1795 })
1796 except UnavailableVideoError:
1797 self._downloader.trouble(u'\nERROR: unable to download video')
1798
1799
1800 class YahooIE(InfoExtractor):
1801 """Information extractor for video.yahoo.com."""
1802
1803 # _VALID_URL matches all Yahoo! Video URLs
1804 # _VPAGE_URL matches only the extractable '/watch/' URLs
1805 _VALID_URL = r'(?:http://)?(?:[a-z]+\.)?video\.yahoo\.com/(?:watch|network)/([0-9]+)(?:/|\?v=)([0-9]+)(?:[#\?].*)?'
1806 _VPAGE_URL = r'(?:http://)?video\.yahoo\.com/watch/([0-9]+)/([0-9]+)(?:[#\?].*)?'
1807 IE_NAME = u'video.yahoo'
1808
1809 def __init__(self, downloader=None):
1810 InfoExtractor.__init__(self, downloader)
1811
1812 def report_download_webpage(self, video_id):
1813 """Report webpage download."""
1814 self._downloader.to_screen(u'[video.yahoo] %s: Downloading webpage' % video_id)
1815
1816 def report_extraction(self, video_id):
1817 """Report information extraction."""
1818 self._downloader.to_screen(u'[video.yahoo] %s: Extracting information' % video_id)
1819
1820 def _real_initialize(self):
1821 return
1822
1823 def _real_extract(self, url, new_video=True):
1824 # Extract ID from URL
1825 mobj = re.match(self._VALID_URL, url)
1826 if mobj is None:
1827 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
1828 return
1829
1830 # At this point we have a new video
1831 self._downloader.increment_downloads()
1832 video_id = mobj.group(2)
1833 video_extension = 'flv'
1834
1835 # Rewrite valid but non-extractable URLs as
1836 # extractable English language /watch/ URLs
1837 if re.match(self._VPAGE_URL, url) is None:
1838 request = urllib2.Request(url)
1839 try:
1840 webpage = urllib2.urlopen(request).read()
1841 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1842 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1843 return
1844
1845 mobj = re.search(r'\("id", "([0-9]+)"\);', webpage)
1846 if mobj is None:
1847 self._downloader.trouble(u'ERROR: Unable to extract id field')
1848 return
1849 yahoo_id = mobj.group(1)
1850
1851 mobj = re.search(r'\("vid", "([0-9]+)"\);', webpage)
1852 if mobj is None:
1853 self._downloader.trouble(u'ERROR: Unable to extract vid field')
1854 return
1855 yahoo_vid = mobj.group(1)
1856
1857 url = 'http://video.yahoo.com/watch/%s/%s' % (yahoo_vid, yahoo_id)
1858 return self._real_extract(url, new_video=False)
1859
1860 # Retrieve video webpage to extract further information
1861 request = urllib2.Request(url)
1862 try:
1863 self.report_download_webpage(video_id)
1864 webpage = urllib2.urlopen(request).read()
1865 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1866 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1867 return
1868
1869 # Extract uploader and title from webpage
1870 self.report_extraction(video_id)
1871 mobj = re.search(r'<meta name="title" content="(.*)" />', webpage)
1872 if mobj is None:
1873 self._downloader.trouble(u'ERROR: unable to extract video title')
1874 return
1875 video_title = mobj.group(1).decode('utf-8')
1876 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
1877
1878 mobj = re.search(r'<h2 class="ti-5"><a href="http://video\.yahoo\.com/(people|profile)/[0-9]+" beacon=".*">(.*)</a></h2>', webpage)
1879 if mobj is None:
1880 self._downloader.trouble(u'ERROR: unable to extract video uploader')
1881 return
1882 video_uploader = mobj.group(1).decode('utf-8')
1883
1884 # Extract video thumbnail
1885 mobj = re.search(r'<link rel="image_src" href="(.*)" />', webpage)
1886 if mobj is None:
1887 self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
1888 return
1889 video_thumbnail = mobj.group(1).decode('utf-8')
1890
1891 # Extract video description
1892 mobj = re.search(r'<meta name="description" content="(.*)" />', webpage)
1893 if mobj is None:
1894 self._downloader.trouble(u'ERROR: unable to extract video description')
1895 return
1896 video_description = mobj.group(1).decode('utf-8')
1897 if not video_description:
1898 video_description = 'No description available.'
1899
1900 # Extract video height and width
1901 mobj = re.search(r'<meta name="video_height" content="([0-9]+)" />', webpage)
1902 if mobj is None:
1903 self._downloader.trouble(u'ERROR: unable to extract video height')
1904 return
1905 yv_video_height = mobj.group(1)
1906
1907 mobj = re.search(r'<meta name="video_width" content="([0-9]+)" />', webpage)
1908 if mobj is None:
1909 self._downloader.trouble(u'ERROR: unable to extract video width')
1910 return
1911 yv_video_width = mobj.group(1)
1912
1913 # Retrieve video playlist to extract media URL
1914 # I'm not completely sure what all these options are, but we
1915 # seem to need most of them, otherwise the server sends a 401.
1916 yv_lg = 'R0xx6idZnW2zlrKP8xxAIR' # not sure what this represents
1917 yv_bitrate = '700' # according to Wikipedia this is hard-coded
1918 request = urllib2.Request('http://cosmos.bcst.yahoo.com/up/yep/process/getPlaylistFOP.php?node_id=' + video_id +
1919 '&tech=flash&mode=playlist&lg=' + yv_lg + '&bitrate=' + yv_bitrate + '&vidH=' + yv_video_height +
1920 '&vidW=' + yv_video_width + '&swf=as3&rd=video.yahoo.com&tk=null&adsupported=v1,v2,&eventid=1301797')
1921 try:
1922 self.report_download_webpage(video_id)
1923 webpage = urllib2.urlopen(request).read()
1924 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1925 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1926 return
1927
1928 # Extract media URL from playlist XML
1929 mobj = re.search(r'<STREAM APP="(http://.*)" FULLPATH="/?(/.*\.flv\?[^"]*)"', webpage)
1930 if mobj is None:
1931 self._downloader.trouble(u'ERROR: Unable to extract media URL')
1932 return
1933 video_url = urllib.unquote(mobj.group(1) + mobj.group(2)).decode('utf-8')
1934 video_url = re.sub(r'(?u)&(.+?);', htmlentity_transform, video_url)
1935
1936 try:
1937 # Process video information
1938 self._downloader.process_info({
1939 'id': video_id.decode('utf-8'),
1940 'url': video_url,
1941 'uploader': video_uploader,
1942 'upload_date': u'NA',
1943 'title': video_title,
1944 'stitle': simple_title,
1945 'ext': video_extension.decode('utf-8'),
1946 'thumbnail': video_thumbnail.decode('utf-8'),
1947 'description': video_description,
1948 'thumbnail': video_thumbnail,
1949 'player_url': None,
1950 })
1951 except UnavailableVideoError:
1952 self._downloader.trouble(u'\nERROR: unable to download video')
1953
1954
1955 class VimeoIE(InfoExtractor):
1956 """Information extractor for vimeo.com."""
1957
1958 # _VALID_URL matches Vimeo URLs
1959 _VALID_URL = r'(?:https?://)?(?:(?:www|player).)?vimeo\.com/(?:groups/[^/]+/)?(?:videos?/)?([0-9]+)'
1960 IE_NAME = u'vimeo'
1961
1962 def __init__(self, downloader=None):
1963 InfoExtractor.__init__(self, downloader)
1964
1965 def report_download_webpage(self, video_id):
1966 """Report webpage download."""
1967 self._downloader.to_screen(u'[vimeo] %s: Downloading webpage' % video_id)
1968
1969 def report_extraction(self, video_id):
1970 """Report information extraction."""
1971 self._downloader.to_screen(u'[vimeo] %s: Extracting information' % video_id)
1972
1973 def _real_initialize(self):
1974 return
1975
1976 def _real_extract(self, url, new_video=True):
1977 # Extract ID from URL
1978 mobj = re.match(self._VALID_URL, url)
1979 if mobj is None:
1980 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
1981 return
1982
1983 # At this point we have a new video
1984 self._downloader.increment_downloads()
1985 video_id = mobj.group(1)
1986
1987 # Retrieve video webpage to extract further information
1988 request = urllib2.Request("http://vimeo.com/moogaloop/load/clip:%s" % video_id, None, std_headers)
1989 try:
1990 self.report_download_webpage(video_id)
1991 webpage = urllib2.urlopen(request).read()
1992 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
1993 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
1994 return
1995
1996 # Now we begin extracting as much information as we can from what we
1997 # retrieved. First we extract the information common to all extractors,
1998 # and latter we extract those that are Vimeo specific.
1999 self.report_extraction(video_id)
2000
2001 # Extract title
2002 mobj = re.search(r'<caption>(.*?)</caption>', webpage)
2003 if mobj is None:
2004 self._downloader.trouble(u'ERROR: unable to extract video title')
2005 return
2006 video_title = mobj.group(1).decode('utf-8')
2007 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
2008
2009 # Extract uploader
2010 mobj = re.search(r'<uploader_url>http://vimeo.com/(.*?)</uploader_url>', webpage)
2011 if mobj is None:
2012 self._downloader.trouble(u'ERROR: unable to extract video uploader')
2013 return
2014 video_uploader = mobj.group(1).decode('utf-8')
2015
2016 # Extract video thumbnail
2017 mobj = re.search(r'<thumbnail>(.*?)</thumbnail>', webpage)
2018 if mobj is None:
2019 self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
2020 return
2021 video_thumbnail = mobj.group(1).decode('utf-8')
2022
2023 # # Extract video description
2024 # mobj = re.search(r'<meta property="og:description" content="(.*)" />', webpage)
2025 # if mobj is None:
2026 # self._downloader.trouble(u'ERROR: unable to extract video description')
2027 # return
2028 # video_description = mobj.group(1).decode('utf-8')
2029 # if not video_description: video_description = 'No description available.'
2030 video_description = 'Foo.'
2031
2032 # Vimeo specific: extract request signature
2033 mobj = re.search(r'<request_signature>(.*?)</request_signature>', webpage)
2034 if mobj is None:
2035 self._downloader.trouble(u'ERROR: unable to extract request signature')
2036 return
2037 sig = mobj.group(1).decode('utf-8')
2038
2039 # Vimeo specific: Extract request signature expiration
2040 mobj = re.search(r'<request_signature_expires>(.*?)</request_signature_expires>', webpage)
2041 if mobj is None:
2042 self._downloader.trouble(u'ERROR: unable to extract request signature expiration')
2043 return
2044 sig_exp = mobj.group(1).decode('utf-8')
2045
2046 video_url = "http://vimeo.com/moogaloop/play/clip:%s/%s/%s" % (video_id, sig, sig_exp)
2047
2048 try:
2049 # Process video information
2050 self._downloader.process_info({
2051 'id': video_id.decode('utf-8'),
2052 'url': video_url,
2053 'uploader': video_uploader,
2054 'upload_date': u'NA',
2055 'title': video_title,
2056 'stitle': simple_title,
2057 'ext': u'mp4',
2058 'thumbnail': video_thumbnail.decode('utf-8'),
2059 'description': video_description,
2060 'thumbnail': video_thumbnail,
2061 'description': video_description,
2062 'player_url': None,
2063 })
2064 except UnavailableVideoError:
2065 self._downloader.trouble(u'ERROR: unable to download video')
2066
2067
2068 class GenericIE(InfoExtractor):
2069 """Generic last-resort information extractor."""
2070
2071 _VALID_URL = r'.*'
2072 IE_NAME = u'generic'
2073
2074 def __init__(self, downloader=None):
2075 InfoExtractor.__init__(self, downloader)
2076
2077 def report_download_webpage(self, video_id):
2078 """Report webpage download."""
2079 self._downloader.to_screen(u'WARNING: Falling back on generic information extractor.')
2080 self._downloader.to_screen(u'[generic] %s: Downloading webpage' % video_id)
2081
2082 def report_extraction(self, video_id):
2083 """Report information extraction."""
2084 self._downloader.to_screen(u'[generic] %s: Extracting information' % video_id)
2085
2086 def _real_initialize(self):
2087 return
2088
2089 def _real_extract(self, url):
2090 # At this point we have a new video
2091 self._downloader.increment_downloads()
2092
2093 video_id = url.split('/')[-1]
2094 request = urllib2.Request(url)
2095 try:
2096 self.report_download_webpage(video_id)
2097 webpage = urllib2.urlopen(request).read()
2098 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2099 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
2100 return
2101 except ValueError, err:
2102 # since this is the last-resort InfoExtractor, if
2103 # this error is thrown, it'll be thrown here
2104 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
2105 return
2106
2107 self.report_extraction(video_id)
2108 # Start with something easy: JW Player in SWFObject
2109 mobj = re.search(r'flashvars: [\'"](?:.*&)?file=(http[^\'"&]*)', webpage)
2110 if mobj is None:
2111 # Broaden the search a little bit
2112 mobj = re.search(r'[^A-Za-z0-9]?(?:file|source)=(http[^\'"&]*)', webpage)
2113 if mobj is None:
2114 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
2115 return
2116
2117 # It's possible that one of the regexes
2118 # matched, but returned an empty group:
2119 if mobj.group(1) is None:
2120 self._downloader.trouble(u'ERROR: Invalid URL: %s' % url)
2121 return
2122
2123 video_url = urllib.unquote(mobj.group(1))
2124 video_id = os.path.basename(video_url)
2125
2126 # here's a fun little line of code for you:
2127 video_extension = os.path.splitext(video_id)[1][1:]
2128 video_id = os.path.splitext(video_id)[0]
2129
2130 # it's tempting to parse this further, but you would
2131 # have to take into account all the variations like
2132 # Video Title - Site Name
2133 # Site Name | Video Title
2134 # Video Title - Tagline | Site Name
2135 # and so on and so forth; it's just not practical
2136 mobj = re.search(r'<title>(.*)</title>', webpage)
2137 if mobj is None:
2138 self._downloader.trouble(u'ERROR: unable to extract title')
2139 return
2140 video_title = mobj.group(1).decode('utf-8')
2141 video_title = sanitize_title(video_title)
2142 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
2143
2144 # video uploader is domain name
2145 mobj = re.match(r'(?:https?://)?([^/]*)/.*', url)
2146 if mobj is None:
2147 self._downloader.trouble(u'ERROR: unable to extract title')
2148 return
2149 video_uploader = mobj.group(1).decode('utf-8')
2150
2151 try:
2152 # Process video information
2153 self._downloader.process_info({
2154 'id': video_id.decode('utf-8'),
2155 'url': video_url.decode('utf-8'),
2156 'uploader': video_uploader,
2157 'upload_date': u'NA',
2158 'title': video_title,
2159 'stitle': simple_title,
2160 'ext': video_extension.decode('utf-8'),
2161 'format': u'NA',
2162 'player_url': None,
2163 })
2164 except UnavailableVideoError, err:
2165 self._downloader.trouble(u'\nERROR: unable to download video')
2166
2167
2168 class YoutubeSearchIE(InfoExtractor):
2169 """Information Extractor for YouTube search queries."""
2170 _VALID_URL = r'ytsearch(\d+|all)?:[\s\S]+'
2171 _TEMPLATE_URL = 'http://www.youtube.com/results?search_query=%s&page=%s&gl=US&hl=en'
2172 _VIDEO_INDICATOR = r'href="/watch\?v=.+?"'
2173 _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
2174 _youtube_ie = None
2175 _max_youtube_results = 1000
2176 IE_NAME = u'youtube:search'
2177
2178 def __init__(self, youtube_ie, downloader=None):
2179 InfoExtractor.__init__(self, downloader)
2180 self._youtube_ie = youtube_ie
2181
2182 def report_download_page(self, query, pagenum):
2183 """Report attempt to download playlist page with given number."""
2184 query = query.decode(preferredencoding())
2185 self._downloader.to_screen(u'[youtube] query "%s": Downloading page %s' % (query, pagenum))
2186
2187 def _real_initialize(self):
2188 self._youtube_ie.initialize()
2189
2190 def _real_extract(self, query):
2191 mobj = re.match(self._VALID_URL, query)
2192 if mobj is None:
2193 self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
2194 return
2195
2196 prefix, query = query.split(':')
2197 prefix = prefix[8:]
2198 query = query.encode('utf-8')
2199 if prefix == '':
2200 self._download_n_results(query, 1)
2201 return
2202 elif prefix == 'all':
2203 self._download_n_results(query, self._max_youtube_results)
2204 return
2205 else:
2206 try:
2207 n = long(prefix)
2208 if n <= 0:
2209 self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
2210 return
2211 elif n > self._max_youtube_results:
2212 self._downloader.to_stderr(u'WARNING: ytsearch returns max %i results (you requested %i)' % (self._max_youtube_results, n))
2213 n = self._max_youtube_results
2214 self._download_n_results(query, n)
2215 return
2216 except ValueError: # parsing prefix as integer fails
2217 self._download_n_results(query, 1)
2218 return
2219
2220 def _download_n_results(self, query, n):
2221 """Downloads a specified number of results for a query"""
2222
2223 video_ids = []
2224 already_seen = set()
2225 pagenum = 1
2226
2227 while True:
2228 self.report_download_page(query, pagenum)
2229 result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
2230 request = urllib2.Request(result_url)
2231 try:
2232 page = urllib2.urlopen(request).read()
2233 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2234 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
2235 return
2236
2237 # Extract video identifiers
2238 for mobj in re.finditer(self._VIDEO_INDICATOR, page):
2239 video_id = page[mobj.span()[0]:mobj.span()[1]].split('=')[2][:-1]
2240 if video_id not in already_seen:
2241 video_ids.append(video_id)
2242 already_seen.add(video_id)
2243 if len(video_ids) == n:
2244 # Specified n videos reached
2245 for id in video_ids:
2246 self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
2247 return
2248
2249 if re.search(self._MORE_PAGES_INDICATOR, page) is None:
2250 for id in video_ids:
2251 self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
2252 return
2253
2254 pagenum = pagenum + 1
2255
2256
2257 class GoogleSearchIE(InfoExtractor):
2258 """Information Extractor for Google Video search queries."""
2259 _VALID_URL = r'gvsearch(\d+|all)?:[\s\S]+'
2260 _TEMPLATE_URL = 'http://video.google.com/videosearch?q=%s+site:video.google.com&start=%s&hl=en'
2261 _VIDEO_INDICATOR = r'videoplay\?docid=([^\&>]+)\&'
2262 _MORE_PAGES_INDICATOR = r'<span>Next</span>'
2263 _google_ie = None
2264 _max_google_results = 1000
2265 IE_NAME = u'video.google:search'
2266
2267 def __init__(self, google_ie, downloader=None):
2268 InfoExtractor.__init__(self, downloader)
2269 self._google_ie = google_ie
2270
2271 def report_download_page(self, query, pagenum):
2272 """Report attempt to download playlist page with given number."""
2273 query = query.decode(preferredencoding())
2274 self._downloader.to_screen(u'[video.google] query "%s": Downloading page %s' % (query, pagenum))
2275
2276 def _real_initialize(self):
2277 self._google_ie.initialize()
2278
2279 def _real_extract(self, query):
2280 mobj = re.match(self._VALID_URL, query)
2281 if mobj is None:
2282 self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
2283 return
2284
2285 prefix, query = query.split(':')
2286 prefix = prefix[8:]
2287 query = query.encode('utf-8')
2288 if prefix == '':
2289 self._download_n_results(query, 1)
2290 return
2291 elif prefix == 'all':
2292 self._download_n_results(query, self._max_google_results)
2293 return
2294 else:
2295 try:
2296 n = long(prefix)
2297 if n <= 0:
2298 self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
2299 return
2300 elif n > self._max_google_results:
2301 self._downloader.to_stderr(u'WARNING: gvsearch returns max %i results (you requested %i)' % (self._max_google_results, n))
2302 n = self._max_google_results
2303 self._download_n_results(query, n)
2304 return
2305 except ValueError: # parsing prefix as integer fails
2306 self._download_n_results(query, 1)
2307 return
2308
2309 def _download_n_results(self, query, n):
2310 """Downloads a specified number of results for a query"""
2311
2312 video_ids = []
2313 already_seen = set()
2314 pagenum = 1
2315
2316 while True:
2317 self.report_download_page(query, pagenum)
2318 result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
2319 request = urllib2.Request(result_url)
2320 try:
2321 page = urllib2.urlopen(request).read()
2322 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2323 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
2324 return
2325
2326 # Extract video identifiers
2327 for mobj in re.finditer(self._VIDEO_INDICATOR, page):
2328 video_id = mobj.group(1)
2329 if video_id not in already_seen:
2330 video_ids.append(video_id)
2331 already_seen.add(video_id)
2332 if len(video_ids) == n:
2333 # Specified n videos reached
2334 for id in video_ids:
2335 self._google_ie.extract('http://video.google.com/videoplay?docid=%s' % id)
2336 return
2337
2338 if re.search(self._MORE_PAGES_INDICATOR, page) is None:
2339 for id in video_ids:
2340 self._google_ie.extract('http://video.google.com/videoplay?docid=%s' % id)
2341 return
2342
2343 pagenum = pagenum + 1
2344
2345
2346 class YahooSearchIE(InfoExtractor):
2347 """Information Extractor for Yahoo! Video search queries."""
2348 _VALID_URL = r'yvsearch(\d+|all)?:[\s\S]+'
2349 _TEMPLATE_URL = 'http://video.yahoo.com/search/?p=%s&o=%s'
2350 _VIDEO_INDICATOR = r'href="http://video\.yahoo\.com/watch/([0-9]+/[0-9]+)"'
2351 _MORE_PAGES_INDICATOR = r'\s*Next'
2352 _yahoo_ie = None
2353 _max_yahoo_results = 1000
2354 IE_NAME = u'video.yahoo:search'
2355
2356 def __init__(self, yahoo_ie, downloader=None):
2357 InfoExtractor.__init__(self, downloader)
2358 self._yahoo_ie = yahoo_ie
2359
2360 def report_download_page(self, query, pagenum):
2361 """Report attempt to download playlist page with given number."""
2362 query = query.decode(preferredencoding())
2363 self._downloader.to_screen(u'[video.yahoo] query "%s": Downloading page %s' % (query, pagenum))
2364
2365 def _real_initialize(self):
2366 self._yahoo_ie.initialize()
2367
2368 def _real_extract(self, query):
2369 mobj = re.match(self._VALID_URL, query)
2370 if mobj is None:
2371 self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
2372 return
2373
2374 prefix, query = query.split(':')
2375 prefix = prefix[8:]
2376 query = query.encode('utf-8')
2377 if prefix == '':
2378 self._download_n_results(query, 1)
2379 return
2380 elif prefix == 'all':
2381 self._download_n_results(query, self._max_yahoo_results)
2382 return
2383 else:
2384 try:
2385 n = long(prefix)
2386 if n <= 0:
2387 self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
2388 return
2389 elif n > self._max_yahoo_results:
2390 self._downloader.to_stderr(u'WARNING: yvsearch returns max %i results (you requested %i)' % (self._max_yahoo_results, n))
2391 n = self._max_yahoo_results
2392 self._download_n_results(query, n)
2393 return
2394 except ValueError: # parsing prefix as integer fails
2395 self._download_n_results(query, 1)
2396 return
2397
2398 def _download_n_results(self, query, n):
2399 """Downloads a specified number of results for a query"""
2400
2401 video_ids = []
2402 already_seen = set()
2403 pagenum = 1
2404
2405 while True:
2406 self.report_download_page(query, pagenum)
2407 result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
2408 request = urllib2.Request(result_url)
2409 try:
2410 page = urllib2.urlopen(request).read()
2411 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2412 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
2413 return
2414
2415 # Extract video identifiers
2416 for mobj in re.finditer(self._VIDEO_INDICATOR, page):
2417 video_id = mobj.group(1)
2418 if video_id not in already_seen:
2419 video_ids.append(video_id)
2420 already_seen.add(video_id)
2421 if len(video_ids) == n:
2422 # Specified n videos reached
2423 for id in video_ids:
2424 self._yahoo_ie.extract('http://video.yahoo.com/watch/%s' % id)
2425 return
2426
2427 if re.search(self._MORE_PAGES_INDICATOR, page) is None:
2428 for id in video_ids:
2429 self._yahoo_ie.extract('http://video.yahoo.com/watch/%s' % id)
2430 return
2431
2432 pagenum = pagenum + 1
2433
2434
2435 class YoutubePlaylistIE(InfoExtractor):
2436 """Information Extractor for YouTube playlists."""
2437
2438 _VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)([0-9A-Za-z]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
2439 _TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
2440 _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
2441 _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
2442 _youtube_ie = None
2443 IE_NAME = u'youtube:playlist'
2444
2445 def __init__(self, youtube_ie, downloader=None):
2446 InfoExtractor.__init__(self, downloader)
2447 self._youtube_ie = youtube_ie
2448
2449 def report_download_page(self, playlist_id, pagenum):
2450 """Report attempt to download playlist page with given number."""
2451 self._downloader.to_screen(u'[youtube] PL %s: Downloading page #%s' % (playlist_id, pagenum))
2452
2453 def _real_initialize(self):
2454 self._youtube_ie.initialize()
2455
2456 def _real_extract(self, url):
2457 # Extract playlist id
2458 mobj = re.match(self._VALID_URL, url)
2459 if mobj is None:
2460 self._downloader.trouble(u'ERROR: invalid url: %s' % url)
2461 return
2462
2463 # Single video case
2464 if mobj.group(3) is not None:
2465 self._youtube_ie.extract(mobj.group(3))
2466 return
2467
2468 # Download playlist pages
2469 # prefix is 'p' as default for playlists but there are other types that need extra care
2470 playlist_prefix = mobj.group(1)
2471 if playlist_prefix == 'a':
2472 playlist_access = 'artist'
2473 else:
2474 playlist_prefix = 'p'
2475 playlist_access = 'view_play_list'
2476 playlist_id = mobj.group(2)
2477 video_ids = []
2478 pagenum = 1
2479
2480 while True:
2481 self.report_download_page(playlist_id, pagenum)
2482 request = urllib2.Request(self._TEMPLATE_URL % (playlist_access, playlist_prefix, playlist_id, pagenum))
2483 try:
2484 page = urllib2.urlopen(request).read()
2485 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2486 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
2487 return
2488
2489 # Extract video identifiers
2490 ids_in_page = []
2491 for mobj in re.finditer(self._VIDEO_INDICATOR, page):
2492 if mobj.group(1) not in ids_in_page:
2493 ids_in_page.append(mobj.group(1))
2494 video_ids.extend(ids_in_page)
2495
2496 if re.search(self._MORE_PAGES_INDICATOR, page) is None:
2497 break
2498 pagenum = pagenum + 1
2499
2500 playliststart = self._downloader.params.get('playliststart', 1) - 1
2501 playlistend = self._downloader.params.get('playlistend', -1)
2502 video_ids = video_ids[playliststart:playlistend]
2503
2504 for id in video_ids:
2505 self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
2506 return
2507
2508
2509 class YoutubeUserIE(InfoExtractor):
2510 """Information Extractor for YouTube users."""
2511
2512 _VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/user/)|ytuser:)([A-Za-z0-9_-]+)'
2513 _TEMPLATE_URL = 'http://gdata.youtube.com/feeds/api/users/%s'
2514 _GDATA_PAGE_SIZE = 50
2515 _GDATA_URL = 'http://gdata.youtube.com/feeds/api/users/%s/uploads?max-results=%d&start-index=%d'
2516 _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
2517 _youtube_ie = None
2518 IE_NAME = u'youtube:user'
2519
2520 def __init__(self, youtube_ie, downloader=None):
2521 InfoExtractor.__init__(self, downloader)
2522 self._youtube_ie = youtube_ie
2523
2524 def report_download_page(self, username, start_index):
2525 """Report attempt to download user page."""
2526 self._downloader.to_screen(u'[youtube] user %s: Downloading video ids from %d to %d' %
2527 (username, start_index, start_index + self._GDATA_PAGE_SIZE))
2528
2529 def _real_initialize(self):
2530 self._youtube_ie.initialize()
2531
2532 def _real_extract(self, url):
2533 # Extract username
2534 mobj = re.match(self._VALID_URL, url)
2535 if mobj is None:
2536 self._downloader.trouble(u'ERROR: invalid url: %s' % url)
2537 return
2538
2539 username = mobj.group(1)
2540
2541 # Download video ids using YouTube Data API. Result size per
2542 # query is limited (currently to 50 videos) so we need to query
2543 # page by page until there are no video ids - it means we got
2544 # all of them.
2545
2546 video_ids = []
2547 pagenum = 0
2548
2549 while True:
2550 start_index = pagenum * self._GDATA_PAGE_SIZE + 1
2551 self.report_download_page(username, start_index)
2552
2553 request = urllib2.Request(self._GDATA_URL % (username, self._GDATA_PAGE_SIZE, start_index))
2554
2555 try:
2556 page = urllib2.urlopen(request).read()
2557 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2558 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
2559 return
2560
2561 # Extract video identifiers
2562 ids_in_page = []
2563
2564 for mobj in re.finditer(self._VIDEO_INDICATOR, page):
2565 if mobj.group(1) not in ids_in_page:
2566 ids_in_page.append(mobj.group(1))
2567
2568 video_ids.extend(ids_in_page)
2569
2570 # A little optimization - if current page is not
2571 # "full", ie. does not contain PAGE_SIZE video ids then
2572 # we can assume that this page is the last one - there
2573 # are no more ids on further pages - no need to query
2574 # again.
2575
2576 if len(ids_in_page) < self._GDATA_PAGE_SIZE:
2577 break
2578
2579 pagenum += 1
2580
2581 all_ids_count = len(video_ids)
2582 playliststart = self._downloader.params.get('playliststart', 1) - 1
2583 playlistend = self._downloader.params.get('playlistend', -1)
2584
2585 if playlistend == -1:
2586 video_ids = video_ids[playliststart:]
2587 else:
2588 video_ids = video_ids[playliststart:playlistend]
2589
2590 self._downloader.to_screen("[youtube] user %s: Collected %d video ids (downloading %d of them)" %
2591 (username, all_ids_count, len(video_ids)))
2592
2593 for video_id in video_ids:
2594 self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % video_id)
2595
2596
2597 class DepositFilesIE(InfoExtractor):
2598 """Information extractor for depositfiles.com"""
2599
2600 _VALID_URL = r'(?:http://)?(?:\w+\.)?depositfiles\.com/(?:../(?#locale))?files/(.+)'
2601 IE_NAME = u'DepositFiles'
2602
2603 def __init__(self, downloader=None):
2604 InfoExtractor.__init__(self, downloader)
2605
2606 def report_download_webpage(self, file_id):
2607 """Report webpage download."""
2608 self._downloader.to_screen(u'[DepositFiles] %s: Downloading webpage' % file_id)
2609
2610 def report_extraction(self, file_id):
2611 """Report information extraction."""
2612 self._downloader.to_screen(u'[DepositFiles] %s: Extracting information' % file_id)
2613
2614 def _real_initialize(self):
2615 return
2616
2617 def _real_extract(self, url):
2618 # At this point we have a new file
2619 self._downloader.increment_downloads()
2620
2621 file_id = url.split('/')[-1]
2622 # Rebuild url in english locale
2623 url = 'http://depositfiles.com/en/files/' + file_id
2624
2625 # Retrieve file webpage with 'Free download' button pressed
2626 free_download_indication = { 'gateway_result' : '1' }
2627 request = urllib2.Request(url, urllib.urlencode(free_download_indication))
2628 try:
2629 self.report_download_webpage(file_id)
2630 webpage = urllib2.urlopen(request).read()
2631 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2632 self._downloader.trouble(u'ERROR: Unable to retrieve file webpage: %s' % str(err))
2633 return
2634
2635 # Search for the real file URL
2636 mobj = re.search(r'<form action="(http://fileshare.+?)"', webpage)
2637 if (mobj is None) or (mobj.group(1) is None):
2638 # Try to figure out reason of the error.
2639 mobj = re.search(r'<strong>(Attention.*?)</strong>', webpage, re.DOTALL)
2640 if (mobj is not None) and (mobj.group(1) is not None):
2641 restriction_message = re.sub('\s+', ' ', mobj.group(1)).strip()
2642 self._downloader.trouble(u'ERROR: %s' % restriction_message)
2643 else:
2644 self._downloader.trouble(u'ERROR: unable to extract download URL from: %s' % url)
2645 return
2646
2647 file_url = mobj.group(1)
2648 file_extension = os.path.splitext(file_url)[1][1:]
2649
2650 # Search for file title
2651 mobj = re.search(r'<b title="(.*?)">', webpage)
2652 if mobj is None:
2653 self._downloader.trouble(u'ERROR: unable to extract title')
2654 return
2655 file_title = mobj.group(1).decode('utf-8')
2656
2657 try:
2658 # Process file information
2659 self._downloader.process_info({
2660 'id': file_id.decode('utf-8'),
2661 'url': file_url.decode('utf-8'),
2662 'uploader': u'NA',
2663 'upload_date': u'NA',
2664 'title': file_title,
2665 'stitle': file_title,
2666 'ext': file_extension.decode('utf-8'),
2667 'format': u'NA',
2668 'player_url': None,
2669 })
2670 except UnavailableVideoError, err:
2671 self._downloader.trouble(u'ERROR: unable to download file')
2672
2673
2674 class FacebookIE(InfoExtractor):
2675 """Information Extractor for Facebook"""
2676
2677 _VALID_URL = r'^(?:https?://)?(?:\w+\.)?facebook\.com/video/video\.php\?(?:.*?)v=(?P<ID>\d+)(?:.*)'
2678 _LOGIN_URL = 'https://login.facebook.com/login.php?m&next=http%3A%2F%2Fm.facebook.com%2Fhome.php&'
2679 _NETRC_MACHINE = 'facebook'
2680 _available_formats = ['highqual', 'lowqual']
2681 _video_extensions = {
2682 'highqual': 'mp4',
2683 'lowqual': 'mp4',
2684 }
2685 IE_NAME = u'facebook'
2686
2687 def __init__(self, downloader=None):
2688 InfoExtractor.__init__(self, downloader)
2689
2690 def _reporter(self, message):
2691 """Add header and report message."""
2692 self._downloader.to_screen(u'[facebook] %s' % message)
2693
2694 def report_login(self):
2695 """Report attempt to log in."""
2696 self._reporter(u'Logging in')
2697
2698 def report_video_webpage_download(self, video_id):
2699 """Report attempt to download video webpage."""
2700 self._reporter(u'%s: Downloading video webpage' % video_id)
2701
2702 def report_information_extraction(self, video_id):
2703 """Report attempt to extract video information."""
2704 self._reporter(u'%s: Extracting video information' % video_id)
2705
2706 def _parse_page(self, video_webpage):
2707 """Extract video information from page"""
2708 # General data
2709 data = {'title': r'class="video_title datawrap">(.*?)</',
2710 'description': r'<div class="datawrap">(.*?)</div>',
2711 'owner': r'\("video_owner_name", "(.*?)"\)',
2712 'upload_date': r'data-date="(.*?)"',
2713 'thumbnail': r'\("thumb_url", "(?P<THUMB>.*?)"\)',
2714 }
2715 video_info = {}
2716 for piece in data.keys():
2717 mobj = re.search(data[piece], video_webpage)
2718 if mobj is not None:
2719 video_info[piece] = urllib.unquote_plus(mobj.group(1).decode("unicode_escape"))
2720
2721 # Video urls
2722 video_urls = {}
2723 for fmt in self._available_formats:
2724 mobj = re.search(r'\("%s_src\", "(.+?)"\)' % fmt, video_webpage)
2725 if mobj is not None:
2726 # URL is in a Javascript segment inside an escaped Unicode format within
2727 # the generally utf-8 page
2728 video_urls[fmt] = urllib.unquote_plus(mobj.group(1).decode("unicode_escape"))
2729 video_info['video_urls'] = video_urls
2730
2731 return video_info
2732
2733 def _real_initialize(self):
2734 if self._downloader is None:
2735 return
2736
2737 useremail = None
2738 password = None
2739 downloader_params = self._downloader.params
2740
2741 # Attempt to use provided username and password or .netrc data
2742 if downloader_params.get('username', None) is not None:
2743 useremail = downloader_params['username']
2744 password = downloader_params['password']
2745 elif downloader_params.get('usenetrc', False):
2746 try:
2747 info = netrc.netrc().authenticators(self._NETRC_MACHINE)
2748 if info is not None:
2749 useremail = info[0]
2750 password = info[2]
2751 else:
2752 raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
2753 except (IOError, netrc.NetrcParseError), err:
2754 self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err))
2755 return
2756
2757 if useremail is None:
2758 return
2759
2760 # Log in
2761 login_form = {
2762 'email': useremail,
2763 'pass': password,
2764 'login': 'Log+In'
2765 }
2766 request = urllib2.Request(self._LOGIN_URL, urllib.urlencode(login_form))
2767 try:
2768 self.report_login()
2769 login_results = urllib2.urlopen(request).read()
2770 if re.search(r'<form(.*)name="login"(.*)</form>', login_results) is not None:
2771 self._downloader.to_stderr(u'WARNING: unable to log in: bad username/password, or exceded login rate limit (~3/min). Check credentials or wait.')
2772 return
2773 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2774 self._downloader.to_stderr(u'WARNING: unable to log in: %s' % str(err))
2775 return
2776
2777 def _real_extract(self, url):
2778 mobj = re.match(self._VALID_URL, url)
2779 if mobj is None:
2780 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
2781 return
2782 video_id = mobj.group('ID')
2783
2784 # Get video webpage
2785 self.report_video_webpage_download(video_id)
2786 request = urllib2.Request('https://www.facebook.com/video/video.php?v=%s' % video_id)
2787 try:
2788 page = urllib2.urlopen(request)
2789 video_webpage = page.read()
2790 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2791 self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % str(err))
2792 return
2793
2794 # Start extracting information
2795 self.report_information_extraction(video_id)
2796
2797 # Extract information
2798 video_info = self._parse_page(video_webpage)
2799
2800 # uploader
2801 if 'owner' not in video_info:
2802 self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
2803 return
2804 video_uploader = video_info['owner']
2805
2806 # title
2807 if 'title' not in video_info:
2808 self._downloader.trouble(u'ERROR: unable to extract video title')
2809 return
2810 video_title = video_info['title']
2811 video_title = video_title.decode('utf-8')
2812 video_title = sanitize_title(video_title)
2813
2814 # simplified title
2815 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
2816 simple_title = simple_title.strip(ur'_')
2817
2818 # thumbnail image
2819 if 'thumbnail' not in video_info:
2820 self._downloader.trouble(u'WARNING: unable to extract video thumbnail')
2821 video_thumbnail = ''
2822 else:
2823 video_thumbnail = video_info['thumbnail']
2824
2825 # upload date
2826 upload_date = u'NA'
2827 if 'upload_date' in video_info:
2828 upload_time = video_info['upload_date']
2829 timetuple = email.utils.parsedate_tz(upload_time)
2830 if timetuple is not None:
2831 try:
2832 upload_date = time.strftime('%Y%m%d', timetuple[0:9])
2833 except:
2834 pass
2835
2836 # description
2837 video_description = video_info.get('description', 'No description available.')
2838
2839 url_map = video_info['video_urls']
2840 if len(url_map.keys()) > 0:
2841 # Decide which formats to download
2842 req_format = self._downloader.params.get('format', None)
2843 format_limit = self._downloader.params.get('format_limit', None)
2844
2845 if format_limit is not None and format_limit in self._available_formats:
2846 format_list = self._available_formats[self._available_formats.index(format_limit):]
2847 else:
2848 format_list = self._available_formats
2849 existing_formats = [x for x in format_list if x in url_map]
2850 if len(existing_formats) == 0:
2851 self._downloader.trouble(u'ERROR: no known formats available for video')
2852 return
2853 if req_format is None:
2854 video_url_list = [(existing_formats[0], url_map[existing_formats[0]])] # Best quality
2855 elif req_format == 'worst':
2856 video_url_list = [(existing_formats[len(existing_formats)-1], url_map[existing_formats[len(existing_formats)-1]])] # worst quality
2857 elif req_format == '-1':
2858 video_url_list = [(f, url_map[f]) for f in existing_formats] # All formats
2859 else:
2860 # Specific format
2861 if req_format not in url_map:
2862 self._downloader.trouble(u'ERROR: requested format not available')
2863 return
2864 video_url_list = [(req_format, url_map[req_format])] # Specific format
2865
2866 for format_param, video_real_url in video_url_list:
2867
2868 # At this point we have a new video
2869 self._downloader.increment_downloads()
2870
2871 # Extension
2872 video_extension = self._video_extensions.get(format_param, 'mp4')
2873
2874 try:
2875 # Process video information
2876 self._downloader.process_info({
2877 'id': video_id.decode('utf-8'),
2878 'url': video_real_url.decode('utf-8'),
2879 'uploader': video_uploader.decode('utf-8'),
2880 'upload_date': upload_date,
2881 'title': video_title,
2882 'stitle': simple_title,
2883 'ext': video_extension.decode('utf-8'),
2884 'format': (format_param is None and u'NA' or format_param.decode('utf-8')),
2885 'thumbnail': video_thumbnail.decode('utf-8'),
2886 'description': video_description.decode('utf-8'),
2887 'player_url': None,
2888 })
2889 except UnavailableVideoError, err:
2890 self._downloader.trouble(u'\nERROR: unable to download video')
2891
2892 class BlipTVIE(InfoExtractor):
2893 """Information extractor for blip.tv"""
2894
2895 _VALID_URL = r'^(?:https?://)?(?:\w+\.)?blip\.tv(/.+)$'
2896 _URL_EXT = r'^.*\.([a-z0-9]+)$'
2897 IE_NAME = u'blip.tv'
2898
2899 def report_extraction(self, file_id):
2900 """Report information extraction."""
2901 self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, file_id))
2902
2903 def report_direct_download(self, title):
2904 """Report information extraction."""
2905 self._downloader.to_screen(u'[%s] %s: Direct download detected' % (self.IE_NAME, title))
2906
2907 def _simplify_title(self, title):
2908 res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
2909 res = res.strip(ur'_')
2910 return res
2911
2912 def _real_extract(self, url):
2913 mobj = re.match(self._VALID_URL, url)
2914 if mobj is None:
2915 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
2916 return
2917
2918 if '?' in url:
2919 cchar = '&'
2920 else:
2921 cchar = '?'
2922 json_url = url + cchar + 'skin=json&version=2&no_wrap=1'
2923 request = urllib2.Request(json_url)
2924 self.report_extraction(mobj.group(1))
2925 info = None
2926 try:
2927 urlh = urllib2.urlopen(request)
2928 if urlh.headers.get('Content-Type', '').startswith('video/'): # Direct download
2929 basename = url.split('/')[-1]
2930 title,ext = os.path.splitext(basename)
2931 ext = ext.replace('.', '')
2932 self.report_direct_download(title)
2933 info = {
2934 'id': title,
2935 'url': url,
2936 'title': title,
2937 'stitle': self._simplify_title(title),
2938 'ext': ext,
2939 'urlhandle': urlh
2940 }
2941 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2942 self._downloader.trouble(u'ERROR: unable to download video info webpage: %s' % str(err))
2943 return
2944 if info is None: # Regular URL
2945 try:
2946 json_code = urlh.read()
2947 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
2948 self._downloader.trouble(u'ERROR: unable to read video info webpage: %s' % str(err))
2949 return
2950
2951 try:
2952 json_data = json.loads(json_code)
2953 if 'Post' in json_data:
2954 data = json_data['Post']
2955 else:
2956 data = json_data
2957
2958 upload_date = datetime.datetime.strptime(data['datestamp'], '%m-%d-%y %H:%M%p').strftime('%Y%m%d')
2959 video_url = data['media']['url']
2960 umobj = re.match(self._URL_EXT, video_url)
2961 if umobj is None:
2962 raise ValueError('Can not determine filename extension')
2963 ext = umobj.group(1)
2964
2965 info = {
2966 'id': data['item_id'],
2967 'url': video_url,
2968 'uploader': data['display_name'],
2969 'upload_date': upload_date,
2970 'title': data['title'],
2971 'stitle': self._simplify_title(data['title']),
2972 'ext': ext,
2973 'format': data['media']['mimeType'],
2974 'thumbnail': data['thumbnailUrl'],
2975 'description': data['description'],
2976 'player_url': data['embedUrl']
2977 }
2978 except (ValueError,KeyError), err:
2979 self._downloader.trouble(u'ERROR: unable to parse video information: %s' % repr(err))
2980 return
2981
2982 self._downloader.increment_downloads()
2983
2984 try:
2985 self._downloader.process_info(info)
2986 except UnavailableVideoError, err:
2987 self._downloader.trouble(u'\nERROR: unable to download video')
2988
2989
2990 class MyVideoIE(InfoExtractor):
2991 """Information Extractor for myvideo.de."""
2992
2993 _VALID_URL = r'(?:http://)?(?:www\.)?myvideo\.de/watch/([0-9]+)/([^?/]+).*'
2994 IE_NAME = u'myvideo'
2995
2996 def __init__(self, downloader=None):
2997 InfoExtractor.__init__(self, downloader)
2998
2999 def report_download_webpage(self, video_id):
3000 """Report webpage download."""
3001 self._downloader.to_screen(u'[myvideo] %s: Downloading webpage' % video_id)
3002
3003 def report_extraction(self, video_id):
3004 """Report information extraction."""
3005 self._downloader.to_screen(u'[myvideo] %s: Extracting information' % video_id)
3006
3007 def _real_initialize(self):
3008 return
3009
3010 def _real_extract(self,url):
3011 mobj = re.match(self._VALID_URL, url)
3012 if mobj is None:
3013 self._download.trouble(u'ERROR: invalid URL: %s' % url)
3014 return
3015
3016 video_id = mobj.group(1)
3017 simple_title = mobj.group(2).decode('utf-8')
3018 # should actually not be necessary
3019 simple_title = sanitize_title(simple_title)
3020 simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', simple_title)
3021
3022 # Get video webpage
3023 request = urllib2.Request('http://www.myvideo.de/watch/%s' % video_id)
3024 try:
3025 self.report_download_webpage(video_id)
3026 webpage = urllib2.urlopen(request).read()
3027 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3028 self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
3029 return
3030
3031 self.report_extraction(video_id)
3032 mobj = re.search(r'<link rel=\'image_src\' href=\'(http://is[0-9].myvideo\.de/de/movie[0-9]+/[a-f0-9]+)/thumbs/[^.]+\.jpg\' />',
3033 webpage)
3034 if mobj is None:
3035 self._downloader.trouble(u'ERROR: unable to extract media URL')
3036 return
3037 video_url = mobj.group(1) + ('/%s.flv' % video_id)
3038
3039 mobj = re.search('<title>([^<]+)</title>', webpage)
3040 if mobj is None:
3041 self._downloader.trouble(u'ERROR: unable to extract title')
3042 return
3043
3044 video_title = mobj.group(1)
3045 video_title = sanitize_title(video_title)
3046
3047 try:
3048 self._downloader.process_info({
3049 'id': video_id,
3050 'url': video_url,
3051 'uploader': u'NA',
3052 'upload_date': u'NA',
3053 'title': video_title,
3054 'stitle': simple_title,
3055 'ext': u'flv',
3056 'format': u'NA',
3057 'player_url': None,
3058 })
3059 except UnavailableVideoError:
3060 self._downloader.trouble(u'\nERROR: Unable to download video')
3061
3062 class ComedyCentralIE(InfoExtractor):
3063 """Information extractor for The Daily Show and Colbert Report """
3064
3065 _VALID_URL = r'^(:(?P<shortname>tds|thedailyshow|cr|colbert|colbertnation|colbertreport))|(https?://)?(www\.)?(?P<showname>thedailyshow|colbertnation)\.com/full-episodes/(?P<episode>.*)$'
3066 IE_NAME = u'comedycentral'
3067
3068 def report_extraction(self, episode_id):
3069 self._downloader.to_screen(u'[comedycentral] %s: Extracting information' % episode_id)
3070
3071 def report_config_download(self, episode_id):
3072 self._downloader.to_screen(u'[comedycentral] %s: Downloading configuration' % episode_id)
3073
3074 def report_index_download(self, episode_id):
3075 self._downloader.to_screen(u'[comedycentral] %s: Downloading show index' % episode_id)
3076
3077 def report_player_url(self, episode_id):
3078 self._downloader.to_screen(u'[comedycentral] %s: Determining player URL' % episode_id)
3079
3080 def _simplify_title(self, title):
3081 res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
3082 res = res.strip(ur'_')
3083 return res
3084
3085 def _real_extract(self, url):
3086 mobj = re.match(self._VALID_URL, url)
3087 if mobj is None:
3088 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
3089 return
3090
3091 if mobj.group('shortname'):
3092 if mobj.group('shortname') in ('tds', 'thedailyshow'):
3093 url = 'http://www.thedailyshow.com/full-episodes/'
3094 else:
3095 url = 'http://www.colbertnation.com/full-episodes/'
3096 mobj = re.match(self._VALID_URL, url)
3097 assert mobj is not None
3098
3099 dlNewest = not mobj.group('episode')
3100 if dlNewest:
3101 epTitle = mobj.group('showname')
3102 else:
3103 epTitle = mobj.group('episode')
3104
3105 req = urllib2.Request(url)
3106 self.report_extraction(epTitle)
3107 try:
3108 htmlHandle = urllib2.urlopen(req)
3109 html = htmlHandle.read()
3110 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3111 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % unicode(err))
3112 return
3113 if dlNewest:
3114 url = htmlHandle.geturl()
3115 mobj = re.match(self._VALID_URL, url)
3116 if mobj is None:
3117 self._downloader.trouble(u'ERROR: Invalid redirected URL: ' + url)
3118 return
3119 if mobj.group('episode') == '':
3120 self._downloader.trouble(u'ERROR: Redirected URL is still not specific: ' + url)
3121 return
3122 epTitle = mobj.group('episode')
3123
3124 mMovieParams = re.findall('<param name="movie" value="(http://media.mtvnservices.com/([^"]*episode.*?:.*?))"/>', html)
3125 if len(mMovieParams) == 0:
3126 self._downloader.trouble(u'ERROR: unable to find Flash URL in webpage ' + url)
3127 return
3128
3129 playerUrl_raw = mMovieParams[0][0]
3130 self.report_player_url(epTitle)
3131 try:
3132 urlHandle = urllib2.urlopen(playerUrl_raw)
3133 playerUrl = urlHandle.geturl()
3134 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3135 self._downloader.trouble(u'ERROR: unable to find out player URL: ' + unicode(err))
3136 return
3137
3138 uri = mMovieParams[0][1]
3139 indexUrl = 'http://shadow.comedycentral.com/feeds/video_player/mrss/?' + urllib.urlencode({'uri': uri})
3140 self.report_index_download(epTitle)
3141 try:
3142 indexXml = urllib2.urlopen(indexUrl).read()
3143 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3144 self._downloader.trouble(u'ERROR: unable to download episode index: ' + unicode(err))
3145 return
3146
3147 idoc = xml.etree.ElementTree.fromstring(indexXml)
3148 itemEls = idoc.findall('.//item')
3149 for itemEl in itemEls:
3150 mediaId = itemEl.findall('./guid')[0].text
3151 shortMediaId = mediaId.split(':')[-1]
3152 showId = mediaId.split(':')[-2].replace('.com', '')
3153 officialTitle = itemEl.findall('./title')[0].text
3154 officialDate = itemEl.findall('./pubDate')[0].text
3155
3156 configUrl = ('http://www.comedycentral.com/global/feeds/entertainment/media/mediaGenEntertainment.jhtml?' +
3157 urllib.urlencode({'uri': mediaId}))
3158 configReq = urllib2.Request(configUrl)
3159 self.report_config_download(epTitle)
3160 try:
3161 configXml = urllib2.urlopen(configReq).read()
3162 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3163 self._downloader.trouble(u'ERROR: unable to download webpage: %s' % unicode(err))
3164 return
3165
3166 cdoc = xml.etree.ElementTree.fromstring(configXml)
3167 turls = []
3168 for rendition in cdoc.findall('.//rendition'):
3169 finfo = (rendition.attrib['bitrate'], rendition.findall('./src')[0].text)
3170 turls.append(finfo)
3171
3172 if len(turls) == 0:
3173 self._downloader.trouble(u'\nERROR: unable to download ' + mediaId + ': No videos found')
3174 continue
3175
3176 # For now, just pick the highest bitrate
3177 format,video_url = turls[-1]
3178
3179 self._downloader.increment_downloads()
3180
3181 effTitle = showId + '-' + epTitle
3182 info = {
3183 'id': shortMediaId,
3184 'url': video_url,
3185 'uploader': showId,
3186 'upload_date': officialDate,
3187 'title': effTitle,
3188 'stitle': self._simplify_title(effTitle),
3189 'ext': 'mp4',
3190 'format': format,
3191 'thumbnail': None,
3192 'description': officialTitle,
3193 'player_url': playerUrl
3194 }
3195
3196 try:
3197 self._downloader.process_info(info)
3198 except UnavailableVideoError, err:
3199 self._downloader.trouble(u'\nERROR: unable to download ' + mediaId)
3200 continue
3201
3202
3203 class EscapistIE(InfoExtractor):
3204 """Information extractor for The Escapist """
3205
3206 _VALID_URL = r'^(https?://)?(www\.)?escapistmagazine\.com/videos/view/(?P<showname>[^/]+)/(?P<episode>[^/?]+)[/?]?.*$'
3207 IE_NAME = u'escapist'
3208
3209 def report_extraction(self, showName):
3210 self._downloader.to_screen(u'[escapist] %s: Extracting information' % showName)
3211
3212 def report_config_download(self, showName):
3213 self._downloader.to_screen(u'[escapist] %s: Downloading configuration' % showName)
3214
3215 def _simplify_title(self, title):
3216 res = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', title)
3217 res = res.strip(ur'_')
3218 return res
3219
3220 def _real_extract(self, url):
3221 htmlParser = HTMLParser.HTMLParser()
3222
3223 mobj = re.match(self._VALID_URL, url)
3224 if mobj is None:
3225 self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
3226 return
3227 showName = mobj.group('showname')
3228 videoId = mobj.group('episode')
3229
3230 self.report_extraction(showName)
3231 try:
3232 webPage = urllib2.urlopen(url).read()
3233 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3234 self._downloader.trouble(u'ERROR: unable to download webpage: ' + unicode(err))
3235 return
3236
3237 descMatch = re.search('<meta name="description" content="([^"]*)"', webPage)
3238 description = htmlParser.unescape(descMatch.group(1))
3239 imgMatch = re.search('<meta property="og:image" content="([^"]*)"', webPage)
3240 imgUrl = htmlParser.unescape(imgMatch.group(1))
3241 playerUrlMatch = re.search('<meta property="og:video" content="([^"]*)"', webPage)
3242 playerUrl = htmlParser.unescape(playerUrlMatch.group(1))
3243 configUrlMatch = re.search('config=(.*)$', playerUrl)
3244 configUrl = urllib2.unquote(configUrlMatch.group(1))
3245
3246 self.report_config_download(showName)
3247 try:
3248 configJSON = urllib2.urlopen(configUrl).read()
3249 except (urllib2.URLError, httplib.HTTPException, socket.error), err:
3250 self._downloader.trouble(u'ERROR: unable to download configuration: ' + unicode(err))
3251 return
3252
3253 # Technically, it's JavaScript, not JSON
3254 configJSON = configJSON.replace("'", '"')
3255
3256 try:
3257 config = json.loads(configJSON)
3258 except (ValueError,), err:
3259 self._downloader.trouble(u'ERROR: Invalid JSON in configuration file: ' + unicode(err))
3260 return
3261
3262 playlist = config['playlist']
3263 videoUrl = playlist[1]['url']
3264
3265 self._downloader.increment_downloads()
3266 info = {
3267 'id': videoId,
3268 'url': videoUrl,
3269 'uploader': showName,
3270 'upload_date': None,
3271 'title': showName,
3272 'stitle': self._simplify_title(showName),
3273 'ext': 'flv',
3274 'format': 'flv',
3275 'thumbnail': imgUrl,
3276 'description': description,
3277 'player_url': playerUrl,
3278 }
3279
3280 try:
3281 self._downloader.process_info(info)
3282 except UnavailableVideoError, err:
3283 self._downloader.trouble(u'\nERROR: unable to download ' + videoId)
3284
3285
3286
3287 class PostProcessor(object):
3288 """Post Processor class.
3289
3290 PostProcessor objects can be added to downloaders with their
3291 add_post_processor() method. When the downloader has finished a
3292 successful download, it will take its internal chain of PostProcessors
3293 and start calling the run() method on each one of them, first with
3294 an initial argument and then with the returned value of the previous
3295 PostProcessor.
3296
3297 The chain will be stopped if one of them ever returns None or the end
3298 of the chain is reached.
3299
3300 PostProcessor objects follow a "mutual registration" process similar
3301 to InfoExtractor objects.
3302 """
3303
3304 _downloader = None
3305
3306 def __init__(self, downloader=None):
3307 self._downloader = downloader
3308
3309 def set_downloader(self, downloader):
3310 """Sets the downloader for this PP."""
3311 self._downloader = downloader
3312
3313 def run(self, information):
3314 """Run the PostProcessor.
3315
3316 The "information" argument is a dictionary like the ones
3317 composed by InfoExtractors. The only difference is that this
3318 one has an extra field called "filepath" that points to the
3319 downloaded file.
3320
3321 When this method returns None, the postprocessing chain is
3322 stopped. However, this method may return an information
3323 dictionary that will be passed to the next postprocessing
3324 object in the chain. It can be the one it received after
3325 changing some fields.
3326
3327 In addition, this method may raise a PostProcessingError
3328 exception that will be taken into account by the downloader
3329 it was called from.
3330 """
3331 return information # by default, do nothing
3332
3333
3334 class FFmpegExtractAudioPP(PostProcessor):
3335
3336 def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, keepvideo=False):
3337 PostProcessor.__init__(self, downloader)
3338 if preferredcodec is None:
3339 preferredcodec = 'best'
3340 self._preferredcodec = preferredcodec
3341 self._preferredquality = preferredquality
3342 self._keepvideo = keepvideo
3343
3344 @staticmethod
3345 def get_audio_codec(path):
3346 try:
3347 cmd = ['ffprobe', '-show_streams', '--', path]
3348 handle = subprocess.Popen(cmd, stderr=file(os.path.devnull, 'w'), stdout=subprocess.PIPE)
3349 output = handle.communicate()[0]
3350 if handle.wait() != 0:
3351 return None
3352 except (IOError, OSError):
3353 return None
3354 audio_codec = None
3355 for line in output.split('\n'):
3356 if line.startswith('codec_name='):
3357 audio_codec = line.split('=')[1].strip()
3358 elif line.strip() == 'codec_type=audio' and audio_codec is not None:
3359 return audio_codec
3360 return None
3361
3362 @staticmethod
3363 def run_ffmpeg(path, out_path, codec, more_opts):
3364 try:
3365 cmd = ['ffmpeg', '-y', '-i', path, '-vn', '-acodec', codec] + more_opts + ['--', out_path]
3366 ret = subprocess.call(cmd, stdout=file(os.path.devnull, 'w'), stderr=subprocess.STDOUT)
3367 return (ret == 0)
3368 except (IOError, OSError):
3369 return False
3370
3371 def run(self, information):
3372 path = information['filepath']
3373
3374 filecodec = self.get_audio_codec(path)
3375 if filecodec is None:
3376 self._downloader.to_stderr(u'WARNING: unable to obtain file audio codec with ffprobe')
3377 return None
3378
3379 more_opts = []
3380 if self._preferredcodec == 'best' or self._preferredcodec == filecodec:
3381 if filecodec in ['aac', 'mp3', 'vorbis']:
3382 # Lossless if possible
3383 acodec = 'copy'
3384 extension = filecodec
3385 if filecodec == 'aac':
3386 more_opts = ['-f', 'adts']
3387 if filecodec == 'vorbis':
3388 extension = 'ogg'
3389 else:
3390 # MP3 otherwise.
3391 acodec = 'libmp3lame'
3392 extension = 'mp3'
3393 more_opts = []
3394 if self._preferredquality is not None:
3395 more_opts += ['-ab', self._preferredquality]
3396 else:
3397 # We convert the audio (lossy)
3398 acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'vorbis': 'libvorbis'}[self._preferredcodec]
3399 extension = self._preferredcodec
3400 more_opts = []
3401 if self._preferredquality is not None:
3402 more_opts += ['-ab', self._preferredquality]
3403 if self._preferredcodec == 'aac':
3404 more_opts += ['-f', 'adts']
3405 if self._preferredcodec == 'vorbis':
3406 extension = 'ogg'
3407
3408 (prefix, ext) = os.path.splitext(path)
3409 new_path = prefix + '.' + extension
3410 self._downloader.to_screen(u'[ffmpeg] Destination: %s' % new_path)
3411 status = self.run_ffmpeg(path, new_path, acodec, more_opts)
3412
3413 if not status:
3414 self._downloader.to_stderr(u'WARNING: error running ffmpeg')
3415 return None
3416
3417 # Try to update the date time for extracted audio file.
3418 if information.get('filetime') is not None:
3419 try:
3420 os.utime(new_path, (time.time(), information['filetime']))
3421 except:
3422 self._downloader.to_stderr(u'WARNING: Cannot update utime of audio file')
3423
3424 if not self._keepvideo:
3425 try:
3426 os.remove(path)
3427 except (IOError, OSError):
3428 self._downloader.to_stderr(u'WARNING: Unable to remove downloaded video file')
3429 return None
3430
3431 information['filepath'] = new_path
3432 return information
3433
3434
3435 def updateSelf(downloader, filename):
3436 ''' Update the program file with the latest version from the repository '''
3437 # Note: downloader only used for options
3438 if not os.access(filename, os.W_OK):
3439 sys.exit('ERROR: no write permissions on %s' % filename)
3440
3441 downloader.to_screen('Updating to latest version...')
3442
3443 try:
3444 try:
3445 urlh = urllib.urlopen(UPDATE_URL)
3446 newcontent = urlh.read()
3447
3448 vmatch = re.search("__version__ = '([^']+)'", newcontent)
3449 if vmatch is not None and vmatch.group(1) == __version__:
3450 downloader.to_screen('youtube-dl is up-to-date (' + __version__ + ')')
3451 return
3452 finally:
3453 urlh.close()
3454 except (IOError, OSError), err:
3455 sys.exit('ERROR: unable to download latest version')
3456
3457 try:
3458 outf = open(filename, 'wb')
3459 try:
3460 outf.write(newcontent)
3461 finally:
3462 outf.close()
3463 except (IOError, OSError), err:
3464 sys.exit('ERROR: unable to overwrite current version')
3465
3466 downloader.to_screen('Updated youtube-dl. Restart youtube-dl to use the new version.')
3467
3468 def parseOpts():
3469 # Deferred imports
3470 import getpass
3471 import optparse
3472
3473 def _format_option_string(option):
3474 ''' ('-o', '--option') -> -o, --format METAVAR'''
3475
3476 opts = []
3477
3478 if option._short_opts: opts.append(option._short_opts[0])
3479 if option._long_opts: opts.append(option._long_opts[0])
3480 if len(opts) > 1: opts.insert(1, ', ')
3481
3482 if option.takes_value(): opts.append(' %s' % option.metavar)
3483
3484 return "".join(opts)
3485
3486 def _find_term_columns():
3487 columns = os.environ.get('COLUMNS', None)
3488 if columns:
3489 return int(columns)
3490
3491 try:
3492 sp = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3493 out,err = sp.communicate()
3494 return int(out.split()[1])
3495 except:
3496 pass
3497 return None
3498
3499 max_width = 80
3500 max_help_position = 80
3501
3502 # No need to wrap help messages if we're on a wide console
3503 columns = _find_term_columns()
3504 if columns: max_width = columns
3505
3506 fmt = optparse.IndentedHelpFormatter(width=max_width, max_help_position=max_help_position)
3507 fmt.format_option_strings = _format_option_string
3508
3509 kw = {
3510 'version' : __version__,
3511 'formatter' : fmt,
3512 'usage' : '%prog [options] url [url...]',
3513 'conflict_handler' : 'resolve',
3514 }
3515
3516 parser = optparse.OptionParser(**kw)
3517
3518 # option groups
3519 general = optparse.OptionGroup(parser, 'General Options')
3520 selection = optparse.OptionGroup(parser, 'Video Selection')
3521 authentication = optparse.OptionGroup(parser, 'Authentication Options')
3522 video_format = optparse.OptionGroup(parser, 'Video Format Options')
3523 postproc = optparse.OptionGroup(parser, 'Post-processing Options')
3524 filesystem = optparse.OptionGroup(parser, 'Filesystem Options')
3525 verbosity = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
3526
3527 general.add_option('-h', '--help',
3528 action='help', help='print this help text and exit')
3529 general.add_option('-v', '--version',
3530 action='version', help='print program version and exit')
3531 general.add_option('-U', '--update',
3532 action='store_true', dest='update_self', help='update this program to latest version')
3533 general.add_option('-i', '--ignore-errors',
3534 action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
3535 general.add_option('-r', '--rate-limit',
3536 dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
3537 general.add_option('-R', '--retries',
3538 dest='retries', metavar='RETRIES', help='number of retries (default is 10)', default=10)
3539 general.add_option('--dump-user-agent',
3540 action='store_true', dest='dump_user_agent',
3541 help='display the current browser identification', default=False)
3542 general.add_option('--list-extractors',
3543 action='store_true', dest='list_extractors',
3544 help='List all supported extractors and the URLs they would handle', default=False)
3545
3546 selection.add_option('--playlist-start',
3547 dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is 1)', default=1)
3548 selection.add_option('--playlist-end',
3549 dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
3550 selection.add_option('--match-title', dest='matchtitle', metavar='REGEX',help='download only matching titles (regex or caseless sub-string)')
3551 selection.add_option('--reject-title', dest='rejecttitle', metavar='REGEX',help='skip download for matching titles (regex or caseless sub-string)')
3552
3553 authentication.add_option('-u', '--username',
3554 dest='username', metavar='USERNAME', help='account username')
3555 authentication.add_option('-p', '--password',
3556 dest='password', metavar='PASSWORD', help='account password')
3557 authentication.add_option('-n', '--netrc',
3558 action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
3559
3560
3561 video_format.add_option('-f', '--format',
3562 action='store', dest='format', metavar='FORMAT', help='video format code')
3563 video_format.add_option('--all-formats',
3564 action='store_const', dest='format', help='download all available video formats', const='all')
3565 video_format.add_option('--max-quality',
3566 action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
3567
3568
3569 verbosity.add_option('-q', '--quiet',
3570 action='store_true', dest='quiet', help='activates quiet mode', default=False)
3571 verbosity.add_option('-s', '--simulate',
3572 action='store_true', dest='simulate', help='do not download the video and do not write anything to disk', default=False)
3573 verbosity.add_option('--skip-download',
3574 action='store_true', dest='skip_download', help='do not download the video', default=False)
3575 verbosity.add_option('-g', '--get-url',
3576 action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
3577 verbosity.add_option('-e', '--get-title',
3578 action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
3579 verbosity.add_option('--get-thumbnail',
3580 action='store_true', dest='getthumbnail',
3581 help='simulate, quiet but print thumbnail URL', default=False)
3582 verbosity.add_option('--get-description',
3583 action='store_true', dest='getdescription',
3584 help='simulate, quiet but print video description', default=False)
3585 verbosity.add_option('--get-filename',
3586 action='store_true', dest='getfilename',
3587 help='simulate, quiet but print output filename', default=False)
3588 verbosity.add_option('--get-format',
3589 action='store_true', dest='getformat',
3590 help='simulate, quiet but print output format', default=False)
3591 verbosity.add_option('--no-progress',
3592 action='store_true', dest='noprogress', help='do not print progress bar', default=False)
3593 verbosity.add_option('--console-title',
3594 action='store_true', dest='consoletitle',
3595 help='display progress in console titlebar', default=False)
3596
3597
3598 filesystem.add_option('-t', '--title',
3599 action='store_true', dest='usetitle', help='use title in file name', default=False)
3600 filesystem.add_option('-l', '--literal',
3601 action='store_true', dest='useliteral', help='use literal title in file name', default=False)
3602 filesystem.add_option('-A', '--auto-number',
3603 action='store_true', dest='autonumber',
3604 help='number downloaded files starting from 00000', default=False)
3605 filesystem.add_option('-o', '--output',
3606 dest='outtmpl', metavar='TEMPLATE', help='output filename template. Use %(stitle)s to get the title, %(uploader)s for the uploader name, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, and %% for a literal percent')
3607 filesystem.add_option('-a', '--batch-file',
3608 dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
3609 filesystem.add_option('-w', '--no-overwrites',
3610 action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
3611 filesystem.add_option('-c', '--continue',
3612 action='store_true', dest='continue_dl', help='resume partially downloaded files', default=False)
3613 filesystem.add_option('--no-continue',
3614 action='store_false', dest='continue_dl',
3615 help='do not resume partially downloaded files (restart from beginning)')
3616 filesystem.add_option('--cookies',
3617 dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
3618 filesystem.add_option('--no-part',
3619 action='store_true', dest='nopart', help='do not use .part files', default=False)
3620 filesystem.add_option('--no-mtime',
3621 action='store_false', dest='updatetime',
3622 help='do not use the Last-modified header to set the file modification time', default=True)
3623 filesystem.add_option('--write-description',
3624 action='store_true', dest='writedescription',
3625 help='write video description to a .description file', default=False)
3626 filesystem.add_option('--write-info-json',
3627 action='store_true', dest='writeinfojson',
3628 help='write video metadata to a .info.json file', default=False)
3629
3630
3631 postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False,
3632 help='convert video files to audio-only files (requires ffmpeg and ffprobe)')
3633 postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
3634 help='"best", "aac", "vorbis" or "mp3"; best by default')
3635 postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K',
3636 help='ffmpeg audio bitrate specification, 128k by default')
3637 postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
3638 help='keeps the video file on disk after the post-processing; the video is erased by default')
3639
3640
3641 parser.add_option_group(general)
3642 parser.add_option_group(selection)
3643 parser.add_option_group(filesystem)
3644 parser.add_option_group(verbosity)
3645 parser.add_option_group(video_format)
3646 parser.add_option_group(authentication)
3647 parser.add_option_group(postproc)
3648
3649 opts, args = parser.parse_args()
3650
3651 return parser, opts, args
3652
3653 def gen_extractors():
3654 """ Return a list of an instance of every supported extractor.
3655 The order does matter; the first extractor matched is the one handling the URL.
3656 """
3657 youtube_ie = YoutubeIE()
3658 google_ie = GoogleIE()
3659 yahoo_ie = YahooIE()
3660 return [
3661 YoutubePlaylistIE(youtube_ie),
3662 YoutubeUserIE(youtube_ie),
3663 YoutubeSearchIE(youtube_ie),
3664 youtube_ie,
3665 MetacafeIE(youtube_ie),
3666 DailymotionIE(),
3667 google_ie,
3668 GoogleSearchIE(google_ie),
3669 PhotobucketIE(),
3670 yahoo_ie,
3671 YahooSearchIE(yahoo_ie),
3672 DepositFilesIE(),
3673 FacebookIE(),
3674 BlipTVIE(),
3675 VimeoIE(),
3676 MyVideoIE(),
3677 ComedyCentralIE(),
3678 EscapistIE(),
3679
3680 GenericIE()
3681 ]
3682
3683 def main():
3684 parser, opts, args = parseOpts()
3685
3686 # Open appropriate CookieJar
3687 if opts.cookiefile is None:
3688 jar = cookielib.CookieJar()
3689 else:
3690 try:
3691 jar = cookielib.MozillaCookieJar(opts.cookiefile)
3692 if os.path.isfile(opts.cookiefile) and os.access(opts.cookiefile, os.R_OK):
3693 jar.load()
3694 except (IOError, OSError), err:
3695 sys.exit(u'ERROR: unable to open cookie file')
3696
3697 # Dump user agent
3698 if opts.dump_user_agent:
3699 print std_headers['User-Agent']
3700 sys.exit(0)
3701
3702 # Batch file verification
3703 batchurls = []
3704 if opts.batchfile is not None:
3705 try:
3706 if opts.batchfile == '-':
3707 batchfd = sys.stdin
3708 else:
3709 batchfd = open(opts.batchfile, 'r')
3710 batchurls = batchfd.readlines()
3711 batchurls = [x.strip() for x in batchurls]
3712 batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
3713 except IOError:
3714 sys.exit(u'ERROR: batch file could not be read')
3715 all_urls = batchurls + args
3716
3717 # General configuration
3718 cookie_processor = urllib2.HTTPCookieProcessor(jar)
3719 opener = urllib2.build_opener(urllib2.ProxyHandler(), cookie_processor, YoutubeDLHandler())
3720 urllib2.install_opener(opener)
3721 socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
3722
3723 extractors = gen_extractors()
3724
3725 if opts.list_extractors:
3726 for ie in extractors:
3727 print(ie.IE_NAME)
3728 matchedUrls = filter(lambda url: ie.suitable(url), all_urls)
3729 all_urls = filter(lambda url: url not in matchedUrls, all_urls)
3730 for mu in matchedUrls:
3731 print(u' ' + mu)
3732 sys.exit(0)
3733
3734 # Conflicting, missing and erroneous options
3735 if opts.usenetrc and (opts.username is not None or opts.password is not None):
3736 parser.error(u'using .netrc conflicts with giving username/password')
3737 if opts.password is not None and opts.username is None:
3738 parser.error(u'account username missing')
3739 if opts.outtmpl is not None and (opts.useliteral or opts.usetitle or opts.autonumber):
3740 parser.error(u'using output template conflicts with using title, literal title or auto number')
3741 if opts.usetitle and opts.useliteral:
3742 parser.error(u'using title conflicts with using literal title')
3743 if opts.username is not None and opts.password is None:
3744 opts.password = getpass.getpass(u'Type account password and press return:')
3745 if opts.ratelimit is not None:
3746 numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
3747 if numeric_limit is None:
3748 parser.error(u'invalid rate limit specified')
3749 opts.ratelimit = numeric_limit
3750 if opts.retries is not None:
3751 try:
3752 opts.retries = long(opts.retries)
3753 except (TypeError, ValueError), err:
3754 parser.error(u'invalid retry count specified')
3755 try:
3756 opts.playliststart = int(opts.playliststart)
3757 if opts.playliststart <= 0:
3758 raise ValueError(u'Playlist start must be positive')
3759 except (TypeError, ValueError), err:
3760 parser.error(u'invalid playlist start number specified')
3761 try:
3762 opts.playlistend = int(opts.playlistend)
3763 if opts.playlistend != -1 and (opts.playlistend <= 0 or opts.playlistend < opts.playliststart):
3764 raise ValueError(u'Playlist end must be greater than playlist start')
3765 except (TypeError, ValueError), err:
3766 parser.error(u'invalid playlist end number specified')
3767 if opts.extractaudio:
3768 if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis']:
3769 parser.error(u'invalid audio format specified')
3770
3771 # File downloader
3772 fd = FileDownloader({
3773 'usenetrc': opts.usenetrc,
3774 'username': opts.username,
3775 'password': opts.password,
3776 'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
3777 'forceurl': opts.geturl,
3778 'forcetitle': opts.gettitle,
3779 'forcethumbnail': opts.getthumbnail,
3780 'forcedescription': opts.getdescription,
3781 'forcefilename': opts.getfilename,
3782 'forceformat': opts.getformat,
3783 'simulate': opts.simulate,
3784 'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
3785 'format': opts.format,
3786 'format_limit': opts.format_limit,
3787 'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding()))
3788 or (opts.format == '-1' and opts.usetitle and u'%(stitle)s-%(id)s-%(format)s.%(ext)s')
3789 or (opts.format == '-1' and opts.useliteral and u'%(title)s-%(id)s-%(format)s.%(ext)s')
3790 or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
3791 or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(stitle)s-%(id)s.%(ext)s')
3792 or (opts.useliteral and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
3793 or (opts.usetitle and u'%(stitle)s-%(id)s.%(ext)s')
3794 or (opts.useliteral and u'%(title)s-%(id)s.%(ext)s')
3795 or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
3796 or u'%(id)s.%(ext)s'),
3797 'ignoreerrors': opts.ignoreerrors,
3798 'ratelimit': opts.ratelimit,
3799 'nooverwrites': opts.nooverwrites,
3800 'retries': opts.retries,
3801 'continuedl': opts.continue_dl,
3802 'noprogress': opts.noprogress,
3803 'playliststart': opts.playliststart,
3804 'playlistend': opts.playlistend,
3805 'logtostderr': opts.outtmpl == '-',
3806 'consoletitle': opts.consoletitle,
3807 'nopart': opts.nopart,
3808 'updatetime': opts.updatetime,
3809 'writedescription': opts.writedescription,
3810 'writeinfojson': opts.writeinfojson,
3811 'matchtitle': opts.matchtitle,
3812 'rejecttitle': opts.rejecttitle,
3813 })
3814 for extractor in extractors:
3815 fd.add_info_extractor(extractor)
3816
3817 # PostProcessors
3818 if opts.extractaudio:
3819 fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, keepvideo=opts.keepvideo))
3820
3821 # Update version
3822 if opts.update_self:
3823 updateSelf(fd, sys.argv[0])
3824
3825 # Maybe do nothing
3826 if len(all_urls) < 1:
3827 if not opts.update_self:
3828 parser.error(u'you must provide at least one URL')
3829 else:
3830 sys.exit()
3831 retcode = fd.download(all_urls)
3832
3833 # Dump cookie jar if requested
3834 if opts.cookiefile is not None:
3835 try:
3836 jar.save()
3837 except (IOError, OSError), err:
3838 sys.exit(u'ERROR: unable to save cookie jar')
3839
3840 sys.exit(retcode)
3841
3842
3843 if __name__ == '__main__':
3844 try:
3845 main()
3846 except DownloadError:
3847 sys.exit(1)
3848 except SameFileError:
3849 sys.exit(u'ERROR: fixed output name but more than one file to download')
3850 except KeyboardInterrupt:
3851 sys.exit(u'\nERROR: Interrupted by user')
3852
3853 # vim: set ts=4 sw=4 sts=4 noet ai si filetype=python: