~ubuntu-branches/ubuntu/maverick/coherence/maverick

« back to all changes in this revision

Viewing changes to coherence/extern/youtubedl/youtubedl.py

  • Committer: Bazaar Package Importer
  • Author(s): Charlie Smotherman
  • Date: 2010-01-02 10:57:15 UTC
  • mfrom: (1.1.7 upstream) (3.2.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100102105715-sghzl2nw4lr5b1ob
Tags: 0.6.6.2-1
*  New  upstream release, summary of changes:
    - adding all necessary files to MANIFEST.in, to compensate for the
      gone 'auto-include-all-files-under-version-control' setuptools
      feature.
    - rearranging genre and genres attribute in DIDLLite - thx Caleb  
    - fix face_path typo, fixes #275.   

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/env python
2
2
# -*- coding: utf-8 -*-
3
 
# Author: Ricardo Garcia Gonzalez, Jean-Michel Sizun
 
3
# Author: Ricardo Garcia Gonzalez 
 
4
# Author: Danny Colligan
 
5
# Author: Jean-Michel Sizun (integration within coherence framework)
4
6
# License: Public domain code
5
7
import htmlentitydefs
6
8
import httplib
14
16
import string
15
17
import sys
16
18
import time
17
 
from urllib import urlencode
 
19
from urllib import urlencode, unquote, unquote_plus
18
20
from coherence.upnp.core.utils import getPage
19
21
 
20
22
std_headers = {
21
 
        'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5',
 
23
    'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2',
22
24
    'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
23
25
    'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
24
26
    'Accept-Language': 'en-us,en;q=0.5',
25
27
}
26
28
 
 
29
 
27
30
simple_title_chars = string.ascii_letters.decode('ascii') + string.digits.decode('ascii')
28
31
 
 
32
def preferredencoding():
 
33
    """Get preferred encoding.
 
34
 
 
35
    Returns the best encoding scheme for the system, based on
 
36
    locale.getpreferredencoding() and some further tweaks.
 
37
    """
 
38
    try:
 
39
        pref = locale.getpreferredencoding()
 
40
        # Mac OSX systems have this problem sometimes
 
41
        if pref == '':
 
42
            return 'UTF-8'
 
43
        return pref
 
44
    except:
 
45
        sys.stderr.write('WARNING: problem obtaining preferred encoding. Falling back to UTF-8.\n')
 
46
        return 'UTF-8'
 
47
 
 
48
 
29
49
class DownloadError(Exception):
30
50
    """Download Error exception.
31
51
 
51
71
    """
52
72
    pass
53
73
 
 
74
class UnavailableFormatError(Exception):
 
75
    """Unavailable Format exception.
 
76
 
 
77
    This exception will be thrown when a video is requested
 
78
    in a format that is not available for that video.
 
79
    """
 
80
    pass
 
81
 
 
82
class ContentTooShortError(Exception):
 
83
    """Content Too Short exception.
 
84
 
 
85
    This exception may be raised by FileDownloader objects when a file they
 
86
    download is too small for what the server announced first, indicating
 
87
    the connection was probably interrupted.
 
88
    """
 
89
    # Both in bytes
 
90
    downloaded = None
 
91
    expected = None
 
92
 
 
93
    def __init__(self, downloaded, expected):
 
94
        self.downloaded = downloaded
 
95
        self.expected = expected
 
96
 
54
97
class FileDownloader(object):
55
98
    """File Downloader class.
56
99
 
64
107
    For this, file downloader objects have a method that allows
65
108
    InfoExtractors to be registered in a given order. When it is passed
66
109
    a URL, the file downloader handles it to the first InfoExtractor it
67
 
    finds that reports being able to handle it. The InfoExtractor returns
68
 
    all the information to the FileDownloader and the latter downloads the
69
 
    file or does whatever it's instructed to do.
 
110
    finds that reports being able to handle it. The InfoExtractor extracts
 
111
    all the information about the video or videos the URL refers to, and
 
112
    asks the FileDownloader to process the video information, possibly
 
113
    downloading the video.
70
114
 
71
115
    File downloaders accept a lot of parameters. In order not to saturate
72
116
    the object constructor with arguments, it receives a dictionary of
73
 
    options instead. These options are available through the get_params()
74
 
    method for the InfoExtractors to use. The FileDownloader also registers
75
 
    itself as the downloader in charge for the InfoExtractors that are
76
 
    added to it, so this is a "mutual registration".
 
117
    options instead. These options are available through the params
 
118
    attribute for the InfoExtractors to use. The FileDownloader also
 
119
    registers itself as the downloader in charge for the InfoExtractors
 
120
    that are added to it, so this is a "mutual registration".
77
121
 
78
122
    Available options:
79
123
 
88
132
    outtmpl:    Template for output names.
89
133
    ignoreerrors:       Do not stop on download errors.
90
134
    ratelimit:  Download speed limit, in bytes/sec.
 
135
    nooverwrites:    Prevent overwriting files.
 
136
    continuedl:    Try to continue downloads if possible.
91
137
    """
92
138
 
93
 
    _params = None
 
139
    params = None
94
140
    _ies = []
95
141
    _pps = []
96
 
 
 
142
    _download_retcode = None
 
143
        
97
144
    def __init__(self, params):
98
145
        """Create a FileDownloader object with the given options."""
99
146
        self._ies = []
100
147
        self._pps = []
101
 
        self.set_params(params)
 
148
        self._download_retcode = 0
 
149
        self.params = params
102
150
 
103
151
    @staticmethod
104
152
    def pmkdir(filename):
114
162
    def format_bytes(bytes):
115
163
        if bytes is None:
116
164
            return 'N/A'
117
 
        if bytes == 0:
 
165
        if type(bytes) is str:
 
166
            bytes = float(bytes)
 
167
        if bytes == 0.0:
118
168
            exponent = 0
119
169
        else:
120
 
            exponent = long(math.log(float(bytes), 1024.0))
 
170
            exponent = long(math.log(bytes, 1024.0))
121
171
        suffix = 'bkMGTPEZY'[exponent]
122
172
        converted = float(bytes) / float(1024**exponent)
123
173
        return '%.2f%s' % (converted, suffix)
154
204
        new_min = max(bytes / 2.0, 1.0)
155
205
        new_max = min(max(bytes * 2.0, 1.0), 4194304) # Do not surpass 4 MB
156
206
        if elapsed_time < 0.001:
157
 
            return int(new_max)
 
207
            return long(new_max)
158
208
        rate = bytes / elapsed_time
159
209
        if rate > new_max:
160
 
            return int(new_max)
 
210
            return long(new_max)
161
211
        if rate < new_min:
162
 
            return int(new_min)
163
 
        return int(rate)
 
212
            return long(new_min)
 
213
        return long(rate)
164
214
 
165
215
    @staticmethod
166
216
    def parse_bytes(bytestr):
172
222
        multiplier = 1024.0 ** 'bkmgtpezy'.index(matchobj.group(2).lower())
173
223
        return long(round(number * multiplier))
174
224
 
175
 
    def set_params(self, params):
176
 
        """Sets parameters."""
177
 
        if type(params) != dict:
178
 
            raise ValueError('params: dictionary expected')
179
 
        self._params = params
180
 
 
181
 
    def get_params(self):
182
 
        """Get parameters."""
183
 
        return self._params
184
 
 
 
225
    @staticmethod
 
226
    def verify_url(url):
 
227
        """Verify a URL is valid and data could be downloaded. Return real data URL."""
 
228
        request = urllib2.Request(url, None, std_headers)
 
229
        data = urllib2.urlopen(request)
 
230
        data.read(1)
 
231
        url = data.geturl()
 
232
        data.close()
 
233
        return url
185
234
    def add_info_extractor(self, ie):
186
235
        """Add an InfoExtractor object to the end of the list."""
187
236
        self._ies.append(ie)
194
243
 
195
244
    def to_stdout(self, message, skip_eol=False):
196
245
        """Print message to stdout if not in quiet mode."""
197
 
        if not self._params.get('quiet', False):
198
 
            print u'%s%s' % (message, [u'\n', u''][skip_eol]),
 
246
        if not self.params.get('quiet', False):
 
247
            print (u'%s%s' % (message, [u'\n', u''][skip_eol])).encode(preferredencoding()),
199
248
            sys.stdout.flush()
200
 
 
 
249
    
201
250
    def to_stderr(self, message):
202
251
        """Print message to stderr."""
203
 
        print >>sys.stderr, message
204
 
 
 
252
        print >>sys.stderr, message.encode(preferredencoding())
 
253
    
205
254
    def fixed_template(self):
206
255
        """Checks if the output template is fixed."""
207
 
        return (re.search(ur'(?u)%\(.+?\)s', self._params['outtmpl']) is None)
 
256
        return (re.search(ur'(?u)%\(.+?\)s', self.params['outtmpl']) is None)
208
257
 
209
258
    def trouble(self, message=None):
210
259
        """Determine action to take when a download problem appears.
211
260
 
212
261
        Depending on if the downloader has been configured to ignore
213
262
        download errors or not, this method may throw an exception or
214
 
        not when errors are found, after printing the message. If it
215
 
        doesn't raise, it returns an error code suitable to be returned
216
 
        later as a program exit code to indicate error.
 
263
        not when errors are found, after printing the message.
217
264
        """
218
265
        if message is not None:
219
266
            self.to_stderr(message)
220
 
        if not self._params.get('ignoreerrors', False):
 
267
        if not self.params.get('ignoreerrors', False):
221
268
            raise DownloadError(message)
222
 
        return 1
 
269
        self._download_retcode = 1
223
270
 
224
271
    def slow_down(self, start_time, byte_counter):
225
272
        """Sleep if the download speed is over the rate limit."""
226
 
        rate_limit = self._params.get('ratelimit', None)
 
273
        rate_limit = self.params.get('ratelimit', None)
227
274
        if rate_limit is None or byte_counter == 0:
228
275
            return
229
276
        now = time.time()
243
290
        self.to_stdout(u'\r[download] %s of %s at %s ETA %s' %
244
291
                (percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
245
292
 
 
293
    def report_resuming_byte(self, resume_len):
 
294
        """Report attemtp to resume at given byte."""
 
295
        self.to_stdout(u'[download] Resuming download at byte %s' % resume_len)
 
296
    
 
297
    def report_file_already_downloaded(self, file_name):
 
298
        """Report file has already been fully downloaded."""
 
299
        self.to_stdout(u'[download] %s has already been downloaded' % file_name)
 
300
    
 
301
    def report_unable_to_resume(self):
 
302
        """Report it was impossible to resume download."""
 
303
        self.to_stdout(u'[download] Unable to resume')
 
304
    
246
305
    def report_finish(self):
247
306
        """Report download finished."""
248
307
        self.to_stdout(u'')
249
308
 
 
309
    def process_info(self, info_dict):
 
310
        """Process a single dictionary returned by an InfoExtractor."""
 
311
        # Do nothing else if in simulate mode
 
312
        if self.params.get('simulate', False):
 
313
            try:
 
314
                info_dict['url'] = self.verify_url(info_dict['url'])
 
315
            except (OSError, IOError, urllib2.URLError, httplib.HTTPException, socket.error), err:
 
316
                raise UnavailableFormatError
 
317
 
 
318
            # Forced printings
 
319
            if self.params.get('forcetitle', False):
 
320
                print info_dict['title'].encode(preferredencoding())
 
321
            if self.params.get('forceurl', False):
 
322
                print info_dict['url'].encode(preferredencoding())
 
323
 
 
324
            return
 
325
            
 
326
        try:
 
327
            template_dict = dict(info_dict)
 
328
            template_dict['epoch'] = unicode(long(time.time()))
 
329
            filename = self.params['outtmpl'] % template_dict
 
330
        except (ValueError, KeyError), err:
 
331
            self.trouble('ERROR: invalid output template or system charset: %s' % str(err))
 
332
        if self.params['nooverwrites'] and os.path.exists(filename):
 
333
            self.to_stderr(u'WARNING: file exists: %s; skipping' % filename)
 
334
            return
 
335
 
 
336
        try:
 
337
            self.pmkdir(filename)
 
338
        except (OSError, IOError), err:
 
339
            self.trouble('ERROR: unable to create directories: %s' % str(err))
 
340
            return
 
341
 
 
342
        try:
 
343
            success = self._do_download(filename, info_dict['url'])
 
344
        except (OSError, IOError), err:
 
345
            raise UnavailableFormatError
 
346
        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
 
347
            self.trouble('ERROR: unable to download video data: %s' % str(err))
 
348
            return
 
349
        except (ContentTooShortError, ), err:
 
350
            self.trouble('ERROR: content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
 
351
            return
 
352
 
 
353
        if success:
 
354
            try:
 
355
                self.post_process(filename, info_dict)
 
356
            except (PostProcessingError), err:
 
357
                self.trouble('ERROR: postprocessing: %s' % str(err))
 
358
                return
 
359
 
250
360
    def download(self, url_list):
251
361
        """Download a given list of URLs."""
252
 
        retcode = 0
253
362
        if len(url_list) > 1 and self.fixed_template():
254
 
            raise SameFileError(self._params['outtmpl'])
 
363
            raise SameFileError(self.params['outtmpl'])
255
364
 
256
365
        for url in url_list:
257
366
            suitable_found = False
258
367
            for ie in self._ies:
 
368
               # Go to next InfoExtractor if not suitable
259
369
                if not ie.suitable(url):
260
370
                    continue
261
371
                # Suitable InfoExtractor found
262
372
                suitable_found = True
263
 
                all_results = ie.extract(url)
264
 
                results = [x for x in all_results if x is not None]
265
 
                if len(results) != len(all_results):
266
 
                    retcode = self.trouble()
267
 
 
268
 
                if len(results) > 1 and self.fixed_template():
269
 
                    raise SameFileError(self._params['outtmpl'])
270
 
 
271
 
                for result in results:
272
 
                    # Forced printings
273
 
                    if self._params.get('forcetitle', False):
274
 
                        print result['title']
275
 
                    if self._params.get('forceurl', False):
276
 
                        print result['url']
277
 
 
278
 
                    # Do nothing else if in simulate mode
279
 
                    if self._params.get('simulate', False):
280
 
                        continue
281
 
 
282
 
                    try:
283
 
                        filename = self._params['outtmpl'] % result
284
 
                        self.report_destination(filename)
285
 
                    except (ValueError, KeyError), err:
286
 
                        retcode = self.trouble('ERROR: invalid output template or system charset: %s' % str(err))
287
 
                        continue
288
 
                    try:
289
 
                        self.pmkdir(filename)
290
 
                    except (OSError, IOError), err:
291
 
                        retcode = self.trouble('ERROR: unable to create directories: %s' % str(err))
292
 
                        continue
293
 
                    try:
294
 
                        outstream = open(filename, 'wb')
295
 
                    except (OSError, IOError), err:
296
 
                        retcode = self.trouble('ERROR: unable to open for writing: %s' % str(err))
297
 
                        continue
298
 
                    try:
299
 
                        self._do_download(outstream, result['url'])
300
 
                        outstream.close()
301
 
                    except (OSError, IOError), err:
302
 
                        retcode = self.trouble('ERROR: unable to write video data: %s' % str(err))
303
 
                        continue
304
 
                    except (urllib2.URLError, httplib.HTTPException, socket.error), err:
305
 
                        retcode = self.trouble('ERROR: unable to download video data: %s' % str(err))
306
 
                        continue
307
 
                    try:
308
 
                        self.post_process(filename, result)
309
 
                    except (PostProcessingError), err:
310
 
                        retcode = self.trouble('ERROR: postprocessing: %s' % str(err))
311
 
                        continue
312
 
 
 
373
                # Extract information from URL and process it
 
374
                ie.extract(url)
 
375
                        
 
376
                # Suitable InfoExtractor had been found; go to next URL
313
377
                break
 
378
                
314
379
            if not suitable_found:
315
 
                retcode = self.trouble('ERROR: no suitable InfoExtractor: %s' % url)
 
380
                self.trouble('ERROR: no suitable InfoExtractor: %s' % url)
316
381
 
317
 
        return retcode
 
382
        return self._download_retcode
318
383
 
319
384
    def get_real_urls(self, url_list):
320
385
        """Download a given list of URLs."""
357
422
            if info is None:
358
423
                break
359
424
 
 
425
# _do_download REMOVED
360
426
 
361
427
class InfoExtractor(object):
362
428
    """Information Extractor class.
364
430
    Information extractors are the classes that, given a URL, extract
365
431
    information from the video (or videos) the URL refers to. This
366
432
    information includes the real video URL, the video title and simplified
367
 
    title, author and others. It is returned in a list of dictionaries when
368
 
    calling its extract() method. It is a list because a URL can refer to
369
 
    more than one video (think of playlists). The dictionaries must include
 
433
    title, author and others. The information is stored in a dictionary
 
434
    which is then passed to the FileDownloader. The FileDownloader
 
435
    processes this information possibly downloading the video to the file
 
436
    system, among other possible outcomes. The dictionaries must include
370
437
    the following fields:
371
438
 
372
439
    id:         Video identifier.
437
504
    _LOGIN_URL = 'http://www.youtube.com/signup?next=/&gl=US&hl=en'
438
505
    _AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
439
506
    _NETRC_MACHINE = 'youtube'
440
 
 
 
507
    _available_formats = ['22', '35', '18', '5', '17', '13', None] # listed in order of priority for -b flag
 
508
    _video_extensions = {
 
509
        '13': '3gp',
 
510
        '17': 'mp4',
 
511
        '18': 'mp4',
 
512
        '22': 'mp4',
 
513
    }
 
514
    
441
515
    @staticmethod
442
516
    def suitable(url):
443
517
        return (re.match(YoutubeIE._VALID_URL, url) is not None)
444
518
 
445
 
        def report_lang(self):
446
 
                """Report attempt to set language."""
447
 
                self.to_stdout(u'[youtube] Setting language')
 
519
    @staticmethod
 
520
    def htmlentity_transform(matchobj):
 
521
        """Transforms an HTML entity to a Unicode character."""
 
522
        entity = matchobj.group(1)
 
523
 
 
524
        # Known non-numeric HTML entity
 
525
        if entity in htmlentitydefs.name2codepoint:
 
526
            return unichr(htmlentitydefs.name2codepoint[entity])
 
527
 
 
528
        # Unicode character
 
529
        mobj = re.match(ur'(?u)#(x?\d+)', entity)
 
530
        if mobj is not None:
 
531
            numstr = mobj.group(1)
 
532
            if numstr.startswith(u'x'):
 
533
                base = 16
 
534
                numstr = u'0%s' % numstr
 
535
            else:
 
536
                base = 10
 
537
            return unichr(long(numstr, base))
 
538
 
 
539
        # Unknown entity in name, return its literal representation
 
540
        return (u'&%s;' % entity)
 
541
 
 
542
 
 
543
    def report_lang(self):
 
544
        """Report attempt to set language."""
 
545
        self._downloader.to_stdout(u'[youtube] Setting language')
448
546
 
449
547
    def report_login(self):
450
548
        """Report attempt to log in."""
451
 
        self.to_stdout(u'[youtube] Logging in')
452
 
 
 
549
        self._downloader.to_stdout(u'[youtube] Logging in')
 
550
    
453
551
    def report_age_confirmation(self):
454
552
        """Report attempt to confirm age."""
455
 
        self.to_stdout(u'[youtube] Confirming age')
456
 
 
457
 
    def report_webpage_download(self, video_id):
458
 
        """Report attempt to download webpage."""
459
 
        self.to_stdout(u'[youtube] %s: Downloading video webpage' % video_id)
460
 
 
 
553
        self._downloader.to_stdout(u'[youtube] Confirming age')
 
554
 
 
555
    def report_video_info_webpage_download(self, video_id):
 
556
        """Report attempt to download video info webpage."""
 
557
        self._downloader.to_stdout(u'[youtube] %s: Downloading video info webpage' % video_id)
 
558
    
461
559
    def report_information_extraction(self, video_id):
462
560
        """Report attempt to extract video information."""
463
 
        self.to_stdout(u'[youtube] %s: Extracting video information' % video_id)
 
561
        self._downloader.to_stdout(u'[youtube] %s: Extracting video information' % video_id)
 
562
 
 
563
    def report_unavailable_format(self, video_id, format):
 
564
        """Report extracted video URL."""
 
565
        self._downloader.to_stdout(u'[youtube] %s: Format %s not available' % (video_id, format))
464
566
 
465
567
    def report_video_url(self, video_id, video_real_url):
466
568
        """Report extracted video URL."""
467
 
        self.to_stdout(u'[youtube] %s: URL: %s' % (video_id, video_real_url))
 
569
        self._downloader.to_stdout(u'[youtube] %s: URL: %s' % (video_id, video_real_url))
468
570
 
469
571
    def _real_initialize(self):
470
572
 
473
575
 
474
576
        username = None
475
577
        password = None
476
 
        downloader_params = self._downloader.get_params()
 
578
        downloader_params = self._downloader.params
477
579
 
478
580
        # Attempt to use provided username and password or .netrc data
479
581
        if downloader_params.get('username', None) is not None:
488
590
                else:
489
591
                    raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
490
592
            except (IOError, netrc.NetrcParseError), err:
491
 
                self.to_stderr(u'WARNING: parsing .netrc: %s' % str(err))
 
593
                self._downloader.to_stderr(u'WARNING: parsing .netrc: %s' % str(err))
492
594
                return
493
595
 
494
 
        # No authentication to be performed
495
 
        if username is None:
496
 
            return
497
 
 
498
 
 
499
 
        #def gotAgeConfirmedPage(result):
500
 
            #print "Age confirmed in Youtube"
 
596
        def gotAgeConfirmedPage(result):
 
597
            print "Age confirmed in Youtube"
501
598
 
502
599
        def gotLoggedInPage(result):
503
600
            data,headers = result
504
601
            if re.search(r'(?i)<form[^>]* name="loginForm"', data) is not None:
505
602
                print 'WARNING: unable to log in: bad username or password'
506
603
                return
507
 
            #print "logged in in Youtube"
 
604
            print "logged in in Youtube"
508
605
 
509
606
            # Confirm age
510
607
            age_form = {
513
610
                        }
514
611
            postdata = urlencode(age_form)
515
612
            d = getPage(self._AGE_URL, postdata=postdata, headers=std_headers)
516
 
            #d.addCallback(gotAgeConfirmedPage)
517
 
 
518
 
 
519
 
        def gotError(error):
520
 
            print "Unable to process Youtube request: %s" % self._LOGIN_URL
521
 
            print "Error: %s" % error
522
 
            return
523
 
 
524
 
        # Log in
525
 
        login_form = {
526
 
                                'current_form': 'loginForm',
527
 
                'next':         '/',
528
 
                'action_login': 'Log In',
529
 
                'username':     username,
530
 
                'password':     password,
531
 
        }
532
 
        postdata = urlencode(login_form)
533
 
        d = getPage(self._LOGIN_URL, method='POST', postdata=postdata, headers=std_headers)
534
 
        d.addCallbacks(gotLoggedInPage, gotError)
 
613
            d.addCallback(gotAgeConfirmedPage)
 
614
 
 
615
        def gotLoginError(error):
 
616
            print "Unable to login to Youtube : %s:%s @ %s" % (username, password, self._LOGIN_URL)
 
617
            print "Error: %s" % error
 
618
            return
 
619
 
 
620
        def gotLanguageSet(result):
 
621
            data,headers = result
 
622
            # No authentication to be performed
 
623
            if username is None:
 
624
                return           
 
625
            # Log in
 
626
            login_form = {
 
627
                    'current_form': 'loginForm',
 
628
                    'next':        '/',
 
629
                    'action_login':    'Log In',
 
630
                    'username':    username,
 
631
                    'password':    password,
 
632
            }
 
633
            postdata = urlencode(login_form)
 
634
            d = getPage(self._LOGIN_URL, method='POST', postdata=postdata, headers=std_headers)
 
635
            d.addCallbacks(gotLoggedInPage, gotLoginError)
 
636
            
 
637
        def gotLanguageSetError(error):
 
638
            print "Unable to process Youtube request: %s" % self._LANG_URL
 
639
            print "Error: %s" % error
 
640
            return
 
641
 
 
642
        # Set language (will lead to log in, and then age confirmation)
 
643
        d = getPage(self._LANG_URL, headers=std_headers)
 
644
        d.addCallbacks(gotLanguageSet, gotLanguageSetError)
535
645
 
536
646
 
537
647
 
539
649
        # Extract video id from URL
540
650
        mobj = re.match(self._VALID_URL, url)
541
651
        if mobj is None:
542
 
            self.to_stderr(u'ERROR: invalid URL: %s' % url)
543
 
            return [None]
 
652
            self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
 
653
            return
544
654
        video_id = mobj.group(2)
545
655
 
546
656
        # Downloader parameters
 
657
        best_quality = False
547
658
        format_param = None
 
659
        video_extension = None
 
660
        
 
661
        quality_index = 0
548
662
        if self._downloader is not None:
549
 
            params = self._downloader.get_params()
 
663
            params = self._downloader.params
550
664
            format_param = params.get('format', None)
551
 
 
552
 
        # Extension
553
 
        video_extension = {'18': 'mp4', '17': '3gp', '22': 'mp4'}.get(format_param, 'flv')
554
 
 
555
 
        # Normalize URL, including format
556
 
        normalized_url = 'http://www.youtube.com/watch?v=%s' % video_id
 
665
            if format_param == '0':
 
666
                format_param = self._available_formats[quality_index]
 
667
                best_quality = True
 
668
        video_extension = self._video_extensions.get(format_param, 'flv')
 
669
 
 
670
        # video info
 
671
        video_info_url = 'http://www.youtube.com/get_video_info?&video_id=%s&el=detailpage&ps=default&eurl=&gl=US&hl=en' % video_id
557
672
        if format_param is not None:
558
 
            normalized_url = '%s&fmt=%s' % (normalized_url, format_param)
559
 
 
560
 
        def gotPage(result, format_param):
561
 
            video_webpage,headers = result
562
 
 
 
673
            video_info_url = '%s&fmt=%s' % (video_info_url, format_param)
 
674
 
 
675
        def gotPage(result, format_param, video_extension):
 
676
            video_info_webpage,headers = result
 
677
            
563
678
            # check format
564
679
            if (format_param == '22'):
565
680
                print "Check if HD video exists..."
566
 
                mobj = re.search(r'var isHDAvailable = true;', video_webpage)
 
681
                mobj = re.search(r'var isHDAvailable = true;', video_info_webpage)
567
682
                if mobj is None:
568
683
                    print "No HD video -> switch back to SD"
569
684
                    format_param = '18'
571
686
                    print "...HD video OK!"
572
687
 
573
688
            # "t" param
574
 
            mobj = re.search(r', "t": "([^"]+)"', video_webpage)
 
689
            mobj = re.search(r'(?m)&token=([^&]+)(?:&|$)', video_info_webpage)
575
690
            if mobj is None:
576
 
                self.to_stderr(u'ERROR: unable to extract "t" parameter')
577
 
                return [None]
578
 
            video_real_url = 'http://uk.youtube.com/get_video?video_id=%s&t=%s' % (video_id, mobj.group(1))
 
691
                # Attempt to see if YouTube has issued an error message
 
692
                mobj = re.search(r'(?m)&reason=([^&]+)(?:&|$)', video_info_webpage)
 
693
                if mobj is None:
 
694
                        self.to_stderr(u'ERROR: unable to extract "t" parameter')
 
695
                        print video_info_webpage
 
696
                        return [None]
 
697
                else:
 
698
                    reason = unquote_plus(mobj.group(1))
 
699
                    self.to_stderr(u'ERROR: YouTube said: %s' % reason.decode('utf-8'))
 
700
            
 
701
            token = unquote(mobj.group(1))
 
702
            video_real_url = 'http://www.youtube.com/get_video?video_id=%s&t=%s&eurl=&el=detailpage&ps=default&gl=US&hl=en' % (video_id, token)
579
703
            if format_param is not None:
580
704
                video_real_url = '%s&fmt=%s' % (video_real_url, format_param)
581
 
 
 
705
            
582
706
            # uploader
583
 
            mobj = re.search(r"var watchUsername = '([^']+)';", video_webpage)
 
707
            mobj = re.search(r'(?m)&author=([^&]+)(?:&|$)', video_info_webpage)
584
708
            if mobj is None:
585
 
                self.to_stderr(u'ERROR: unable to extract uploader nickname')
586
 
                return [None]
587
 
            video_uploader = mobj.group(1)
588
 
 
 
709
                self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
 
710
                return
 
711
            video_uploader = unquote(mobj.group(1))
 
712
            
589
713
            # title
590
 
            mobj = re.search(r'(?im)<title>YouTube - ([^<]*)</title>', video_webpage)
 
714
            mobj = re.search(r'(?m)&title=([^&]+)(?:&|$)', video_info_webpage)
591
715
            if mobj is None:
592
 
                self.to_stderr(u'ERROR: unable to extract video title')
593
 
                return [None]
594
 
            video_title = mobj.group(1).decode('utf-8')
595
 
            video_title = re.sub(ur'(?u)&(.+?);', lambda x: unichr(htmlentitydefs.name2codepoint[x.group(1)]), video_title)
 
716
                self._downloader.trouble(u'ERROR: unable to extract video title')
 
717
                return
 
718
            video_title = unquote(mobj.group(1))
 
719
            video_title = video_title.decode('utf-8')
 
720
            video_title = re.sub(ur'(?u)&(.+?);', self.htmlentity_transform, video_title)
596
721
            video_title = video_title.replace(os.sep, u'%')
597
 
 
 
722
            
598
723
            # simplified title
599
724
            simple_title = re.sub(ur'(?u)([^%s]+)' % simple_title_chars, ur'_', video_title)
600
725
            simple_title = simple_title.strip(ur'_')
601
 
 
 
726
            
602
727
            # Return information
603
728
            return [{
604
729
                'id':           video_id.decode('utf-8'),
614
739
            print "Error: %s" % error
615
740
            return [None]
616
741
 
617
 
        d = getPage(normalized_url, headers=std_headers)
618
 
        d.addCallback(gotPage, format_param)
 
742
        d = getPage(video_info_url, headers=std_headers)
 
743
        d.addCallback(gotPage, format_param, video_extension)
619
744
        d.addErrback(gotError)
620
745
        return d
621
746
 
626
751
 
627
752
    _VALID_URL = r'(?:http://)?(?:www\.)?metacafe\.com/watch/([^/]+)/([^/]+)/.*'
628
753
    _DISCLAIMER = 'http://www.metacafe.com/family_filter/'
 
754
    _FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user'
629
755
    _youtube_ie = None
630
756
 
631
757
    def __init__(self, youtube_ie, downloader=None):
638
764
 
639
765
    def report_disclaimer(self):
640
766
        """Report disclaimer retrieval."""
641
 
        self.to_stdout(u'[metacafe] Retrieving disclaimer')
 
767
        self._downloader.to_stdout(u'[metacafe] Retrieving disclaimer')
642
768
 
643
769
    def report_age_confirmation(self):
644
770
        """Report attempt to confirm age."""
645
 
        self.to_stdout(u'[metacafe] Confirming age')
646
 
 
 
771
        self._downloader.to_stdout(u'[metacafe] Confirming age')
 
772
    
647
773
    def report_download_webpage(self, video_id):
648
774
        """Report webpage download."""
649
 
        self.to_stdout(u'[metacafe] %s: Downloading webpage' % video_id)
650
 
 
 
775
        self._downloader.to_stdout(u'[metacafe] %s: Downloading webpage' % video_id)
 
776
    
651
777
    def report_extraction(self, video_id):
652
778
        """Report information extraction."""
653
 
        self.to_stdout(u'[metacafe] %s: Extracting information' % video_id)
 
779
        self._downloader.to_stdout(u'[metacafe] %s: Extracting information' % video_id)
654
780
 
655
781
    def _real_initialize(self):
656
782
        # Retrieve disclaimer
659
785
            self.report_disclaimer()
660
786
            disclaimer = urllib2.urlopen(request).read()
661
787
        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
662
 
            self.to_stderr(u'ERROR: unable to retrieve disclaimer: %s' % str(err))
 
788
            self._downloader.trouble(u'ERROR: unable to retrieve disclaimer: %s' % str(err))
663
789
            return
664
790
 
665
791
        # Confirm age
667
793
            'filters': '0',
668
794
            'submit': "Continue - I'm over 18",
669
795
            }
670
 
        request = urllib2.Request('http://www.metacafe.com/', urllib.urlencode(disclaimer_form), std_headers)
 
796
        request = urllib2.Request(self._FILTER_POST, urllib.urlencode(disclaimer_form), std_headers)
671
797
        try:
672
798
            self.report_age_confirmation()
673
799
            disclaimer = urllib2.urlopen(request).read()
674
800
        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
675
 
            self.to_stderr(u'ERROR: unable to confirm age: %s' % str(err))
 
801
            self._downloader.trouble(u'ERROR: unable to confirm age: %s' % str(err))
676
802
            return
677
 
 
 
803
    
678
804
    def _real_extract(self, url):
679
805
        # Extract id and simplified title from URL
680
806
        mobj = re.match(self._VALID_URL, url)
681
807
        if mobj is None:
682
 
            self.to_stderr(u'ERROR: invalid URL: %s' % url)
683
 
            return [None]
 
808
            self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
 
809
            return
684
810
 
685
811
        video_id = mobj.group(1)
686
812
 
687
813
        # Check if video comes from YouTube
688
814
        mobj2 = re.match(r'^yt-(.*)$', video_id)
689
815
        if mobj2 is not None:
690
 
            return self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % mobj2.group(1))
691
 
 
 
816
            self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % mobj2.group(1))
 
817
            return
 
818
            
692
819
        simple_title = mobj.group(2).decode('utf-8')
693
820
        video_extension = 'flv'
694
821
 
698
825
            self.report_download_webpage(video_id)
699
826
            webpage = urllib2.urlopen(request).read()
700
827
        except (urllib2.URLError, httplib.HTTPException, socket.error), err:
701
 
            self.to_stderr(u'ERROR: unable retrieve video webpage: %s' % str(err))
702
 
            return [None]
 
828
            self._downloader.trouble(u'ERROR: unable retrieve video webpage: %s' % str(err))
 
829
            return
703
830
 
704
831
        # Extract URL, uploader and title from webpage
705
832
        self.report_extraction(video_id)
706
 
        mobj = re.search(r'(?m)"mediaURL":"(http.*?\.flv)"', webpage)
707
 
        if mobj is None:
708
 
            self.to_stderr(u'ERROR: unable to extract media URL')
709
 
            return [None]
710
 
        mediaURL = mobj.group(1).replace('\\', '')
711
 
 
712
 
        mobj = re.search(r'(?m)"gdaKey":"(.*?)"', webpage)
713
 
        if mobj is None:
714
 
            self.to_stderr(u'ERROR: unable to extract gdaKey')
715
 
            return [None]
716
 
        gdaKey = mobj.group(1)
717
 
 
718
 
        video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
 
833
        mobj = re.search(r'(?m)&mediaURL=([^&]+)', webpage)
 
834
        if mobj is None:
 
835
            self._downloader.trouble(u'ERROR: unable to extract media URL')
 
836
            return
 
837
        mediaURL = urllib.unquote(mobj.group(1))
 
838
 
 
839
       #mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
 
840
        #if mobj is None:
 
841
        #    self._downloader.trouble(u'ERROR: unable to extract gdaKey')
 
842
        #    return
 
843
        #gdaKey = mobj.group(1)
 
844
        #
 
845
        #video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
 
846
 
 
847
        video_url = mediaURL
719
848
 
720
849
        mobj = re.search(r'(?im)<title>(.*) - Video</title>', webpage)
721
850
        if mobj is None:
722
 
            self.to_stderr(u'ERROR: unable to extract title')
723
 
            return [None]
 
851
            self._downloader.trouble(u'ERROR: unable to extract title')
 
852
            return
724
853
        video_title = mobj.group(1).decode('utf-8')
725
 
 
726
 
        mobj = re.search(r'(?m)<li id="ChnlUsr">.*?Submitter:<br />(.*?)</li>', webpage)
727
 
        if mobj is None:
728
 
            self.to_stderr(u'ERROR: unable to extract uploader nickname')
729
 
            return [None]
730
 
        video_uploader = re.sub(r'<.*?>', '', mobj.group(1))
731
 
 
732
 
        # Return information
733
 
        return [{
734
 
            'id':               video_id.decode('utf-8'),
735
 
            'url':              video_url.decode('utf-8'),
736
 
            'uploader': video_uploader.decode('utf-8'),
737
 
            'title':    video_title,
738
 
            'stitle':   simple_title,
739
 
            'ext':              video_extension.decode('utf-8'),
740
 
            }]
 
854
        
 
855
        mobj = re.search(r'(?ms)<li id="ChnlUsr">.*?Submitter:.*?<a .*?>(.*?)<', webpage)
 
856
        if mobj is None:
 
857
            self._downloader.trouble(u'ERROR: unable to extract uploader nickname')
 
858
            return
 
859
        video_uploader = mobj.group(1)
 
860
 
 
861
        try:
 
862
            # Process video information
 
863
            self._downloader.process_info({
 
864
                'id':        video_id.decode('utf-8'),
 
865
                'url':        video_url.decode('utf-8'),
 
866
                'uploader':    video_uploader.decode('utf-8'),
 
867
                'title':    video_title,
 
868
                'stitle':    simple_title,
 
869
                'ext':        video_extension.decode('utf-8'),
 
870
            })
 
871
        except UnavailableFormatError:
 
872
            self._downloader.trouble(u'ERROR: format not available for video')
 
873
 
 
874
 
 
875
class YoutubeSearchIE(InfoExtractor):
 
876
    """Information Extractor for YouTube search queries."""
 
877
    _VALID_QUERY = r'ytsearch(\d+|all)?:[\s\S]+'
 
878
    _TEMPLATE_URL = 'http://www.youtube.com/results?search_query=%s&page=%s&gl=US&hl=en'
 
879
    _VIDEO_INDICATOR = r'href="/watch\?v=.+?"'
 
880
    _MORE_PAGES_INDICATOR = r'(?m)>\s*Next\s*</a>'
 
881
    _youtube_ie = None
 
882
    _max_youtube_results = 1000
 
883
 
 
884
    def __init__(self, youtube_ie, downloader=None):
 
885
        InfoExtractor.__init__(self, downloader)
 
886
        self._youtube_ie = youtube_ie
 
887
    
 
888
    @staticmethod
 
889
    def suitable(url):
 
890
        return (re.match(YoutubeSearchIE._VALID_QUERY, url) is not None)
 
891
 
 
892
    def report_download_page(self, query, pagenum):
 
893
        """Report attempt to download playlist page with given number."""
 
894
        self._downloader.to_stdout(u'[youtube] query "%s": Downloading page %s' % (query, pagenum))
 
895
 
 
896
    def _real_initialize(self):
 
897
        self._youtube_ie.initialize()
 
898
    
 
899
    def _real_extract(self, query):
 
900
        mobj = re.match(self._VALID_QUERY, query)
 
901
        if mobj is None:
 
902
            self._downloader.trouble(u'ERROR: invalid search query "%s"' % query)
 
903
            return
 
904
 
 
905
        prefix, query = query.split(':')
 
906
        prefix = prefix[8:]
 
907
        if prefix == '':
 
908
            self._download_n_results(query, 1)
 
909
            return
 
910
        elif prefix == 'all':
 
911
            self._download_n_results(query, self._max_youtube_results)
 
912
            return
 
913
        else:
 
914
            try:
 
915
                n = long(prefix)
 
916
                if n <= 0:
 
917
                    self._downloader.trouble(u'ERROR: invalid download number %s for query "%s"' % (n, query))
 
918
                    return
 
919
                elif n > self._max_youtube_results:
 
920
                    self._downloader.to_stderr(u'WARNING: ytsearch returns max %i results (you requested %i)'  % (self._max_youtube_results, n))
 
921
                    n = self._max_youtube_results
 
922
                self._download_n_results(query, n)
 
923
                return
 
924
            except ValueError: # parsing prefix as integer fails
 
925
                self._download_n_results(query, 1)
 
926
                return
 
927
 
 
928
    def _download_n_results(self, query, n):
 
929
        """Downloads a specified number of results for a query"""
 
930
 
 
931
        video_ids = []
 
932
        already_seen = set()
 
933
        pagenum = 1
 
934
 
 
935
        while True:
 
936
            self.report_download_page(query, pagenum)
 
937
            result_url = self._TEMPLATE_URL % (urllib.quote_plus(query), pagenum)
 
938
            request = urllib2.Request(result_url, None, std_headers)
 
939
            try:
 
940
                page = urllib2.urlopen(request).read()
 
941
            except (urllib2.URLError, httplib.HTTPException, socket.error), err:
 
942
                self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
 
943
                return
 
944
 
 
945
            # Extract video identifiers
 
946
            for mobj in re.finditer(self._VIDEO_INDICATOR, page):
 
947
                video_id = page[mobj.span()[0]:mobj.span()[1]].split('=')[2][:-1]
 
948
                if video_id not in already_seen:
 
949
                    video_ids.append(video_id)
 
950
                    already_seen.add(video_id)
 
951
                    if len(video_ids) == n:
 
952
                        # Specified n videos reached
 
953
                        for id in video_ids:
 
954
                            self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
 
955
                        return
 
956
 
 
957
            if re.search(self._MORE_PAGES_INDICATOR, page) is None:
 
958
                for id in video_ids:
 
959
                    self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
 
960
                return
 
961
 
 
962
            pagenum = pagenum + 1
 
963
 
741
964
 
742
965
class YoutubePlaylistIE(InfoExtractor):
743
966
    """Information Extractor for YouTube playlists."""
744
967
 
745
 
    _VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/view_play_list\?p=(.+)'
 
968
    _VALID_URL = r'(?:http://)?(?:\w+\.)?youtube.com/(?:view_play_list|my_playlists)\?.*?p=([^&]+).*'
746
969
    _TEMPLATE_URL = 'http://www.youtube.com/view_play_list?p=%s&page=%s&gl=US&hl=en'
747
970
    _VIDEO_INDICATOR = r'/watch\?v=(.+?)&'
748
 
    _MORE_PAGES_INDICATOR = r'/view_play_list?p=%s&amp;page=%s'
 
971
    _MORE_PAGES_INDICATOR = r'/view_play_list?p=%s&page=%s'
749
972
    _youtube_ie = None
750
973
 
751
974
    def __init__(self, youtube_ie, downloader=None):
767
990
        # Extract playlist id
768
991
        mobj = re.match(self._VALID_URL, url)
769
992
        if mobj is None:
770
 
            self.to_stderr(u'ERROR: invalid url: %s' % url)
771
 
            return [None]
 
993
            self._downloader.trouble(u'ERROR: invalid url: %s' % url)
 
994
            return
772
995
 
773
996
        # Download playlist pages
774
997
        playlist_id = mobj.group(1)
781
1004
            try:
782
1005
                page = urllib2.urlopen(request).read()
783
1006
            except (urllib2.URLError, httplib.HTTPException, socket.error), err:
784
 
                self.to_stderr(u'ERROR: unable to download webpage: %s' % str(err))
785
 
                return [None]
 
1007
                self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
 
1008
                return
786
1009
 
787
1010
            # Extract video identifiers
788
1011
            ids_in_page = []
791
1014
                    ids_in_page.append(mobj.group(1))
792
1015
            video_ids.extend(ids_in_page)
793
1016
 
794
 
            if (self._MORE_PAGES_INDICATOR % (playlist_id, pagenum + 1)) not in page:
 
1017
            if (self._MORE_PAGES_INDICATOR % (playlist_id.upper(), pagenum + 1)) not in page:
795
1018
                break
796
1019
            pagenum = pagenum + 1
797
1020
 
798
 
        information = []
799
1021
        for id in video_ids:
800
 
            information.extend(self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id))
801
 
        return information
 
1022
            self._youtube_ie.extract('http://www.youtube.com/watch?v=%s' % id)
 
1023
        return
802
1024
 
803
1025
class PostProcessor(object):
804
1026
    """Post Processor class.
839
1061
        """Run the PostProcessor.
840
1062
 
841
1063
        The "information" argument is a dictionary like the ones
842
 
        returned by InfoExtractors. The only difference is that this
 
1064
        composed by InfoExtractors. The only difference is that this
843
1065
        one has an extra field called "filepath" that points to the
844
1066
        downloaded file.
845
1067
 
870
1092
        # Parse command line
871
1093
        parser = optparse.OptionParser(
872
1094
                usage='Usage: %prog [options] url...',
873
 
                version='2009.01.31',
 
1095
                version='2009.09.13',
874
1096
                conflict_handler='resolve',
875
1097
                )
876
1098
        parser.add_option('-h', '--help',
877
1099
                action='help', help='print this help text and exit')
878
1100
        parser.add_option('-v', '--version',
879
1101
                action='version', help='print program version and exit')
880
 
        parser.add_option('-u', '--username',
 
1102
        parser.add_option('-i', '--ignore-errors',
 
1103
                action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
 
1104
        parser.add_option('-r', '--rate-limit',
 
1105
                dest='ratelimit', metavar='L', help='download rate limit (e.g. 50k or 44.6m)')
 
1106
 
 
1107
        authentication = optparse.OptionGroup(parser, 'Authentication Options')
 
1108
        authentication.add_option('-u', '--username',
881
1109
                dest='username', metavar='UN', help='account username')
882
 
        parser.add_option('-p', '--password',
 
1110
        authentication.add_option('-p', '--password',
883
1111
                dest='password', metavar='PW', help='account password')
884
 
        parser.add_option('-o', '--output',
885
 
                dest='outtmpl', metavar='TPL', help='output filename template')
886
 
        parser.add_option('-q', '--quiet',
 
1112
        authentication.add_option('-n', '--netrc',
 
1113
                action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
 
1114
        parser.add_option_group(authentication)
 
1115
 
 
1116
        video_format = optparse.OptionGroup(parser, 'Video Format Options')
 
1117
        video_format.add_option('-f', '--format',
 
1118
                action='store', dest='format', metavar='FMT', help='video format code')
 
1119
        video_format.add_option('-b', '--best-quality',
 
1120
                action='store_const', dest='format', help='download the best quality video possible', const='0')
 
1121
        video_format.add_option('-m', '--mobile-version',
 
1122
                action='store_const', dest='format', help='alias for -f 17', const='17')
 
1123
        video_format.add_option('-d', '--high-def',
 
1124
                action='store_const', dest='format', help='alias for -f 22', const='22')
 
1125
        parser.add_option_group(video_format)
 
1126
 
 
1127
        verbosity = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
 
1128
        verbosity.add_option('-q', '--quiet',
887
1129
                action='store_true', dest='quiet', help='activates quiet mode', default=False)
888
 
        parser.add_option('-s', '--simulate',
 
1130
        verbosity.add_option('-s', '--simulate',
889
1131
                action='store_true', dest='simulate', help='do not download video', default=False)
890
 
        parser.add_option('-t', '--title',
 
1132
        verbosity.add_option('-g', '--get-url',
 
1133
                action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
 
1134
        verbosity.add_option('-e', '--get-title',
 
1135
                action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
 
1136
        parser.add_option_group(verbosity)
 
1137
 
 
1138
        filesystem = optparse.OptionGroup(parser, 'Filesystem Options')
 
1139
        filesystem.add_option('-t', '--title',
891
1140
                action='store_true', dest='usetitle', help='use title in file name', default=False)
892
 
        parser.add_option('-l', '--literal',
 
1141
        filesystem.add_option('-l', '--literal',
893
1142
                action='store_true', dest='useliteral', help='use literal title in file name', default=False)
894
 
        parser.add_option('-n', '--netrc',
895
 
                action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
896
 
        parser.add_option('-g', '--get-url',
897
 
                action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
898
 
        parser.add_option('-e', '--get-title',
899
 
                action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
900
 
        parser.add_option('-f', '--format',
901
 
                dest='format', metavar='FMT', help='video format code')
902
 
        parser.add_option('-b', '--best-quality',
903
 
                action='store_const', dest='format', help='alias for -f 18', const='18')
904
 
        parser.add_option('-m', '--mobile-version',
905
 
                action='store_const', dest='format', help='alias for -f 17', const='17')
906
 
        parser.add_option('-i', '--ignore-errors',
907
 
                action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
908
 
        parser.add_option('-r', '--rate-limit',
909
 
                dest='ratelimit', metavar='L', help='download rate limit (e.g. 50k or 44.6m)')
910
 
        parser.add_option('-a', '--batch-file',
911
 
                                dest='batchfile', metavar='F', help='file containing URLs to download')
 
1143
        filesystem.add_option('-o', '--output',
 
1144
                dest='outtmpl', metavar='TPL', help='output filename template')
 
1145
        filesystem.add_option('-a', '--batch-file',
 
1146
                dest='batchfile', metavar='F', help='file containing URLs to download')
 
1147
        filesystem.add_option('-w', '--no-overwrites',
 
1148
                action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
 
1149
        filesystem.add_option('-c', '--continue',
 
1150
                action='store_true', dest='continue_dl', help='resume partially downloaded files', default=False)
 
1151
        parser.add_option_group(filesystem)
 
1152
 
912
1153
        (opts, args) = parser.parse_args()
913
1154
 
914
 
                # Batch file verification
 
1155
        # Batch file verification
915
1156
        batchurls = []
916
1157
        if opts.batchfile is not None:
917
 
                        try:
918
 
                                batchurls = [line.strip() for line in open(opts.batchfile, 'r')]
919
 
                        except IOError:
920
 
                                sys.exit(u'ERROR: batch file could not be read')
 
1158
            try:
 
1159
                batchurls = open(opts.batchfile, 'r').readlines()
 
1160
                batchurls = [x.strip() for x in batchurls]
 
1161
                batchurls = [x for x in batchurls if len(x) > 0]
 
1162
            except IOError:
 
1163
                sys.exit(u'ERROR: batch file could not be read')
921
1164
        all_urls = batchurls + args
922
1165
 
923
 
                # Conflicting, missing and erroneous options
 
1166
        # Conflicting, missing and erroneous options
924
1167
        if len(all_urls) < 1:
925
 
            sys.exit(u'ERROR: you must provide at least one URL')
 
1168
            parser.error(u'you must provide at least one URL')
926
1169
        if opts.usenetrc and (opts.username is not None or opts.password is not None):
927
 
            sys.exit(u'ERROR: using .netrc conflicts with giving username/password')
 
1170
            parser.error(u'using .netrc conflicts with giving username/password')
928
1171
        if opts.password is not None and opts.username is None:
929
 
            sys.exit(u'ERROR: account username missing')
 
1172
            parser.error(u'account username missing')
930
1173
        if opts.outtmpl is not None and (opts.useliteral or opts.usetitle):
931
 
            sys.exit(u'ERROR: using output template conflicts with using title or literal title')
 
1174
            parser.error(u'using output template conflicts with using title or literal title')
932
1175
        if opts.usetitle and opts.useliteral:
933
 
            sys.exit(u'ERROR: using title conflicts with using literal title')
 
1176
            parser.error(u'using title conflicts with using literal title')
934
1177
        if opts.username is not None and opts.password is None:
935
1178
            opts.password = getpass.getpass(u'Type account password and press return:')
936
1179
        if opts.ratelimit is not None:
937
1180
            numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
938
1181
            if numeric_limit is None:
939
 
                sys.exit(u'ERROR: invalid rate limit specified')
 
1182
                parser.error(u'invalid rate limit specified')
940
1183
            opts.ratelimit = numeric_limit
941
 
 
 
1184
        
942
1185
        # Information extractors
943
1186
        youtube_ie = YoutubeIE()
944
1187
        metacafe_ie = MetacafeIE(youtube_ie)
945
1188
        youtube_pl_ie = YoutubePlaylistIE(youtube_ie)
946
 
 
 
1189
        youtube_search_ie = YoutubeSearchIE(youtube_ie)
 
1190
        
947
1191
        # File downloader
948
 
        charset = locale.getdefaultlocale()[1]
949
 
        if charset is None:
950
 
            charset = 'ascii'
951
1192
        fd = FileDownloader({
952
1193
            'usenetrc': opts.usenetrc,
953
1194
            'username': opts.username,
957
1198
            'forcetitle': opts.gettitle,
958
1199
            'simulate': (opts.simulate or opts.geturl or opts.gettitle),
959
1200
            'format': opts.format,
960
 
            'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(charset))
 
1201
            'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding()))
961
1202
                or (opts.usetitle and u'%(stitle)s-%(id)s.%(ext)s')
962
1203
                or (opts.useliteral and u'%(title)s-%(id)s.%(ext)s')
963
1204
                or u'%(id)s.%(ext)s'),
964
1205
            'ignoreerrors': opts.ignoreerrors,
965
1206
            'ratelimit': opts.ratelimit,
 
1207
            'nooverwrites': opts.nooverwrites,
 
1208
            'continuedl': opts.continue_dl,
966
1209
            })
 
1210
        fd.add_info_extractor(youtube_search_ie)
967
1211
        fd.add_info_extractor(youtube_pl_ie)
968
1212
        fd.add_info_extractor(metacafe_ie)
969
1213
        fd.add_info_extractor(youtube_ie)