Below are the makemkvcon options used (not sure if --profile matters):
-r --minlength=0 --profile=:.mmcp.xml info
Usage:
Code: Select all
mmc2json.py -p
mmc2json.py -p "file.json"
Code: Select all
Version 1, 2023-11-12
JSON from the stdout of 'makemkvcon' using the below:
makemkvcon -r --minlength=0 --profile=:.mmcp.xml info disc:0
-b, --binary : set a binary, default is path: "makemkvcon"
-d, --device : set a device, default is: "disc:0"
-j, --JSON : print JSON in 1 line to stdout
-p, --pretty : print JSON line by line to stdout with 2 space indent
* can be followed by a filename to write to, ie. -p "mkv.json"
-l, --line : print JSON line by line to stdout without 2 space indent
-g, --guess : guess the main title number, but guesses poorly.
* does not support playlist obfustication
it guesses by greatest $byte_size + $most_chapters
if no chapters, guess by $byte_size + $length_in_seconds
if that fails, guess solely by the greatest $byte_size
-v, --version : print script version
-h, --help : print this help
Code: Select all
#!/usr/bin/env python3
import json
import subprocess
import sys
binary = "makemkvcon"
device = "disc:0"
# does "--profile" work with makemkvcon?
options = ["-r", "--minlength=0", "--profile=:.mmcp.xml", "info"]
# OK is not used but it is set to true on msg 5011 in msg_parse()
# MSG:5011,0,0,"Operation successfully completed","Operation successfully completed"
OK = False
json_dict = {
"script_version": 1,
"script_version_date": "2023-11-12",
"binary": binary,
"options": options,
"device": device,
"version": "",
"profile": False,
"mode": "",
"access": "",
"disc": {},
"msg": [],
"tcount": 0,
"drive": [],
"drv": [],
"dropped": [],
"titles": [],
"skipped": [],
"dupes": [],
"info": [],
"missed_key_numbers": []
}
def clean_string(arg: list | str, individually: bool = True) -> list | str | int:
if not isinstance(arg, list) and not isinstance(arg, str):
raise TypeError("Argument to be cleaned was not a string or list, type was:", str(type(arg)))
ret = None
if individually:
if isinstance(arg, list):
ret = []
for i in arg:
tmp = i.strip()
if tmp[0] == '"':
tmp = tmp[1:]
if tmp[-1] == '"':
tmp = tmp[:-1]
ret.append( tmp )
elif isinstance(arg, str):
ret = arg.strip()
if ret[0] == '"':
ret = ret[1:]
if ret[-1] == '"':
ret = ret[:-1]
else:
if isinstance(arg, list):
ret = []
for i in arg:
tmp = i.strip()
if tmp[0] + tmp[-1] == '""':
tmp = tmp[1:-1]
ret.append( tmp )
elif isinstance(arg, str):
ret = arg.strip()
if ret[0] + ret[-1] == '""':
ret = ret[1:-1]
return ret
def segments_parse(segments: str) -> list | int:
# csv deliminated string eg. 1-22,23,24,25,26-46,47
if not isinstance(segments, str):
raise TypeError("Supplied segments argument was not a string, type was:", str(type(segments)))
tmp = segments.split(",")
ret = []
if len(tmp) == 1 and tmp[0].isdigit():
ret.append(int(tmp[0]))
return ret
elif len(tmp) > 1:
while len(tmp) > 0:
if "-" in tmp[0]:
ranges = tmp[0].split("-")
while len(ranges) > 0:
range_start = -1
range_end = -1
if ranges[0].isdigit():
range_start = int(ranges[0])
else:
ranges.pop(0)
continue
if len(ranges) > 1 and ranges[1].isdigit():
range_end = int(ranges[1])
if range_start > -1 and range_end > -1:
ret.append(range_start)
if range_end > range_start:
for _ in range(range_end - range_start):
ret.append(ret[len(ret) - 1] + 1)
ranges.pop(0)
else:
if tmp[0].isdigit():
ret.append(int(tmp[0]))
tmp.pop(0)
return ret
def guess_main_title() -> int:
# TODO: Find a real way to determine main title.
# Wouldn't it be marked in a mpls or some
# file on the disc or in the Java stuff?
# Doing this by length of time, or
# size + bytes, or whatever is very poor
# time based only
# longest = [-1, -1]
# for i in range(len(json_dict["info"])):
# if "seconds" in json_dict["info"][i]:
# print(json_dict["info"][i]["seconds"], json_dict["info"][i]["title"])
# if json_dict["info"][i]["seconds"] > longest[0]:
# longest[0] = json_dict["info"][i]["seconds"]
# longest[1] = json_dict["info"][i]["title"]
# if longest[0] > -1:
# print(longest[1])
#
results = []
chapters_in_at_least_one_title = False
for i in range(len(json_dict["info"])):
tbytes = -1
tchaps = -1
tsecs = -1
if "title" in json_dict["info"][i]:
if json_dict["info"][i]["title"] >= 0:
if "bytes" in json_dict["info"][i]:
tbytes = json_dict["info"][i]["bytes"]
if "chapters" in json_dict["info"][i]:
chapters_in_at_least_one_title = True
tchaps = json_dict["info"][i]["chapters"]
if "seconds" in json_dict["info"][i]:
tsecs = json_dict["info"][i]["seconds"]
if chapters_in_at_least_one_title:
results.append([ json_dict["info"][i]["title"], tbytes, tsecs, tchaps ])
else:
results.append([ json_dict["info"][i]["title"], tbytes, tsecs ])
results.sort(key=lambda el: el[1], reverse=True)
cands = [ -1, -1, -1, -1]
biggest = [ -1, -1 ] # biggest = [ title number, bytes ]
for i in results:
# ([ title number, bytes] if bytes >, new biggest byte value)
biggest = [ i[0], i[1] ] if i[1] > biggest[1] else biggest
if chapters_in_at_least_one_title:
# more bytes with 1+ chapters or bytes equal and more chapters
if (i[1] > cands[1] and i[3] > 0) or (i[1] == cands[1] and i[3] > cands[3]):
cands[0] = i[0]
cands[1] = i[1]
cands[2] = i[2]
cands[3] = i[3]
else:
# more bytes or bytes equal and longer in seconds
if i[1] > cands[1] or (i[1] == cands[1] and i[2] > cands[2]):
cands[0] = i[0]
cands[1] = i[1]
cands[2] = i[2]
if cands[0] < 1:
cands[0] = biggest[0]
print(cands[0])
return 0
def get_seconds(timecode: str) -> int:
# only works with 3 segment string, eg. 0:02:32
if not isinstance(timecode, str):
raise TypeError("Supplied timecode was not a string")
vals = timecode.strip().split(":")
if len(vals) == 3 and vals[0].isdigit() and vals[1].isdigit() and vals[2].isdigit():
return (int(vals[0]) * 60 * 60) + (int(vals[1]) * 60) + int(vals[2])
raise AssertionError("Could not parse seconds from timecode, timecode was:", str(timecode))
def get_title_number(line: str) -> int:
# eg. TINFO:0,8,0,"2"
if not isinstance(line, str):
raise TypeError("Supplied line with title number was not a string, type was:", str(type(line)))
toks = line.strip().split(",")
if len(toks) > 3:
title_num = toks[0].split(":") # get the 0 in TINFO:0,...
if len(title_num) == 2 and title_num[1].isdigit():
return int(title_num[1])
raise AssertionError("Could not determin title number from line, line was:", str(line))
def assign_title(title_num: int) -> int:
if not isinstance(title_num, int):
raise TypeError("Supplied title number was not a int, type was:", str(type(title_num)))
index = None
for i in range(len(json_dict["info"])):
if "title" in json_dict["info"][i]:
if title_num == json_dict["info"][i]["title"]:
return i
index = len(json_dict["info"])
json_dict["info"].append( { "title": title_num } )
return index
def assign_stream(idx_title: int, stream_num: int) -> int:
if not isinstance(idx_title, int) and not isinstance(stream_num, int):
raise TypeError("Supplied title index or stream index were not integers, title index:", str(type(idx_title)), ", stream index:", str(type(stream_num)))
if "sinfo" in json_dict["info"][idx_title]:
for i in range(len(json_dict["info"][idx_title]["sinfo"])):
if json_dict["info"][idx_title]["sinfo"][i]["stream"] == stream_num:
return i
else:
json_dict["info"][idx_title]["sinfo"] = [];
json_dict["info"][idx_title]["sinfo"].append({
"stream": stream_num })
return (len(json_dict["info"][idx_title]["sinfo"]) - 1)
def msg_parse(line: str) -> int:
# eg. MSG:1005,0,1,"MakeMKV v1.17.5 linux(x64-release) started","%1 started","MakeMKV v1.17.5 linux(x64-release)"
if not isinstance(line, str):
raise TypeError("Supplied msg argument was not a string, type:", str(type(line)))
toks = clean_string( line.split(",") )
if not len(toks) >= 5: # at least 5 array elements
raise AssertionError("Splitting of msg line was not >= 5 tokens, len(toks):", str(len(toks)))
num = toks[0].split(":") # split "MSG:1005"
if len(num) != 2:
raise AssertionError("Parsing of msg number failed because toks[0].split(\":\") didn't result in 2 elements, toks[0]:", str(toks[0]))
if not num[1].isdigit():
raise AssertionError("Value for msg number failed isdigit(), value:", str(num[1]))
num = int(num[1]) # message number 1005
if num < 0 or num > 65535:
raise AssertionError("Parsing of message number failed because it was < 0 or > 65535, value:", str(num))
if num == 3028 and len(toks) == 10:
# MSG:3028,16777216,3,"Title #5 was added (2 cell(s), 0:01:44)","Title #%1 was added (%2 cell(s), %3)","5","2","0:01:44"
seconds = get_seconds(toks[9])
if not seconds >= 0 or not toks[7].isdigit() or not toks[8].isdigit():
raise AssertionError("Parsing of title seconds, cells, or number failed, seconds >= 0:", str(seconds), ", cells.isdigit():", str(toks[8]), ", number.isdigit():", str(toks[7]))
json_dict["titles"].append( {
"seconds": seconds,
"cells": int(toks[8]),
"title": int(toks[7])
})
elif num == 3307 and len(toks) == 7:
# MSG:3307,0,2,"File 00241.mpls was added as title #2","File %1 was added as title #%2","00241.mpls","2"
if not toks[6].isdigit() or not len(toks[5]) > 0:
raise AssertionError("Parsing of title resulted in an array with too few elements or title number failed .isdigit(): len(toks[5]):", str(len(toks[5])), ", toks[6].isdigit():", str(toks[6]))
json_dict["titles"].append( {
"title": int(toks[6]),
"file": toks[5]
})
elif num == 3309 and len(toks) == 7:
# MSG:3309,16777216,2,"Title 00005.mpls is equal to title 00105.mpls and was skipped","Title %1 is equal to title %2 and was skipped","00005.mpls","00105.mpls"
if len(toks[6]) > 0 and len(toks[5]) > 0:
json_dict["dupes"].append( {
"source": toks[6],
"dupe": toks[5]
})
elif num == 1005 and len(toks) == 6:
# MSG:1005,0,1,"MakeMKV v1.17.5 linux(x64-release) started","%1 started","MakeMKV v1.17.5 linux(x64-release)"
json_dict["version"] = ",".join(toks[5:])
elif num == 1009:
# MSG:1009,0,1,"Profile parsing error: default profile missing, using builtin default","Profile parsing error: %1","default profile missing, using builtin default"
if "profile missing" in line:
json_dict["profile"] = False
elif num == 1011 and len(toks) == 6:
# MSG:1011,0,1,"Using LibreDrive mode (v06.3 id=96929CF53B04)","%1","Using LibreDrive mode (v06.3 id=96929CF53B04)"
json_dict["mode"] = toks[5]
elif num == 3007 and len(toks) == 5:
# MSG:3007,0,0,"Using direct disc access mode","Using direct disc access mode"
if toks[4] == 'Using direct disc access mode':
json_dict["access"] = "direct"
else:
json_dict["access"] = toks[4]
elif num == 3025:
# MSG:3025,16777216,3,"Title #00295.m2ts has length of 29 seconds which is less than minimum \
# title length of 5000 seconds and was therefore skipped","Title #%1 has length of %2 seconds \
# which is less than minimum title length of %3 seconds and was therefore skipped","00295.m2ts","29","5000"
json_dict["skipped"].append([toks[-3], line])
elif num == 5011:
# MSG:5011,0,0,"Operation successfully completed","Operation successfully completed"
global OK
OK = True
return 0
def cinfo_keyname(num: int) -> str:
if not isinstance(num, int):
return ""
if num == 1:
return "media"
if num == 2:
return "title"
if num == 28:
return "lang"
if num == 29:
return "language"
if num == 30:
return "title_1"
if num == 31:
return "p_cinfo_31"
if num == 32:
return "volume"
if num == 33:
return "cinfo_33"
if num == 49:
return "cinfo_49"
return ""
def cinfo_parse(line: str) -> int:
# eg. CINFO:1,6209,"Blu-ray disc"
if not isinstance(line, str):
raise TypeError("Supplied cinfo line argument was not a string, type was:", str(type(line)))
toks = clean_string( line.split(",") )
if not len(toks) >= 3: # at least 3
raise AssertionError("Spliting of line did not result in >= 3 elements, line.split(\",\"):", str(len(toks)))
num = toks[0].split(":")
if len(num) != 2:
raise AssertionError("Splitting for finding cinfo number != 2 elements, length:", str(len(num)))
if not num[1].isdigit():
raise AssertionError("Value for cinfo number failed isdigit(), value:", str(num[1]))
key = cinfo_keyname(int(num[1]))
if key:
temp = clean_string( ",".join(toks[2:]) )
if key == "media":
if temp == "Blu-ray disc":
json_dict["disc"]["p_media"] = temp
temp = 1
elif temp == "DVD disc":
json_dict["disc"]["p_media"] = temp
temp = 2
json_dict["disc"][key] = temp
else:
json_dict["missed_key_numbers"].append(["cinfo", int(num[1]), line])
return 0
def tinfo_keyname(num: int) -> str:
if not isinstance(num, int):
return ""
if num == 2:
return "name"
elif num == 8:
return "chapters"
elif num == 9:
return "p_timecode"
elif num == 10:
return "p_size"
elif num == 11:
return "bytes"
elif num == 16:
return "file"
elif num == 24:
return "tinfo_24"
elif num == 25:
return "segment_count"
elif num == 26:
return "p_segments"
elif num == 27:
return "mkv_file"
elif num == 28:
return "lang"
elif num == 29:
return "language"
elif num == 30:
return "p_summary"
elif num == 31:
return "p_tinfo_31"
elif num == 33:
return "tinfo_33"
elif num == 49:
return "tinfo_49"
return ""
def tinfo_parse(line: str) -> int:
# eg. TINFO:0,8,0,"2"
if not isinstance(line, str):
raise TypeError("Supplied tinfo line argument was not a string, type was:", str(type(line)))
title_num = get_title_number(line)
if title_num < 0:
raise AssertionError("Failed to parse tinfo title number")
toks = clean_string( line.split(",") )
if not len(toks) >= 4 or not toks[1].isdigit():
raise AssertionError("Array was not >= 4, len(toks):", str(len(toks)))
if not toks[1].isdigit():
raise AssertionError("Value for tinfo number failed isdigit(), value:", str(toks[1]))
val = ','.join(toks[3:])
key = tinfo_keyname(int(toks[1])) # toks[1] ie. the 8 in TINFO:0,8,...
if key:
if key == "p_timecode": # TODO: change key name to "p_timecode"
seconds = get_seconds(val)
if seconds >= 0:
json_dict["info"][assign_title(title_num)]["seconds"] = seconds
elif key == "p_segments":
segments = segments_parse(",".join(toks[3:]))
if len(segments) > 0:
json_dict["info"][assign_title(title_num)]["segments"] = segments
elif key == "segment_count" or key == "bytes" or key == "chapters":
if val.isdigit():
val = int(val)
json_dict["info"][assign_title(title_num)][key] = val
else:
json_dict["missed_key_numbers"].append(["tinfo", int(toks[1]), line])
return 0
def sinfo_keyname(num: int) -> str:
if not isinstance(num, int):
return ""
if num == 1:
return "type"
elif num == 2:
return "p_channels_long"
elif num == 3:
return "lang"
elif num == 4:
return "language"
elif num == 5:
return "mime"
elif num == 6:
return "p_name_short"
elif num == 7:
return "p_name_medium"
elif num == 13:
return "p_bitrate"
elif num == 14:
return "channels"
elif num == 17:
return "sample_rate"
elif num == 18:
return "bits_per_sample"
elif num == 19:
return "p_resolution"
elif num == 20:
return "aspect"
elif num == 21:
return "p_framerate"
elif num == 22:
return "sinfo_22"
elif num == 28:
return "lang_1"
elif num == 29:
return "language_1"
elif num == 30:
return "p_name_long"
elif num == 31:
return "p_sinfo_31"
elif num == 33:
return "sinfo_33"
elif num == 34:
return "sinfo_34"
elif num == 38:
return "sinfo_38"
elif num == 39:
return "priority"
elif num == 40:
return "p_channels_short"
elif num == 41:
return "sinfo_41"
elif num == 42:
return "sinfo_42"
return ""
def sinfo_parse(line: str) -> int:
# eg. SINFO:0,0,1,6201,"Video"
if not isinstance(line, str):
raise TypeError("Supplied sinfo line argument was not a string, type was:", str(type(line)))
title_num = get_title_number(line)
if title_num > -1:
toks = clean_string( line.split(",") )
if not len(toks) >= 5:
raise AssertionError("Splitting of sinfo line was not >= 5 elements, length:", str(len(toks)))
if not toks[1].isdigit() or not toks[2].isdigit():
raise AssertionError("Value for title number or stream number failed isdigit(), title:", str(toks[1], ", stream:", str(toks[2])))
idx_title = assign_title(title_num)
idx_stream = assign_stream(idx_title, int(toks[1]))
val = ",".join(toks[4:])
key = sinfo_keyname(int(toks[2]))
if key:
if key == "channels" or key == "bits_per_sample" or key == "sample_rate":
if val.isdigit():
val = int(val)
elif key == "p_resolution":
res = val.split("x")
if res[0].isdigit() and res[1].isdigit():
json_dict["info"][idx_title]["sinfo"][idx_stream]["vres"] = int(res[0])
json_dict["info"][idx_title]["sinfo"][idx_stream]["hres"] = int(res[1])
elif key == "p_framerate":
tmp = clean_string( val.split(" ") )
if tmp[0].replace('.', '', 1).isdigit():
json_dict["info"][idx_title]["sinfo"][idx_stream]["framerate"] = float(tmp[0])
json_dict["info"][idx_title]["sinfo"][idx_stream][key] = val
else:
json_dict["missed_key_numbers"].append(["sinfo", int(toks[2]), line])
return 0
def parse_line(line: str, guess_title: bool = False) -> int:
# eg. MSG:1005,0,1,"MakeMKV v1.17.5 linux(x64-release) started","%1 started","MakeMKV v1.17.5 linux(x64-release)"
if not isinstance(line, str):
raise TypeError("Supplied line argument was not a string, type was:", str(type(line)))
if guess_title:
if line[:4] == "TINF":
tinfo_parse(line)
return 0
if line[:4] == "SINF":
# SINFO:0,0,1,6201,"Video"
sinfo_parse(line)
elif line[:4] == "TINF":
# TINFO:0,16,0,"00105.mpls"
tinfo_parse(line)
elif line[:4] == "MSG:":
# MSG:3307,0,2,"File 00241.mpls was added as title #2","File %1 was added as title #%2","00241.mpls","2"
msg_parse(line)
json_dict["msg"].append(line)
elif line[:4] == "DRV:":
# DRV:0,2,999,12,"BD-RE ASUS BW-16D1HT 3.02 KL2J5FB2412","GLADIATOR_2000","/dev/sr0"
drive = clean_string( line.split(",") )
if len(drive) >= 7 and drive[4] and drive[5] and drive[6] \
and drive[4] != "" and drive[5] != "" and drive[6] != "":
json_dict["drive"].append( {
"model": drive[4],
"volume": drive[5],
"mount": drive[6]
})
json_dict["drv"].append(line)
elif line[:4] == "CINF":
# CINFO:1,6209,"Blu-ray disc"
cinfo_parse(line)
elif line[:4] == "TCOU":
# TCOUNT:63
tcount = line.strip().split(":")
if len(tcount) == 2 and tcount[1].isdigit():
json_dict["tcount"] = int(tcount[1])
else:
json_dict["dropped"].append(line)
return 0
def run_proc(command: list, guess_title: bool) -> int:
if not isinstance(command, list):
raise TypeError("Supplied command argument was not a list, type was:", str(type(command)))
if not isinstance(guess_title, bool):
raise TypeError("Supplied guess title argument was not a bool, type was:", str(type(guess_title)))
proc = None
try:
proc = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, encoding='utf-8', universal_newlines=True);
except (OSError, IOError) as e:
raise e
while proc and proc.stdout.readable():
line = proc.stdout.readline()
if not line:
break
# exit on any line with errors
parse_line(line, guess_title)
proc.wait()
return proc.returncode
def print_version() -> None:
print("Version " + str(json_dict["script_version"]) + ", " + str(json_dict["script_version_date"]))
def help() -> None:
print_version()
print("JSON from the stdout of 'makemkvcon' using the below:")
print(" ", json_dict["binary"], " ".join(json_dict["options"]), json_dict["device"])
print(" -b, --binary : set a binary, default is path: \"makemkvcon\"")
print(" -d, --device : set a device, default is: \"disc:0\"")
print(" -j, --JSON : print JSON in 1 line to stdout")
print(" -p, --pretty : print JSON line by line to stdout with 2 space indent")
print(" * can be followed by a filename to write to, ie. -p \"mkv.json\"")
print(" -l, --line : print JSON line by line to stdout without 2 space indent")
print(" -g, --guess : guess the main title number, but guesses poorly.")
print(" * does _NOT_ support playlist obfustication")
print(" it guesses by greatest $byte_size + $most_chapters")
print(" if no chapters, guess by $byte_size + $length_in_seconds")
print(" if that fails, guess solely by the greatest $byte_size")
print(" -v, --version : print script version")
print(" -h, --help : print this help")
def json_file_write(filename: str, json: str) -> None:
if not isinstance(filename, str) or not isinstance(json, str):
raise TypeError("Filename or JSON was not a string, filename:", str(type(filename)), ", JSON:", str(type(json)))
try:
with open(filename, "w") as fp:
fp.write( json );
fp.flush();
except (OSError, IOError) as e:
raise e
def run() -> int:
if len(sys.argv) > 1:
binary = None
if "-b" in sys.argv:
idx = sys.argv.index("-b")
binary = sys.argv[idx + 1]
del sys.argv[idx]
del sys.argv[idx]
if "--binary" in sys.argv:
idx = sys.argv.index("--binary")
binary = sys.argv[idx + 1]
del sys.argv[idx]
del sys.argv[idx]
if binary:
json_dict["binary"] = binary
device = None
if "-d" in sys.argv:
idx = sys.argv.index("-d")
device = sys.argv[idx + 1]
del sys.argv[idx]
del sys.argv[idx]
if "--device" in sys.argv:
idx = sys.argv.index("--device")
device = sys.argv[idx + 1]
del sys.argv[idx]
del sys.argv[idx]
if device:
json_dict["device"] = device
if sys.argv[1] == '-v' or sys.argv[1] == "--version":
print_version()
return 0
elif sys.argv[1] == '-h' or sys.argv[1] == "--help":
help()
return 0
if sys.argv[1] not in [
"-g", "--guess",
"-j", "--json",
"-p", "--pretty",
"-l", "--line" ]:
raise AssertionError("Unrecognized command(s):", str(" ".join(sys.argv)))
if not json_dict["device"]:
raise AssertionError("Device argument (-d or --device) is required.")
if not json_dict["binary"]:
raise AssertionError("Binary argument (-b or --binary) is required.")
json_dict["options"].insert(0, json_dict["binary"])
json_dict["options"].append(json_dict["device"])
if sys.argv[1] == '-g' or sys.argv[1] == "--guess":
run_proc(json_dict["options"], True)
guess_main_title()
elif sys.argv[1] == '-j' or sys.argv[1] == "--json":
run_proc(json_dict["options"], False)
print(json.dumps(json_dict, sort_keys=True))
elif sys.argv[1] == '-p' or sys.argv[1] == "--pretty":
run_proc(json_dict["options"], False)
if len(sys.argv) == 2:
print(json.dumps(json_dict, indent=2, sort_keys=True))
elif len(sys.argv) == 3:
json_file_write( sys.argv[2], json.dumps(json_dict, indent=2, sort_keys=True) )
elif sys.argv[1] == '-l' or sys.argv[1] == "--line":
run_proc(json_dict["options"], False)
print(json.dumps(json_dict, indent=0, sort_keys=True))
else:
help()
return 0
def all_exceptions(exctype, value, traceback) -> None:
print(exctype, value)
sys.__excepthook__(exctype, value, traceback)
sys.exit(1)
if __name__ == "__main__":
sys.excepthook = all_exceptions;
run()
sys.exit(0)
Code: Select all
{
"access": "direct",
"binary": "makemkvcon",
"device": "disc:0",
"disc": {
"cinfo_33": "0",
"media": 2,
"p_cinfo_31": "<b>Source information</b><br>",
"p_media": "DVD disc",
"title": "WONDERFUL_LIFE",
"title_1": "WONDERFUL_LIFE",
"volume": "WONDERFUL_LIFE"
},
"drive": [
{
"model": "DVD+R-DL HL-DT-ST DVDRW GX40N RQ00 KZHC6S05805",
"mount": "/dev/sr0",
"volume": "WONDERFUL_LIFE"
}
],
"dropped": [],
"drv": [
"DRV:0,2,999,1,\"DVD+R-DL HL-DT-ST DVDRW GX40N RQ00 KZHC6S05805\",\"WONDERFUL_LIFE\",\"/dev/sr0\"\n",
"DRV:1,256,999,0,\"\",\"\",\"\"\n",
"DRV:2,256,999,0,\"\",\"\",\"\"\n",
"DRV:3,256,999,0,\"\",\"\",\"\"\n",
"DRV:4,256,999,0,\"\",\"\",\"\"\n",
"DRV:5,256,999,0,\"\",\"\",\"\"\n",
"DRV:6,256,999,0,\"\",\"\",\"\"\n",
"DRV:7,256,999,0,\"\",\"\",\"\"\n",
"DRV:8,256,999,0,\"\",\"\",\"\"\n",
"DRV:9,256,999,0,\"\",\"\",\"\"\n",
"DRV:10,256,999,0,\"\",\"\",\"\"\n",
"DRV:11,256,999,0,\"\",\"\",\"\"\n",
"DRV:12,256,999,0,\"\",\"\",\"\"\n",
"DRV:13,256,999,0,\"\",\"\",\"\"\n",
"DRV:14,256,999,0,\"\",\"\",\"\"\n",
"DRV:15,256,999,0,\"\",\"\",\"\"\n"
],
"dupes": [],
"info": [
{
"bytes": 6576545792,
"chapters": 28,
"mkv_file": "B1_t00.mkv",
"p_segments": "1-21,22-46",
"p_size": "6.1 GB",
"p_summary": "28 chapter(s),6.1 GB (B1)",
"p_timecode": "2:10:10",
"p_tinfo_31": "<b>Title information</b><br>",
"seconds": 7810,
"segment_count": 2,
"segments": [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46
],
"sinfo": [
{
"aspect": "4:3",
"framerate": 29.97,
"hres": 480,
"mime": "V_MPEG2",
"p_bitrate": "9.8 Mb/s",
"p_framerate": "29.97 (30000/1001)",
"p_name_long": "Mpeg2",
"p_name_medium": "Mpeg2",
"p_name_short": "Mpeg2",
"p_resolution": "720x480",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "0",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 0,
"type": "Video",
"vres": 720
},
{
"channels": 2,
"lang": "eng",
"language": "English",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo English",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 1,
"type": "Audio"
},
{
"channels": 2,
"lang": "fre",
"language": "French",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo French",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 2,
"type": "Audio"
},
{
"lang": "eng",
"language": "English",
"mime": "S_VOBSUB",
"p_name_long": " English",
"p_name_medium": "Dvd Subtitles",
"p_name_short": "",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 3,
"type": "Subtitles"
},
{
"lang": "fre",
"language": "French",
"mime": "S_VOBSUB",
"p_name_long": " French",
"p_name_medium": "Dvd Subtitles",
"p_name_short": "",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 4,
"type": "Subtitles"
},
{
"lang": "eng",
"language": "English",
"mime": "S_CC608/DVD",
"p_bitrate": "9.8 Mb/s",
"p_name_long": "CC\u2192Text English ( Lossy conversion )",
"p_name_medium": "Closed Captions",
"p_name_short": "CC",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_34": "Text subtitles ( Lossy conversion )",
"sinfo_38": "",
"sinfo_41": "Text",
"sinfo_42": "( Lossy conversion )",
"stream": 5,
"type": "Subtitles"
}
],
"tinfo_24": "01",
"tinfo_33": "0",
"tinfo_49": "B1",
"title": 0
},
{
"bytes": 99321856,
"chapters": 2,
"mkv_file": "A1_t01.mkv",
"p_segments": "1-2,3",
"p_size": "94.7 MB",
"p_summary": "2 chapter(s),94.7 MB (A1)",
"p_timecode": "0:02:32",
"p_tinfo_31": "<b>Title information</b><br>",
"seconds": 152,
"segment_count": 2,
"segments": [
1,
2,
3
],
"sinfo": [
{
"aspect": "4:3",
"framerate": 29.97,
"hres": 480,
"mime": "V_MPEG2",
"p_bitrate": "9.8 Mb/s",
"p_framerate": "29.97 (30000/1001)",
"p_name_long": "Mpeg2",
"p_name_medium": "Mpeg2",
"p_name_short": "Mpeg2",
"p_resolution": "720x480",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "0",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 0,
"type": "Video",
"vres": 720
},
{
"channels": 2,
"lang": "eng",
"language": "English",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo English",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 1,
"type": "Audio"
}
],
"tinfo_24": "02",
"tinfo_33": "0",
"tinfo_49": "A1",
"title": 1
},
{
"bytes": 914821120,
"chapters": 2,
"mkv_file": "C1_t02.mkv",
"p_segments": "1-3,4",
"p_size": "872.4 MB",
"p_summary": "2 chapter(s),872.4 MB (C1)",
"p_timecode": "0:22:42",
"p_tinfo_31": "<b>Title information</b><br>",
"seconds": 1362,
"segment_count": 2,
"segments": [
1,
2,
3,
4
],
"sinfo": [
{
"aspect": "4:3",
"framerate": 29.97,
"hres": 480,
"mime": "V_MPEG2",
"p_bitrate": "9.8 Mb/s",
"p_framerate": "29.97 (30000/1001)",
"p_name_long": "Mpeg2",
"p_name_medium": "Mpeg2",
"p_name_short": "Mpeg2",
"p_resolution": "720x480",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "0",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 0,
"type": "Video",
"vres": 720
},
{
"channels": 2,
"lang": "eng",
"language": "English",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo English",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 1,
"type": "Audio"
},
{
"lang": "eng",
"language": "English",
"mime": "S_VOBSUB",
"p_name_long": " English",
"p_name_medium": "Dvd Subtitles",
"p_name_short": "",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 2,
"type": "Subtitles"
},
{
"lang": "fre",
"language": "French",
"mime": "S_VOBSUB",
"p_name_long": " French",
"p_name_medium": "Dvd Subtitles",
"p_name_short": "",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 3,
"type": "Subtitles"
},
{
"lang": "eng",
"language": "English",
"mime": "S_CC608/DVD",
"p_bitrate": "9.8 Mb/s",
"p_name_long": "CC\u2192Text English ( Lossy conversion )",
"p_name_medium": "Closed Captions",
"p_name_short": "CC",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_34": "Text subtitles ( Lossy conversion )",
"sinfo_38": "",
"sinfo_41": "Text",
"sinfo_42": "( Lossy conversion )",
"stream": 4,
"type": "Subtitles"
}
],
"tinfo_24": "03",
"tinfo_33": "0",
"tinfo_49": "C1",
"title": 2
},
{
"bytes": 569491456,
"chapters": 2,
"mkv_file": "C2_t03.mkv",
"p_segments": "1-2,3",
"p_size": "543.1 MB",
"p_summary": "2 chapter(s),543.1 MB (C2)",
"p_timecode": "0:14:04",
"p_tinfo_31": "<b>Title information</b><br>",
"seconds": 844,
"segment_count": 2,
"segments": [
1,
2,
3
],
"sinfo": [
{
"aspect": "4:3",
"framerate": 29.97,
"hres": 480,
"mime": "V_MPEG2",
"p_bitrate": "9.8 Mb/s",
"p_framerate": "29.97 (30000/1001)",
"p_name_long": "Mpeg2",
"p_name_medium": "Mpeg2",
"p_name_short": "Mpeg2",
"p_resolution": "720x480",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "0",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 0,
"type": "Video",
"vres": 720
},
{
"channels": 2,
"lang": "eng",
"language": "English",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo English",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 1,
"type": "Audio"
},
{
"lang": "eng",
"language": "English",
"mime": "S_VOBSUB",
"p_name_long": " English",
"p_name_medium": "Dvd Subtitles",
"p_name_short": "",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 2,
"type": "Subtitles"
},
{
"lang": "fre",
"language": "French",
"mime": "S_VOBSUB",
"p_name_long": " French",
"p_name_medium": "Dvd Subtitles",
"p_name_short": "",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 3,
"type": "Subtitles"
},
{
"lang": "eng",
"language": "English",
"mime": "S_CC608/DVD",
"p_bitrate": "9.8 Mb/s",
"p_name_long": "CC\u2192Text English ( Lossy conversion )",
"p_name_medium": "Closed Captions",
"p_name_short": "CC",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_34": "Text subtitles ( Lossy conversion )",
"sinfo_38": "",
"sinfo_41": "Text",
"sinfo_42": "( Lossy conversion )",
"stream": 4,
"type": "Subtitles"
}
],
"tinfo_24": "04",
"tinfo_33": "0",
"tinfo_49": "C2",
"title": 3
},
{
"bytes": 69318656,
"chapters": 2,
"mkv_file": "C3_t04.mkv",
"p_segments": "1,2",
"p_size": "66.1 MB",
"p_summary": "2 chapter(s),66.1 MB (C3)",
"p_timecode": "0:01:44",
"p_tinfo_31": "<b>Title information</b><br>",
"seconds": 104,
"segment_count": 2,
"segments": [
1,
2
],
"sinfo": [
{
"aspect": "4:3",
"framerate": 29.97,
"hres": 480,
"mime": "V_MPEG2",
"p_bitrate": "9.8 Mb/s",
"p_framerate": "29.97 (30000/1001)",
"p_name_long": "Mpeg2",
"p_name_medium": "Mpeg2",
"p_name_short": "Mpeg2",
"p_resolution": "720x480",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "0",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 0,
"type": "Video",
"vres": 720
},
{
"channels": 2,
"lang": "eng",
"language": "English",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo English",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 1,
"type": "Audio"
}
],
"tinfo_24": "05",
"tinfo_33": "0",
"tinfo_49": "C3",
"title": 4
},
{
"bytes": 99321856,
"chapters": 2,
"mkv_file": "C4_t05.mkv",
"p_segments": "1-2,3",
"p_size": "94.7 MB",
"p_summary": "2 chapter(s),94.7 MB (C4)",
"p_timecode": "0:02:32",
"p_tinfo_31": "<b>Title information</b><br>",
"seconds": 152,
"segment_count": 2,
"segments": [
1,
2,
3
],
"sinfo": [
{
"aspect": "4:3",
"framerate": 29.97,
"hres": 480,
"mime": "V_MPEG2",
"p_bitrate": "9.8 Mb/s",
"p_framerate": "29.97 (30000/1001)",
"p_name_long": "Mpeg2",
"p_name_medium": "Mpeg2",
"p_name_short": "Mpeg2",
"p_resolution": "720x480",
"p_sinfo_31": "<b>Track information</b><br>",
"sinfo_22": "0",
"sinfo_33": "0",
"sinfo_38": "",
"sinfo_42": "( Lossless conversion )",
"stream": 0,
"type": "Video",
"vres": 720
},
{
"channels": 2,
"lang": "eng",
"language": "English",
"mime": "A_AC3",
"p_bitrate": "192 Kb/s",
"p_channels_long": "Stereo",
"p_channels_short": "stereo",
"p_name_long": "DD Stereo English",
"p_name_medium": "Dolby Digital",
"p_name_short": "DD",
"p_sinfo_31": "<b>Track information</b><br>",
"priority": "Default",
"sample_rate": 48000,
"sinfo_22": "0",
"sinfo_33": "90",
"sinfo_38": "d",
"sinfo_42": "( Lossless conversion )",
"stream": 1,
"type": "Audio"
}
],
"tinfo_24": "06",
"tinfo_33": "0",
"tinfo_49": "C4",
"title": 5
}
],
"missed_key_numbers": [],
"mode": "",
"msg": [
"MSG:1005,0,1,\"MakeMKV v1.17.5 linux(x64-release) started\",\"%1 started\",\"MakeMKV v1.17.5 linux(x64-release)\"\n",
"MSG:3007,0,0,\"Using direct disc access mode\",\"Using direct disc access mode\"\n",
"MSG:3028,16777216,3,\"Title #1 was added (46 cell(s), 2:10:10)\",\"Title #%1 was added (%2 cell(s), %3)\",\"1\",\"46\",\"2:10:10\"\n",
"MSG:3028,0,3,\"Title #2 was added (3 cell(s), 0:02:32)\",\"Title #%1 was added (%2 cell(s), %3)\",\"2\",\"3\",\"0:02:32\"\n",
"MSG:3028,0,3,\"Title #3 was added (4 cell(s), 0:22:42)\",\"Title #%1 was added (%2 cell(s), %3)\",\"3\",\"4\",\"0:22:42\"\n",
"MSG:3028,16777216,3,\"Title #4 was added (3 cell(s), 0:14:04)\",\"Title #%1 was added (%2 cell(s), %3)\",\"4\",\"3\",\"0:14:04\"\n",
"MSG:3028,16777216,3,\"Title #5 was added (2 cell(s), 0:01:44)\",\"Title #%1 was added (%2 cell(s), %3)\",\"5\",\"2\",\"0:01:44\"\n",
"MSG:3028,0,3,\"Title #6 was added (3 cell(s), 0:02:32)\",\"Title #%1 was added (%2 cell(s), %3)\",\"6\",\"3\",\"0:02:32\"\n",
"MSG:5011,0,0,\"Operation successfully completed\",\"Operation successfully completed\"\n"
],
"options": [
"makemkvcon",
"-r",
"--minlength=0",
"--profile=:.mmcp.xml",
"info",
"disc:0"
],
"profile": false,
"script_version": 1,
"script_version_date": "2023-11-12",
"skipped": [],
"tcount": 6,
"titles": [
{
"cells": 46,
"seconds": 7810,
"title": 1
},
{
"cells": 3,
"seconds": 152,
"title": 2
},
{
"cells": 4,
"seconds": 1362,
"title": 3
},
{
"cells": 3,
"seconds": 844,
"title": 4
},
{
"cells": 2,
"seconds": 104,
"title": 5
},
{
"cells": 3,
"seconds": 152,
"title": 6
}
],
"version": "MakeMKV v1.17.5 linux(x64-release)"
}
.