浏览代码

Now in package

ZiB 10 月之前
父节点
当前提交
8ac75dafae
共有 10 个文件被更改,包括 145 次插入 和 124 次删除
  1. 5 0
      remux/__init__.py
  2. 63 0
      remux/__main__.py
  3. 0 34
      remux/meta.py
  4. 0 51
      remux/remux.py
  5. 35 0
      remux/remux/consts.py
  6. 12 10
      remux/remux/lang.py
  7. 12 11
      remux/remux/mux.py
  8. 2 4
      remux/remux/probe.py
  9. 13 10
      remux/remux/scan.py
  10. 3 4
      remux/remux/tag.py

+ 5 - 0
remux/__init__.py

@@ -0,0 +1,5 @@
+from .remux.consts import Consts
+from .remux.tag import Tag
+from .remux.scan import Scan
+from .remux.mux import Mux
+from .remux.lang import Lang

+ 63 - 0
remux/__main__.py

@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+import sys, os, argparse, pathlib, logging
+import remux
+
+def main():
+  # Static config
+  debug         = False
+  log_level     = logging.WARN
+
+  # ENV config
+  if 'DEBUG' in os.environ:
+    if os.environ['DEBUG'] == 'INFO':
+      log_level = logging.INFO
+    else:
+      log_level = logging.DEBUG
+
+  # CLI config
+  parser = argparse.ArgumentParser(prog='remux',
+    description="Calls mkvmerge, mkvpropedit and ffprobe in useful ways")
+  parser.add_argument('root', metavar='base_directory', type=pathlib.Path,
+    help='Recursively process all media files in this directory')
+  parser.add_argument('--debug',   '-d', action='store_true')
+  parser.add_argument('--verbose', '-v', action='store_true')
+
+  args = None
+  try:
+    args = parser.parse_args()
+  except:
+    parser.print_help()
+    sys.exit(-1)
+  if args.debug:
+    log_level = logging.DEBUG
+  elif args.verbose:
+    log_level = logging.INFO
+
+  # Sanity
+  if not args.root.is_dir():
+    print(f"Invalid base_directory: {args.root}", file=sys.stderr)
+    parser.print_help()
+    sys.exit(-1)
+
+  # Logging
+  logging.basicConfig(stream=sys.stderr)
+  logger = logging.getLogger('remux')
+  logger.setLevel(log_level)
+
+  # Module init
+  mux  = remux.Mux(logLevel=log_level)
+  scan = remux.Scan(logLevel=log_level)
+  lang = remux.Lang(logLevel=log_level)
+
+  # Walk
+  logger.debug(f"Root: {args.root}")
+  for (parent, dirs, files) in args.root.walk():
+    logger.debug(f"Walking: {parent}")
+
+    media = scan.scan(files, parent)
+    mux.mux(media)
+    lang.lang(media)
+
+# Exec
+if __name__ == "__main__":
+  main()

+ 0 - 34
remux/meta.py

@@ -1,34 +0,0 @@
-# Default file metadata
-_default = {
-  'path':      None,
-  'name':      '',
-  'ext':       '',
-  'group':     '',
-  'lang':      None,
-  'sdh':       False,
-  'mux_vers':  None,
-  'lang_vers': None,
-}
-
-# Parsing constants
-video_ext     = [ r'mkv', r'mp4', r'm4v' ]
-subs_ext      = [ r'srt' ]
-subs_sdh      = [ r'sdh' ]
-subs_lang     = [ r'\w\w' ]
-media_ext     = video_ext + subs_ext
-
-# Tag data
-mux_tag  = 'zib.mux'
-mux_vers = 1
-lang_tag  = 'zib.lang'
-lang_vers = 1
-
-# Validation size factor
-min_size = 0.9
-
-# Stream constants
-lang_default = 'eng'
-lang_codecs  = [ 'subtitle', 'audio' ]
-
-def default():
-  return _default.copy()

+ 0 - 51
remux/remux.py

@@ -1,51 +0,0 @@
-#!/usr/bin/env python3
-import sys, os, pathlib, logging
-import tag, scan, mux, lang
-
-# Static config
-debug         = False
-propedit_exec = 'mkvpropedit'
-mux_vers      = 1
-min_size      = 0.9
-log_level     = logging.WARN
-
-# ENV config
-if 'DEBUG' in os.environ:
-  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:
-  print(f"Usage: {sys.argv[0]} base_directory", file=sys.stderr)
-  sys.exit(-1)
-root = pathlib.Path(sys.argv[1])
-
-# Logging
-logging.basicConfig(stream=sys.stderr)
-logger = logging.getLogger('remux')
-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)
-
-# Walk
-for (parent, dirs, files) in root.walk():
-  logger.debug(f"Walking: {parent}")
-  media = scans.scan(files, parent)
-  muxes.mux(media)
-  langs.lang(media)

+ 35 - 0
remux/remux/consts.py

@@ -0,0 +1,35 @@
+class Consts:
+  # Default file metadata
+  _default = {
+    'path':      None,
+    'name':      '',
+    'ext':       '',
+    'group':     '',
+    'lang':      None,
+    'sdh':       False,
+    'mux_vers':  None,
+    'lang_vers': None,
+  }
+
+  # Parsing constants
+  video_ext     = [ r'mkv', r'mp4', r'm4v' ]
+  subs_ext      = [ r'srt' ]
+  subs_sdh      = [ r'sdh' ]
+  subs_lang     = [ r'\w\w' ]
+  media_ext     = video_ext + subs_ext
+
+  # Tag data
+  mux_tag  = 'zib.mux'
+  mux_vers = 1
+  lang_tag  = 'zib.lang'
+  lang_vers = 1
+
+  # Validation size factor
+  min_size = 0.9
+
+  # Stream constants
+  lang_default = 'eng'
+  lang_codecs  = [ 'subtitle', 'audio' ]
+
+  def default():
+    return Consts._default.copy()

+ 12 - 10
remux/lang.py → remux/remux/lang.py

@@ -1,5 +1,7 @@
 import subprocess, logging
-import meta, tag, probe
+from .consts import Consts
+from .tag import Tag
+from .probe import Probe
 
 class Lang:
   def __init__(self, propeditExec='mkvpropedit', logLevel=logging.WARN):
@@ -11,8 +13,8 @@ class Lang:
     self.propedit_exec = propeditExec
 
     # Module init
-    self.tags = tag.Tag(logLevel=logLevel)
-    self.probes = probe.Probe(logLevel=logLevel)
+    self.tags = Tag(logLevel=logLevel)
+    self.probes = Probe(logLevel=logLevel)
 
   def lang(self, media):
     self.logger.info(f"Processing {len(media)} files")
@@ -21,10 +23,10 @@ class Lang:
       self.logger.info(f"Found media: {file['name']}")
 
       # Skip files that do not need lang processing
-      if not file['ext'] in meta.video_ext:
+      if not file['ext'] in Consts.video_ext:
         self.logger.debug(f"Skipping non-video item: {file['path']}")
         continue
-      if 'lang_vers' in file and file['lang_vers'] and file['lang_vers'] >= meta.lang_vers:
+      if 'lang_vers' in file and file['lang_vers'] and file['lang_vers'] >= Consts.lang_vers:
         self.logger.debug(f"Skipping item with current lang_vers: {file['path']}")
         continue
 
@@ -38,7 +40,7 @@ class Lang:
       # Find missing and undefined languages for subtitle and audio streams
       updates = []
       for stream in streams:
-        if 'codec_type' not in stream or stream['codec_type'] not in meta.lang_codecs:
+        if 'codec_type' not in stream or stream['codec_type'] not in Consts.lang_codecs:
           self.logger.debug(f"Skipping missing or ignored codec_type: {file['name']}:{stream['index']}")
           continue
         if 'TAG:language' not in stream or stream['TAG:language'] in [ None, 'und' ]:
@@ -49,9 +51,9 @@ class Lang:
       if updates:
         args = [ self.propedit_exec, file['path'] ]
         for index in updates:
-          self.logger.info(f"Setting lang ({meta.lang_default}): {file['name']}:{index}")
+          self.logger.info(f"Setting lang ({Consts.lang_default}): {file['name']}:{index}")
           # mkvpropedit starts counting at 1
-          args.extend([ '--edit', f"track:{index + 1}", '--set', f"language={meta.lang_default}" ])
+          args.extend([ '--edit', f"track:{index + 1}", '--set', f"language={Consts.lang_default}" ])
 
         self.logger.debug(f"propedit command: {args}")
         propedit = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -61,6 +63,6 @@ class Lang:
           continue
 
       # Set tag
-      if not self.tags.set_int(file['path'], meta.lang_tag, meta.lang_vers):
+      if not self.tags.set_int(file['path'], Consts.lang_tag, Consts.lang_vers):
         continue
-      file['lang_vers'] = meta.lang_vers
+      file['lang_vers'] = Consts.lang_vers

+ 12 - 11
remux/mux.py → remux/remux/mux.py

@@ -1,5 +1,6 @@
 import sys, os, subprocess, pathlib, logging
-import meta, tag
+from .consts import Consts
+from .tag import Tag
 
 class Mux:
   def __init__(self, logLevel=logging.WARN):
@@ -8,7 +9,7 @@ class Mux:
     self.logger.setLevel(logLevel)
 
     # Tag init
-    self.tags = tag.Tag(logLevel=logLevel)
+    self.tags = Tag(logLevel=logLevel)
 
   def mux(self, media):
     self.logger.info(f"Processing {len(media)} files")
@@ -31,7 +32,7 @@ class Mux:
 
       # 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'] >= meta.mux_vers:
+        if 'mux_vers' in group[0] and group[0]['mux_vers'] and group[0]['mux_vers'] >= Consts.mux_vers:
           self.logger.debug(f"Skipping single-item group with current mux_vers: {group[0]['path']}")
           continue
 
@@ -40,10 +41,10 @@ class Mux:
       subs    = []
       videos  = []
       for file in group:
-        if file['ext'] in meta.video_ext:
+        if file['ext'] in Consts.video_ext:
           self.logger.info(f"Found video: {file['name']}")
           videos.append(file)
-        elif file['ext'] in meta.subs_ext:
+        elif file['ext'] in Consts.subs_ext:
           self.logger.info(f"Found subtitle: {file['name']}")
           subs.append(file)
         else:
@@ -59,7 +60,7 @@ class Mux:
       # Build args
       final   = videos[0]['path'].parent.joinpath(f'{videos[0]['group']}.mkv')
       temp    = final.with_suffix('.mkv-remux')
-      args    = [ 'mkvmerge', '--default-language', meta.lang_default, '-o', temp ]
+      args    = [ 'mkvmerge', '--default-language', Consts.lang_default, '-o', temp ]
       args.append(videos[0]['path'])
 
       # Subtitles get language and SDH metadata
@@ -85,14 +86,14 @@ class Mux:
         err = f"Non-zero exit code: {merge.returncode}"
       elif not temp.exists():
         err = 'file missing'
-      elif temp.stat().st_size < (meta.min_size * size):
+      elif temp.stat().st_size < (Consts.min_size * size):
         err = 'file too small'
       if err is not None:
         self.logger.error(f"Mux error ({err}):\n{merge.stdout.decode('utf-8')}")
         continue
 
       # Set tag
-      if not self.tags.set_int(temp, meta.mux_tag, meta.mux_vers):
+      if not self.tags.set_int(temp, Consts.mux_tag, Consts.mux_vers):
         continue
 
       # Remove sources
@@ -110,18 +111,18 @@ class Mux:
       try:
         self.logger.info(f"Renaming: {temp.name} -> {final.name}")
         temp.rename(final)
-        info = meta.default()
+        info = Consts.default()
         info['path']     = final
         info['name']     = final.name
         info['ext']      = 'mkv'
         info['group']    = videos[0]['group']
-        info['mux_vers'] = meta.mux_vers
+        info['mux_vers'] = Consts.mux_vers
         media.append(info)
       except BaseException as e:
         err = e
       if not final.exists():
         err = 'file missing'
-      elif final.stat().st_size < (meta.min_size * size):
+      elif final.stat().st_size < (Consts.min_size * size):
         err = 'file too small'
       if err is not None:
         self.logger.error(f"Rename error ({final}): {err}")

+ 2 - 4
remux/probe.py → remux/remux/probe.py

@@ -1,7 +1,7 @@
 import subprocess, logging
 
 class Probe:
-  encoding      = 'utf-8'
+  encoding     = 'utf-8'
   ffprobe_exec = 'ffprobe'
   
   def __init__(self, ffprobe_exec='ffprobe', logLevel=logging.WARN):
@@ -12,6 +12,7 @@ class Probe:
       self.ffprobe_exec = ffprobe_exec
 
   def probe(self, path):
+    self.logger.debug(f"Probing: {path}")
     streams = []
   
     # ffprobe
@@ -29,14 +30,11 @@ class Probe:
     stream = None
     for line in data.splitlines():
       if line == "[STREAM]":
-        self.logger.debug('Stream start')
         stream = {}
       elif line == "[/STREAM]":
-        self.logger.debug('Stream end')
         streams.append(stream)
         steam = None
       elif stream is not None:
-        self.logger.debug(f"\tStream data: {line}")
         try:
           (key, val) = line.split('=', 1)
         except ValueError:

+ 13 - 10
remux/scan.py → remux/remux/scan.py

@@ -1,6 +1,9 @@
 import sys, re, pathlib, logging
 import langcodes
-import meta, tag, mux, lang
+from .consts import Consts
+from .tag import Tag
+from .mux import Mux
+from .lang import Lang
 
 class Scan:  
   def __init__(self, logLevel=logging.WARN):
@@ -9,14 +12,14 @@ class Scan:
     self.logger.setLevel(logLevel)
 
     # Tag init
-    self.tags = tag.Tag(logLevel=logLevel)
+    self.tags = Tag(logLevel=logLevel)
 
-    # Build metadata-parsing regex
+    # Build metadata-parsing filename regex
     self.re_media = re.compile(
       r'^(.*?)' +
-      r'(?:\.(' + '|'.join(meta.subs_lang)  + r'))?' +
-      r'(?:\.(' + '|'.join(meta.subs_sdh)   + r'))?' +
-      r'\.('    + '|'.join(meta.media_ext)  + r')$'  ,
+      r'(?:\.(' + '|'.join(Consts.subs_lang)  + r'))?' +
+      r'(?:\.(' + '|'.join(Consts.subs_sdh)   + r'))?' +
+      r'\.('    + '|'.join(Consts.media_ext)  + r')$'  ,
       re.IGNORECASE
     )
 
@@ -28,7 +31,7 @@ class Scan:
     for file in files:
       self.logger.info(f"Found file: {file}")
       
-      info = meta.default()
+      info = Consts.default()
       info['path'] = parent.joinpath(file)
       info['name'] = file
 
@@ -45,7 +48,7 @@ class Scan:
             logger.warning(f"Unable to match language code: {m.group(2)}")
 
         if m.group(3):
-          info['sdh'] = m.group(3).lower() in meta.subs_sdh
+          info['sdh'] = m.group(3).lower() in Consts.subs_sdh
 
         info['ext'] = m.group(4).lower()
       del m
@@ -56,8 +59,8 @@ class Scan:
         continue
 
       # Read tags
-      info['mux_vers']  = self.tags.get_int(info['path'], meta.mux_tag)
-      info['lang_vers'] = self.tags.get_int(info['path'], meta.lang_tag)
+      info['mux_vers']  = self.tags.get_int(info['path'], Consts.mux_tag)
+      info['lang_vers'] = self.tags.get_int(info['path'], Consts.lang_tag)
 
       # Loop end
       media.append(info)

+ 3 - 4
remux/tag.py → remux/remux/tag.py

@@ -3,8 +3,7 @@ import xattr
 
 class Tag:
   # Internals
-  xattr    = True
-  encoding = 'utf-8'
+  _encoding = 'utf-8'
 
   def __init__(self, logLevel=logging.WARN):
     self.logger = logging.getLogger(__name__)
@@ -23,7 +22,7 @@ class Tag:
     ret = self.get_bytes(path, attr)
     if ret:
       try:
-        ret = ret.decode(self.encoding)
+        ret = ret.decode(self._encoding)
       except:
         self.logger.error(f"Unable to decode str ({path}): {attr} => {ret}")
     return ret
@@ -60,7 +59,7 @@ class Tag:
 
   def set_str(self, path, attr, value):
     try:
-      bval = value.encode(self.encoding)
+      bval = value.encode(self._encoding)
       return self.set_bytes(path, attr, bval)
     except:
       self.logger.error(f"Unable to encode str: {value}")