import ffmpy, json, os, sys, unidecode, requests, subprocess, time, pycountry, html, tqdm, re, glob, base64, binascii from titlecase import titlecase from configs.config import tool from helpers.proxy_environ import hold_proxy import tldextract from collections import namedtuple, Sequence from natsort import natsorted import logging import unicodedata, string class EpisodesNumbersHandler: def __init__(self): return def numberRange(self, start: int, end: int): if list(range(start, end + 1)) != []: return list(range(start, end + 1)) if list(range(end, start + 1)) != []: return list(range(end, start + 1)) return [start] def ListNumber(self, Number: str): if Number.isdigit(): return [int(Number)] if Number.strip() == "~" or Number.strip() == "": return self.numberRange(1, 999) if "-" in Number: start, end = Number.split("-") if start.strip() == "" or end.strip() == "": raise ValueError("wrong Number: {}".format(Number)) return self.numberRange(int(start), int(end)) if "~" in Number: start, _ = Number.split("~") if start.strip() == "": raise ValueError("wrong Number: {}".format(Number)) return self.numberRange(int(start), 999) return def sortNumbers(self, Numbers): SortedNumbers = [] for Number in Numbers.split(","): SortedNumbers += self.ListNumber(Number.strip()) return natsorted(list(set(SortedNumbers))) class ripprocess(object): def __init__(self): self.tool = tool() self.logger = logging.getLogger(__name__) self.bin = self.tool.bin() def sort_list(self, media_list, keyword1=None, keyword2=None): if keyword1: if keyword2: return sorted( media_list, key=lambda k: (int(k[keyword1]), int(k[keyword2])) ) else: sorted(media_list, key=lambda k: int(k[keyword1])) return media_list def yt2json(self, url, proxies=None): jsonfile = "info.info.json" yt_cmd = [ self.bin["youtube"], "--skip-download", "--write-info-json", "--quiet", "--no-warnings", "-o", "info", url, ] if proxies: yt_cmd += ["--proxy", proxies.get("https")] subprocess.call(yt_cmd) while not os.path.isfile(jsonfile): time.sleep(0.2) with open(jsonfile) as js: data = json.load(js) if os.path.isfile(jsonfile): os.remove(jsonfile) return data def getKeyId(self, mp4_file): data = subprocess.check_output( [self.bin["mp4dump"], "--format", "json", "--verbosity", "1", mp4_file] ) try: return re.sub( " ", "", re.compile(r"default_KID.*\[(.*)\]").search(data.decode()).group(1), ) except AttributeError: return None def flatten(self, l): return list(self.flatten_g(l)) def flatten_g(self, l): basestring = (str, bytes) for el in l: if isinstance(el, Sequence) and not isinstance(el, basestring): for sub in self.flatten_g(el): yield sub else: yield el def removeExtentsion(self, string: str): if "." in string: return ".".join(string.split(".")[:-1]) else: raise ValueError("string has no extentsion: {}".format(string)) def replaceExtentsion(self, string: str, ext: str): if "." in string: return ".".join(string.split(".")[:-1]) + f".{ext}" else: raise ValueError("string has no extentsion: {}".format(string)) def domain(self, url): return "{0.domain}.{0.suffix}".format(tldextract.extract(url)) def remove_dups(self, List, keyword=""): Added_ = set() Proper_ = [] for L in List: if L[keyword] not in Added_: Proper_.append(L) Added_.add(L[keyword]) return Proper_ def find_str(self, s, char): index = 0 if char in s: c = char[0] for ch in s: if ch == c: if s[index : index + len(char)] == char: return index index += 1 return -1 def updt(self, total, progress): barLength, status = 80, "" progress = float(progress) / float(total) if progress >= 1.0: progress, status = 1, "\r\n" block = int(round(barLength * progress)) text = "\rProgress: {} | {:.0f}% {}".format( "█" * block + "" * (barLength - block), round(progress * 100, 0), status, ) sys.stdout.write(text) sys.stdout.flush() def Get_PSSH(self, mp4_file): WV_SYSTEM_ID = "[ed ef 8b a9 79 d6 4a ce a3 c8 27 dc d5 1d 21 ed]" pssh = None data = subprocess.check_output( [self.bin["mp4dump"], "--format", "json", "--verbosity", "1", mp4_file] ) data = json.loads(data) for atom in data: if atom["name"] == "moov": for child in atom["children"]: if child["name"] == "pssh": if child["system_id"] == WV_SYSTEM_ID: pssh = child["data"][1:-1].replace(" ", "") pssh = binascii.unhexlify(pssh) if pssh.startswith(b"\x08\x01"): pssh = pssh[0:] pssh = base64.b64encode(pssh).decode("utf-8") return pssh return None def SubtitleEdit( self, contain=None, file=None, removeSDH=False, silent=True, extra_commands=[] ): if file: subtitle_command = [ self.bin["SubtitleEdit"], "/convert", file, "srt", "/overwrite", "/multiplereplace:.", "/MergeShortLines", "/FixCommonErrors", ] subtitle_command += extra_commands if removeSDH: subtitle_command.append("/RemoveTextForHI") subprocess.call( subtitle_command, stdout=open(os.devnull, "wb") ) if silent else subprocess.call(subtitle_command) if contain: subtitle_command = [ self.bin["SubtitleEdit"], "/convert", "{}*.srt".format(contain), "srt", "/overwrite", "/multiplereplace:.", "/MergeShortLines", "/FixCommonErrors", ] subtitle_command += extra_commands if removeSDH: subtitle_command.append("/removetextforhi") subprocess.call( subtitle_command, stdout=open(os.devnull, "wb") ) if silent else subprocess.call(subtitle_command) return def parseCookieFile(self, cookiesfile): cookies = {} with open(cookiesfile, "r") as fp: for line in fp: if not re.match(r"^\#", line): lineFields = line.strip().split("\t") try: cookies[lineFields[5]] = lineFields[6] except Exception: pass return cookies def ReplaceCodeLanguages(self, X): X = X.lower() X = ( X.replace("_subtitle_dialog_0", "") .replace("_narrative_dialog_0", "") .replace("_caption_dialog_0", "") .replace("_dialog_0", "") .replace("_descriptive_0", "_descriptive") .replace("_descriptive", "_descriptive") .replace("_sdh", "-sdh") .replace("es-es", "es") .replace("en-es", "es") .replace("kn-in", "kn") .replace("gu-in", "gu") .replace("ja-jp", "ja") .replace("mni-in", "mni") .replace("si-in", "si") .replace("as-in", "as") .replace("ml-in", "ml") .replace("sv-se", "sv") .replace("hy-hy", "hy") .replace("sv-sv", "sv") .replace("da-da", "da") .replace("fi-fi", "fi") .replace("nb-nb", "nb") .replace("is-is", "is") .replace("uk-uk", "uk") .replace("hu-hu", "hu") .replace("bg-bg", "bg") .replace("hr-hr", "hr") .replace("lt-lt", "lt") .replace("et-et", "et") .replace("el-el", "el") .replace("he-he", "he") .replace("ar-ar", "ar") .replace("fa-fa", "fa") .replace("ro-ro", "ro") .replace("sr-sr", "sr") .replace("cs-cs", "cs") .replace("sk-sk", "sk") .replace("mk-mk", "mk") .replace("hi-hi", "hi") .replace("bn-bn", "bn") .replace("ur-ur", "ur") .replace("pa-pa", "pa") .replace("ta-ta", "ta") .replace("te-te", "te") .replace("mr-mr", "mr") .replace("kn-kn", "kn") .replace("gu-gu", "gu") .replace("ml-ml", "ml") .replace("si-si", "si") .replace("as-as", "as") .replace("mni-mni", "mni") .replace("tl-tl", "tl") .replace("id-id", "id") .replace("ms-ms", "ms") .replace("vi-vi", "vi") .replace("th-th", "th") .replace("km-km", "km") .replace("ko-ko", "ko") .replace("zh-zh", "zh") .replace("ja-ja", "ja") .replace("ru-ru", "ru") .replace("tr-tr", "tr") .replace("it-it", "it") .replace("es-mx", "es-la") .replace("ar-sa", "ar") .replace("zh-cn", "zh") .replace("nl-nl", "nl") .replace("pl-pl", "pl") .replace("pt-pt", "pt") .replace("hi-in", "hi") .replace("mr-in", "mr") .replace("bn-in", "bn") .replace("te-in", "te") .replace("cmn-hans", "zh-hans") .replace("cmn-hant", "zh-hant") .replace("ko-kr", "ko") .replace("en-au", "en") .replace("es-419", "es-la") .replace("es-us", "es-la") .replace("en-us", "en") .replace("en-gb", "en") .replace("fr-fr", "fr") .replace("de-de", "de") .replace("las-419", "es-la") .replace("ar-ae", "ar") .replace("da-dk", "da") .replace("yue-hant", "yue") .replace("bn-in", "bn") .replace("ur-in", "ur") .replace("ta-in", "ta") .replace("sl-si", "sl") .replace("cs-cz", "cs") .replace("hi-jp", "hi") .replace("-001", "") .replace("en-US", "en") .replace("deu", "de") .replace("eng", "en") .replace("ca-es", "cat") .replace("fil-ph", "fil") .replace("en-ca", "en") .replace("eu-es", "eu") .replace("ar-eg", "ar") .replace("he-il", "he") .replace("el-gr", "he") .replace("nb-no", "nb") .replace("es-ar", "es-la") .replace("en-ph", "en") .replace("sq-al", "sq") .replace("bs-ba", "bs") ) return X def countrycode(self, code, site_domain="None"): languageCodes = { "zh-Hans": "zhoS", "zh-Hant": "zhoT", "pt-BR": "brPor", "es-ES": "euSpa", "en-GB": "enGB", "en-PH": "enPH", "nl-BE": "nlBE", "fil": "enPH", "yue": "zhoS", "fr-CA": "caFra", } if code == "cmn-Hans": return "Mandarin Chinese (Simplified)", "zh-Hans" elif code == "cmn-Hant": return "Mandarin Chinese (Traditional)", "zh-Hant" elif code == "zh-TW": return "Chinese", "zho" elif code == "zh-CN": return "Chinese", "zho" elif code == "es-419": return "Spanish", "spa" elif code == "es-ES": return "European Spanish", "euSpa" elif code == "pt-BR": return "Brazilian Portuguese", "brPor" elif code == "pt-PT": return "Portuguese", "por" elif code == "fr-CA": return "French Canadian", "caFra" elif code == "fr-FR": return "French", "fra" elif code == "iw": return "Modern Hebrew", "heb" elif code == "es" and site_domain == "google": return "European Spanish", "euSpa" lang_code = code[: code.index("-")] if "-" in code else code lang = pycountry.languages.get(alpha_2=lang_code) if lang is None: lang = pycountry.languages.get(alpha_3=lang_code) try: languagecode = languageCodes[code] except KeyError: languagecode = lang.alpha_3 return lang.name, languagecode def tqdm_downloader(self, url, file_name, proxies=None): # self.logger.info(file_name) r = requests.get(url, stream=True) file_size = int(r.headers["Content-Length"]) chunk = 1 chunk_size = 1024 num_bars = int(file_size / chunk_size) with open(file_name, "wb") as fp: for chunk in tqdm.tqdm( r.iter_content(chunk_size=chunk_size), total=num_bars, unit="KB", desc=file_name, leave=True, # progressbar stays ): fp.write(chunk) return def silent_aria2c_download(self, url, file_name, disable_proxy=True): holder = hold_proxy() if disable_proxy: holder.disable() commands = [ self.bin["aria2c"], url, '--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"', "--allow-overwrite=true", "--auto-file-renaming=false", "--retry-wait=5", "-x16", "-j16", "-s16", "-o", file_name, ] try: aria = subprocess.call(commands, stdout=open(os.devnull, "wb"),) except FileNotFoundError: self.logger.info("UNABLE TO FIND {}".format("aria2c.exe")) exit(-1) if aria != 0: raise ValueError("Aria2c exited with code {}".format(aria)) if disable_proxy: holder.enable() def aria2c_download(self, commands, extra_commands, disable_proxy=False): LogFile = self.bin["aria2c"].replace("exe", "log") if os.path.isfile(LogFile): os.remove(LogFile) aria2_commands = [] aria2_commands.append(self.bin["aria2c"]) aria2_commands.append("--log={}".format(LogFile)) aria2_commands += commands + extra_commands holder = hold_proxy() if disable_proxy: holder.disable() try: aria = subprocess.call(aria2_commands) except FileNotFoundError: self.logger.info("UNABLE TO FIND {}".format("aria2c.exe")) exit(-1) if aria != 0: self.logger.info("Aria2c exited with code {}".format(aria)) exit(-1) if disable_proxy: holder.enable() self.logger.info() def isduplelist(self, a, b): return set(a) == set(b) and len(a) == len(b) def readfile(self, file, lines=False): read = "" if os.path.isfile(file): with open(file, "r") as f: if lines: read = f.readlines() return read read = f.read() else: self.logger.info("File: %s, is not found" % file) return None return read def strip(self, inputint, left=True, right=False): if left: return str(inputint.lstrip("0")) if right: return str(inputint.rstrip("0")) return def CleanMyFileNamePlease(self, filename): # edit here... filename = filename.replace("666", "666") ################################################################################################################################ # dont edit here... filename = ( filename.replace(" ", ".") .replace("'", "") .replace(",", "") .replace("-", "") .replace("-.", ".") .replace(".-.", ".") ) filename = re.sub(" +", ".", filename) for i in range(10): filename = re.sub(r"(\.\.)", ".", filename) return filename def RemoveExtraWords(self, name): if re.search("[eE]pisode [0-9]+", name): name = name.replace((re.search("[eE]pisode [0-9]+", name)).group(0), "") if re.search(r"(\(.+?)\)", name): name = name.replace(re.search(r"(\(.+?)\)", name).group(), "") name = re.sub(" +", " ", name) name = name.strip() name = ( name.replace(" : ", " - ") .replace(": ", " - ") .replace(":", " - ") .replace("&", "and") .replace("ó", "o") .replace("*", "x") ) return name def DecodeString(self, text): for encoding in ("utf-8-sig", "utf-8", "utf-16"): try: return text.decode(encoding) except UnicodeDecodeError: continue return text.decode("latin-1") def EncodeString(self, text): for encoding in ("utf-8-sig", "utf-8", "utf-16"): try: return text.encode(encoding) except UnicodeDecodeError: continue return text.encode("latin-1") def clean_text(self, text): whitelist = ( "-_.() %s%s" % (string.ascii_letters, string.digits) + "',&#$%@`~!^&+=[]{}" ) cleaned_text = ( unicodedata.normalize("NFKD", text).encode("ASCII", "ignore").decode() ) return "".join(c for c in cleaned_text if c in whitelist) def RemoveCharcters(self, text): text = self.EncodeString(text) text = self.DecodeString(text) text = self.RemoveExtraWords(text) text = self.clean_text(text) text = unidecode.unidecode(titlecase(text)) return text def do_clean(self, contain, exclude=[], added=[]): """contain= string name in the file/files you want to delete. exclude= the files that has a specified extension you do not want to delete. send by list like ['.sfv', '.whatever'] added= another extensions not in the default extension. send by list like ['.sfv', '.whatever']""" error = [] extensions = [ ".mp4", ".h265", ".h264", ".eac3", ".m4a", ".ac3", ".srt", ".vtt", ".txt", ".aac", ".m3u8", ".mpd", ] extensions += added erased_files = [] for ext in extensions: if ext not in exclude: erased_files += glob.glob(contain + f"*{ext}") if not erased_files == []: for files in erased_files: try: os.remove(files) except Exception: error.append(files) if not error == []: self.logger.info( f"some files not deleted with extensions: " + ", ".join(str(x) for x in error) + "." ) return def mediainfo_(self, file): mediainfo_output = subprocess.Popen( [self.bin["MediaInfo"], "--Output=JSON", "-f", file], stdout=subprocess.PIPE, ) mediainfo_json = json.load(mediainfo_output.stdout) return mediainfo_json def DemuxAudio(self, inputName, replace_str): if os.path.isfile(inputName): self.logger.info("\nDemuxing audio...") mediainfo = self.mediainfo_(inputName) for m in mediainfo["media"]["track"]: if m["@type"] == "Audio": codec_name = m["Format"] ext = ".ac3" if codec_name == "AAC": ext = ".m4a" else: if codec_name == "E-AC-3": ext = ".eac3" else: if codec_name == "AC-3": ext = ".ac3" if codec_name == "DTS": ext = ".dts" outputName = inputName.replace(replace_str, ext) self.logger.info(("{} -> {}").format(inputName, outputName)) #直接重命名到目标格式,移除ffmpeg混流过程 os.rename(inputName, outputName) #ff = ffmpy.FFmpeg( # executable=self.bin["ffmpeg"], # inputs={inputName: None}, # outputs={outputName: "-c:a copy"}, # global_options="-vn -sn -y -hide_banner -loglevel panic", #) #ff.run() #time.sleep(0.05) #if os.path.isfile(outputName) and os.path.getsize(outputName) > 1024 * 1024: # os.remove(inputName) self.logger.info("Done!") return def shaka_decrypt(self, encrypted, decrypted, keys, stream): self.logger.info("\nDecrypting: {}".format(encrypted)) decrypt_command = [ self.bin["shaka-packager"], "--enable_raw_key_decryption", "-quiet", "input={},stream={},output={}".format(encrypted, stream, decrypted), ] for key in keys: decrypt_command.append("--keys") decrypt_command.append( "key={}:key_id={}".format( key["KEY"], "00000000000000000000000000000000" ) ) self.logger.info("\nDecrypting KEYS: ") for key in keys: self.logger.info(("{}:{}".format(key["KID"], key["KEY"]))) wvdecrypt_process = subprocess.Popen(decrypt_command) stdoutdata, stderrdata = wvdecrypt_process.communicate() wvdecrypt_process.wait() self.logger.info("Done!") return True def mp4_decrypt( self, encrypted, decrypted, keys, moded_decrypter=True, no_kid=True, silent=False, ): self.logger.info("\nDecrypting: {}".format(encrypted)) decrypt_command = [ self.bin["mp4decrypt"] if not moded_decrypter else self.bin["mp4decrypt_moded"] ] decrypt_command += ["--show-progress", encrypted, decrypted] for key in keys: decrypt_command.append("--key") decrypt_command.append( "{}:{}".format(key["ID"] if no_kid else key["KID"], key["KEY"]) ) self.logger.info("\nDecrypting KEYS: ") for key in keys: self.logger.info( ("{}:{}".format(key["ID"] if no_kid else key["KID"], key["KEY"])) ) if silent: wvdecrypt_process = subprocess.Popen( decrypt_command, stdout=open(os.devnull, "wb") ) else: wvdecrypt_process = subprocess.Popen(decrypt_command) stdoutdata, stderrdata = wvdecrypt_process.communicate() wvdecrypt_process.wait() if wvdecrypt_process.returncode == 0: self.logger.info("Done!") return True return False def DemuxVideo( self, outputVideoTemp, outputVideo, ffmpeg=False, mp4box=False, ffmpeg_version="ffmpeg", ): if ffmpeg: self.logger.info("\nRemuxing video...") # if not outputVideo.endswith(".h264"): # os.rename(outputVideoTemp, outputVideo) # self.logger.info("Done!") # return True ff = ffmpy.FFmpeg( executable=self.bin[ffmpeg_version], inputs={outputVideoTemp: None}, outputs={outputVideo: "-c copy"}, global_options="-y -hide_banner -loglevel panic", ).run() time.sleep(0.05) if ( os.path.isfile(outputVideo) and os.path.getsize(outputVideo) > 1024 * 1024 ): os.remove(outputVideoTemp) self.logger.info("Done!") return True if mp4box: self.logger.info("\nRemuxing video...") if not outputVideo.endswith(".h264"): os.rename(outputVideoTemp, outputVideo) self.logger.info("Done!") return True subprocess.call( [ self.bin["mp4box"], "-quiet", "-raw", "1", "-out", outputVideo, outputVideoTemp, ] ) if ( os.path.isfile(outputVideo) and os.path.getsize(outputVideo) > 1024 * 1024 ): os.remove(outputVideoTemp) self.logger.info("Done!") return True return False