From 3b6f5f360e214d59415fe031b701f5791ac9873d Mon Sep 17 00:00:00 2001 From: Hannes Date: Thu, 12 Feb 2026 00:24:39 +0100 Subject: [PATCH] Repaired a lot of Media Management --- check_video/__init__.py | 1 + .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 256 bytes .../__pycache__/check_video.cpython-314.pyc | Bin 0 -> 1909 bytes .../__pycache__/languages.cpython-314.pyc | Bin 0 -> 16410 bytes check_video/check_video.py | 42 ++ ff | 247 +++----- languages/__init__.py | 1 + .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 217 bytes .../__pycache__/languages.cpython-314.pyc | Bin 0 -> 16408 bytes languages/languages.py | 562 ++++++++++++++++++ set_1_ger | 47 ++ set_audio_lang | 112 ++++ set_subtitle_lang[untested] | 125 ++++ show_path | 2 +- 14 files changed, 958 insertions(+), 181 deletions(-) create mode 100644 check_video/__init__.py create mode 100644 check_video/__pycache__/__init__.cpython-314.pyc create mode 100644 check_video/__pycache__/check_video.cpython-314.pyc create mode 100644 check_video/__pycache__/languages.cpython-314.pyc create mode 100644 check_video/check_video.py create mode 100644 languages/__init__.py create mode 100644 languages/__pycache__/__init__.cpython-314.pyc create mode 100644 languages/__pycache__/languages.cpython-314.pyc create mode 100644 languages/languages.py create mode 100755 set_1_ger create mode 100755 set_audio_lang create mode 100644 set_subtitle_lang[untested] diff --git a/check_video/__init__.py b/check_video/__init__.py new file mode 100644 index 0000000..c6035b7 --- /dev/null +++ b/check_video/__init__.py @@ -0,0 +1 @@ +from .check_video import check_video_ext, normalize_language, normalize_lang_code \ No newline at end of file diff --git a/check_video/__pycache__/__init__.cpython-314.pyc b/check_video/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..926c9bc6dff182e63408ccda5b57d1ddc82de3a6 GIT binary patch literal 256 zcmdPqqMCV literal 0 HcmV?d00001 diff --git a/check_video/__pycache__/check_video.cpython-314.pyc b/check_video/__pycache__/check_video.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3cb7c522bd2055c48921c2401f292fc60929ba7 GIT binary patch literal 1909 zcmbtU%}*Og6o0e3{#u)0APp&U-6d8qt5Rr$RfVcb6g4IxNr5hnRkc7Oez4SjC`Vn*(h&P3 z_E~>#P+BZWEsnbO==Z4&Oo5 zfDGJGE`AMA#I+cFM3#s}vS{(dQnGD_VNAg*)#qZ)L-Y%m+O?8nb3dbQo!RJxT5}oK z<{hVO67FAcSS9b)#I@)6$k>uop{Q(|u8xh)m+Y~^5-r@#EtiVac@>XVS9v6t%iFf& z=3Sc0u`WEphhJ>~RRfMdB|Qg1t=IO&u<6R#AzDF3Du56XrHu4b1R~Peb`FHKC8^dp zD|Vg`3sS8=vRd0Tfxyd^MG|1ioNzMw@uaD8U)foqjQcPTr7G8tb5t`43*dh;8AB|B zkaxcle-3xD)3_?bJcpnGyMgF8_rAQ>44mBw#2*LZ4{OapVsq_D;KE*a-(EP%PT{uy zRapGr6H(YLmHBbTwQ}BGWI=>mgoSWG*TXn?b(F5lf=DG;eq5E;NdEz#TA$k&!{(fv zb=<_gQd(8f4!3bCs*-FO7SR&J=aBdhm1rFfAR_{P3Tj0weG%WBo`@5ju^?SX`!!C zd=R!oY3su)(@A}%?t1%LCY^_F+eNxivT4!OSTDN8dV~@=V}gRF%KCBOzR&BFt@6-9 zyqd8H}dLA8l12@QmeTD#^kq(jQUiZMhAG&%T+_-<^ zyIVVv_~S^t88I4ZTn%>ppg#!={L=mU!}C9N8;^pSb?yIEJ|7JxTCu9&Z*e_G99*O-?SKAF! z7|W_bLO zHZBu4ZHBm7sisifUFK?jxrE3;v{FH}P+sQR3Z1WTwajq7SXwXxJk;i1twUhehnLD; z=fxnL*#dcmN8RB26{lD)(@Sg=O`;?<5!Sv+2zd&rXW^R-{iTL&|D}#y=4ccPEdTxk DNFHUb literal 0 HcmV?d00001 diff --git a/check_video/__pycache__/languages.cpython-314.pyc b/check_video/__pycache__/languages.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bf1b3b55d82172e94bbbc0c2e0bf4e7e9436a4b GIT binary patch literal 16410 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 bool: + if extension.lower().strip('.') in extensions: + return True + return False + +def normalize_language(lang: str) -> str: + """ + Input: 'ger', 'DE', 'German', 'GERMAN' + Output: 'German' + """ + if not lang: + return "Undefined" + + # 1. Clean the input + query = lang.strip().lower() + + # 2. Check if it's already a code (e.g., 'de' or 'ger') + if query in LANG_CODES: + return LANG_CODES[query] + + # 3. Check if it's a full name (e.g., 'german') + # We need a case-insensitive check against the names in REVERSE_LANG_CODES + for full_name in REVERSE_LANG_CODES: + if full_name.lower() == query: + return full_name + + return "Undefined" + +def normalize_lang_code(lang: str) -> str: + """ + Input: 'ger', 'DE', 'German', 'GERMAN' + Output: 'de' (the canonical media code (ISO 639-1)) + """ + # First, get the standard full name + full_name = normalize_language(lang) + + # Then, look up that full name in the reverse table + return REVERSE_LANG_CODES.get(full_name, "und") \ No newline at end of file diff --git a/ff b/ff index 6319e22..78c45cc 100755 --- a/ff +++ b/ff @@ -3,10 +3,7 @@ import sys import os import subprocess import json - -# cmd = "ls ../Videos/OBS/*.mkv" -# result = subprocess.run(cmd, shell=True, capture_output=True, text=True) -# print(result.stdout) +from check_video import check_video_ext, normalize_language, normalize_lang_code color = True try: @@ -18,8 +15,6 @@ except ImportError: print("For nicer output install termcolor:\npip install termcolor") color = False -EXT=[".mp4", ".mkv", ".avi", ".mov", ".wmv", ".flv", ".webm", ".lrv", ".gif"] - NORMAL_STYLE = ("white", None, []) ERROR_STYLE = ("red", None, ["bold"]) WARN_STYLE = ("yellow", None, ["bold"]) @@ -78,7 +73,7 @@ def get_interlace_label(fo): return "Progressive" # Default assumption for modern web video class video_lines: - def __init__(self, stream, duration): + def __init__(self, stream): if stream.get("index"): self.id = stream.get("index") else: @@ -92,7 +87,7 @@ class video_lines: if stream.get("name"): self.duration = seconds_to_hms(stream.get("duration")) else: - self.duration = duration + self.duration = None if stream.get("codec_name"): self.codec = stream.get("codec_name") @@ -160,89 +155,8 @@ class video_lines: string += f" [{self.field_order}]" return string -LANG_CODES = { - "eng": "English", "en": "English", "spa": "Spanish", "es": "Spanish", - "fra": "French", "fr": "French", "deu": "German", "ger": "German", "de": "German", - "jpn": "Japanese", "ja": "Japanese", "ita": "Italian", "it": "Italian", - "por": "Portuguese", "pt": "Portuguese", "rus": "Russian", "ru": "Russian", - "chi": "Chinese", "zho": "Chinese", "zh": "Chinese", "kor": "Korean", "ko": "Korean", - "dut": "Dutch", "nl": "Dutch", "swe": "Swedish", "sv": "Swedish", - "fin": "Finnish", "fi": "Finnish", "pol": "Polish", "pl": "Polish", - "ara": "Arabic", "ar": "Arabic", "hin": "Hindi", "hi": "Hindi", - "tur": "Turkish", "tr": "Turkish", "und": "Undefined", " ": "Undefined", - "ab": "Abkhazian", "abk": "Abkhazian", "aa": "Afar", "aar": "Afar", - "af": "Afrikaans", "afr": "Afrikaans", "ak": "Akan", "aka": "Akan", - "twi": "Twi", "fat": "Fanti", "sq": "Albanian", "sqi": "Albanian", "alb": "Albanian", - "am": "Amharic", "amh": "Amharic", "arb": "Arabic", "an": "Aragonese", "arg": "Aragonese", - "hy": "Armenian", "hye": "Armenian", "arm": "Armenian", "as": "Assamese", "asm": "Assamese", - "av": "Avaric", "ava": "Avaric", "ae": "Avestan", "ave": "Avestan", "ay": "Aymara", "aym": "Aymara", - "az": "Azerbaijani", "aze": "Azerbaijani", "bm": "Bambara", "bam": "Bambara", - "ba": "Bashkir", "bak": "Bashkir", "eu": "Basque", "eus": "Basque", "baq": "Basque", - "be": "Belarusian", "bel": "Belarusian", "bn": "Bengali", "ben": "Bengali", - "bi": "Bislama", "bis": "Bislama", "bs": "Bosnian", "bos": "Bosnian", - "br": "Breton", "bre": "Breton", "bg": "Bulgarian", "bul": "Bulgarian", - "my": "Burmese", "mya": "Burmese", "ca": "Catalan", "cat": "Catalan", - "ch": "Chamorro", "cha": "Chamorro", "ce": "Chechen", "che": "Chechen", - "ny": "Chichewa", "nya": "Chichewa", "cu": "Church Slavonic", "chu": "Church Slavonic", - "cv": "Chuvash", "chv": "Chuvash", "kw": "Cornish", "cor": "Cornish", - "co": "Corsican", "cos": "Corsican", "cr": "Cree", "cre": "Cree", - "hr": "Croatian", "hrv": "Croatian", "cs": "Czech", "ces": "Czech", "cze": "Czech", - "da": "Danish", "dan": "Danish", "dv": "Divehi", "div": "Divehi", "dz": "Dzongkha", "dzo": "Dzongkha", - "eo": "Esperanto", "epo": "Esperanto", "et": "Estonian", "est": "Estonian", - "ee": "Ewe", "ewe": "Ewe", "fo": "Faroese", "fao": "Faroese", "fj": "Fijian", "fij": "Fijian", - "fre": "French", "fy": "Western Frisian", "fry": "Western Frisian", "ff": "Fulah", "ful": "Fulah", - "gd": "Gaelic, Scottish Gaelic", "gla": "Gaelic", "gl": "Galician", "glg": "Galician", - "lg": "Ganda", "lug": "Ganda", "ka": "Georgian", "kat": "Georgian", "geo": "Georgian", - "el": "Greek", "ell": "Greek", "gre": "Greek", "kl": "Kalaallisut", "kal": "Kalaallisut", - "gn": "Guarani", "grn": "Guarani", "gu": "Gujarati", "guj": "Gujarati", - "ht": "Haitian Creole", "hat": "Haitian Creole", "ha": "Hausa", "hau": "Hausa", - "he": "Hebrew", "heb": "Hebrew", "hz": "Herero", "her": "Herero", "ho": "Hiri Motu", "hmo": "Hiri Motu", - "hu": "Hungarian", "hun": "Hungarian", "is": "Icelandic", "isl": "Icelandic", "ice": "Icelandic", - "io": "Ido", "ido": "Ido", "ig": "Igbo", "ibo": "Igbo", "id": "Indonesian", "ind": "Indonesian", - "ia": "Interlingua", "ina": "Interlingua", "ie": "Interlingue", "ile": "Interlingue", - "iu": "Inuktitut", "iku": "Inuktitut", "ik": "Inupiaq", "ipk": "Inupiaq", - "ga": "Irish", "gle": "Irish", "jv": "Javanese", "jav": "Javanese", - "kn": "Kannada", "kan": "Kannada", "kr": "Kanuri", "kau": "Kanuri", - "ks": "Kashmiri", "kas": "Kashmiri", "kk": "Kazakh", "kaz": "Kazakh", - "km": "Central Khmer", "khm": "Central Khmer", "ki": "Kikuyu", "kik": "Kikuyu", - "rw": "Kinyarwanda", "kin": "Kinyarwanda", "ky": "Kyrgyz", "kir": "Kyrgyz", - "kv": "Komi", "kom": "Komi", "kg": "Kongo", "kon": "Kongo", "kj": "Kuanyama", "kua": "Kuanyama", - "ku": "Kurdish", "kur": "Kurdish", "lo": "Lao", "lao": "Lao", "la": "Latin", "lat": "Latin", - "lv": "Latvian", "lav": "Latvian", "li": "Limburgan", "lim": "Limburgan", - "ln": "Lingala", "lin": "Lingala", "lt": "Lithuanian", "lit": "Lithuanian", - "lu": "Luba-Katanga", "lub": "Luba-Katanga", "lb": "Luxembourgish", "ltz": "Luxembourgish", - "mk": "Macedonian", "mkd": "Macedonian", "mac": "Macedonian", "mg": "Malagasy", "mlg": "Malagasy", - "ms": "Malay", "msa": "Malay", "ml": "Malayalam", "mal": "Malayalam", "mt": "Maltese", "mlt": "Maltese", - "gv": "Manx", "glv": "Manx", "mi": "Maori", "mri": "Maori", "mao": "Maori", - "mr": "Marathi", "mar": "Marathi", "mh": "Marshallese", "mah": "Marshallese", - "mn": "Mongolian", "mon": "Mongolian", "na": "Nauru", "nau": "Nauru", "nv": "Navajo", "nav": "Navajo", - "nd": "North Ndebele", "nde": "North Ndebele", "nr": "South Ndebele", "nbl": "South Ndebele", - "ng": "Ndonga", "ndo": "Ndonga", "ne": "Nepali", "nep": "Nepali", "no": "Norwegian", "nor": "Norwegian", - "nb": "Norwegian Bokmål", "nob": "Norwegian Bokmål", "nn": "Norwegian Nynorsk", "nno": "Norwegian Nynorsk", - "oc": "Occitan", "oci": "Occitan", "oj": "Ojibwa", "oji": "Ojibwa", "or": "Oriya", "ori": "Oriya", - "om": "Oromo", "orm": "Oromo", "os": "Ossetian", "oss": "Ossetian", "pi": "Pali", "pli": "Pali", - "ps": "Pashto", "pus": "Pashto", "fa": "Persian", "fas": "Persian", "per": "Persian", - "pa": "Punjabi", "pan": "Punjabi", "qu": "Quechua", "que": "Quechua", "ro": "Romanian", "ron": "Romanian", "rum": "Romanian", - "rm": "Romansh", "roh": "Romansh", "rn": "Rundi", "run": "Rundi", "se": "Northern Sami", "sme": "Northern Sami", - "sm": "Samoan", "smo": "Samoan", "sg": "Sango", "sag": "Sango", "sa": "Sanskrit", "san": "Sanskrit", - "sc": "Sardinian", "srd": "Sardinian", "sr": "Serbian", "srp": "Serbian", "sn": "Shona", "sna": "Shona", - "sd": "Sindhi", "snd": "Sindhi", "si": "Sinhala", "sin": "Sinhala", "sk": "Slovak", "slk": "Slovak", "slo": "Slovak", - "sl": "Slovenian", "slv": "Slovenian", "so": "Somali", "som": "Somali", "st": "Southern Sotho", "sot": "Southern Sotho", - "su": "Sundanese", "sun": "Sundanese", "sw": "Swahili", "swa": "Swahili", "ss": "Swati", "ssw": "Swati", - "tl": "Tagalog", "tgl": "Tagalog", "ty": "Tahitian", "tah": "Tahitian", "tg": "Tajik", "tgk": "Tajik", - "ta": "Tamil", "tam": "Tamil", "tt": "Tatar", "tat": "Tatar", "te": "Telugu", "tel": "Telugu", - "th": "Thai", "tha": "Thai", "bo": "Tibetan", "bod": "Tibetan", "tib": "Tibetan", - "ti": "Tigrinya", "tir": "Tigrinya", "to": "Tongan", "ton": "Tongan", "ts": "Tsonga", "tso": "Tsonga", - "tn": "Tswana", "tsn": "Tswana", "tk": "Turkmen", "tuk": "Turkmen", "ug": "Uighur", "uig": "Uighur", - "uk": "Ukrainian", "ukr": "Ukrainian", "ur": "Urdu", "urd": "Urdu", "uz": "Uzbek", "uzb": "Uzbek", - "ve": "Venda", "ven": "Venda", "vi": "Vietnamese", "vie": "Vietnamese", "vo": "Volapük", "vol": "Volapük", - "wa": "Walloon", "wln": "Walloon", "cy": "Welsh", "cym": "Welsh", "wel": "Welsh", "wo": "Wolof", "wol": "Wolof", - "xh": "Xhosa", "xho": "Xhosa", "ii": "Sichuan Yi", "iii": "Sichuan Yi", "yi": "Yiddish", "yid": "Yiddish", - "yo": "Yoruba", "yor": "Yoruba", "za": "Zhuang", "zha": "Zhuang", "zu": "Zulu", "zul": "Zulu" -} - class audio_lines: - def __init__(self, stream, file_duration): + def __init__(self, stream): # 1. Basic ID self.id = stream.get("index") @@ -251,14 +165,14 @@ class audio_lines: # 3. Language (usually in tags) raw_lang = stream.get("tags", {}).get("language", "und").lower() - self.language = LANG_CODES.get(raw_lang, raw_lang.capitalize()) + self.language = normalize_language(raw_lang) # 4. Duration (fallback to file duration if stream duration is missing) stream_dur = stream.get("duration") if stream_dur: self.duration = seconds_to_hms(float(stream_dur)) else: - self.duration = file_duration + self.duration = None # 5. Codec self.codec = stream.get("codec_name", "") @@ -315,17 +229,17 @@ class audio_lines: return string class subtitles: - def __init__(self, stream, file_duration): + def __init__(self, stream): self.id = stream.get("index") self.name = stream.get("tags", {}).get("title", "") # Language translation raw_lang = stream.get("tags", {}).get("language", "und") - self.language = LANG_CODES.get(raw_lang, raw_lang.capitalize()) + self.language = normalize_language(raw_lang) # Duration logic stream_dur = stream.get("duration") - self.duration = seconds_to_hms(float(stream_dur)) if stream_dur else file_duration + self.duration = seconds_to_hms(float(stream_dur)) if stream_dur else None # Codec (e.g., srt, ass, subrip) self.codec = stream.get("codec_name", "") @@ -359,72 +273,51 @@ class subtitles: return " ".join(parts) -def get_video_lines(file): - cmd = ["ffprobe", - "-v", "error", - "-select_streams", "v", # video streams only - "-show_entries", - "stream=index,codec_name,width,height,r_frame_rate,bit_rate,duration,nb_frames,pix_fmt,field_order,time_base,display_aspect_ratio,color_space,color_transfer,color_primaries,bits_per_raw_sample:stream_tags=title", - "-of", "json", - file - ] - - result = subprocess.run(cmd, capture_output=True, text=True) - info = json.loads(result.stdout) - streams = info.get("streams", []) - return streams - -def get_audio_lines(file): - cmd = [ - "ffprobe", - "-v", "error", - "-select_streams", "a", # Select audio streams only - "-show_entries", - # Entries mapped to your requirements: - "stream=index,codec_name,sample_rate,channels,bits_per_sample,bits_per_raw_sample,bit_rate,duration" + - ":stream_tags=language,title", - "-of", "json", - file - ] - - try: - result = subprocess.run(cmd, capture_output=True, text=True, check=True) - info = json.loads(result.stdout) - return info.get("streams", []) - except subprocess.CalledProcessError as e: - print(f"Error running ffprobe: {e.stderr}") - return [] - -def get_subtitle_lines(file): - cmd = [ - "ffprobe", - "-v", "error", - "-select_streams", "s", # Subtitle streams only - "-show_entries", - "stream=index,codec_name,duration:stream_tags=language,title:stream_disposition=forced,default", - "-of", "json", - file - ] - - try: - result = subprocess.run(cmd, capture_output=True, text=True, check=True) - info = json.loads(result.stdout) - return info.get("streams", []) - except subprocess.CalledProcessError: - return [] - -def get_video_duration(file_path): +def get_media_info(file): cmd = [ "ffprobe", "-v", "error", - "-show_entries", "format=duration", - "-of", "default=noprint_wrappers=1:nokey=1", - file_path + "-show_entries", + ( + "format=duration:" + "stream=index,codec_type,codec_name," + "width,height,r_frame_rate,bit_rate,duration,nb_frames," + "pix_fmt,field_order,time_base,display_aspect_ratio," + "color_space,color_transfer,color_primaries,bits_per_raw_sample," + "sample_rate,channels,bits_per_sample," + "stream_disposition=forced,default:" + "stream_tags=language,title" + ), + "-of", "json", + file ] - result = subprocess.run(cmd, capture_output=True, text=True) - duration = float(result.stdout.strip()) - return duration + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + info = json.loads(result.stdout) + except subprocess.CalledProcessError as e: + print(f"Error running ffprobe: {e.stderr}") + return None, [], [], [] + + # Container / file duration (string seconds, per ffprobe convention) + duration = None + if "format" in info: + duration = info["format"].get("duration") + + video_streams = [] + audio_streams = [] + subtitle_streams = [] + + for stream in info.get("streams", []): + stream_type = stream.get("codec_type") + if stream_type == "video": + video_streams.append(stream) + elif stream_type == "audio": + audio_streams.append(stream) + elif stream_type == "subtitle": + subtitle_streams.append(stream) + + return float(duration), video_streams, audio_streams, subtitle_streams def seconds_to_hms(seconds): h = int(seconds // 3600) @@ -432,42 +325,34 @@ def seconds_to_hms(seconds): s = int(seconds % 60) return f"{h:02}:{m:02}:{s:02}" - -def get_stream_bitrate(file_path, stream=None): - # Get duration in seconds - duration = get_video_duration(file_path) - - # Get file size in bits - size_bits = os.path.getsize(file_path) * 8 - - # Approximate average bitrate - avg_bitrate = size_bits / duration if duration > 0 else 0 - return avg_bitrate # bits per second - +def get_stream_bitrate(file_size, duration): + return int((file_size * 8)/duration/1000000) if duration > 0 else 0 class video_file: def __init__(self, path, base_tab=""): self.base_tab = base_tab # \t self.path = path # folder/25.mkv self.name = os.path.basename(path) # 25.mkv - self.size = human_readable_size(os.path.getsize(path)) # 198MB - self.duration = seconds_to_hms(get_video_duration(path)) - self.bitrate = int((os.path.getsize(path) * 8)/get_video_duration(path))/1000000 if get_video_duration(path) > 0 else 0 - # self. = get_video_lines(path) + self.size = os.path.getsize(path) + self.videos = [] self.audios = [] self.subtitles = [] - for vl in get_video_lines(path): - video_line = video_lines(vl, self.duration) + self.duration, videos, audios, subtitles = get_media_info(path) + + for vl in videos: + video_line = video_lines(vl) self.videos.append(video_line) - for al in get_audio_lines(path): - audio_line = audio_lines(al, self.duration) + for al in audios: + audio_line = audio_lines(al) self.videos.append(audio_line) - for st in get_subtitle_lines(path): - subtitle = subtitles(st, self.duration) + for st in subtitles: + subtitle = subtitles(st) self.videos.append(subtitle) - + self.bitrate = get_stream_bitrate(self.size, self.duration) + self.size = human_readable_size(self.size) # 198MB + self.duration = seconds_to_hms(self.duration) def print(self): if self.base_tab == "\t": @@ -525,6 +410,8 @@ def handle_files(files, all_files): all_files.extend(grouped) + + def handle_folders(dirs, all_files): if(dirs != []): dirs.sort(key=lambda f: os.path.dirname(f)) @@ -533,7 +420,7 @@ def handle_folders(dirs, all_files): for file in os.scandir(dir): if file.is_file(): file = file.path - if os.path.splitext(file)[1].lower() in EXT: + if check_video_ext(os.path.splitext(file)[1]): dir_files.append(file) else: np(f"{file} is not a compatabile Video file", WARN_STYLE) diff --git a/languages/__init__.py b/languages/__init__.py new file mode 100644 index 0000000..8aeadb5 --- /dev/null +++ b/languages/__init__.py @@ -0,0 +1 @@ +from .languages import LANG_CODES, REVERSE_LANG_CODES \ No newline at end of file diff --git a/languages/__pycache__/__init__.cpython-314.pyc b/languages/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8a4242db021eedf2276c5099cb15aec9df3faa0 GIT binary patch literal 217 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 {out.name}") + + cmd = [ + "ffmpeg", "-y", + "-i", str(file), + "-map", "0", + "-c", "copy", + "-metadata:s:a:0", f"language={lang}", + str(out) + ] + + result = subprocess.run( + cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True + ) + + if result.returncode != 0: + print(f"ERROR processing {file.name}") + print(result.stderr.strip()) + return + + + preserve_original(file) + + +def main(): + if len(sys.argv) == 1: + targets = ["."] + lang = input("Language: ") + elif len(sys.argv) == 2: + targets = ["."] + lang = sys.argv[1] + else: + targets = sys.argv[1:-1] + lang = sys.argv[-1] + + lang = normalize_language(lang) + + for f in collect_files(targets): + set_audio_lang(f, lang) + +if __name__ == "__main__": + main() diff --git a/set_subtitle_lang[untested] b/set_subtitle_lang[untested] new file mode 100644 index 0000000..b615401 --- /dev/null +++ b/set_subtitle_lang[untested] @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import json +import pathlib + +VIDEO_EXTS = {".mkv", ".mp4"} + +LANG_CODES = { + "eng": "English", "en": "English", "english": "English", + "spa": "Spanish", "es": "Spanish", "spanish": "Spanish", + "fra": "French", "fre": "French", "fr": "French", "french": "French", + "deu": "German", "ger": "German", "de": "German", "german": "German", +} + +CANONICAL = { + "English": "eng", + "Spanish": "spa", + "French": "fra", + "German": "deu", +} + +def normalize_language(value): + key = value.lower() + if key not in LANG_CODES: + raise ValueError(f"Unknown language: {value}") + return CANONICAL[LANG_CODES[key]] + +def probe(file): + cmd = [ + "ffprobe", + "-v", "error", + "-print_format", "json", + "-show_streams", + str(file), + ] + return json.loads(subprocess.check_output(cmd)) + +def collect_files(paths): + files = [] + for p in paths: + p = pathlib.Path(p) + if p.is_dir(): + files.extend( + f for f in p.iterdir() + if f.suffix.lower() in VIDEO_EXTS + ) + elif p.suffix.lower() in VIDEO_EXTS: + files.append(p) + return files + +def preserve_original(file): + original = file.with_name(f"{file.stem}_original{file.suffix}") + if original.exists(): + return + file.rename(original) + print(f"preserve: {file.name} -> {original.name}") + +def set_sub_lang(file, lang): + data = probe(file) + subs = [s for s in data["streams"] if s["codec_type"] == "subtitle"] + + if not subs: + return + + for i, s in enumerate(subs): + cur = s.get("tags", {}).get("language", "unset") + print(f"{i}: subtitle ({cur})") + + sel = input(f"{file.name}: select subtitle index (blank = skip): ") + if sel == "": + return + + idx = int(sel) + current = subs[idx].get("tags", {}).get("language") + + if current: + ans = input(f"Overwrite existing '{current}'? [y/N] ") + if ans.lower() != "y": + return + + out = file.with_name(f"{file.stem}_sub-{lang}{file.suffix}") + print(f"{file.name} -> {out.name}") + + cmd = [ + "ffmpeg", "-y", + "-i", str(file), + "-map", "0", + "-c", "copy", + f"-metadata:s:s:{idx}", f"language={lang}", + str(out) + ] + + result = subprocess.run( + cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + text=True + ) + + if result.returncode != 0: + print(f"ERROR processing {file.name}") + print(result.stderr.strip()) + return + + preserve_original(file) + +def main(): + if len(sys.argv) == 1: + targets = ["."] + lang = input("Subtitle language: ") + elif len(sys.argv) == 2: + targets = ["."] + lang = sys.argv[1] + else: + targets = sys.argv[1:-1] + lang = sys.argv[-1] + + lang = normalize_language(lang) + + for f in collect_files(targets): + set_sub_lang(f, lang) + +if __name__ == "__main__": + main() diff --git a/show_path b/show_path index 05032e1..d2988ec 100755 --- a/show_path +++ b/show_path @@ -1,3 +1,3 @@ #!/bin/bash -echo $PATH | tr " " "\n" | nl +echo $PATH | tr ":" "\n" | nl