570 lines
22 KiB
Python
Executable File
570 lines
22 KiB
Python
Executable File
#!/bin/python
|
|
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)
|
|
|
|
color = True
|
|
try:
|
|
from termcolor import colored
|
|
except ImportError:
|
|
if os.name == "posix":
|
|
print("For nicer output install termcolor:\nsudo \'your installer\' python-termcolor")
|
|
else:
|
|
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"])
|
|
INFO_STYLE = ("cyan", None, [])
|
|
SUCCESS_STYLE = ("green", None, ["bold"])
|
|
DEBUG_STYLE = ("magenta", None, ["dark"])
|
|
|
|
def np(string, style, end = "\n"):
|
|
if color:
|
|
print(colored(string, *style), end=end)
|
|
else:
|
|
print(string, end=end)
|
|
|
|
def human_readable_size(size, decimal_places=2):
|
|
for unit in ['B','KB','MB','GB','TB']:
|
|
if size < 1024:
|
|
return f"{size:.{decimal_places}f} {unit}"
|
|
size /= 1024
|
|
|
|
def calculate_aspect(width: int, height: int) -> str:
|
|
temp = 0
|
|
|
|
def gcd(a, b):
|
|
"""The GCD (greatest common divisor) is the highest number that evenly divides both width and height."""
|
|
return a if b == 0 else gcd(b, a % b)
|
|
|
|
if width == height:
|
|
return "1:1"
|
|
|
|
if width < height:
|
|
temp = width
|
|
width = height
|
|
height = temp
|
|
|
|
divisor = gcd(width, height)
|
|
|
|
x = int(width / divisor) if not temp else int(height / divisor)
|
|
y = int(height / divisor) if not temp else int(width / divisor)
|
|
|
|
return f"{x}:{y}"
|
|
|
|
def get_interlace_label(fo):
|
|
if not fo:
|
|
return "Progressive"
|
|
|
|
fo = str(fo).lower()
|
|
|
|
# Map ffprobe codes to standard labels
|
|
if fo in ["tt", "tff", "tb"]:
|
|
return "Interlaced (TFF)"
|
|
elif fo in ["bb", "bff", "bt"]:
|
|
return "Interlaced (BFF)"
|
|
elif "progressive" in fo:
|
|
return "Progressive"
|
|
|
|
return "Progressive" # Default assumption for modern web video
|
|
|
|
class video_lines:
|
|
def __init__(self, stream, duration):
|
|
if stream.get("index"):
|
|
self.id = stream.get("index")
|
|
else:
|
|
self.id = None
|
|
|
|
if stream.get("name"):
|
|
self.name = stream.get("name")
|
|
else:
|
|
self.name = ""
|
|
|
|
if stream.get("name"):
|
|
self.duration = seconds_to_hms(stream.get("duration"))
|
|
else:
|
|
self.duration = duration
|
|
|
|
if stream.get("codec_name"):
|
|
self.codec = stream.get("codec_name")
|
|
else:
|
|
self.codec = ""
|
|
|
|
if stream.get("width"):
|
|
self.width = stream.get("width")
|
|
else:
|
|
self.width = ""
|
|
|
|
if stream.get("height"):
|
|
self.height = stream.get("height")
|
|
else:
|
|
self.height = ""
|
|
|
|
self.resolution = f"{self.width}x{self.height}"
|
|
|
|
if stream.get("r_frame_rate"):
|
|
num, den = map(int, stream.get("r_frame_rate").split("/"))
|
|
self.framerate = round(num / den, 2)
|
|
else:
|
|
self.framerate = ""
|
|
|
|
if stream.get("display_aspect_ratio"):
|
|
self.aspect_ratio = stream.get("display_aspect_ratio")
|
|
elif self.resolution != "x":
|
|
self.aspect_ratio = calculate_aspect(self.width, self.height)
|
|
else:
|
|
self.aspect_ratio = ""
|
|
|
|
if stream.get("pix_fmt"):
|
|
self.pix_fmt = stream.get("pix_fmt")
|
|
else:
|
|
self.pix_fmt = ""
|
|
|
|
if stream.get("color_space"):
|
|
self.color_space = stream.get("color_space")
|
|
else:
|
|
self.color_space = ""
|
|
|
|
if stream.get("field_order"):
|
|
self.field_order = get_interlace_label(stream.get("field_order"))
|
|
else:
|
|
self.field_order = ""
|
|
|
|
def __str__(self):
|
|
string = "Video"
|
|
if self.id != None:
|
|
string += f" {self.id}: "
|
|
else:
|
|
string += f": "
|
|
string += f"{self.codec}"
|
|
if self.duration:
|
|
string += f" {self.duration}s"
|
|
if self.resolution != "x":
|
|
string += f"({self.resolution}"
|
|
if self.framerate != "x":
|
|
string += f"@{self.framerate})"
|
|
if self.aspect_ratio:
|
|
string += f" [{self.aspect_ratio}]"
|
|
if self.pix_fmt and self.color_space:
|
|
string += f" [{self.pix_fmt}, {self.color_space}]"
|
|
if self.field_order:
|
|
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):
|
|
# 1. Basic ID
|
|
self.id = stream.get("index")
|
|
|
|
# 2. Name (usually in tags as 'title')
|
|
self.name = stream.get("tags", {}).get("title", "")
|
|
|
|
# 3. Language (usually in tags)
|
|
raw_lang = stream.get("tags", {}).get("language", "und").lower()
|
|
self.language = LANG_CODES.get(raw_lang, raw_lang.capitalize())
|
|
|
|
# 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
|
|
|
|
# 5. Codec
|
|
self.codec = stream.get("codec_name", "")
|
|
|
|
# 6. Sample Rate (converted to kHz for readability, e.g., 48000 -> 48.0)
|
|
sr = stream.get("sample_rate")
|
|
self.sample_rate = f"{int(sr) / 1000} kHz" if sr else ""
|
|
|
|
# 7. Channels
|
|
self.channels = stream.get("channels", "")
|
|
|
|
# 8. Bit Depth
|
|
# PCM uses bits_per_sample; lossy like AAC/MP3 might use bits_per_raw_sample
|
|
depth = stream.get("bits_per_sample") or stream.get("bits_per_raw_sample")
|
|
self.bit_depth = f"{depth}-bit" if depth else ""
|
|
|
|
# 9. Bitrate (converted to kbps)
|
|
br = stream.get("bit_rate")
|
|
self.bitrate = f"{int(br) // 1000} kbps" if br else ""
|
|
|
|
def __str__(self):
|
|
string = "Audio"
|
|
if self.id is not None:
|
|
string += f" {self.id}: "
|
|
else:
|
|
string += ": "
|
|
|
|
string += f"{self.codec}"
|
|
|
|
if self.language:
|
|
string += f" [{self.language}]"
|
|
|
|
if self.duration:
|
|
string += f" {self.duration}s"
|
|
|
|
# Grouping audio specs: Channels, Sample Rate, and Bit Depth
|
|
specs = []
|
|
if self.channels:
|
|
specs.append(f"{self.channels}ch")
|
|
if self.sample_rate:
|
|
specs.append(self.sample_rate)
|
|
if self.bit_depth:
|
|
specs.append(self.bit_depth)
|
|
|
|
if specs:
|
|
string += f" ({', '.join(specs)})"
|
|
|
|
if self.bitrate:
|
|
string += f" @{self.bitrate}"
|
|
|
|
if self.name:
|
|
string += f" [{self.name}]"
|
|
|
|
return string
|
|
|
|
class subtitles:
|
|
def __init__(self, stream, file_duration):
|
|
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())
|
|
|
|
# Duration logic
|
|
stream_dur = stream.get("duration")
|
|
self.duration = seconds_to_hms(float(stream_dur)) if stream_dur else file_duration
|
|
|
|
# Codec (e.g., srt, ass, subrip)
|
|
self.codec = stream.get("codec_name", "")
|
|
|
|
# Disposition (Extra helpful info for subs)
|
|
dispo = stream.get("disposition", {})
|
|
self.is_forced = dispo.get("forced") == 1
|
|
self.is_default = dispo.get("default") == 1
|
|
|
|
def __str__(self):
|
|
parts = [f"Subtitle {self.id}:" if self.id is not None else "Subtitle:"]
|
|
|
|
if self.codec:
|
|
parts.append(self.codec.upper())
|
|
|
|
if self.language:
|
|
parts.append(f"[{self.language}]")
|
|
|
|
if self.duration:
|
|
parts.append(f"{self.duration}s")
|
|
|
|
# Add flags for Forced/Default
|
|
flags = []
|
|
if self.is_forced: flags.append("FORCED")
|
|
if self.is_default: flags.append("Default")
|
|
if flags:
|
|
parts.append(f"({'/'.join(flags)})")
|
|
|
|
if self.name:
|
|
parts.append(f"[{self.name}]")
|
|
|
|
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):
|
|
cmd = [
|
|
"ffprobe",
|
|
"-v", "error",
|
|
"-show_entries", "format=duration",
|
|
"-of", "default=noprint_wrappers=1:nokey=1",
|
|
file_path
|
|
]
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
duration = float(result.stdout.strip())
|
|
return duration
|
|
|
|
def seconds_to_hms(seconds):
|
|
h = int(seconds // 3600)
|
|
m = int((seconds % 3600) // 60)
|
|
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
|
|
|
|
|
|
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.videos = []
|
|
self.audios = []
|
|
self.subtitles = []
|
|
for vl in get_video_lines(path):
|
|
video_line = video_lines(vl, self.duration)
|
|
self.videos.append(video_line)
|
|
for al in get_audio_lines(path):
|
|
audio_line = audio_lines(al, self.duration)
|
|
self.videos.append(audio_line)
|
|
for st in get_subtitle_lines(path):
|
|
subtitle = subtitles(st, self.duration)
|
|
self.videos.append(subtitle)
|
|
|
|
|
|
|
|
def print(self):
|
|
if self.base_tab == "\t":
|
|
np(f"{self.base_tab}{self.name} ({self.size}, {self.duration}, {self.bitrate} MB/s):", NORMAL_STYLE)
|
|
else:
|
|
np(f"{os.path.dirname(self.path)}/", INFO_STYLE)
|
|
np(f"\t{self.name} ({self.size}, {self.duration}, {self.bitrate} MB/s):", NORMAL_STYLE)
|
|
for video in self.videos:
|
|
np(f"\t\t{video}", NORMAL_STYLE)
|
|
for audio in self.audios:
|
|
np(f"\t\t{audio}", NORMAL_STYLE)
|
|
for subtitle in self.subtitles:
|
|
np(f"\t\t{subtitle}", NORMAL_STYLE)
|
|
print()
|
|
|
|
|
|
def get_folder_info(files):
|
|
np(f"Videos in {os.path.dirname(files[0])}/", INFO_STYLE)
|
|
for file in files:
|
|
file = video_file(file, "\t")
|
|
file.print()
|
|
|
|
def get_file_info(file, file_name):
|
|
file = video_file(file, "")
|
|
file.print()
|
|
|
|
def handle_files(files, all_files):
|
|
if(files != []):
|
|
files.sort()
|
|
|
|
grouped = []
|
|
current_dir = None
|
|
current_group = []
|
|
|
|
for f in files:
|
|
dir_path = os.path.dirname(f)
|
|
if dir_path != current_dir:
|
|
if current_group:
|
|
if len(current_group) == 1:
|
|
grouped.append(current_group[0]) # singleton as string
|
|
else:
|
|
grouped.append(current_group) # multiple files as list
|
|
current_dir = dir_path
|
|
current_group = [f]
|
|
else:
|
|
current_group.append(f)
|
|
|
|
# Add the last group
|
|
if current_group:
|
|
if len(current_group) == 1:
|
|
grouped.append(current_group[0])
|
|
else:
|
|
grouped.append(current_group)
|
|
|
|
|
|
all_files.extend(grouped)
|
|
|
|
def handle_folders(dirs, all_files):
|
|
if(dirs != []):
|
|
dirs.sort(key=lambda f: os.path.dirname(f))
|
|
for dir in dirs:
|
|
dir_files = []
|
|
for file in os.scandir(dir):
|
|
if file.is_file():
|
|
file = file.path
|
|
if os.path.splitext(file)[1].lower() in EXT:
|
|
dir_files.append(file)
|
|
else:
|
|
np(f"{file} is not a compatabile Video file", WARN_STYLE)
|
|
if(dir_files != []):
|
|
dir_files.sort()
|
|
all_files.append(dir_files)
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) == 0:
|
|
print("Something went horribly wrong!")
|
|
if len(sys.argv) == 1:
|
|
current_dir = os.path.dirname(os.path.realpath(__file__))
|
|
print(current_dir)
|
|
else:
|
|
files = []
|
|
dirs = []
|
|
for argv in sys.argv[1:]:
|
|
if os.path.isfile(argv):
|
|
files.append(os.path.abspath(argv))
|
|
elif os.path.isdir(argv):
|
|
dirs.append(os.path.abspath(argv))
|
|
else:
|
|
np(f"This is not a file or directory: {argv}\nNow canceling!", ERROR_STYLE)
|
|
sys.exit()
|
|
file_dir_array = []
|
|
handle_folders(dirs, file_dir_array)
|
|
handle_files(files, file_dir_array)
|
|
|
|
for element in file_dir_array:
|
|
if type(element) == list:
|
|
get_folder_info(element)
|
|
else:
|
|
file = element
|
|
get_file_info(file, file) |