3 # Copyright (c) 2006 Ricardo Garcia Gonzalez 
   5 # Permission is hereby granted, free of charge, to any person obtaining a 
   6 # copy of this software and associated documentation files (the "Software"), 
   7 # to deal in the Software without restriction, including without limitation 
   8 # the rights to use, copy, modify, merge, publish, distribute, sublicense, 
   9 # and/or sell copies of the Software, and to permit persons to whom the 
  10 # Software is furnished to do so, subject to the following conditions: 
  12 # The above copyright notice and this permission notice shall be included 
  13 # in all copies or substantial portions of the Software. 
  15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
  18 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
  19 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
  20 # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
  21 # OTHER DEALINGS IN THE SOFTWARE. 
  23 # Except as contained in this notice, the name(s) of the above copyright 
  24 # holders shall not be used in advertising or otherwise to promote the 
  25 # sale, use or other dealings in this Software without prior written 
  33 # Exit status constants 
  38 const_video_url_str 
= 'http://www.youtube.com/watch?v=%s' 
  39 const_video_url_re 
= re
.compile(r
'http://(?:www\.)?youtube\.com/(?:v/|(?:watch)?\?v=)([^&]+).*') 
  40 const_login_url_str 
= 'http://www.youtube.com/signup?next=/' 
  41 const_login_post_str 
= 'current_form=loginForm&next=%%2F&username=%s&password=%s&action_login=Log+In' 
  42 const_age_url_str 
= 'http://www.youtube.com/verify_age?next_url=/watch%%3Fv%%3D%s' 
  43 const_age_post_str 
= 'next_url=%%2Fwatch%%3Fv%%3D%s&action_confirm=Confirm' 
  44 const_video_url_params_re 
= re
.compile(r
'player2\.swf\?video_id=([^&]+)&.*t=([^&]+)&', re
.M
) 
  45 const_video_url_real_str 
= 'http://www.youtube.com/get_video?video_id=%s&t=%s' 
  47 const_block_size 
= 10 * const_1k
 
  49 # Print error message, followed by standard advice information, and then exit 
  50 def error_advice_exit(error_text
): 
  52         sys
.stderr
.write('Error: %s.\n' % error_text
) 
  53         sys
.stderr
.write('Try again several times. It may be a temporal problem.\n') 
  54         sys
.stderr
.write('Other typical problems:\n\n') 
  55         sys
.stderr
.write('\tVideo no longer exists.\n') 
  56         sys
.stderr
.write('\tVideo requires age confirmation but you did not provide an account.\n') 
  57         sys
.stderr
.write('\tYou provided the account data, but it is not valid.\n') 
  58         sys
.stderr
.write('\tThe connection was cut suddenly for some reason.\n') 
  59         sys
.stderr
.write('\tYouTube changed their system, and the program no longer works.\n') 
  60         sys
.stderr
.write('\nTry to confirm you are able to view the video using a web browser.\n') 
  61         sys
.stderr
.write('Use the same video URL and account information, if needed, with this program.\n') 
  62         sys
.stderr
.write('Try again several times and contact me if the problem persists.\n') 
  63         sys
.exit(exit_failure
) 
  65 # Wrapper to create custom requests with typical headers 
  66 def request_create(url
, data
=None): 
  67         retval 
= urllib2
.Request(url
) 
  70         # Try to mimic Firefox, at least a little bit 
  71         retval
.add_header('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.6) Gecko/20060728 Firefox/1.5.0.6') 
  72         retval
.add_header('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7') 
  73         retval
.add_header('Accept', 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5') 
  74         retval
.add_header('Accept-Language', 'en-us,en;q=0.5') 
  77 # Perform a request, process headers and return response 
  78 def perform_request(url
, data
=None): 
  79         request 
= request_create(url
, data
) 
  80         response 
= urllib2
.urlopen(request
) 
  83 # Convert bytes to KiB 
  86         return bytes / const_1k
 
  91         if not cmdl_opts
.quiet
: 
  95 # Create the command line options parser and parse command line 
  96 cmdl_usage 
= 'usage: %prog [options] video_url' 
  97 cmdl_version 
= '2006.08.28' 
  98 cmdl_parser 
= optparse
.OptionParser(usage
=cmdl_usage
, version
=cmdl_version
, conflict_handler
='resolve') 
  99 cmdl_parser
.add_option('-h', '--help', action
='help', help='print this help text and exit') 
 100 cmdl_parser
.add_option('-v', '--version', action
='version', help='print program version and exit') 
 101 cmdl_parser
.add_option('-u', '--username', dest
='username', metavar
='USERNAME', help='account username') 
 102 cmdl_parser
.add_option('-p', '--password', dest
='password', metavar
='PASSWORD', help='account password') 
 103 cmdl_parser
.add_option('-o', '--output', dest
='outfile', metavar
='FILE', help='output video file name') 
 104 cmdl_parser
.add_option('-q', '--quiet', action
='store_true', dest
='quiet', help='activates quiet mode') 
 105 cmdl_parser
.add_option('-s', '--simulate', action
='store_true', dest
='simulate', help='do not download video') 
 106 (cmdl_opts
, cmdl_args
) = cmdl_parser
.parse_args() 
 109 if len(cmdl_args
) != 1: 
 110         cmdl_parser
.print_help() 
 111         sys
.exit(exit_failure
) 
 112 video_url_cmdl 
= cmdl_args
[0] 
 114 # Verify video URL format and convert to "standard" format 
 115 video_url_mo 
= const_video_url_re
.match(video_url_cmdl
) 
 116 if video_url_mo 
is None: 
 117         sys
.exit('Error: URL does not seem to be a youtube video URL. If it is, report a bug.\n') 
 118 video_url_id 
= video_url_mo
.group(1) 
 119 video_url 
= const_video_url_str 
% video_url_id
 
 121 # Check conflicting options 
 122 if not cmdl_opts
.outfile 
is None and cmdl_opts
.simulate
: 
 123         sys
.stderr
.write('Warning: video file name given but will not be used.\n') 
 125 # Get output file name  
 126 if cmdl_opts
.outfile 
is None: 
 127         video_filename 
= '%s.flv' % video_url_id
 
 129         video_filename 
= cmdl_opts
.outfile
 
 132 if not video_filename
.lower().endswith('.flv'): 
 133         sys
.stderr
.write('Warning: video file name does not end in .flv\n') 
 135 # Verify both or none present 
 136 if ((cmdl_opts
.username 
is None and not cmdl_opts
.password 
is None) or 
 137     (not cmdl_opts
.username 
is None and cmdl_opts
.password 
is None)): 
 138         sys
.exit('Error: both username and password must be given, or none.') 
 141 if not cmdl_opts
.simulate
: 
 143                 disk_test 
= open(video_filename
, 'wb') 
 146         except (OSError, IOError): 
 147                 sys
.exit('Error: unable to open %s for writing.' % video_filename
) 
 149 # Install cookie handler 
 150 urllib2
.install_opener(urllib2
.build_opener(urllib2
.HTTPCookieProcessor())) 
 152 # Login and confirm age if needed 
 153 if not cmdl_opts
.username 
is None: 
 156                 cond_print('Logging in... ') 
 157                 perform_request(const_login_url_str
, const_login_post_str 
% (cmdl_opts
.username
, cmdl_opts
.password
)).read() 
 158                 cond_print('done.\n') 
 160         except (urllib2
.URLError
, ValueError): 
 161                 cond_print('failed.\n') 
 162                 error_advice_exit('unable to login') 
 164         except KeyboardInterrupt: 
 165                 sys
.exit(exit_failure
) 
 168                 # Get age confirmation cookie 
 169                 cond_print('Confirming age... ') 
 170                 perform_request(const_age_url_str 
% video_url_id
, const_age_post_str 
% video_url_id
).read() 
 171                 cond_print('done.\n') 
 173         except (urllib2
.URLError
, ValueError): 
 174                 cond_print('failed.\n') 
 175                 error_advice_exit('unable to confirm age') 
 177         except KeyboardInterrupt: 
 178                 sys
.exit(exit_failure
) 
 180 # Retrieve video webpage 
 182         cond_print('Retrieving video webpage... ') 
 183         video_webpage 
= perform_request(video_url
).read() 
 185 except (urllib2
.URLError
, ValueError): 
 186         cond_print('failed.\n') 
 187         error_advice_exit('unable to download video webpage') 
 189 except KeyboardInterrupt: 
 190         sys
.exit(exit_failure
) 
 192 cond_print('done.\n') 
 194 # Extract needed video URL parameters 
 196         cond_print('Extracting video URL parameters... ') 
 197         video_url_params_mo 
= const_video_url_params_re
.search(video_webpage
) 
 199         if video_url_params_mo 
is None: 
 200                 cond_print('failed.\n') 
 201                 error_advice_exit('unable to extract URL parameters') 
 203         video_real_id 
= video_url_params_mo
.group(1) 
 204         video_t_param 
= video_url_params_mo
.group(2) 
 205         video_url_real 
= const_video_url_real_str 
% (video_real_id
, video_t_param
) 
 206         cond_print('done.\n') 
 208 except KeyboardInterrupt: 
 209         sys
.exit(exit_failure
) 
 211 # Retrieve video data 
 213         video_data 
= perform_request(video_url_real
) 
 214         cond_print('Video data found at %s\n' % video_data
.geturl()) 
 216         # Abort here if in simulate mode 
 217         if cmdl_opts
.simulate
: 
 220         cond_print('Retrieving video data... ') 
 221         video_file 
= open(video_filename
, 'wb') 
 223                 video_len_str 
= '%sk' % to_k(int(video_data
.info()['Content-length'])) 
 225                 video_len_str 
= '(unknown)' 
 228         video_block 
= video_data
.read(const_block_size
) 
 229         while len(video_block
) != 0: 
 230                 byte_counter 
+= len(video_block
) 
 231                 video_file
.write(video_block
) 
 232                 cond_print('\rRetrieving video data... %sk of %s ' % (to_k(byte_counter
), video_len_str
)) 
 233                 video_block 
= video_data
.read(const_block_size
) 
 237 except (urllib2
.URLError
, ValueError): 
 238         cond_print('failed.\n') 
 239         error_advice_exit('unable to download video data') 
 241 except KeyboardInterrupt: 
 242         sys
.exit(exit_failure
) 
 244 cond_print('done.\n') 
 245 cond_print('Video data saved to %s\n' % video_filename
)