mcu.epls: Add playlist for MCU films.
[epls] / mkm3u
diff --git a/mkm3u b/mkm3u
index b36d527..6fc83e4 100755 (executable)
--- a/mkm3u
+++ b/mkm3u
@@ -136,12 +136,17 @@ class Source (object):
   PREFIX = ""
   TITLEP = CHAPTERP = False
 
   PREFIX = ""
   TITLEP = CHAPTERP = False
 
-  def __init__(me, fn):
+  def __init__(me, fn, prefix = None):
     me.fn = fn
     me.neps = None
     me.used_titles = set()
     me.used_chapters = set()
     me.nuses = 0
     me.fn = fn
     me.neps = None
     me.used_titles = set()
     me.used_chapters = set()
     me.nuses = 0
+    me._pfx = prefix
+
+  def _prefix(me):
+    if me._pfx is not None: return me._pfx
+    else: return me.PREFIX
 
   def _duration(me, title, start_chapter, end_chapter):
     return -1
 
   def _duration(me, title, start_chapter, end_chapter):
     return -1
@@ -224,14 +229,16 @@ class Source (object):
     else:
       for ch in range(start_chapter, end_chapter):
         me.used_chapters.add((title, ch))
     else:
       for ch in range(start_chapter, end_chapter):
         me.used_chapters.add((title, ch))
-    return me.PREFIX + ROOT + urlencode(me.fn) + suffix, duration
+    return me._prefix() + ROOT + urlencode(me.fn) + suffix, duration
 
 class DVDFile (Source):
   PREFIX = "dvd://"
   TITLEP = CHAPTERP = True
 
 
 class DVDFile (Source):
   PREFIX = "dvd://"
   TITLEP = CHAPTERP = True
 
-  def __init__(me, fn, *args, **kw):
-    super().__init__(fn, *args, **kw)
+  def __init__(me, fn, menuhack = False, *args, **kw):
+    pfx = None
+    if menuhack: pfx = "dvdsimple://"
+    super().__init__(fn, prefix = pfx, *args, **kw)
     me.neps = 0
 
   def _duration(me, title, start_chapter, end_chapter):
     me.neps = 0
 
   def _duration(me, title, start_chapter, end_chapter):
@@ -278,21 +285,21 @@ class DVDDir (object):
                           list(map(lambda pat:
                                      RX.compile("^" + pat + r"\.iso$", RX.X),
                                    pats)),
                           list(map(lambda pat:
                                      RX.compile("^" + pat + r"\.iso$", RX.X),
                                    pats)),
-    [[r""" S (?P<si> \d+) \. \ (?P<stitle> .*) — (?: D \d+ \. \ )?
+    [[r""" S (?P<si> \d+) \. \ (?P<stitle> .*) — (?: D (?P<di> \d+) \. \ )?
            (?P<epex> .*) """,
            (?P<epex> .*) """,
-      r""" S (?P<si> \d+) (?: D \d+)? \. \ (?P<epex> .*) """,
+      r""" S (?P<si> \d+) (?: D (?P<di> \d+))? \. \ (?P<epex> .*) """,
       r""" S (?P<si> \d+) \. \ (?P<epex> E \d+ .*) """,
       r""" S (?P<si> \d+) \. \ (?P<epex> E \d+ .*) """,
-      r""" S (?P<si> \d+) (?P<epex> E \d+) \. \ .* """],
+      r""" S (?P<si> \d+) E (?P<ei> \d+) \. \ .* """],
      [r""" (?P<si> \d+) [A-Z]? \. \ (?P<stitle> .*) — (?P<epex> .*) """],
      [r""" (?P<si> \d+) [A-Z]? \. \ (?P<stitle> .*) — (?P<epex> .*) """],
-     [r""" \d+ \. \ (?P<epex> [ES] \d+ .*) """],
-     [r""" (?P<epnum> \d+ ) \. \ .* """]]))
+     [r""" (?P<di> \d+) \. \ (?P<epex> [ES] \d+ .*) """],
+     [r""" (?P<ei> \d+ ) \. \ .* """]]))
 
   _R_ISO_EP = RX.compile(r""" ^
         (?: S (?P<si> \d+) \ )?
         E (?P<ei> \d+) (?: – (?P<ej> \d+))? $
   """, RX.X)
 
 
   _R_ISO_EP = RX.compile(r""" ^
         (?: S (?P<si> \d+) \ )?
         E (?P<ei> \d+) (?: – (?P<ej> \d+))? $
   """, RX.X)
 
-  def __init__(me, dir):
+  def __init__(me, dir, hacks):
     me.dir = dir
     fns = OS.listdir(OS.path.join(ROOT, dir))
     fns.sort()
     me.dir = dir
     fns = OS.listdir(OS.path.join(ROOT, dir))
     fns.sort()
@@ -330,12 +337,23 @@ class DVDDir (object):
           check(stitle == season.title,
                 "season title `%s' /= `%s'" % (stitle, season.title))
 
           check(stitle == season.title,
                 "season title `%s' /= `%s'" % (stitle, season.title))
 
-      disc = DVDFile(path)
       ts = season
       any, bad = False, False
       ts = season
       any, bad = False, False
-      epnum = match_group(m, "epnum")
-      if epnum is not None: eplist = ["E" + epnum]
+      ei = match_group(m, "ei")
+      if ei is not None: eplist = ["E" + ei]
       else: eplist = match_group(m, "epex", mustp = True).split(", ")
       else: eplist = match_group(m, "epex", mustp = True).split(", ")
+
+      di = filter(match_group(m, "di"), int)
+      discid = ""
+      if si is not None: discid += "S%02d" % si
+      if di is not None: discid += "D%02d" % di
+      elif ei is not None: discid += "E%02d" % int(ei)
+      menuhack = False
+      for h in hacks.get(discid, []):
+        if h == "menu": menuhack = True
+        else: raise ExpectedError("unknown hack `%s'" % h)
+      disc = DVDFile(path, menuhack)
+
       for eprange in eplist:
         mm = me._R_ISO_EP.match(eprange)
         if mm is None:
       for eprange in eplist:
         mm = me._R_ISO_EP.match(eprange)
         if mm is None:
@@ -411,6 +429,26 @@ class AudioDir (SingleFileDir):
   def _mkepisode(me, path, i):
     return AudioEpisode(path, i)
 
   def _mkepisode(me, path, i):
     return AudioEpisode(path, i)
 
+class VideoFile (Source):
+  PREFIX = "file://"
+  TITLEP = CHAPTERP = False
+
+  def _duration(me, title, start_chapter, end_chaptwr):
+    out = program_output(["mediainfo", "--output=General;%Duration%",
+                          OS.path.join(ROOT, me.fn)])
+    return int(out)//1000
+
+class VideoEpisode (VideoFile):
+  def __init__(me, fn, i, *args, **kw):
+    super().__init__(fn, *args, **kw)
+    me.i = i
+
+class VideoDir (SingleFileDir):
+  _EXT = ".mp4"
+  _CHECK_COMPLETE = False
+
+  def _mkepisode(me, path, i):
+    return VideoEpisode(path, i)
 
 class Chapter (object):
   def __init__(me, episode, title, i):
 
 class Chapter (object):
   def __init__(me, episode, title, i):
@@ -527,6 +565,7 @@ class Playlist (object):
     me.nseries = 0
     me.single_series_p = False
     me.series_title = None
     me.nseries = 0
     me.single_series_p = False
     me.series_title = None
+    me.series_sep = ""
 
   def add_episode(me, episode):
     me.episodes.append(episode)
 
   def add_episode(me, episode):
     me.episodes.append(episode)
@@ -544,9 +583,9 @@ class Playlist (object):
         label = ep.label()
         if me.nseries > 1 and ep.series_title_p and \
            ep.season.series.title is not None:
         label = ep.label()
         if me.nseries > 1 and ep.series_title_p and \
            ep.season.series.title is not None:
-          if ep.season.i is None: sep = ": "
-          else: sep = " "
-          label = ep.season.series.title + sep + label
+          if ep.season.i is None: sep = ":"
+          else: sep = me.series_sep
+          label = ep.season.series.title + sep + " " + label
         if not ep.chapters:
           f.write("#EXTINF:%d,,%s\n%s\n" % (ep.duration, label, ep.url))
         else:
         if not ep.chapters:
           f.write("#EXTINF:%d,,%s\n%s\n" % (ep.duration, label, ep.url))
         else:
@@ -568,7 +607,7 @@ class Playlist (object):
         title = ep.season.series.full_title
         if me.single_series_p:
           stag = "-"
         title = ep.season.series.full_title
         if me.single_series_p:
           stag = "-"
-          if title is not None: label = title + " " + label
+          if title is not None: label = title + me.series_sep + " " + label
         else:
           if title is None: title = me.series_title
           stag = ep.season.series.name
         else:
           if title is None: title = me.series_title
           stag = ep.season.series.name
@@ -629,6 +668,7 @@ class EpisodeListParser (object):
     me._series_wanted = series_wanted
     me._chaptersp = chapters_wanted_p
     me._explen, me._expvar = None, DEFAULT_EXPVAR
     me._series_wanted = series_wanted
     me._chaptersp = chapters_wanted_p
     me._explen, me._expvar = None, DEFAULT_EXPVAR
+    me._hacks = {}
     if series_wanted is None: me._mode = MODE_UNSET
     else: me._mode = MODE_MULTI
 
     if series_wanted is None: me._mode = MODE_UNSET
     else: me._mode = MODE_MULTI
 
@@ -724,8 +764,7 @@ class EpisodeListParser (object):
 
     elif cmd == "season":
       series = me._opts_series(cmd, opts)
 
     elif cmd == "season":
       series = me._opts_series(cmd, opts)
-      w = ww.nextword();
-      check(w is not None, "missing season number")
+      w = ww.nextword(); check(w is not None, "missing season number")
       if w == "-":
         if not series.wantedp: return
         series.add_movies(ww.rest())
       if w == "-":
         if not series.wantedp: return
         series.add_movies(ww.rest())
@@ -760,22 +799,41 @@ class EpisodeListParser (object):
       if not series.wantedp: return
       series.ensure_season().ep_i = epi
 
       if not series.wantedp: return
       series.ensure_season().ep_i = epi
 
-    elif cmd == "dvd":
+    elif cmd == "hacks":
+      series = me._opts_series(cmd, opts)
+      discid = ww.nextword(); check(discid is not None, "missing disc id")
+      hh = me._hacks.setdefault(series.name, {}).setdefault(discid, [])
+      while True:
+        h = ww.nextword()
+        if h is None: break
+        hh.append(h)
+
+    elif cmd == "dvd" or cmd == "dvdsimple":
       series = me._opts_series(cmd, opts)
       fn = ww.rest(); check(fn is not None, "missing filename")
       series = me._opts_series(cmd, opts)
       fn = ww.rest(); check(fn is not None, "missing filename")
+      menuhack = cmd == "dvdsimple"
       if not series.wantedp: return
       if fn == "-": forget(me._isos, series.name)
       else:
         check(OS.path.exists(OS.path.join(ROOT, fn)),
               "dvd iso file `%s' not found" % fn)
       if not series.wantedp: return
       if fn == "-": forget(me._isos, series.name)
       else:
         check(OS.path.exists(OS.path.join(ROOT, fn)),
               "dvd iso file `%s' not found" % fn)
-        me._isos[series.name] = DVDFile(fn)
+        me._isos[series.name] = DVDFile(fn, menuhack)
 
     elif cmd == "dvddir":
       series = me._opts_series(cmd, opts)
       dir = ww.rest(); check(dir is not None, "missing directory")
       if not series.wantedp: return
       if dir == "-": forget(me._vdirs, series.name)
 
     elif cmd == "dvddir":
       series = me._opts_series(cmd, opts)
       dir = ww.rest(); check(dir is not None, "missing directory")
       if not series.wantedp: return
       if dir == "-": forget(me._vdirs, series.name)
-      else: me._vdirs[series.name] = DVDDir(dir)
+      else:
+        me._vdirs[series.name] = \
+          DVDDir(dir, me._hacks.get(series.name, {}))
+
+    elif cmd == "vdir":
+      series = me._opts_series(cmd, opts)
+      dir = ww.rest(); check(dir is not None, "missing directory")
+      if not series.wantedp: return
+      if dir == "-": forget(me._sfdirs, series.name)
+      else: me._sfdirs[series.name] = VideoDir(dir)
 
     elif cmd == "adir":
       series = me._opts_series(cmd, opts)
 
     elif cmd == "adir":
       series = me._opts_series(cmd, opts)
@@ -790,6 +848,10 @@ class EpisodeListParser (object):
       src = me._auto_epsrc(series)
       src.nuses += n
 
       src = me._auto_epsrc(series)
       src.nuses += n
 
+    elif cmd == "sep":
+      sep = ww.rest(); check(sep is not None, "missing separator")
+      me._pl.series_sep = sep
+
     else:
       raise ExpectedError("unknown command `%s'" % cmd)
 
     else:
       raise ExpectedError("unknown command `%s'" % cmd)