Эх сурвалжийг харах

Set the title "SDH" to help bazarr; it does not read HI flags
Bump lang_vers to 2
Rework subprocess calls to reduce code duplication
Use JSON output from ffprobe
Remember that dict.get() exists

ZiB 10 сар өмнө
parent
commit
e946697edc

+ 1 - 0
zz_remux/zz_remux/__init__.py

@@ -3,3 +3,4 @@ from .tag import Tag
 from .scan import Scan
 from .mux import Mux
 from .lang import Lang
+from .proc import Proc

+ 2 - 1
zz_remux/zz_remux/consts.py

@@ -22,12 +22,13 @@ class Consts:
   mux_tag   = 'zib.mux'
   mux_vers  = 1
   lang_tag  = 'zib.lang'
-  lang_vers = 1
+  lang_vers = 2
 
   # Validation size factor
   min_size = 0.9
 
   # Stream constants
+  lang_undef   = 'und'
   lang_default = 'eng'
   lang_codecs  = [ 'subtitle', 'audio' ]
 

+ 26 - 20
zz_remux/zz_remux/lang.py

@@ -1,20 +1,22 @@
-import subprocess, logging
+import logging
 from .consts import Consts
 from .tag import Tag
 from .probe import Probe
+from .proc import Proc
 
 class Lang:
+  sdh_title = 'SDH'
+
   def __init__(self, propeditExec='mkvpropedit', logLevel=logging.WARN):
     # Logging
     self.logger = logging.getLogger(__name__)
     self.logger.setLevel(logLevel)
     
-    # Executable path
-    self.propedit_exec = propeditExec
-
     # Module init
+    self.propedit_exec = propeditExec
     self.tags = Tag(logLevel=logLevel)
     self.probes = Probe(logLevel=logLevel)
+    self.proc = Proc(logLevel=logLevel)
 
   def lang(self, media):
     self.logger.info(f"Processing {len(media)} files")
@@ -38,32 +40,36 @@ class Lang:
           continue
 
       # Find missing and undefined languages for subtitle and audio streams
-      updates = []
+      # Find hearing_impaired subtitles without SDH title
+      add_lang = []
+      add_sdh  = []
       for stream in streams:
-        if 'codec_type' not in stream or stream['codec_type'] not in Consts.lang_codecs:
+        if stream.get('codec_type') not in Consts.lang_codecs:
           self.logger.debug(f"Skipping missing or ignored codec_type: {file['name']}:{stream['index']}")
           continue
+        if stream.get('tags', {}).get('language') in [ None, Consts.lang_undef ]:
+          self.logger.info(f"Missing/undefined language in {file['name']}:{stream['index']}:{stream['codec_type']}")
+          add_lang.append(int(stream['index']))
+        title = stream.get('tags', {}).get('title')
         if (
-          'tags' not in stream or
-          'language' not in stream['tags'] or
-          stream['tags']['language'] in [ None, 'und' ]
+          stream.get('disposition', {}).get('hearing_impaired') == 1
+          and (not title or self.sdh_title not in title)
         ):
-          self.logger.debug(f"Missing or undefined language in {file['name']}:{stream['codec_type']}:{stream['index']}")
-          updates.append(int(stream['index']))
+          self.logger.info(f"Missing SDH title for hearing-impaired track {file['name']}:{stream['index']}:{stream['codec_type']}")
+          add_sdh.append(int(stream['index']))
+        del title
 
       # mkvpropedit
-      if updates:
+      if add_lang or add_sdh:
+        # Remember mkvpropedit starts counting at 1
         args = [ self.propedit_exec, file['path'] ]
-        for index in updates:
+        for index in add_lang:
           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={Consts.lang_default}" ])
-
-        self.logger.debug(f"propedit command: {args}")
-        propedit = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        self.logger.debug(f"propedit result: {propedit}")
-        if propedit.returncode != 0:
-          self.logger.error(f"propedit error ({propedit.returncode}): {propedit.stdout.decode('utf-8')}")
+        for index in add_sdh:
+          self.logger.info(f"Setting hearing-impaired title ({self.sdh_title}): {file['name']}:{index}")
+          args.extend([ '--edit', f"track:{index + 1}", '--set', f"name={self.sdh_title}" ])
+        if self.proc.run(args)['code'] != 0:
           continue
 
       # Set tag

+ 10 - 15
zz_remux/zz_remux/mux.py

@@ -1,6 +1,7 @@
-import sys, os, subprocess, pathlib, logging
+import sys, os, pathlib, logging
 from .consts import Consts
 from .tag import Tag
+from .proc import Proc
 
 class Mux:
   def __init__(self, logLevel=logging.WARN):
@@ -8,8 +9,9 @@ class Mux:
     self.logger = logging.getLogger(__name__)
     self.logger.setLevel(logLevel)
 
-    # Tag init
+    # Module init
     self.tags = Tag(logLevel=logLevel)
+    self.proc = Proc(logLevel=logLevel)
 
   def mux(self, media):
     self.logger.info(f"Processing {len(media)} files")
@@ -76,25 +78,18 @@ class Mux:
       if temp.exists():
         self.logger.error(f"Skipping because mux output exists: {temp}")
         continue
-      self.logger.debug(f"Mux command: {args}")
-      merge = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-      self.logger.debug(f"Mux result: {merge}")
+      merge = self.proc.run(args)
 
       # Verify
       err = None
-      if not merge or not hasattr(merge, 'returncode'):
-        err = 'Unknown'
-      elif merge.returncode != 0:
-        err = merge.returncode
+      if merge['code'] != 0:
+        err = f"Non-zero return ({merge['code']})"
       elif not temp.exists():
-        err = 'File missing'
+        err = 'Output file missing: {temp}'
       elif temp.stat().st_size < (Consts.min_size * size):
-        err = 'File too small'
+        err = 'Output file too small ({temp.stat().st_size}/{(Consts.min_size * size)})'
       if err is not None:
-        eout = '<No output>'
-        if merge and hasattr(merge, 'stdout'):
-          eout = merge.stdout.decode('utf-8')
-        self.logger.error(f"Mux error ({videos[0]['group']}): {err}\n{eout}")
+        self.logger.error(f"Mux error ({videos[0]['group']}): {err}\n{merge['err']}\n{merge['out']}")
         continue
 
       # Set tag

+ 25 - 26
zz_remux/zz_remux/probe.py

@@ -1,50 +1,49 @@
-import json, subprocess, logging
+import json, logging
+import pprint
+from .proc import Proc
 
 class Probe:
-  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
+    # Module init
+    self.ffprobe_exec = ffprobe_exec
+    self.proc = Proc(logLevel=logLevel)
 
   def probe(self, path):
     self.logger.debug(f"Probing: {path}")
-    streams = {}
 
     # Sanity
     if not path.exists():
       self.logger.warn(f"No such file: {path}")
-      return streams
+      return {}
 
     # ffprobe
-    args = [ self.ffprobe_exec, '-hide_banner', '-show_streams', '-print_format', 'json',  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}")
+    probe = self.proc.run([
+      self.ffprobe_exec,
+      '-hide_banner', '-show_streams',
+      '-print_format', 'json',
+      path
+    ])
+
+    # Parse and verify
+    err = None
+    streams = None
     try:
-      data = probe.stdout.decode(self.encoding)
-      streams = json.loads(data)['streams']
+      streams = json.loads(probe['out'])['streams']
     except:
       pass
-
-    # Verify
-    err = None
-    if not probe or not hasattr(probe, 'returncode'):
-      err = 'Unknown'
-    elif probe.returncode != 0:
-      err = probe.returncode
-    elif not streams or len(streams) < 1:
-      err = f'Unable to parse stream data'
+    if not streams or len(streams) < 1:
+      err = f"Unable to parse: {path}"
+    elif 'index' not in streams[0]:
+      err = f"Stream missing index: {path}"
     if err is not None:
-      eout = '<No output>'
-      if probe and hasattr(probe, 'stderr'):
-        eout = probe.stderr.decode('utf-8')
-      self.logger.error(f"ffprobe error ({path}): {err}\n{eout}")
-      return streams
+      self.logger.error(err)
+      return {}
 
     # Return
+    self.logger.debug(f"Streams: {streams}")
     return streams

+ 48 - 0
zz_remux/zz_remux/proc.py

@@ -0,0 +1,48 @@
+import subprocess, logging
+
+class Proc:
+	encoding = 'utf-8'
+
+	def __init__(self, stdout=subprocess.PIPE, stderr=subprocess.PIPE, logLevel=logging.WARN):
+		self.logger = logging.getLogger(__name__)
+		self.logger.setLevel(logLevel)
+
+		self.stdout = stdout
+		self.stderr = stderr
+
+	def run(self, cmd):
+		# Return dict
+		ret = {'out': None, 'err': None, 'code': int(-111) }
+
+		# Sanity
+		if not cmd or len(cmd) < 1:
+			self.logger.error(f"Empty cmd")
+			return ret
+
+		# Exec
+		self.logger.debug(f"Cmd: {cmd}")
+		proc = subprocess.run(cmd, stdout=self.stdout, stderr=self.stderr)
+		self.logger.debug(f"Result: {proc}")
+
+		# Exit code
+		try:
+			ret['code'] = proc.returncode
+		except:
+			pass
+
+		# Decode outputs
+		if hasattr(proc, 'stdout') and len(proc.stdout) > 0:
+			try:
+				ret['out'] = proc.stdout.decode(self.encoding)
+			except:
+				pass
+		if hasattr(proc, 'stderr') and len(proc.stderr) > 0:
+			try:
+				ret['err'] = proc.stderr.decode(self.encoding)
+			except:
+				pass
+
+		# Return
+		if ret['code'] != 0:
+			self.logger.error(f"Error {cmd[0]}({ret['code']}): {ret['err']}")
+		return ret