fsubs (39468B)
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 # OpenSubtitlesDownload.py / Version 4.2 5 # This software is designed to help you find and download subtitles for your favorite videos! 6 7 # You can browse the project's GitHub page: 8 # - https://github.com/emericg/OpenSubtitlesDownload 9 # Learn much more about configuring OpenSubtitlesDownload.py on its wiki: 10 # - https://github.com/emericg/OpenSubtitlesDownload/wiki 11 12 # Copyright (c) 2020 by Emeric GRANGE <emeric.grange@gmail.com> 13 # 14 # This program is free software: you can redistribute it and/or modify 15 # it under the terms of the GNU General Public License as published by 16 # the Free Software Foundation, either version 3 of the License, or 17 # (at your option) any later version. 18 # 19 # This program is distributed in the hope that it will be useful, 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 # GNU General Public License for more details. 23 # 24 # You should have received a copy of the GNU General Public License 25 # along with this program. If not, see <https://www.gnu.org/licenses/>. 26 27 # Contributors / special thanks: 28 # Thiago Alvarenga Lechuga <thiagoalz@gmail.com> for his work on the 'Windows CLI' and the 'folder search' 29 # jeroenvdw for his work on the 'subtitles automatic selection' and the 'search by filename' 30 # Gui13 for his work on the arguments parsing 31 # Tomáš Hnyk <tomashnyk@gmail.com> for his work on the 'multiple language' feature 32 # Carlos Acedo <carlos@linux-labs.net> for his work on the original script 33 34 import os 35 import re 36 import sys 37 import time 38 import gzip 39 import struct 40 import argparse 41 import mimetypes 42 import subprocess 43 44 if sys.version_info >= (3, 0): 45 import shutil 46 import urllib.request 47 from xmlrpc.client import ServerProxy, Error 48 else: # python2 49 import urllib 50 from xmlrpclib import ServerProxy, Error 51 52 # ==== Opensubtitles.org server settings ======================================= 53 54 # XML-RPC server domain for opensubtitles.org: 55 osd_server = ServerProxy('https://api.opensubtitles.org/xml-rpc') 56 57 # You can use your opensubtitles.org VIP account to avoid "in-subtitles" advertisement and bypass download limits. 58 # Be careful about your password security, it will be stored right here in plain text... 59 # You can also change opensubtitles.org language, it will be used for error codes and stuff. 60 # Can be overridden at run time with '-u' and '-p' arguments. 61 osd_username = 'miksa234' 62 osd_password = 'kaxedor' 63 osd_language = 'en' 64 65 # ==== Language settings ======================================================= 66 67 # 1/ Change the search language by using any supported 3 letter language codes: 68 # > Supported language codes: https://www.opensubtitles.org/addons/export_languages.php 69 # 2/ Search for subtitles in several languages at once by using multiple codes separated by a comma: 70 # > Ex: opt_languages = ['eng,fre'] 71 opt_languages = ['eng'] 72 73 # Write 2-letter language code (ex: _en) at the end of the subtitles file. 'on', 'off' or 'auto'. 74 # If you are regularly searching for several language at once, you sould use 'on'. 75 # The language code used will be the one provided in opt_languages. 76 # Can be overridden at run time with '-x' arguments. 77 opt_language_suffix = 'auto' 78 opt_language_separator = '_' 79 80 # Force downloading and storing UTF-8 encoded subtitles files. 81 opt_force_utf8 = True 82 83 # ==== Search settings ========================================================= 84 85 # Subtitles search mode. Can be overridden at run time with '-s' argument. 86 # - hash (search by hash only) 87 # - filename (search by filename only) 88 # - hash_then_filename (search by hash, then if no results by filename) 89 # - hash_and_filename (search using both methods) 90 opt_search_mode = 'hash_then_filename' 91 92 # Search and download a subtitles even if a subtitles file already exists. 93 opt_search_overwrite = True 94 95 # Subtitles selection mode. Can be overridden at run time with '-t' argument. 96 # - manual (always let you choose the subtitles you want) 97 # - default (in case of multiple results, let you choose the subtitles you want) 98 # - auto (automatically select the best subtitles found) 99 opt_selection_mode = 'default' 100 101 # Customize subtitles download path. Can be overridden at run time with '-o' argument. 102 # By default, subtitles are downloaded next to their video file. 103 opt_output_path = '' 104 105 # ==== GUI settings ============================================================ 106 107 # Select your GUI. Can be overridden at run time with '--gui=xxx' argument. 108 # - auto (autodetection, fallback on CLI) 109 # - gnome (GNOME/GTK based environments, using 'zenity' backend) 110 # - kde (KDE/Qt based environments, using 'kdialog' backend) 111 # - cli (Command Line Interface) 112 opt_gui = 'auto' 113 114 # Change the subtitles selection GUI size: 115 opt_gui_width = 720 116 opt_gui_height = 320 117 118 # Various GUI options. You can set them to 'on', 'off' or 'auto'. 119 opt_selection_hi = 'auto' 120 opt_selection_language = 'auto' 121 opt_selection_match = 'auto' 122 opt_selection_rating = 'off' 123 opt_selection_count = 'off' 124 125 # ==== Exit codes ============================================================== 126 127 # Exit code returned by the software. You can use them to improve scripting behaviours. 128 # 0: Success, and subtitles downloaded 129 # 1: Success, but no subtitles found 130 # 2: Failure 131 132 # ==== Super Print ============================================================= 133 # priority: info, warning, error 134 # title: only for zenity and kdialog messages 135 # message: full text, with tags and breaks (tags will be cleaned up for CLI) 136 137 def superPrint(priority, title, message): 138 """Print messages through terminal, zenity or kdialog""" 139 if opt_gui == 'gnome': 140 subprocess.call(['zenity', '--width=' + str(opt_gui_width), '--' + priority, '--title=' + title, '--text=' + message]) 141 elif opt_gui == 'kde': 142 # Adapt to kdialog 143 message = message.replace("\n", "<br>") 144 message = message.replace('\\"', '"') 145 if priority == 'warning': 146 priority = 'sorry' 147 elif priority == 'info': 148 priority = 'msgbox' 149 150 subprocess.call(['kdialog', '--geometry=' + str(opt_gui_width) + 'x' + str(opt_gui_height), '--title=' + title, '--' + priority + '=' + message]) 151 else: 152 # Clean up formating tags from the zenity messages 153 message = message.replace("\n\n", "\n") 154 message = message.replace('\\"', '"') 155 message = message.replace("<i>", "") 156 message = message.replace("</i>", "") 157 message = message.replace("<b>", "") 158 message = message.replace("</b>", "") 159 160 print(">> " + message) 161 162 # ==== Check file path & type ================================================== 163 164 def checkFileValidity(path): 165 """Check mimetype and/or file extension to detect valid video file""" 166 if os.path.isfile(path) is False: 167 return False 168 169 fileMimeType, encoding = mimetypes.guess_type(path) 170 if fileMimeType is None: 171 fileExtension = path.rsplit('.', 1) 172 if fileExtension[1] not in ['avi', 'mp4', 'mov', 'mkv', 'mk3d', 'webm', \ 173 'ts', 'mts', 'm2ts', 'ps', 'vob', 'evo', 'mpeg', 'mpg', \ 174 'm1v', 'm2p', 'm2v', 'm4v', 'movhd', 'movx', 'qt', \ 175 'mxf', 'ogg', 'ogm', 'ogv', 'rm', 'rmvb', 'flv', 'swf', \ 176 'asf', 'wm', 'wmv', 'wmx', 'divx', 'x264', 'xvid']: 177 #superPrint("error", "File type error!", "This file is not a video (unknown mimetype AND invalid file extension):\n<i>" + path + "</i>") 178 return False 179 else: 180 fileMimeType = fileMimeType.split('/', 1) 181 if fileMimeType[0] != 'video': 182 #superPrint("error", "File type error!", "This file is not a video (unknown mimetype):\n<i>" + path + "</i>") 183 return False 184 185 return True 186 187 # ==== Check for existing subtitles file ======================================= 188 189 def checkSubtitlesExists(path): 190 """Check if a subtitles already exists for the current file""" 191 for ext in ['srt', 'sub', 'sbv', 'smi', 'ssa', 'ass', 'usf']: 192 subPath = path.rsplit('.', 1)[0] + '.' + ext 193 if os.path.isfile(subPath) is True: 194 superPrint("info", "Subtitles already downloaded!", "A subtitles file already exists for this file:\n<i>" + subPath + "</i>") 195 return True 196 # With language code? 197 if opt_language_suffix in ('on', 'auto'): 198 if len(opt_languages) == 1: 199 splitted_languages_list = opt_languages[0].split(',') 200 else: 201 splitted_languages_list = opt_languages.split 202 for language in splitted_languages_list: 203 # Rough method to try 2 and 3 letters language codes 204 if len(language) == 3: 205 splitted_languages_list.append(language[0:2]) 206 for language in splitted_languages_list: 207 subPath = path.rsplit('.', 1)[0] + opt_language_separator + language + '.' + ext 208 if os.path.isfile(subPath) is True: 209 superPrint("info", "Subtitles already downloaded!", "A subtitles file already exists for this file:\n<i>" + subPath + "</i>") 210 return True 211 212 return False 213 214 # ==== Hashing algorithm ======================================================= 215 # Info: https://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes 216 # This particular implementation is coming from SubDownloader: https://subdownloader.net 217 218 def hashFile(path): 219 """Produce a hash for a video file: size + 64bit chksum of the first and 220 last 64k (even if they overlap because the file is smaller than 128k)""" 221 try: 222 longlongformat = 'Q' # unsigned long long little endian 223 bytesize = struct.calcsize(longlongformat) 224 fmt = "<%d%s" % (65536//bytesize, longlongformat) 225 226 f = open(path, "rb") 227 228 filesize = os.fstat(f.fileno()).st_size 229 filehash = filesize 230 231 if filesize < 65536 * 2: 232 superPrint("error", "File size error!", "File size error while generating hash for this file:\n<i>" + path + "</i>") 233 return "SizeError" 234 235 buf = f.read(65536) 236 longlongs = struct.unpack(fmt, buf) 237 filehash += sum(longlongs) 238 239 f.seek(-65536, os.SEEK_END) # size is always > 131072 240 buf = f.read(65536) 241 longlongs = struct.unpack(fmt, buf) 242 filehash += sum(longlongs) 243 filehash &= 0xFFFFFFFFFFFFFFFF 244 245 f.close() 246 returnedhash = "%016x" % filehash 247 return returnedhash 248 249 except IOError: 250 superPrint("error", "I/O error!", "Input/Output error while generating hash for this file:\n<i>" + path + "</i>") 251 return "IOError" 252 253 # ==== GNOME selection window ================================================== 254 255 def selectionGnome(subtitlesList): 256 """GNOME subtitles selection window using zenity""" 257 subtitlesSelected = '' 258 subtitlesItems = '' 259 subtitlesMatchedByHash = 0 260 subtitlesMatchedByName = 0 261 columnHi = '' 262 columnLn = '' 263 columnMatch = '' 264 columnRate = '' 265 columnCount = '' 266 267 # Generate selection window content 268 for item in subtitlesList['data']: 269 if item['MatchedBy'] == 'moviehash': 270 subtitlesMatchedByHash += 1 271 else: 272 subtitlesMatchedByName += 1 273 274 subtitlesItems += '"' + item['SubFileName'] + '" ' 275 276 if opt_selection_hi == 'on': 277 columnHi = '--column="HI" ' 278 if item['SubHearingImpaired'] == '1': 279 subtitlesItems += '"✔" ' 280 else: 281 subtitlesItems += '"" ' 282 if opt_selection_language == 'on': 283 columnLn = '--column="Language" ' 284 subtitlesItems += '"' + item['LanguageName'] + '" ' 285 if opt_selection_match == 'on': 286 columnMatch = '--column="MatchedBy" ' 287 if item['MatchedBy'] == 'moviehash': 288 subtitlesItems += '"HASH" ' 289 else: 290 subtitlesItems += '"" ' 291 if opt_selection_rating == 'on': 292 columnRate = '--column="Rating" ' 293 subtitlesItems += '"' + item['SubRating'] + '" ' 294 if opt_selection_count == 'on': 295 columnCount = '--column="Downloads" ' 296 subtitlesItems += '"' + item['SubDownloadsCnt'] + '" ' 297 298 if subtitlesMatchedByName == 0: 299 tilestr = ' --title="Subtitles for: ' + videoTitle + '"' 300 textstr = ' --text="<b>Video title:</b> ' + videoTitle + '\n<b>File name:</b> ' + videoFileName + '"' 301 elif subtitlesMatchedByHash == 0: 302 tilestr = ' --title="Subtitles for: ' + videoFileName + '"' 303 textstr = ' --text="Search results using file name, NOT video detection. <b>May be unreliable...</b>\n<b>File name:</b> ' + videoFileName + '" ' 304 else: # a mix of the two 305 tilestr = ' --title="Subtitles for: ' + videoTitle + '"' 306 textstr = ' --text="Search results using file name AND video detection.\n<b>Video title:</b> ' + videoTitle + '\n<b>File name:</b> ' + videoFileName + '"' 307 308 # Spawn zenity "list" dialog 309 process_subtitlesSelection = subprocess.Popen('zenity --width=' + str(opt_gui_width) + ' --height=' + str(opt_gui_height) + ' --list' + tilestr + textstr \ 310 + ' --column="Available subtitles" ' + columnHi + columnLn + columnMatch + columnRate + columnCount + subtitlesItems, shell=True, stdout=subprocess.PIPE) 311 312 # Get back the result 313 result_subtitlesSelection = process_subtitlesSelection.communicate() 314 315 # The results contain a subtitles? 316 if result_subtitlesSelection[0]: 317 if sys.version_info >= (3, 0): 318 subtitlesSelected = str(result_subtitlesSelection[0], 'utf-8').strip("\n") 319 else: # python2 320 subtitlesSelected = str(result_subtitlesSelection[0]).strip("\n") 321 322 # Hack against recent zenity version? 323 if len(subtitlesSelected.split("|")) > 1: 324 if subtitlesSelected.split("|")[0] == subtitlesSelected.split("|")[1]: 325 subtitlesSelected = subtitlesSelected.split("|")[0] 326 else: 327 if process_subtitlesSelection.returncode == 0: 328 subtitlesSelected = subtitlesList['data'][0]['SubFileName'] 329 330 # Return the result 331 return subtitlesSelected 332 333 # ==== KDE selection window ==================================================== 334 335 def selectionKde(subtitlesList): 336 """KDE subtitles selection window using kdialog""" 337 subtitlesSelected = '' 338 subtitlesItems = '' 339 subtitlesMatchedByHash = 0 340 subtitlesMatchedByName = 0 341 342 # Generate selection window content 343 # TODO doesn't support additional columns 344 index = 0 345 for item in subtitlesList['data']: 346 if item['MatchedBy'] == 'moviehash': 347 subtitlesMatchedByHash += 1 348 else: 349 subtitlesMatchedByName += 1 350 351 # key + subtitles name 352 subtitlesItems += str(index) + ' "' + item['SubFileName'] + '" ' 353 index += 1 354 355 if subtitlesMatchedByName == 0: 356 tilestr = ' --title="Subtitles for ' + videoTitle + '"' 357 menustr = ' --menu="<b>Video title:</b> ' + videoTitle + '<br><b>File name:</b> ' + videoFileName + '" ' 358 elif subtitlesMatchedByHash == 0: 359 tilestr = ' --title="Subtitles for ' + videoFileName + '"' 360 menustr = ' --menu="Search results using file name, NOT video detection. <b>May be unreliable...</b><br><b>File name:</b> ' + videoFileName + '" ' 361 else: # a mix of the two 362 tilestr = ' --title="Subtitles for ' + videoTitle + '" ' 363 menustr = ' --menu="Search results using file name AND video detection.<br><b>Video title:</b> ' + videoTitle + '<br><b>File name:</b> ' + videoFileName + '" ' 364 365 # Spawn kdialog "radiolist" 366 process_subtitlesSelection = subprocess.Popen('kdialog --geometry=' + str(opt_gui_width) + 'x' + str(opt_gui_height) + tilestr + menustr + subtitlesItems, shell=True, stdout=subprocess.PIPE) 367 368 # Get back the result 369 result_subtitlesSelection = process_subtitlesSelection.communicate() 370 371 # The results contain the key matching a subtitles? 372 if result_subtitlesSelection[0]: 373 if sys.version_info >= (3, 0): 374 keySelected = int(str(result_subtitlesSelection[0], 'utf-8').strip("\n")) 375 else: # python2 376 keySelected = int(str(result_subtitlesSelection[0]).strip("\n")) 377 378 subtitlesSelected = subtitlesList['data'][keySelected]['SubFileName'] 379 380 # Return the result 381 return subtitlesSelected 382 383 # ==== CLI selection mode ====================================================== 384 385 def selectionCLI(subtitlesList): 386 """Command Line Interface, subtitles selection inside your current terminal""" 387 subtitlesIndex = 0 388 subtitlesItem = '' 389 390 # Print video infos 391 print("\n>> Title: " + videoTitle) 392 print(">> Filename: " + videoFileName) 393 394 # Print subtitles list on the terminal 395 print(">> Available subtitles:") 396 for item in subtitlesList['data']: 397 subtitlesIndex += 1 398 subtitlesItem = '"' + item['SubFileName'] + '" ' 399 400 if opt_selection_hi == 'on' and item['SubHearingImpaired'] == '1': 401 subtitlesItem += '> "HI" ' 402 if opt_selection_language == 'on': 403 subtitlesItem += '> "Language: ' + item['LanguageName'] + '" ' 404 if opt_selection_match == 'on': 405 subtitlesItem += '> "MatchedBy: ' + item['MatchedBy'] + '" ' 406 if opt_selection_rating == 'on': 407 subtitlesItem += '> "SubRating: ' + item['SubRating'] + '" ' 408 if opt_selection_count == 'on': 409 subtitlesItem += '> "SubDownloadsCnt: ' + item['SubDownloadsCnt'] + '" ' 410 411 if item['MatchedBy'] == 'moviehash': 412 print("\033[92m[" + str(subtitlesIndex) + "]\033[0m " + subtitlesItem) 413 else: 414 print("\033[93m[" + str(subtitlesIndex) + "]\033[0m " + subtitlesItem) 415 416 # Ask user selection 417 print("\033[91m[0]\033[0m Cancel search") 418 sub_selection = -1 419 while(sub_selection < 0 or sub_selection > subtitlesIndex): 420 try: 421 if sys.version_info >= (3, 0): 422 sub_selection = int(input(">> Enter your choice (0-" + str(subtitlesIndex) + "): ")) 423 else: # python 2 424 sub_selection = int(raw_input(">> Enter your choice (0-" + str(subtitlesIndex) + "): ")) 425 except KeyboardInterrupt: 426 sys.exit(0) 427 except: 428 sub_selection = -1 429 430 # Return the result 431 if sub_selection == 0: 432 print("Cancelling search...") 433 return "" 434 435 return subtitlesList['data'][sub_selection-1]['SubFileName'] 436 437 # ==== Automatic selection mode ================================================ 438 439 def selectionAuto(subtitlesList): 440 """Automatic subtitles selection using filename match""" 441 442 if len(opt_languages) == 1: 443 splitted_languages_list = list(reversed(opt_languages[0].split(','))) 444 else: 445 splitted_languages_list = opt_languages 446 447 videoFileParts = videoFileName.replace('-', '.').replace(' ', '.').replace('_', '.').lower().split('.') 448 maxScore = -1 449 450 for subtitle in subtitlesList['data']: 451 score = 0 452 # points to respect languages priority 453 score += splitted_languages_list.index(subtitle['SubLanguageID']) * 100 454 # extra point if the sub is found by hash 455 if subtitle['MatchedBy'] == 'moviehash': 456 score += 1 457 # points for filename mach 458 subFileParts = subtitle['SubFileName'].replace('-', '.').replace(' ', '.').replace('_', '.').lower().split('.') 459 for subPart in subFileParts: 460 for filePart in videoFileParts: 461 if subPart == filePart: 462 score += 1 463 if score > maxScore: 464 maxScore = score 465 subtitlesSelected = subtitle['SubFileName'] 466 467 return subtitlesSelected 468 469 # ==== Check dependencies ====================================================== 470 471 def dependencyChecker(): 472 """Check the availability of tools used as dependencies""" 473 474 if opt_gui != 'cli': 475 if sys.version_info >= (3, 3): 476 for tool in ['gunzip', 'wget']: 477 path = shutil.which(tool) 478 if path is None: 479 superPrint("error", "Missing dependency!", "The <b>'" + tool + "'</b> tool is not available, please install it!") 480 return False 481 482 return True 483 484 # ============================================================================== 485 # ==== Main program (execution starts here) ==================================== 486 # ============================================================================== 487 488 ExitCode = 2 489 490 # ==== Argument parsing 491 492 # Get OpenSubtitlesDownload.py script absolute path 493 if os.path.isabs(sys.argv[0]): 494 scriptPath = sys.argv[0] 495 else: 496 scriptPath = os.getcwd() + "/" + str(sys.argv[0]) 497 498 # Setup ArgumentParser 499 parser = argparse.ArgumentParser(prog='OpenSubtitlesDownload.py', 500 description='Automatically find and download the right subtitles for your favorite videos!', 501 formatter_class=argparse.RawTextHelpFormatter) 502 503 parser.add_argument('--cli', help="Force CLI mode", action='store_true') 504 parser.add_argument('-g', '--gui', help="Select the GUI you want from: auto, kde, gnome, cli (default: auto)") 505 parser.add_argument('-l', '--lang', help="Specify the language in which the subtitles should be downloaded (default: eng).\nSyntax:\n-l eng,fre: search in both language\n-l eng -l fre: download both language", nargs='?', action='append') 506 parser.add_argument('-i', '--skip', help="Skip search if an existing subtitles file is detected", action='store_true') 507 parser.add_argument('-s', '--search', help="Search mode: hash, filename, hash_then_filename, hash_and_filename (default: hash_then_filename)") 508 parser.add_argument('-t', '--select', help="Selection mode: manual, default, auto") 509 parser.add_argument('-a', '--auto', help="Force automatic selection and download of the best subtitles found", action='store_true') 510 parser.add_argument('-o', '--output', help="Override subtitles download path, instead of next their video file") 511 parser.add_argument('-x', '--suffix', help="Enable language code suffix", action='store_true') 512 parser.add_argument('-u', '--username', help="Set opensubtitles.org account username") 513 parser.add_argument('-p', '--password', help="Set opensubtitles.org account password") 514 515 parser.add_argument('filePathListArg', help="The video file(s) for which subtitles should be searched and downloaded", nargs='+') 516 517 # Only use ArgumentParser if we have arguments... 518 if len(sys.argv) > 1: 519 result = parser.parse_args() 520 521 # Handle results 522 if result.cli: 523 opt_gui = 'cli' 524 if result.gui: 525 opt_gui = result.gui 526 if result.search: 527 opt_search_mode = result.search 528 if result.skip: 529 opt_search_overwrite = False 530 if result.select: 531 opt_selection_mode = result.select 532 if result.auto: 533 opt_selection_mode = 'auto' 534 if result.output: 535 opt_output_path = result.output 536 if result.lang: 537 if opt_languages != result.lang: 538 opt_languages = result.lang 539 opt_selection_language = 'on' 540 if result.suffix: 541 opt_language_suffix = 'on' 542 if result.username and result.password: 543 osd_username = result.username 544 osd_password = result.password 545 546 # GUI auto detection 547 if opt_gui == 'auto': 548 # Note: "ps cax" only output the first 15 characters of the executable's names 549 ps = str(subprocess.Popen(['ps', 'cax'], stdout=subprocess.PIPE).communicate()[0]).split('\n') 550 for line in ps: 551 if ('gnome-session' in line) or ('cinnamon-sessio' in line) or ('mate-session' in line) or ('xfce4-session' in line): 552 opt_gui = 'gnome' 553 break 554 elif 'ksmserver' in line: 555 opt_gui = 'kde' 556 break 557 558 # Sanitize settings 559 if opt_search_mode not in ['hash', 'filename', 'hash_then_filename', 'hash_and_filename']: 560 opt_search_mode = 'hash_then_filename' 561 562 if opt_selection_mode not in ['manual', 'default', 'auto']: 563 opt_selection_mode = 'default' 564 565 if opt_gui not in ['gnome', 'kde', 'cli']: 566 opt_gui = 'cli' 567 opt_search_mode = 'hash_then_filename' 568 opt_selection_mode = 'auto' 569 print("Unknown GUI, falling back to an automatic CLI mode") 570 571 # ==== Check for the necessary tools (must be done after GUI auto detection) 572 573 if dependencyChecker() is False: 574 sys.exit(2) 575 576 # ==== Get valid video paths 577 578 videoPathList = [] 579 580 if 'result' in locals(): 581 # Go through the paths taken from arguments, and extract only valid video paths 582 for i in result.filePathListArg: 583 filePath = os.path.abspath(i) 584 if os.path.isdir(filePath): 585 if opt_gui == 'cli': 586 # If it is a folder, check all of its files recursively 587 for root, _, items in os.walk(filePath): 588 for item in items: 589 localPath = os.path.join(root, item) 590 if checkFileValidity(localPath): 591 videoPathList.append(localPath) 592 else: 593 # If it is a folder, check all of its files 594 for item in os.listdir(filePath): 595 localPath = os.path.join(filePath, item) 596 if checkFileValidity(localPath): 597 videoPathList.append(localPath) 598 elif checkFileValidity(filePath): 599 # If it is a valid file, use it 600 videoPathList.append(filePath) 601 else: 602 superPrint("error", "No file provided!", "No file provided!") 603 sys.exit(2) 604 605 # If videoPathList is empty, abort! 606 if not videoPathList: 607 parser.print_help() 608 sys.exit(1) 609 610 # Check if the subtitles files already exists 611 if not opt_search_overwrite: 612 videoPathList = [path for path in videoPathList if not checkSubtitlesExists(path)] 613 614 # If videoPathList is empty, exit! 615 if not videoPathList: 616 sys.exit(1) 617 618 # ==== Instances dispatcher ==================================================== 619 620 # The first video file will be processed by this instance 621 videoPath = videoPathList[0] 622 videoPathList.pop(0) 623 624 # The remaining file(s) are dispatched to new instance(s) of this script 625 for videoPathDispatch in videoPathList: 626 627 # Handle current options 628 command = [ sys.executable, scriptPath, "-g", opt_gui, "-s", opt_search_mode, "-t", opt_selection_mode ] 629 630 for resultlangs in opt_languages: 631 command.append("-l") 632 command.append(resultlangs) 633 634 if not opt_search_overwrite: 635 command.append("--skip") 636 637 command.append(videoPathDispatch) 638 639 # Do not spawn too many instances at once 640 time.sleep(1) 641 642 if opt_gui == 'cli' and opt_selection_mode != 'auto': 643 # Synchronous call 644 process_videoDispatched = subprocess.call(command) 645 else: 646 # Asynchronous call 647 process_videoDispatched = subprocess.Popen(command) 648 649 # ==== Search and download subtitles =========================================== 650 651 try: 652 # ==== Connection to OpenSubtitlesDownload 653 try: 654 session = osd_server.LogIn(osd_username, osd_password, osd_language, 'opensubtitles-download 4.2') 655 except Exception: 656 # Retry once after a delay (could just be a momentary overloaded server?) 657 time.sleep(3) 658 try: 659 session = osd_server.LogIn(osd_username, osd_password, osd_language, 'opensubtitles-download 4.2') 660 except Exception: 661 superPrint("error", "Connection error!", "Unable to reach opensubtitles.org servers!\n\nPlease check:\n" + \ 662 "- Your Internet connection status\n- www.opensubtitles.org availability\n- Your downloads limit (200 subtitles per 24h)\n\n" + \ 663 "The subtitles search and download service is powered by opensubtitles.org. Be sure to donate if you appreciate the service provided!") 664 sys.exit(2) 665 666 # Connection refused? 667 if session['status'] != '200 OK': 668 superPrint("error", "Connection error!", "Opensubtitles.org servers refused the connection: " + session['status'] + ".\n\nPlease check:\n" + \ 669 "- Your Internet connection status\n - www.opensubtitles.org availability\n - Your downloads limit (200 subtitles per 24h)\n\n" + \ 670 "The subtitles search and download service is powered by opensubtitles.org. Be sure to donate if you appreciate the service provided!") 671 sys.exit(2) 672 673 # Count languages marked for this search 674 searchLanguage = 0 675 searchLanguageResult = 0 676 for SubLanguageID in opt_languages: 677 searchLanguage += len(SubLanguageID.split(',')) 678 679 searchResultPerLanguage = [searchLanguage] 680 681 # ==== Get file hash, size and name 682 videoTitle = '' 683 videoHash = hashFile(videoPath) 684 videoSize = os.path.getsize(videoPath) 685 videoFileName = os.path.basename(videoPath) 686 687 # ==== Search for available subtitles on OpenSubtitlesDownload 688 for SubLanguageID in opt_languages: 689 searchList = [] 690 subtitlesList = {} 691 692 if opt_search_mode in ('hash', 'hash_then_filename', 'hash_and_filename'): 693 searchList.append({'sublanguageid':SubLanguageID, 'moviehash':videoHash, 'moviebytesize':str(videoSize)}) 694 if opt_search_mode in ('filename', 'hash_and_filename'): 695 searchList.append({'sublanguageid':SubLanguageID, 'query':videoFileName}) 696 697 ## Primary search 698 try: 699 subtitlesList = osd_server.SearchSubtitles(session['token'], searchList) 700 except Exception: 701 # Retry once after a delay (we are already connected, the server may be momentary overloaded) 702 time.sleep(3) 703 try: 704 subtitlesList = osd_server.SearchSubtitles(session['token'], searchList) 705 except Exception: 706 superPrint("error", "Search error!", "Unable to reach opensubtitles.org servers!\n<b>Search error</b>") 707 708 #if (opt_search_mode == 'hash_and_filename'): 709 # TODO Cleanup duplicate between moviehash and filename results 710 711 ## Fallback search 712 if ((opt_search_mode == 'hash_then_filename') and (('data' in subtitlesList) and (not subtitlesList['data']))): 713 searchList[:] = [] # searchList.clear() 714 searchList.append({'sublanguageid':SubLanguageID, 'query':videoFileName}) 715 subtitlesList.clear() 716 try: 717 subtitlesList = osd_server.SearchSubtitles(session['token'], searchList) 718 except Exception: 719 # Retry once after a delay (we are already connected, the server may be momentary overloaded) 720 time.sleep(3) 721 try: 722 subtitlesList = osd_server.SearchSubtitles(session['token'], searchList) 723 except Exception: 724 superPrint("error", "Search error!", "Unable to reach opensubtitles.org servers!\n<b>Search error</b>") 725 726 ## Parse the results of the XML-RPC query 727 if ('data' in subtitlesList) and (subtitlesList['data']): 728 729 # Mark search as successful 730 searchLanguageResult += 1 731 subtitlesSelected = '' 732 733 # If there is only one subtitles (matched by file hash), auto-select it (except in CLI mode) 734 if (len(subtitlesList['data']) == 1) and (subtitlesList['data'][0]['MatchedBy'] == 'moviehash'): 735 if opt_selection_mode != 'manual': 736 subtitlesSelected = subtitlesList['data'][0]['SubFileName'] 737 738 # Get video title 739 videoTitle = subtitlesList['data'][0]['MovieName'] 740 741 # Title and filename may need string sanitizing to avoid zenity/kdialog handling errors 742 if opt_gui != 'cli': 743 videoTitle = videoTitle.replace('"', '\\"') 744 videoTitle = videoTitle.replace("'", "\\'") 745 videoTitle = videoTitle.replace('`', '\\`') 746 videoTitle = videoTitle.replace("&", "&") 747 videoFileName = videoFileName.replace('"', '\\"') 748 videoFileName = videoFileName.replace("'", "\\'") 749 videoFileName = videoFileName.replace('`', '\\`') 750 videoFileName = videoFileName.replace("&", "&") 751 752 # If there is more than one subtitles and opt_selection_mode != 'auto', 753 # then let the user decide which one will be downloaded 754 if not subtitlesSelected: 755 # Automatic subtitles selection? 756 if opt_selection_mode == 'auto': 757 subtitlesSelected = selectionAuto(subtitlesList) 758 else: 759 # Go through the list of subtitles and handle 'auto' settings activation 760 for item in subtitlesList['data']: 761 if opt_selection_match == 'auto': 762 if opt_search_mode == 'hash_and_filename': 763 opt_selection_match = 'on' 764 if opt_selection_language == 'auto': 765 if searchLanguage > 1: 766 opt_selection_language = 'on' 767 if opt_selection_hi == 'auto': 768 if item['SubHearingImpaired'] == '1': 769 opt_selection_hi = 'on' 770 if opt_selection_rating == 'auto': 771 if item['SubRating'] != '0.0': 772 opt_selection_rating = 'on' 773 if opt_selection_count == 'auto': 774 opt_selection_count = 'on' 775 776 # Spaw selection window 777 if opt_gui == 'gnome': 778 subtitlesSelected = selectionGnome(subtitlesList) 779 elif opt_gui == 'kde': 780 subtitlesSelected = selectionKde(subtitlesList) 781 else: # CLI 782 subtitlesSelected = selectionCLI(subtitlesList) 783 784 # If a subtitles has been selected at this point, download it! 785 if subtitlesSelected: 786 subIndex = 0 787 subIndexTemp = 0 788 789 # Select the subtitles file to download 790 for item in subtitlesList['data']: 791 if item['SubFileName'] == subtitlesSelected: 792 subIndex = subIndexTemp 793 break 794 else: 795 subIndexTemp += 1 796 797 subLangId = opt_language_separator + SubLanguageID 798 subLangName = subtitlesList['data'][subIndex]['LanguageName'] 799 subURL = subtitlesList['data'][subIndex]['SubDownloadLink'] 800 subEncoding = subtitlesList['data'][subIndex]['SubEncoding'] 801 802 subPath = videoPath.rsplit('.', 1)[0] + '.' + subtitlesList['data'][subIndex]['SubFormat'] 803 if opt_output_path and os.path.isdir(os.path.abspath(opt_output_path)): 804 subPath = os.path.abspath(opt_output_path) + "/" + subPath.rsplit('/', 1)[1] 805 806 # Write language code into the filename? 807 if ((opt_language_suffix == 'on') or (opt_language_suffix == 'auto' and searchLanguageResult > 1)): 808 subPath = subPath.rsplit('.', 1)[0] + subLangId + '.' + subtitlesList['data'][subIndex]['SubFormat'] 809 810 # Escape non-alphanumeric characters from the subtitles path 811 if opt_gui != 'cli': 812 subPath = re.escape(subPath) 813 subPath = subPath.replace('"', '\\"') 814 subPath = subPath.replace("'", "\\'") 815 subPath = subPath.replace('`', '\\`') 816 817 # Make sure we are downloading an UTF8 encoded file 818 if opt_force_utf8: 819 downloadPos = subURL.find("download/") 820 if downloadPos > 0: 821 subURL = subURL[:downloadPos+9] + "subencoding-utf8/" + subURL[downloadPos+9:] 822 823 ## Download and unzip the selected subtitles (with progressbar) 824 if opt_gui == 'gnome': 825 process_subtitlesDownload = subprocess.call("(wget -q -O - " + subURL + " | gunzip > " + subPath + ") 2>&1" + ' | (zenity --auto-close --progress --pulsate --title="Downloading subtitles, please wait..." --text="Downloading <b>' + subtitlesList['data'][subIndex]['LanguageName'] + '</b> subtitles for <b>' + videoTitle + '</b>...")', shell=True) 826 elif opt_gui == 'kde': 827 process_subtitlesDownload = subprocess.call("(wget -q -O - " + subURL + " | gunzip > " + subPath + ") 2>&1", shell=True) 828 else: # CLI 829 print(">> Downloading '" + subtitlesList['data'][subIndex]['LanguageName'] + "' subtitles for '" + videoTitle + "'") 830 831 if sys.version_info >= (3, 0): 832 tmpFile1, headers = urllib.request.urlretrieve(subURL) 833 tmpFile2 = gzip.GzipFile(tmpFile1) 834 byteswritten = open(subPath, 'wb').write(tmpFile2.read()) 835 if byteswritten > 0: 836 process_subtitlesDownload = 0 837 else: 838 process_subtitlesDownload = 1 839 else: # python 2 840 tmpFile1, headers = urllib.urlretrieve(subURL) 841 tmpFile2 = gzip.GzipFile(tmpFile1) 842 open(subPath, 'wb').write(tmpFile2.read()) 843 process_subtitlesDownload = 0 844 845 # If an error occurs, say so 846 if process_subtitlesDownload != 0: 847 superPrint("error", "Subtitling error!", "An error occurred while downloading or writing <b>" + subtitlesList['data'][subIndex]['LanguageName'] + "</b> subtitles for <b>" + videoTitle + "</b>.") 848 osd_server.LogOut(session['token']) 849 sys.exit(2) 850 851 ## Print a message if no subtitles have been found, for any of the languages 852 if searchLanguageResult == 0: 853 superPrint("info", "No subtitles available :-(", '<b>No subtitles found</b> for this video:\n<i>' + videoFileName + '</i>') 854 ExitCode = 1 855 else: 856 ExitCode = 0 857 858 except (OSError, IOError, RuntimeError, TypeError, NameError, KeyError): 859 860 # Do not warn about remote disconnection # bug/feature of python 3.5? 861 if "http.client.RemoteDisconnected" in str(sys.exc_info()[0]): 862 sys.exit(ExitCode) 863 864 # An unknown error occur, let's apologize before exiting 865 superPrint("error", "Unexpected error!", 866 "OpenSubtitlesDownload encountered an <b>unknown error</b>, sorry about that...\n\n" + \ 867 "Error: <b>" + str(sys.exc_info()[0]).replace('<', '[').replace('>', ']') + "</b>\n" + \ 868 "Line: <b>" + str(sys.exc_info()[-1].tb_lineno) + "</b>\n\n" + \ 869 "Just to be safe, please check:\n- www.opensubtitles.org availability\" + \n" + \ 870 "- Your downloads limit (200 subtitles per 24h)\n" + \ 871 "- Your Internet connection status\n" + \ 872 "- That are using the latest version of this software ;-)") 873 874 except Exception: 875 876 # Catch unhandled exceptions but do not spawn an error window 877 print("Unexpected error (line " + str(sys.exc_info()[-1].tb_lineno) + "): " + str(sys.exc_info()[0])) 878 879 # Disconnect from opensubtitles.org server, then exit 880 if session and session['token']: 881 osd_server.LogOut(session['token']) 882 883 sys.exit(ExitCode)