|
|
@@ -1,6 +1,6 @@
|
|
|
#!/usr/bin/env python3
|
|
|
-import sys, re, os, subprocess, pathlib, logging
|
|
|
-import tag, scan, lang
|
|
|
+import sys, os, pathlib, logging
|
|
|
+import tag, scan, mux, lang
|
|
|
|
|
|
# Static config
|
|
|
debug = False
|
|
|
@@ -11,7 +11,12 @@ log_level = logging.WARN
|
|
|
|
|
|
# ENV config
|
|
|
if 'DEBUG' in os.environ:
|
|
|
- debug = bool(os.environ['DEBUG'])
|
|
|
+ if os.environ['DEBUG'] == 'INFO':
|
|
|
+ log_level = logging.INFO
|
|
|
+ elif os.environ['DEBUG'] == 'WARN':
|
|
|
+ log_level = logging.INFO
|
|
|
+ else:
|
|
|
+ log_level = logging.DEBUG
|
|
|
|
|
|
# CLI config
|
|
|
if len(sys.argv) < 2:
|
|
|
@@ -22,138 +27,25 @@ root = pathlib.Path(sys.argv[1])
|
|
|
# Logging
|
|
|
logging.basicConfig(stream=sys.stderr)
|
|
|
logger = logging.getLogger('remux')
|
|
|
-if debug:
|
|
|
- log_level = logging.DEBUG
|
|
|
logger.setLevel(log_level)
|
|
|
|
|
|
+# Start
|
|
|
+logger.debug(f"Root: {root}")
|
|
|
+
|
|
|
+# Validate
|
|
|
+if not root.is_dir():
|
|
|
+ print(f"Invalid root directory: {root}", file=sys.stderr)
|
|
|
+ sys.exit(-1)
|
|
|
+
|
|
|
# Module init
|
|
|
tags = tag.Tag()
|
|
|
+muxes = mux.Mux(logLevel=log_level)
|
|
|
scans = scan.Scan(logLevel=log_level)
|
|
|
langs = lang.Lang(logLevel=log_level)
|
|
|
|
|
|
-def mux(media):
|
|
|
- logger.debug(f"Muxing {len(media)} files")
|
|
|
-
|
|
|
- # Find groups
|
|
|
- groups = {}
|
|
|
- for file in media:
|
|
|
- if not file['group']:
|
|
|
- logger.debug(f"Skipping non-grouped file: {file['name']}")
|
|
|
- continue
|
|
|
- gnorm = file['group'].lower()
|
|
|
- if gnorm not in groups:
|
|
|
- groups[gnorm] = []
|
|
|
- if file not in groups[gnorm]:
|
|
|
- groups[gnorm].append(file)
|
|
|
-
|
|
|
- # Merge groups
|
|
|
- for group in groups.values():
|
|
|
- logger.debug(f"Muxing group: {group[0]['group']}")
|
|
|
-
|
|
|
- # Skip groups that do not need muxing
|
|
|
- if len(group) == 1:
|
|
|
- if 'mux_vers' in group[0] and group[0]['mux_vers'] and group[0]['mux_vers'] >= mux_vers:
|
|
|
- logger.debug(f"Skipping single-item group with current mux_vers: {group[0]['path']}")
|
|
|
- continue
|
|
|
-
|
|
|
- # Find all sources for the group
|
|
|
- size = 0
|
|
|
- subs = []
|
|
|
- videos = []
|
|
|
- for file in group:
|
|
|
- if file['ext'] in scans.video_ext:
|
|
|
- logger.debug(f"Found video: {file['name']}")
|
|
|
- videos.append(file)
|
|
|
- elif file['ext'] in scans.subs_ext:
|
|
|
- logger.debug(f"Found subtitle: {file['name']}")
|
|
|
- subs.append(file)
|
|
|
- else:
|
|
|
- logger.warning(f"Skipping unexpected source while muxing: {file['name']}")
|
|
|
- # Track total size for post-mux verification
|
|
|
- size += file['path'].stat().st_size
|
|
|
-
|
|
|
- # We should find exactly one video in the group
|
|
|
- if len(videos) != 1:
|
|
|
- logger.error(f"Did not find exactly 1 video in group: {group[0]['group']}")
|
|
|
- continue
|
|
|
-
|
|
|
- # Build args
|
|
|
- final = videos[0]['path'].parent.joinpath(f'{videos[0]['group']}.mkv')
|
|
|
- temp = final.with_suffix('.mkv-remux')
|
|
|
- args = [ 'mkvmerge', '--default-language', langs.lang_default, '-o', temp ]
|
|
|
- args.append(videos[0]['path'])
|
|
|
-
|
|
|
- # Subtitles get language and SDH metadata
|
|
|
- # Index is always 0; these files contain only one stream
|
|
|
- for sub in subs:
|
|
|
- args.extend([ '--language', f"0:{sub['lang']}" ])
|
|
|
- if sub['sdh']:
|
|
|
- args.extend([ '--hearing-impaired-flag', '0' ])
|
|
|
- args.append(sub['path'])
|
|
|
-
|
|
|
- # mkvmerge
|
|
|
- if temp.exists():
|
|
|
- logger.error(f"Skipping because mux output exists: {temp}")
|
|
|
- continue
|
|
|
- logger.debug(f"Mux command: {args}")
|
|
|
- merge = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
- logger.debug(f"Mux result: {merge}")
|
|
|
-
|
|
|
- # Verify
|
|
|
- err = None
|
|
|
- if merge.returncode != 0:
|
|
|
- err = f"Non-zero exit code: {merge.returncode}"
|
|
|
- elif not temp.exists():
|
|
|
- err = 'file missing'
|
|
|
- elif temp.stat().st_size < (min_size * size):
|
|
|
- err = 'file too small'
|
|
|
- if err is not None:
|
|
|
- logger.error(f"Mux error ({err}):\n{merge.stdout.decode('utf-8')}")
|
|
|
- continue
|
|
|
-
|
|
|
- # Set tag
|
|
|
- if not tags.set_int(temp, tags.mux_tag, mux_vers):
|
|
|
- continue
|
|
|
-
|
|
|
- # Remove sources
|
|
|
- try:
|
|
|
- for source in (videos + subs):
|
|
|
- logger.debug(f"Removing: {source['path']}")
|
|
|
- source['path'].unlink()
|
|
|
- media.remove(source)
|
|
|
- except BaseException as e:
|
|
|
- logger.error(f"Unlink error: {e}")
|
|
|
- continue
|
|
|
-
|
|
|
- # Rename
|
|
|
- err = None
|
|
|
- try:
|
|
|
- logger.debug(f"Renaming: {temp.name} -> {final.name}")
|
|
|
- temp.rename(final)
|
|
|
- info = scans.meta_default.copy()
|
|
|
- info['path'] = final
|
|
|
- info['name'] = final.name
|
|
|
- info['ext'] = 'mkv'
|
|
|
- info['group'] = videos[0]['group']
|
|
|
- info['mux_vers'] = mux_vers
|
|
|
- media.append(info)
|
|
|
- except BaseException as e:
|
|
|
- err = e
|
|
|
- if not final.exists():
|
|
|
- err = 'file missing'
|
|
|
- elif final.stat().st_size < (min_size * size):
|
|
|
- err = 'file too small'
|
|
|
- if err is not None:
|
|
|
- logger.error(f"Rename error ({final}): {err}")
|
|
|
- continue
|
|
|
-
|
|
|
-# Execute
|
|
|
-logger.debug(f"Root: {root}")
|
|
|
-if not root.is_dir():
|
|
|
- print(f"Invalid root directory: {root}", file=sys.stderr)
|
|
|
- sys.exit(-1)
|
|
|
+# Walk
|
|
|
for (parent, dirs, files) in root.walk():
|
|
|
logger.debug(f"Walking: {parent}")
|
|
|
media = scans.scan(files, parent)
|
|
|
- mux(media)
|
|
|
+ muxes.mux(media)
|
|
|
langs.lang(media)
|