├── .gitignore ├── Pyrrot2.py ├── Pyrrot2Service.py ├── README.markdown └── urllib2_file.py /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .pydevproject 3 | *.pyc 4 | *.prt -------------------------------------------------------------------------------- /Pyrrot2.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 02/04/2010 3 | 4 | @author: Jr. Hames 5 | ''' 6 | 7 | #edit the following options according to your needs 8 | PYRROT_DIR = "" 9 | DIRECTORIES = ["/path/to/your/video/files", "/path/to/your/video/files2"] 10 | LANGUAGES = ["pt","en"] 11 | HASHES_FILE = 'pyrrot-uploaded.prt' 12 | LOG_FILE = 'pyrrot-log.txt' 13 | MOVIE_EXTS = ['.avi', '.mkv', '.mp4', '.m4v', '.mov', '.mpg', '.wmv'] 14 | SUBS_EXTS = ['.srt', '.sub'] 15 | #end of configurations 16 | 17 | 18 | import logging 19 | import cPickle 20 | import io 21 | import hashlib 22 | import os 23 | import random 24 | import re 25 | import time 26 | import urllib 27 | import urllib2 28 | import urllib2_file 29 | 30 | base_url = 'http://api.thesubdb.com/?{0}' 31 | user_agent = 'SubDB/1.0 (Pyrrot/0.1; http://github.com/jrhames/pyrrot-cli)' 32 | logger = None 33 | uploaded = {} 34 | retry = 0 35 | 36 | def get_hash(name): 37 | readsize = 64 * 1024 38 | with open(name, 'rb') as f: 39 | size = os.path.getsize(name) 40 | data = f.read(readsize) 41 | f.seek(-readsize, os.SEEK_END) 42 | data += f.read(readsize) 43 | return hashlib.md5(data).hexdigest() 44 | 45 | def download(language, hash, filename): 46 | global retry 47 | params = {'action': 'download', 'language': language, 'hash': hash} 48 | url = base_url.format(urllib.urlencode(params)) 49 | req = urllib2.Request(url) 50 | req.add_header('User-Agent', user_agent) 51 | try: 52 | response = urllib2.urlopen(req) 53 | ext = response.info()['Content-Disposition'].split(".")[1] 54 | file = os.path.splitext(filename)[0] + "." + ext 55 | with open(file, "wb") as fout: 56 | fout.write(response.read()) 57 | retry = 0 58 | return 200 59 | except urllib2.HTTPError, e: 60 | retry = 0 61 | return e.code 62 | except urllib2.URLError as e: 63 | if retry < 1800: 64 | retry += 60 65 | else: 66 | return -1 67 | logger.debug("service did not respond, waiting %ds before retry" % retry) 68 | time.sleep(retry) 69 | download(language, hash, filename) 70 | 71 | def upload(hash, filename): 72 | global retry 73 | for ext in SUBS_EXTS: 74 | file = os.path.splitext(filename)[0] + ext 75 | if os.path.isfile(file): 76 | fd_file = open(file, 'rb') 77 | fd = io.BytesIO() 78 | fd.name = hash + ".srt" 79 | fd.write(fd_file.read()) 80 | data = { 'hash': hash, 'file': fd } 81 | params = {'action': 'upload', 'hash': hash} 82 | url = base_url.format(urllib.urlencode(params)) 83 | req = urllib2.Request(url) 84 | req.add_header('User-Agent', user_agent) 85 | try: 86 | urllib2.urlopen(req, data) 87 | except urllib2.HTTPError as e: 88 | retry = 0 89 | return e.code 90 | except urllib2.URLError as e: 91 | if retry < 1800: 92 | retry += 60 93 | else: 94 | return -1 95 | logger.debug("service did not respond, waiting %ds before retry" % retry) 96 | time.sleep(retry) 97 | upload(hash, filename) 98 | 99 | def get_movie_files(rootdir, with_subs=False): 100 | filelist = [] 101 | for root, subfolders, files in os.walk(rootdir): 102 | for file in files: 103 | name, ext = os.path.splitext(file) 104 | if ext in MOVIE_EXTS: 105 | if with_subs == has_subs(root, name): 106 | filelist.append(os.path.join(root, file)) 107 | return filelist 108 | 109 | def has_subs(root, name): 110 | for ext in SUBS_EXTS: 111 | filename = os.path.join(root, name + ext) 112 | if os.path.isfile(filename): 113 | return True 114 | return False 115 | 116 | #search for subtitles to download 117 | def download_subtitles(rootdir, languages): 118 | filelist = get_movie_files(rootdir, with_subs=False) 119 | for file in filelist: 120 | if os.path.isfile(file): 121 | result = download(','.join(languages), get_hash(file), file) 122 | if result == 200: 123 | uploaded[file] = result 124 | logger.info("download subtitle " + file) 125 | elif result == 404: 126 | logger.debug("subtitle not found " + file) 127 | save() 128 | time.sleep(random.uniform(1,5)) 129 | 130 | #search for subtitles to upload 131 | def upload_subtitles(rootdir): 132 | filelist = get_movie_files(rootdir, with_subs=True) 133 | for file in filelist: 134 | if os.path.isfile(file): 135 | if file in uploaded: 136 | continue 137 | hash = get_hash(file) 138 | result = upload(hash, file) 139 | if result == 201: 140 | uploaded[file] = result 141 | logger.info("uploaded subtitle " + file) 142 | elif result == 403: 143 | uploaded[file] = result 144 | logger.debug("subtitle already exists " + file) 145 | elif result == 415: 146 | uploaded[file] = result 147 | logger.warning("unsupported media type or the file is bigger than 200k " + file) 148 | else: 149 | logger.error("cannot upload subtitle %s - result: %s" % (file, result)) 150 | save() 151 | time.sleep(random.uniform(1,10)) 152 | 153 | def save(): 154 | with open(HASHES_FILE, 'wb') as hashes_file: 155 | cPickle.dump(uploaded, hashes_file) 156 | 157 | def parse_options(): 158 | global DIRECTORIES, HASHES_FILE, LOG_FILE, base_url 159 | def parse_list(option, opt, value, parser): 160 | setattr(parser.values, option.dest, value.split(',')) 161 | from optparse import OptionParser 162 | parser = OptionParser() 163 | parser.add_option('-a', '--hashes', help='File where to store database of uploaded hashes') 164 | parser.add_option('-b', '--base', type='string', help='Set the basedir (application directory) of Pyrrot') 165 | parser.add_option('-d', '--dirs', type='string', action='callback', callback=parse_list, help='Folders to scan for movies: DIR1[,DIR2]') 166 | parser.add_option('-l', '--log', help='File where to write logging output') 167 | parser.add_option('-j', '--host', help='SubDB HOST in http://[HOST]:[PORT]/subdb?query') 168 | parser.add_option('-p', '--port', help='SubDB PORT in http://[HOST]:[PORT]/subdb?query') 169 | parser.add_option('-u', '--url', help='SubDB URL [URL]?query. Overrides --host and --port') 170 | (options, args) = parser.parse_args() 171 | if options.base: 172 | PYRROT_DIR = options.base 173 | if options.url: 174 | base_url = options.url + '?{0}' 175 | elif options.host: 176 | netloc = options.host 177 | if options.port: 178 | netloc += ':' + options.port 179 | base_url = list(urllib2.urlparse.urlsplit(base_url)) 180 | base_url[1] = netloc 181 | base_url = urllib2.urlparse.urlunsplit(base_url) 182 | if options.dirs: 183 | DIRECTORIES = options.dirs 184 | if options.hashes: 185 | HASHES_FILE = options.hashes 186 | if options.log: 187 | LOG_FILE = options.log.strip() 188 | 189 | def load_hashes_db(): 190 | global uploaded 191 | if not os.path.isfile(HASHES_FILE): 192 | logger.info("hash file does not exist yet") 193 | return 194 | try: 195 | with open(HASHES_FILE, 'rb') as hashes_file: 196 | uploaded = cPickle.load(hashes_file) 197 | except EOFError: 198 | logger.info("hash file corrupted, will create new one") 199 | 200 | def init_logger(): 201 | global logger 202 | format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 203 | if not LOG_FILE: 204 | logging.basicConfig(format=format) 205 | else: 206 | logging.basicConfig(filename=LOG_FILE, format=format) 207 | logger = logging.getLogger("Pyrrot2") 208 | logger.setLevel(logging.INFO) 209 | 210 | 211 | if __name__ == '__main__': 212 | parse_options() 213 | if PYRROT_DIR != "": 214 | os.chdir(PYRROT_DIR) 215 | init_logger() 216 | if LOG_FILE: 217 | print "running... see the log in", LOG_FILE 218 | load_hashes_db() 219 | for folder in DIRECTORIES: 220 | download_subtitles(folder, LANGUAGES) 221 | upload_subtitles(folder) 222 | save() 223 | logger.info("-------------------------------------------") 224 | print "done" 225 | -------------------------------------------------------------------------------- /Pyrrot2Service.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 02/04/2010 3 | 4 | @author: Jr. Hames 5 | 6 | 7 | Usage : python Pyrrot2Service.py install 8 | Usage : python Pyrrot2Service.py start 9 | Usage : python Pyrrot2Service.py stop 10 | Usage : python Pyrrot2Service.py remove 11 | 12 | C:\>python Pyrrot2Service.py --username --password --startup auto install 13 | 14 | ''' 15 | 16 | import Pyrrot2 17 | import win32service 18 | import win32serviceutil 19 | import win32api 20 | import win32con 21 | import win32event 22 | import win32evtlogutil 23 | 24 | class Pyrrot2Service(win32serviceutil.ServiceFramework): 25 | _svc_name_ = "Pyrrot" 26 | _svc_display_name_ = "Pyrrot2" 27 | _svc_description_ = "A multiplatform (should work on Windows, Linux and Mac) client written in Python for SubDB." 28 | 29 | def __init__(self, args): 30 | win32serviceutil.ServiceFramework.__init__(self, args) 31 | self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 32 | 33 | def SvcStop(self): 34 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 35 | win32event.SetEvent(self.hWaitStop) 36 | 37 | def SvcDoRun(self): 38 | import servicemanager 39 | servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,(self._svc_name_, '')) 40 | 41 | self.timeout = 10000 42 | self.can_run = True 43 | self.retries = 0 44 | 45 | while 1: 46 | # Wait for service stop signal, if I timeout, loop again 47 | rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout) 48 | # Check to see if self.hWaitStop happened 49 | if rc == win32event.WAIT_OBJECT_0: 50 | # Stop signal encountered 51 | servicemanager.LogInfoMsg("Pyrrot2 - STOPPED") 52 | break 53 | else: 54 | self.retries += 1 55 | if self.can_run: 56 | for folder in Pyrrot2.DIRECTORIES: 57 | Pyrrot2.download_subtitles(folder, Pyrrot2.LANGUAGES) 58 | Pyrrot2.upload_subtitles(folder) 59 | self.can_run = False 60 | elif self.retries == 180: 61 | self.can_run = True 62 | self.retries = 0 63 | 64 | 65 | def ctrlHandler(ctrlType): 66 | return True 67 | 68 | if __name__ == '__main__': 69 | win32api.SetConsoleCtrlHandler(ctrlHandler, True) 70 | win32serviceutil.HandleCommandLine(Pyrrot2Service) 71 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ![SubDB - a free subtitle database](http://thesubdb.com/subdb-logo.png) 2 | 3 | #README 4 | 5 | Pyrrot is a multiplatform client for [SubDB](http://thesubdb.com "SubDB - a free subtitle database") written in Python. It can run on Windows, Linux and MacOS. 6 | 7 | 8 | ##REQUIREMENTS 9 | 10 | - Python 2.6.5 for all systems. 11 | - Mark Hammond's Win32all if you want to run Pyrrot as a service on Windows. 12 | 13 | 14 | ##INSTALLING 15 | 16 | You need to copy Pyrrot2.py and urllib2_file.py to a folder of your preference. Then, after you've done with the configurations (see below), all you need to do is: 17 | 18 | `python Pyrrot2.py` 19 | or 20 | `c:\Python2.6.5\python.exe c:\path\to\Pyrrot2.py (if you're using windows)` 21 | 22 | You can also run Pyrrot using cron, or install Pyrrot as a Windows service. To do this: 23 | 24 | Cron (Linux): 25 | `*/30 * * * * python /path/to/Pyrrot2.py` 26 | 27 | Service (Windows): 28 | `c:\Python2.6.5\python.exe c:\path\to\Pyrrot2Service.py --startup auto install` 29 | 30 | As you may have noticed, you need to copy Pyrrot2Service.py too. 31 | 32 | 33 | ##CONFIGURATION 34 | 35 | You need to edit the following lines on Pyrrot2.py before use it: 36 | 37 | `PYRROT_DIR = "/path/to/pyrrot" #The path to where Pyrrot is. Can be empty if you're not running Pyrrot as a Windows Service.` 38 | 39 | `DIRECTORIES = ["/path/to/your/video/files", "/path/to/your/video/files2"] #Configure here the directories where your video files are.` 40 | 41 | `LANGUAGES = ["pt", "en"] #The languages to download subtitles, in order of preference.` 42 | 43 | `MOVIE_EXTS = ['.avi', '.mkv', '.mp4', '.mov', '.mpg', '.wmv'] #The video file extensions that you want Pyrrot to looking for.` 44 | 45 | `SUBS_EXTS = ['.srt', '.sub'] #The subtitle extensions that you want Pyrrot to looking for.` 46 | 47 | If you are on Windows, the paths will be like: 48 | 49 | `PYRROT_DIR = "c:\\path\\to\\pyrrot"` 50 | 51 | 52 | ##FOUND A BUG 53 | 54 | 55 | 56 | 57 | ##LICENSE 58 | 59 | This software is distribute under Creative Commons Attribution-Noncommercial-Share Alike 3.0. You can read the entire license on: 60 | 61 | -------------------------------------------------------------------------------- /urllib2_file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #### 3 | # Version: 0.2.1 4 | # - StringIO workaround (Laurent Coustet), does not work with cStringIO 5 | # 6 | # Version: 0.2.0 7 | # - UTF-8 filenames are now allowed (Eli Golovinsky) 8 | # - File object is no more mandatory, Object only needs to have seek() read() attributes (Eli Golovinsky) 9 | # 10 | # Version: 0.1.0 11 | # - upload is now done with chunks (Adam Ambrose) 12 | # 13 | # Version: older 14 | # THANKS TO: 15 | # bug fix: kosh @T aesaeion.com 16 | # HTTPS support : Ryan Grow 17 | 18 | # Copyright (C) 2004,2005,2006,2008,2009 Fabien SEISEN 19 | # 20 | # This library is free software; you can redistribute it and/or 21 | # modify it under the terms of the GNU Lesser General Public 22 | # License as published by the Free Software Foundation; either 23 | # version 2.1 of the License, or (at your option) any later version. 24 | # 25 | # This library is distributed in the hope that it will be useful, 26 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 28 | # Lesser General Public License for more details. 29 | # 30 | # You should have received a copy of the GNU Lesser General Public 31 | # License along with this library; if not, write to the Free Software 32 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 33 | # 34 | # you can contact me at: 35 | # http://fabien.seisen.org/python/ 36 | # 37 | # Also modified by Adam Ambrose (aambrose @T pacbell.net) to write data in 38 | # chunks (hardcoded to CHUNK_SIZE for now), so the entire contents of the file 39 | # don't need to be kept in memory. 40 | # 41 | """ 42 | enable to upload files using multipart/form-data 43 | 44 | idea from: 45 | upload files in python: 46 | http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 47 | 48 | timeoutsocket.py: overriding Python socket API: 49 | http://www.timo-tasi.org/python/timeoutsocket.py 50 | http://mail.python.org/pipermail/python-announce-list/2001-December/001095.html 51 | 52 | import urllib2_files 53 | import urllib2 54 | u = urllib2.urlopen('http://site.com/path' [, data]) 55 | 56 | data can be a mapping object or a sequence of two-elements tuples 57 | (like in original urllib2.urlopen()) 58 | varname still need to be a string and 59 | value can be string of a file object 60 | eg: 61 | ((varname, value), 62 | (varname2, value), 63 | ) 64 | or 65 | { name: value, 66 | name2: value2 67 | } 68 | 69 | """ 70 | 71 | import httplib 72 | import mimetools 73 | import mimetypes 74 | import io 75 | import os 76 | import os.path 77 | import socket 78 | import stat 79 | import sys 80 | import urllib 81 | import urllib2 82 | 83 | CHUNK_SIZE = 65536 84 | 85 | def get_content_type(filename): 86 | return mimetypes.guess_type(filename)[0] or 'application/octet-stream' 87 | 88 | # if sock is None, return the estimate size 89 | def send_data(v_vars, v_files, boundary, sock=None): 90 | l = 0 91 | for (k, v) in v_vars: 92 | buffer='' 93 | buffer += '--%s\r\n' % boundary 94 | buffer += 'Content-Disposition: form-data; name="%s"\r\n' % k 95 | buffer += '\r\n' 96 | buffer += v + '\r\n' 97 | if sock: 98 | sock.send(buffer) 99 | l += len(buffer) 100 | for (k, v) in v_files: 101 | fd = v 102 | 103 | if not hasattr(fd, 'read'): 104 | raise TypeError("file descriptor MUST have read attribute") 105 | 106 | if hasattr(fd, 'fileno'): 107 | # a File 108 | try: 109 | name = fd.name.split(os.path.sep)[-1] 110 | file_size = os.fstat(fd.fileno())[stat.ST_SIZE] 111 | except io.UnsupportedOperation: 112 | name = fd.name 113 | file_size = len(fd.getvalue()) 114 | fd.seek(0) 115 | elif hasattr(fd, 'len'): 116 | # StringIO 117 | name = k 118 | file_size = fd.len 119 | fd.seek(0) # START 120 | else: 121 | raise TypeError("file descriptor might be File of StringIO but MUST have fileno or len attribute") 122 | 123 | if isinstance(name, unicode): 124 | name = name.encode('UTF-8') 125 | buffer='' 126 | buffer += '--%s\r\n' % boundary 127 | buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' \ 128 | % (k, name) 129 | buffer += 'Content-Type: %s\r\n' % get_content_type(name) 130 | buffer += 'Content-Length: %s\r\n' % file_size 131 | buffer += '\r\n' 132 | 133 | l += len(buffer) 134 | if sock: 135 | sock.send(buffer) 136 | if hasattr(fd, 'seek'): 137 | fd.seek(0) 138 | # read file only of sock is defined 139 | if sock: 140 | while True: 141 | chunk = fd.read(CHUNK_SIZE) 142 | if not chunk: 143 | break 144 | if sock: 145 | sock.send(chunk) 146 | l += file_size 147 | buffer='\r\n' 148 | buffer += '--%s--\r\n' % boundary 149 | buffer += '\r\n' 150 | if sock: 151 | sock.send(buffer) 152 | l += len(buffer) 153 | return l 154 | 155 | # mainly a copy of HTTPHandler from urllib2 156 | class newHTTPHandler(urllib2.BaseHandler): 157 | def http_open(self, req): 158 | return self.do_open(httplib.HTTP, req) 159 | 160 | def do_open(self, http_class, req): 161 | data = req.get_data() 162 | v_files=[] 163 | v_vars=[] 164 | # mapping object (dict) 165 | if req.has_data() and type(data) != str: 166 | if hasattr(data, 'items'): 167 | data = data.items() 168 | else: 169 | try: 170 | if len(data) and not isinstance(data[0], tuple): 171 | raise TypeError 172 | except TypeError: 173 | ty, va, tb = sys.exc_info() 174 | raise TypeError, "not a valid non-string sequence or mapping object", tb 175 | 176 | for (k, v) in data: 177 | if hasattr(v, 'read'): 178 | v_files.append((k, v)) 179 | else: 180 | v_vars.append( (k, v) ) 181 | # no file ? convert to string 182 | if len(v_vars) > 0 and len(v_files) == 0: 183 | data = urllib.urlencode(v_vars) 184 | v_files=[] 185 | v_vars=[] 186 | host = req.get_host() 187 | if not host: 188 | raise urllib2.URLError('no host given') 189 | h = http_class(host) # will parse host:port 190 | if req.has_data(): 191 | h.putrequest('POST', req.get_selector()) 192 | if not 'Content-type' in req.headers: 193 | if len(v_files) > 0: 194 | boundary = mimetools.choose_boundary() 195 | l = send_data(v_vars, v_files, boundary) 196 | h.putheader('Content-Type', 197 | 'multipart/form-data; boundary=%s' % boundary) 198 | h.putheader('Content-length', str(l)) 199 | else: 200 | h.putheader('Content-type', 201 | 'application/x-www-form-urlencoded') 202 | if not 'Content-length' in req.headers: 203 | h.putheader('Content-length', '%d' % len(data)) 204 | else: 205 | h.putrequest('GET', req.get_selector()) 206 | 207 | scheme, sel = urllib.splittype(req.get_selector()) 208 | sel_host, sel_path = urllib.splithost(sel) 209 | h.putheader('Host', sel_host or host) 210 | for name, value in self.parent.addheaders: 211 | name = name.capitalize() 212 | if name not in req.headers: 213 | h.putheader(name, value) 214 | for k, v in req.headers.items(): 215 | h.putheader(k, v) 216 | # httplib will attempt to connect() here. be prepared 217 | # to convert a socket error to a URLError. 218 | try: 219 | h.endheaders() 220 | except socket.error, err: 221 | raise urllib2.URLError(err) 222 | 223 | if req.has_data(): 224 | if len(v_files) >0: 225 | l = send_data(v_vars, v_files, boundary, h) 226 | elif len(v_vars) > 0: 227 | # if data is passed as dict ... 228 | data = urllib.urlencode(v_vars) 229 | h.send(data) 230 | else: 231 | # "normal" urllib2.urlopen() 232 | h.send(data) 233 | 234 | code, msg, hdrs = h.getreply() 235 | fp = h.getfile() 236 | if code == 200: 237 | resp = urllib.addinfourl(fp, hdrs, req.get_full_url()) 238 | resp.code = code 239 | resp.msg = msg 240 | return resp 241 | else: 242 | return self.parent.error('http', req, fp, code, msg, hdrs) 243 | 244 | urllib2._old_HTTPHandler = urllib2.HTTPHandler 245 | urllib2.HTTPHandler = newHTTPHandler 246 | 247 | class newHTTPSHandler(newHTTPHandler): 248 | def https_open(self, req): 249 | return self.do_open(httplib.HTTPS, req) 250 | 251 | urllib2.HTTPSHandler = newHTTPSHandler 252 | 253 | if __name__ == '__main__': 254 | import getopt 255 | import urllib2 256 | import urllib2_file 257 | import string 258 | import sys 259 | 260 | def usage(progname): 261 | print """ 262 | SYNTAX: %s -u url -f file [-v] 263 | """ % progname 264 | 265 | try: 266 | opts, args = getopt.getopt(sys.argv[1:], 'hvu:f:') 267 | except getopt.GetoptError, errmsg: 268 | print "ERROR:", errmsg 269 | sys.exit(1) 270 | 271 | v_url = '' 272 | v_verbose = 0 273 | v_file = '' 274 | 275 | for name, value in opts: 276 | if name in ('-h',): 277 | usage(sys.argv[0]) 278 | sys.exit(0) 279 | elif name in ('-v',): 280 | v_verbose += 1 281 | elif name in ('-u',): 282 | v_url = value 283 | elif name in ('-f',): 284 | v_file = value 285 | else: 286 | print "invalid argument:", name 287 | sys.exit(2) 288 | 289 | error = 0 290 | if v_url == '': 291 | print "need -u" 292 | error += 1 293 | if v_file == '': 294 | print "need -f" 295 | error += 1 296 | 297 | if error > 0: 298 | sys.exit(3) 299 | 300 | fd = open(v_file, 'r') 301 | data = { 302 | 'filename' : fd, 303 | } 304 | # u = urllib2.urlopen(v_url, data) 305 | req = urllib2.Request(v_url, data, {}) 306 | try: 307 | u = urllib2.urlopen(req) 308 | except urllib2.HTTPError, errobj: 309 | print "HTTPError:", errobj.code 310 | 311 | else: 312 | buf = u.read() 313 | print "OK" --------------------------------------------------------------------------------