Просмотр исходного кода

Parameterize the regex
Do all name parsing during the scan

ZiB 10 месяцев назад
Родитель
Сommit
1f900cc798
2 измененных файлов с 71 добавлено и 65 удалено
  1. 1 1
      loki/probe.py
  2. 70 64
      loki/remux.py

+ 1 - 1
loki/probe.py

@@ -6,7 +6,7 @@ def probe(path, ffprobe_exec='ffprobe', logger=None):
   # Logging
   # Logging
   if not logger:
   if not logger:
     logger = logging.getLogger(__name__)
     logger = logging.getLogger(__name__)
-    logging.basicConfig(level=logging.WARN, stream=sys.stderr)
+    logger.setLevel(logging.WARN)
 
 
   # ffprobe
   # ffprobe
   args = [ ffprobe_exec, '-hide_banner', '-show_streams', path ]
   args = [ ffprobe_exec, '-hide_banner', '-show_streams', path ]

+ 70 - 64
loki/remux.py

@@ -4,15 +4,18 @@ import probe
 
 
 # Static config
 # Static config
 debug         = False
 debug         = False
+propedit_exec = 'mkvpropedit'
 mux_xattr     = 'zib.mux'
 mux_xattr     = 'zib.mux'
 mux_vers      = 1
 mux_vers      = 1
 lang_xattr    = 'zib.lang'
 lang_xattr    = 'zib.lang'
 lang_vers     = 1
 lang_vers     = 1
-min_size      = 0.9
 lang_default  = 'eng'
 lang_default  = 'eng'
-propedit_exec = 'mkvpropedit'
-re_subs  = re.compile(r'(?:\.(\w\w))?(?:\.(sdh))?\.srt$', re.IGNORECASE)
-re_media = re.compile(r'^(.*?)(?:\.\w\w)?(?:\.sdh)?\.((?:srt)|(?:mp4|m4v|mkv))$', re.IGNORECASE)
+lang_codecs   = [ 'subtitle', 'audio' ]
+min_size      = 0.9
+video_ext     = [ r'mkv', r'mp4', r'm4v' ]
+subs_ext      = [ r'srt' ]
+subs_sdh      = [ r'sdh' ]
+subs_lang     = [ r'\w\w' ]
 
 
 # ENV config
 # ENV config
 if 'DEBUG' in os.environ:
 if 'DEBUG' in os.environ:
@@ -20,6 +23,12 @@ if 'DEBUG' in os.environ:
 if 'PROPEDIT' in os.environ:
 if 'PROPEDIT' in os.environ:
   propedit_exec = Pathlib.path(os.environ['PROPEDIT'])
   propedit_exec = Pathlib.path(os.environ['PROPEDIT'])
 
 
+# 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
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 if debug:
 if debug:
@@ -27,12 +36,14 @@ if debug:
 else:
 else:
   logging.basicConfig(level=logging.WARN, stream=sys.stderr)
   logging.basicConfig(level=logging.WARN, stream=sys.stderr)
 
 
-# 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])
-logger.debug(f"Root: {root}")
+# Build filename-metadata parsing regex
+re_media = re.compile(
+  r'^(.*?)' +
+  r'(?:\.(' + '|'.join(subs_lang)             + r'))?' +
+  r'(?:\.(' + '|'.join(subs_sdh)              + r'))?' +
+  r'\.('    + '|'.join(video_ext + subs_ext)  + r')$'  ,
+  re.IGNORECASE
+)
 
 
 def read_xattr(path, attr):
 def read_xattr(path, attr):
   value = None
   value = None
@@ -60,18 +71,26 @@ def scan(files):
 
 
   # Scan this directory for media
   # Scan this directory for media
   for file in files:
   for file in files:
-    info = {
-      'name': file,
-      'path': parent.joinpath(file),
-    }
+    info = { 'name': file, 'path': parent.joinpath(file), 'lang': None, 'sdh': False }
 
 
     # Parse media paths
     # Parse media paths
     try:
     try:
       m = re_media.search(info['name'])
       m = re_media.search(info['name'])
-      if m:
+      if m and len(m.groups()) == 4:
         info['base']   = m.group(1)
         info['base']   = m.group(1)
-        info['ext']    = m.group(2).lower()
         info['g_base'] = info['base'].lower()
         info['g_base'] = info['base'].lower()
+
+        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
+
+        info['ext'] = m.group(4).lower()
     except BaseException as e:
     except BaseException as e:
       logger.error(f"Skipping file due to name error ({info['name']}): {e}")
       logger.error(f"Skipping file due to name error ({info['name']}): {e}")
       continue
       continue
@@ -85,8 +104,9 @@ def scan(files):
     info['mux_vers']  = read_xattr(info['path'], mux_xattr)
     info['mux_vers']  = read_xattr(info['path'], mux_xattr)
     info['lang_vers'] = read_xattr(info['path'], lang_xattr)
     info['lang_vers'] = read_xattr(info['path'], lang_xattr)
 
 
-    logger.debug(f"Found media: {info}")
+    # Loop end
     media.append(info)
     media.append(info)
+    logger.debug(f"Found media: {info}")
   
   
   # Return
   # Return
   return media
   return media
@@ -114,52 +134,30 @@ def mux(media):
         logger.debug(f"Skipping single-item group with current mux_vers: {group[0]['path']}")
         logger.debug(f"Skipping single-item group with current mux_vers: {group[0]['path']}")
         continue
         continue
 
 
-    # Construct args and file lists
+    # Find all sources for the group
     size    = 0
     size    = 0
-    sources = []
     subs    = []
     subs    = []
     videos  = []
     videos  = []
-    final   = group[0]['path'].parent.joinpath(f'{group[0]['base']}.mkv')
-    temp    = final.with_suffix('.mkv-remux')
-    args    = [ 'mkvmerge', '--default-language', lang_default, '-o', temp ]
     for file in group:
     for file in group:
-
-      # Track all source files
-      size += file['path'].stat().st_size
-      sources.append(file)
-
-      # Find the video source
-      if file['ext'] in [ 'mkv', 'mp4', 'm4v' ]:
+      if file['ext'] in video_ext:
         logger.debug(f"Found video: {file['path'].name}")
         logger.debug(f"Found video: {file['path'].name}")
         videos.append(file)
         videos.append(file)
-
-      # Parse subtitle languages
       else:
       else:
         logger.debug(f"Found subtitle: {file['path'].name}")
         logger.debug(f"Found subtitle: {file['path'].name}")
-        sub = { 'path': file['path'], 'lang': lang_default, 'sdh': False }
-        m = re_subs.search(file['path'].name)
-        if m:
-          for part in m.groups():
-            if part.lower() == 'sdh':
-              sub['sdh'] = True
-            else:
-              code = langcodes.Language.get(part.lower())
-              if code:
-                sub['lang'] = code.to_alpha3()
-              else:
-                logger.error(f"Cound not match language ({file['path']}): {part}")
-              logger.debug(f"Using language: {sub['lang']}")
-        subs.append(sub)
+        subs.append(file)
+      # Track total size for post-mux verification
+      size += file['path'].stat().st_size
 
 
-    # We should find exactly one video source
+    # We should find exactly one video in the group
     if len(videos) != 1:
     if len(videos) != 1:
       logger.error(f"Did not find exactly 1 video: {group[0]['base']}")
       logger.error(f"Did not find exactly 1 video: {group[0]['base']}")
       continue
       continue
-    video = videos[0]
-    del videos
     
     
-    # Video input file
-    args.append(video['path'])
+    # Build args
+    final   = group[0]['path'].parent.joinpath(f'{group[0]['base']}.mkv')
+    temp    = final.with_suffix('.mkv-remux')
+    args    = [ 'mkvmerge', '--default-language', lang_default, '-o', temp ]
+    args.append(videos[0]['path'])
     
     
     # Subtitles get language and SDH metadata
     # Subtitles get language and SDH metadata
     # Index is always 0; these files contain only one stream
     # Index is always 0; these files contain only one stream
@@ -168,7 +166,6 @@ def mux(media):
       if sub['sdh']:
       if sub['sdh']:
         args.extend([ '--hearing-impaired-flag', '0' ])
         args.extend([ '--hearing-impaired-flag', '0' ])
       args.append(sub['path'])
       args.append(sub['path'])
-    del subs
 
 
     # mkvmerge
     # mkvmerge
     if temp.exists():
     if temp.exists():
@@ -186,11 +183,11 @@ def mux(media):
     # Set xattr
     # Set xattr
     if not write_xattr(temp, mux_xattr, mux_vers):
     if not write_xattr(temp, mux_xattr, mux_vers):
       continue
       continue
-    video['mux_vers'] = mux_vers
+    videos[0]['mux_vers'] = mux_vers
 
 
     # Remove sources
     # Remove sources
     try:
     try:
-      for source in sources:
+      for source in (videos + subs):
         logger.debug(f"Removing: {source['path']}")
         logger.debug(f"Removing: {source['path']}")
         source['path'].unlink()
         source['path'].unlink()
         media.remove(source)
         media.remove(source)
@@ -203,9 +200,9 @@ def mux(media):
     try:
     try:
       logger.debug(f"Renaming: {temp.name} -> {final.name}")
       logger.debug(f"Renaming: {temp.name} -> {final.name}")
       temp.rename(final)
       temp.rename(final)
-      video['path'] = final
-      video['name'] = final.name
-      media.append(video)
+      videos[0]['path'] = final
+      videos[0]['name'] = final.name
+      media.append(videos[0])
     except BaseException as e:
     except BaseException as e:
       err = e
       err = e
     if not final.exists():
     if not final.exists():
@@ -223,20 +220,29 @@ def lang(media):
     logger.debug(f"Lang: {file['name']}")
     logger.debug(f"Lang: {file['name']}")
     
     
     # Skip files tha do not need lang processing
     # Skip files tha do not need lang processing
+    if not file['ext'] in video_ext:
+      logger.debug(f"Skipping non-video item: {file['path']}")
+      continue
     if 'lang_vers' in file and file['lang_vers'] and file['lang_vers'] >= lang_vers:
     if 'lang_vers' in file and file['lang_vers'] and file['lang_vers'] >= lang_vers:
       logger.debug(f"Skipping item with current lang_vers: {file['path']}")
       logger.debug(f"Skipping item with current lang_vers: {file['path']}")
       continue
       continue
     
     
     # Probe streams
     # Probe streams
-    streams = probe.probe(file['path'], logger=logger)
+    streams = probe.probe(file['path'])
+    for stream in streams:
+      if not 'index' in stream:
+        logger.error(f"No index for stream ({file['name']}): {stream}")
+        continue
     
     
     # Find missing and undefined languages for subtitle and audio streams
     # Find missing and undefined languages for subtitle and audio streams
     updates = []
     updates = []
     for stream in streams:
     for stream in streams:
-      if all(name in stream for name in [ 'codec_type', 'index' ]) and stream['codec_type'] in [ 'subtitle', 'audio' ]:
-        if 'TAG:language' not in stream or stream['TAG:language'] in [ None, 'und' ]:
-          logger.debug(f"Missing or undefined language in {file['name']}:{stream['codec_type'][0]}:{stream['index']}")
-          updates.append(int(stream['index']))
+      if 'codec_type' not in stream or stream['codec_type'] not in lang_codecs:
+        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' ]:
+        logger.debug(f"Missing or undefined language in {file['name']}:{stream['codec_type']}:{stream['index']}")
+        updates.append(int(stream['index']))
 
 
     # mkvpropedit
     # mkvpropedit
     if updates:
     if updates:
@@ -244,13 +250,13 @@ def lang(media):
       for index in updates:
       for index in updates:
         # mkvpropedit starts counting at 1
         # mkvpropedit starts counting at 1
         args.extend([ '--edit', f"track:{index + 1}", '--set', f"language={lang_default}" ])
         args.extend([ '--edit', f"track:{index + 1}", '--set', f"language={lang_default}" ])
-    
+
       logger.debug(f"propedit command: {args}")
       logger.debug(f"propedit command: {args}")
       propedit = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
       propedit = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
       logger.debug(f"propedit result: {propedit}")
       logger.debug(f"propedit result: {propedit}")
       if propedit.returncode != 0:
       if propedit.returncode != 0:
-        logger.error(f"ffprobe error ({propedit.returncode}): {propedit.stdout.decode('utf-8')}")
-        sys.exit(propedit.returncode)
+        logger.error(f"propedit error ({propedit.returncode}): {propedit.stdout.decode('utf-8')}")
+        continue
     
     
     # Set xattr
     # Set xattr
     if not write_xattr(file['path'], lang_xattr, lang_vers):
     if not write_xattr(file['path'], lang_xattr, lang_vers):