mkm3u: Add (commented-out) debugging machinery for video parser.
[epls] / mkm3u
diff --git a/mkm3u b/mkm3u
index 5ef5832..bcc3536 100755 (executable)
--- a/mkm3u
+++ b/mkm3u
@@ -81,6 +81,10 @@ class Source (object):
   TITLEP = CHAPTERP = False
   def __init__(me, fn):
     me.fn = fn
+    me.neps = None
+    me.used_titles = dict()
+    me.used_chapters = set()
+    me.nuses = 0
   def url(me, title = None, chapter = None):
     if title is None:
       if me.TITLEP: raise ExpectedError("missing title number")
@@ -95,30 +99,36 @@ class Source (object):
       raise ExpectedError("can't specify chapter with `%s'" % me.fn)
     else:
       suffix = "#%d:%d-%d:%d" % (title, chapter, title, chapter)
+    if chapter is not None: key, set = (title, chapter), me.used_chapters
+    else: key, set = title, me.used_titles
+    if key in set:
+      if title is None:
+        raise ExpectedError("`%s' already used" % me.fn)
+      elif chapter is None:
+        raise ExpectedError("`%s' title %d already used" % (me.fn, title))
+      else:
+        raise ExpectedError("`%s' title %d chapter %d already used" %
+                            (me.fn, title, chapter))
+    if chapter is not None: me.used_chapters.add((title, chapter))
     return me.PREFIX + ROOT + urlencode(me.fn) + suffix
 
 class VideoDisc (Source):
   PREFIX = "dvd://"
   TITLEP = CHAPTERP = True
 
-class VideoEpisodes (VideoDisc):
-  def __init__(me, fn, season, eps, *args, **kw):
+  def __init__(me, fn, *args, **kw):
     super().__init__(fn, *args, **kw)
-    me.season = season
-    me.eps = eps
+    me.neps = 0
 
 class VideoSeason (object):
   def __init__(me, i, title):
     me.i = i
     me.title = title
     me.episodes = {}
-  def add_disc(me, fn, eps):
-    d = VideoEpisodes(fn, me, eps)
-    for i in eps:
-      if i in me.episodes:
-        raise ExpectedError("season %d episode %d already taken" % (me.i, i))
-      me.episodes[i] = d
-    return d
+  def set_episode_disc(me, i, disc):
+    if i in me.episodes:
+      raise ExpectedError("season %d episode %d already taken" % (me.i, i))
+    me.episodes[i] = disc; disc.neps += 1
 
 def some_group(m, *gg):
   for g in gg:
@@ -128,8 +138,7 @@ def some_group(m, *gg):
 
 class VideoDir (object):
 
-  _R_ISO_PRE = RX.compile(r"""
-        ^
+  _R_ISO_PRE = RX.compile(r""" ^
         (?: S (?P<si> \d+) (?: \. \ (?P<st> .*)—)? (?: D (?P<sdi> \d+))? |
             (?P<di> \d+))
         \. \  #
@@ -137,57 +146,70 @@ class VideoDir (object):
         \. iso $
   """, RX.X)
 
-  _R_ISO_EP = RX.compile(r"""
-        ^ E (?P<ei> \d+) (?: – (?P<ej> \d+))? $
+  _R_ISO_EP = RX.compile(r""" ^
+        (?: S (?P<si> \d+) \ )?
+        E (?P<ei> \d+) (?: – (?P<ej> \d+))? $
   """, RX.X)
 
   def __init__(me, dir):
+    me.dir = dir
     fns = OS.listdir(OS.path.join(ROOT, dir))
     fns.sort()
-    season, last_j = None, 0
+    season = None
     seasons = {}
     for fn in fns:
       path = OS.path.join(dir, fn)
       if not fn.endswith(".iso"): continue
       m = me._R_ISO_PRE.match(fn)
-      if not m: continue
+      if not m:
+        #print(";; `%s' ignored" % path, file = SYS.stderr)
+        continue
 
-      i = filter(m.group("si"), int, 1)
+      i = filter(m.group("si"), int)
       stitle = m.group("st")
-      if season is None or i != season.i:
-        check(season is None or i == season.i + 1,
-              "season %d /= %d" % (i, season is None and -1 or season.i + 1))
-        check(i not in seasons, "season %d already seen" % i)
-        seasons[i] = season = VideoSeason(i, stitle)
-        last_j = 0
-      else:
-        check(stitle == season.title,
-              "season title `%s' /= `%s'" % (stitle, season.title))
-      j = filter(some_group(m, "sdi", "di"), int)
-      if j is not None:
-        check(j == last_j + 1,
-              "season %d disc %d /= %d" % (season.i, j, last_j + 1))
-
-      eps = set()
-      bad = False
+      check(i is not None or stitle is None,
+            "explicit season title without number in `%s'" % fn)
+      if i is not None:
+        if season is None or i != season.i:
+          check(season is None or i == season.i + 1,
+                "season %d /= %d" %
+                  (i, season is None and -1 or season.i + 1))
+          check(i not in seasons, "season %d already seen" % i)
+          seasons[i] = season = VideoSeason(i, stitle)
+        else:
+          check(stitle == season.title,
+                "season title `%s' /= `%s'" % (stitle, season.title))
+
+      disc = VideoDisc(path)
+      ts = season
+      any, bad = False, False
       for eprange in m.group("eps").split(", "):
         mm = me._R_ISO_EP.match(eprange)
         if mm is None: bad = True; continue
+        if not any:
+          #print(";; `%s'" % path, file = SYS.stderr)
+          any = True
+        i = filter(mm.group("si"), int)
+        if i is not None:
+          try: ts = seasons[i]
+          except KeyError: ts = seasons[i] = VideoSeason(i, None)
+        if ts is None:
+          ts = season = seasons[1] = VideoSeason(1, None)
         start = filter(mm.group("ei"), int)
         end = filter(mm.group("ej"), int, start)
-        for k in range(start, end + 1): eps.add(k)
-      if bad and eps:
-        raise ExpectedError("bad ep list in `%s'", fn)
-      season.add_disc(path, eps)
-      last_j = j
+        for k in range(start, end + 1):
+          ts.set_episode_disc(k, disc)
+          #print(";;\tepisode %d.%d" % (ts.i, k), file = SYS.stderr)
+      if not any: pass #print(";; `%s' ignored" % path, file = SYS.stderr)
+      elif bad: raise ExpectedError("bad ep list in `%s'", fn)
     me.seasons = seasons
 
 class AudioDisc (Source):
-  #PREFIX = "file://"
+  PREFIX = "file://"
   TITLEP = CHAPTERP = False
 
 class AudioEpisode (Source):
-  #PREFIX = "file://"
+  PREFIX = "file://"
   TITLEP = CHAPTERP = False
   def __init__(me, fn, i, *args, **kw):
     super().__init__(fn, *args, **kw)
@@ -195,14 +217,14 @@ class AudioEpisode (Source):
 
 class AudioDir (object):
 
-  _R_FLAC = RX.compile(r"""
-          ^
+  _R_FLAC = RX.compile(r""" ^
           E (\d+)
           (?: \. \ (.*))?
           \. flac $
   """, RX.X)
 
   def __init__(me, dir):
+    me.dir = dir
     fns = OS.listdir(OS.path.join(ROOT, dir))
     fns.sort()
     episodes = {}
@@ -409,18 +431,31 @@ def parse_list(fn):
             if i is None:
               check(ads, "no title, but no audio directory")
               check(season.implicitp, "audio source, but explicit season")
-              src = ads.episodes[ep_i]
+              try: src = ads.episodes[ep_i]
+              except KeyError:
+                raise ExpectedError("episode %d not found in audio dir `%s'" %
+                                    ep_i, ads.dir)
 
             elif iso:
               src = iso
 
             else:
               check(vdname in vds, "title, but no iso or video directory")
-              src = vds[vdname].seasons[season.i].episodes[ep_i]
+              try: vdir = vds[vdname]
+              except KeyError:
+                raise ExpectedError("video dir label `%s' not set" % vdname)
+              try: s = vdir.seasons[season.i]
+              except KeyError:
+                raise ExpectedError("season %d not found in video dir `%s'" %
+                                    (season.i, vdir.dir))
+              try: src = s.episodes[ep_i]
+              except KeyError:
+                raise ExpectedError("episode %d.%d not found in video dir `%s'" %
+                                    (season.i, ep_i, vdir.dir))
 
             episode = season.add_episode(fake_epi, neps, title, src, i)
             chapter = None
-            ep_i += neps
+            ep_i += neps; src.nuses += neps
 
           else:
             ww = Words(line)
@@ -432,6 +467,16 @@ def parse_list(fn):
             else: j += 1
             chapter = episode.add_chapter(title, j)
 
+  discs = set()
+  for vdir in vds.values():
+    for s in vdir.seasons.values():
+      for d in s.episodes.values():
+        discs.add(d)
+  for d in sorted(discs, key = lambda d: d.fn):
+    if d.neps != d.nuses:
+      raise ExpectedError("disc `%s' has %d episodes, used %d times" %
+                          (d.fn, d.neps, d.nuses))
+
   return playlist
 
 ROOT = "/mnt/dvd/archive/"