ZiB 10 месяцев назад
Родитель
Сommit
3986f13a95
4 измененных файлов с 159 добавлено и 95 удалено
  1. 0 42
      loki/probe.py
  2. 48 0
      remux/probe.py
  3. 33 53
      remux/remux.py
  4. 78 0
      remux/tag.py

+ 0 - 42
loki/probe.py

@@ -1,42 +0,0 @@
-import sys, subprocess, logging
-
-def probe(path, ffprobe_exec='ffprobe', logger=None):
-  streams = []
-  
-  # Logging
-  if not logger:
-    logger = logging.getLogger(__name__)
-    logger.setLevel(logging.WARN)
-
-  # ffprobe
-  args = [ ffprobe_exec, '-hide_banner', '-show_streams', path ]
-  logger.debug(f"ffprobe command: {args}")
-  probe = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-  logger.debug(f"ffprobe result: {probe}")
-  data = probe.stdout.decode('utf-8')
-  if probe.returncode != 0:
-    logger.error(f"ffprobe error ({probe.returncode}): {probe.stderr.decode('utf-8')}")
-    logger.debug(f"ffprobe stdout: {data}")
-    sys.exit(probe.returncode)
-
-  # Parse streams
-  stream = None
-  for line in data.splitlines():
-    if line == "[STREAM]":
-      logger.debug('Stream start')
-      stream = {}
-    elif line == "[/STREAM]":
-      logger.debug('Stream end')
-      streams.append(stream)
-      steam = None
-    elif stream is not None:
-      logger.debug(f"\tStream data: {line}")
-      try:
-        (key, val) = line.split('=', 1)
-      except ValueError:
-        logger.error(f"Skipping unexpected stream data: {line}")
-        continue
-      stream[key] = val
-
-  # Return
-  return streams

+ 48 - 0
remux/probe.py

@@ -0,0 +1,48 @@
+import subprocess, logging
+
+class ffprobe:
+  encoding      = 'utf-8'
+  ffprobe_exec = 'ffprobe'
+  
+  def __init__(self, ffprobe_exec='ffprobe', logLevel=logging.WARN):
+    self.logger = logging.getLogger(__name__)
+    self.logger.setLevel(logLevel)
+
+    if ffprobe_exec:
+      self.ffprobe_exec = ffprobe_exec
+
+  def probe(self, path):
+    streams = []
+  
+    # ffprobe
+    args = [ self.ffprobe_exec, '-hide_banner', '-show_streams', path ]
+    self.logger.debug(f"ffprobe command: {args}")
+    probe = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    self.logger.debug(f"ffprobe result: {probe}")
+    data = probe.stdout.decode(self.encoding)
+    if probe.returncode != 0:
+      self.logger.error(f"ffprobe error ({probe.returncode}): {probe.stderr.decode(self.encoding)}")
+      self.logger.debug(f"ffprobe stdout: {data}")
+      return None
+
+    # Parse streams
+    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:
+          self.logger.error(f"Skipping unexpected stream data: {line}")
+          continue
+        stream[key] = val
+
+    # Return
+    return streams

+ 33 - 53
loki/remux.py → remux/remux.py

@@ -1,13 +1,14 @@
 #!/usr/bin/env python3
-import sys, re, os, subprocess, pathlib, xattr, langcodes, logging
-import probe
+import sys, re, os, subprocess, pathlib, logging
+import langcodes
+import tag, probe
 
 # Static config
 debug         = False
 propedit_exec = 'mkvpropedit'
-mux_xattr     = 'zib.mux'
+mux_tag       = 'zib.mux'
 mux_vers      = 1
-lang_xattr    = 'zib.lang'
+lang_tag      = 'zib.lang'
 lang_vers     = 1
 lang_default  = 'eng'
 lang_codecs   = [ 'subtitle', 'audio' ]
@@ -32,8 +33,6 @@ meta_default = {
 # ENV config
 if 'DEBUG' in os.environ:
   debug = bool(os.environ['DEBUG'])
-if 'PROPEDIT' in os.environ:
-  propedit_exec = Pathlib.path(os.environ['PROPEDIT'])
 
 # CLI config
 if len(sys.argv) < 2:
@@ -42,12 +41,16 @@ if len(sys.argv) < 2:
 root = pathlib.Path(sys.argv[1])
 
 # Logging
-logger = logging.getLogger(__name__)
+logger = logging.getLogger('remux')
 if debug:
   logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
 else:
   logging.basicConfig(level=logging.WARN, stream=sys.stderr)
 
+# Tag and probe init
+tags = tag.tag()
+probes = probe.ffprobe()
+
 # Build filename-metadata parsing regex
 re_media = re.compile(
   r'^(.*?)' +
@@ -57,26 +60,6 @@ re_media = re.compile(
   re.IGNORECASE
 )
 
-def read_xattr(path, attr):
-  value = None
-  try:
-    if attr in xattr.listxattr(path):
-      value = int.from_bytes(xattr.getxattr(path, attr))
-      logger.debug(f"got xattr: {attr} => {value}")
-  except BaseException as e:
-    logger.error(f"xattr error ({attr}): {e}")
-  return value
-
-def write_xattr(path, attr, value):
-  error = True
-  try:
-    xattr.setxattr(path, attr, value.to_bytes())
-    error = False
-    logger.debug(f"set xattr: {attr} => {value}")
-  except BaseException as e:
-    logger.error(f"xattr error ({attr}): {e}")
-  return not error
-
 def scan(files):
   logger.debug(f"Scanning {len(files)} files")
   media = []
@@ -88,38 +71,35 @@ def scan(files):
     info['name'] = file
 
     # Parse media paths
-    try:
-      m = re_media.search(info['name'])
-      if m and len(m.groups()) == 4:
-        info['group']  = m.group(1)
+    m = re_media.search(info['name'])
+    if m and len(m.groups()) == 4:
+      info['group']  = m.group(1)
 
-        try:
-          lang = langcodes.Language.get(m.group(2).lower())
-          info['lang'] = lang.to_alpha3()
-        except:
-          if m.group(2):
-            logger.warning(f"Unable to match language code: {m.group(2)}")
+      try:
+        lang = langcodes.Language.get(m.group(2).lower())
+        info['lang'] = lang.to_alpha3()
+      except:
+        if m.group(2):
+          logger.warning(f"Unable to match language code: {m.group(2)}")
 
-        if m.group(3):
-          info['sdh'] = m.group(3).lower() in subs_sdh
+      if m.group(3):
+        info['sdh'] = m.group(3).lower() in subs_sdh
 
-        info['ext'] = m.group(4).lower()
-    except BaseException as e:
-      logger.error(f"Skipping file due to name error ({info['name']}): {e}")
-      continue
+      info['ext'] = m.group(4).lower()
+    del m
 
-    # Skip non-media files
+    # Skip non-media files (and parsing errors)
     if 'ext' not in info:
       logger.debug(f"Skipping non-media file: {info['name']}")
       continue
 
-    # Read xattrs
-    info['mux_vers']  = read_xattr(info['path'], mux_xattr)
-    info['lang_vers'] = read_xattr(info['path'], lang_xattr)
+    # Read tags
+    info['mux_vers']  = tags.get_int(info['path'], mux_tag)
+    info['lang_vers'] = tags.get_int(info['path'], lang_tag)
 
     # Loop end
     media.append(info)
-    logger.debug(f"Found media: {info}")
+    logger.debug(f"Found file: {info}")
 
   # Return
   return media
@@ -204,8 +184,8 @@ def mux(media):
       logger.error(f"Mux error ({err}):\n{merge.stdout.decode('utf-8')}")
       continue
 
-    # Set xattr
-    if not write_xattr(temp, mux_xattr, mux_vers):
+    # Set tag
+    if not tags.set_int(temp, mux_tag, mux_vers):
       continue
 
     # Remove sources
@@ -255,7 +235,7 @@ def lang(media):
       continue
 
     # Probe streams
-    streams = probe.probe(file['path'])
+    streams = probes.probe(file['path'])
     for stream in streams:
       if not 'index' in stream:
         logger.error(f"No index for stream ({file['name']}): {stream}")
@@ -285,8 +265,8 @@ def lang(media):
         logger.error(f"propedit error ({propedit.returncode}): {propedit.stdout.decode('utf-8')}")
         continue
 
-    # Set xattr
-    if not write_xattr(file['path'], lang_xattr, lang_vers):
+    # Set tag
+    if not tags.set_int(file['path'], lang_tag, lang_vers):
       continue
     file['lang_vers'] = lang_vers
 

+ 78 - 0
remux/tag.py

@@ -0,0 +1,78 @@
+import pathlib, logging
+import xattr
+
+class tag:
+  xattr    = True
+  encoding = 'utf-8'
+  
+  def __init__(self, logLevel=logging.WARN):
+    self.logger = logging.getLogger(__name__)
+    self.logger.setLevel(logLevel)
+
+  def get_int(self, path, attr):
+    ret = self.get_bytes(path, attr)
+    if ret:
+      try:
+        ret = int.from_bytes(ret)
+      except:
+        self.logger.error(f"Unable to decode int ({path}): {attr} => {ret}")
+    return ret
+
+  def get_str(self, path, attr):
+    ret = self.get_bytes(path, attr)
+    if ret:
+      try:
+        ret = ret.decode(self.encoding)
+      except:
+        self.logger.error(f"Unable to decode str ({path}): {attr} => {ret}")
+    return ret
+
+  def get(self, path, attr):
+    return self.get_str(path, attr)
+
+  def get_bytes(self, path, attr):
+    self.logger.error('Hi')
+    value = None
+    try:
+      if attr in xattr.listxattr(path):
+        value = xattr.getxattr(path, attr)
+        self.logger.debug(f"Got tag ({path}): {attr} => {value}")
+    except BaseException as e:
+      self.logger.error(f"Tag error ({path}:{attr}): {e}")
+    return value
+
+  def set_bytes(self, path, attr, value):
+    try:
+      xattr.setxattr(path, attr, value)
+      self.logger.debug(f"Wrote tag ({path}): {attr} => {value}")
+      return True
+    except BaseException as e:
+      self.logger.error(f"Tag error ({path}:{attr}): {e}")
+      return False
+
+  def set_int(self, path, attr, value):
+    try:
+      bval = value.to_bytes()
+      return self.set_bytes(path, attr, bval)
+    except:
+      self.logger.error(f"Unable to encode int: {value}")
+      return False
+
+  def set_str(self, path, attr, value):
+    try:
+      bval = value.encode(self.encoding)
+      return self.set_bytes(path, attr, bval)
+    except:
+      self.logger.error(f"Unable to encode str: {value}")
+      return False
+
+  def set(self, path, attr, value):
+    if isinstance(value, bytes):
+      return self.set_bytes(path, attr, value)
+    elif hasattr(value, 'to_bytes'):
+      return self.set_int(path, attr, value)
+    elif isinstance(value, str):
+      return self.set_str(path, attr, value)
+    else:
+      self.logger.error(f"Unable to encode as a byte sequence: {value}")
+      return False