+ elif cmd == "epname":
+ for k, v in me._keyvals(opts): me._bad_keyval("epname", k, v)
+ name = ww.rest(); check(name is not None, "missing episode name")
+ try: sep = name.index("::")
+ except ValueError: names = name + "s"
+ else: name, names = name[:sep], name[sep + 2:]
+ me._pl.epname, me._pl.epnames = name, names
+
+ elif cmd == "epno":
+ series = me._opts_series(cmd, opts)
+ w = ww.rest(); check(w is not None, "missing episode number")
+ epi = getint(w)
+ if not series.wantedp: return
+ series.ensure_season().ep_i = epi
+
+ 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")
+ 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)
+ 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)
+ 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)
+ 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] = AudioDir(dir)
+
+ elif cmd == "displaced":
+ series = me._opts_series(cmd, opts)
+ w = ww.rest(); check(w is not None, "missing count"); n = getint(w)
+ 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)
+
+ def _process_episode(me, ww):
+
+ opts = ww.nextword(); check(opts is not None, "missing title/options")
+ ti = -1; sname = None; neps = 1; epi = None; loch = hich = -1
+ explen, expvar, explicitlen = me._explen, me._expvar, False
+ series_title_p = True
+ for k, v in me._keyvals(opts):
+ if k is None:
+ if v.isdigit(): ti = int(v)
+ elif v == "-": ti = -1
+ else: sname = v
+ elif k == "s": sname = v
+ elif k == "n": neps = getint(v)
+ elif k == "ep": epi = getint(v)
+ elif k == "st": series_title_p = getbool(v)
+ elif k == "l":
+ if v == "-": me._explen, me._expvar = None, DEFAULT_EXPVAR
+ else:
+ explen, expvar = parse_duration(v, explen, expvar)
+ explicitlen = True
+ elif k == "ch":
+ try: sep = v.index("-")
+ except ValueError: loch, hich = getint(v), -1
+ else: loch, hich = getint(v[:sep]), getint(v[sep + 1:]) + 1
+ else: raise ExpectedError("unknown episode option `%s'" % k)
+ check(ti is not None, "missing title number")
+ series = me._get_series(sname)
+ me._cur_chapter = None
+
+ title = ww.rest()
+ if not series.wantedp: return
+ season = series.ensure_season()
+ if epi is None: epi = season.ep_i
+
+ if ti == -1:
+ check(season.implicitp or season.i is None,
+ "audio source, but explicit non-movie season")
+ dir = lookup(me._sfdirs, series.name,
+ "no title, and no single-file directory")
+ src = lookup(dir.episodes, season.ep_i,
+ "episode %d not found in single-file dir `%s'" %
+ (epi, dir.dir))
+
+ else:
+ try: src = me._isos[series.name]
+ except KeyError: src = me._auto_epsrc(series)
+
+ episode = season.add_episode(epi, neps, title, src,
+ series_title_p, ti, loch, hich)
+
+ if episode.duration != -1 and explen is not None:
+ if not explicitlen: explen *= neps
+ if not explen*(1 - expvar) <= episode.duration <= explen*(1 + expvar):
+ if season.i is None: epid = "episode %d" % epi
+ else: epid = "episode %d.%d" % (season.i, epi)
+ raise ExpectedError \
+ ("%s duration %s %g%% > %g%% from expected %s" %
+ (epid, format_duration(episode.duration),
+ abs(100*(episode.duration - explen)/explen), 100*expvar,
+ format_duration(explen)))
+ me._pl.add_episode(episode)
+ me._cur_episode = episode
+
+ def _process_chapter(me, ww):
+ check(me._cur_episode is not None, "no current episode")
+ check(me._cur_episode.source.CHAPTERP,
+ "episode source doesn't allow chapters")
+ if me._chaptersp:
+ if me._cur_chapter is None: i = 1
+ else: i = me._cur_chapter.i + 1
+ me._cur_chapter = me._cur_episode.add_chapter(ww.rest(), i)
+
+ def parse_file(me, fn):
+ with location(FileLocation(fn, 0)) as floc:
+ with open(fn, "r") as f:
+ for line in f:
+ floc.stepline()
+ sline = line.lstrip()
+ if sline == "" or sline.startswith(";"): continue
+
+ if line.startswith("!"): me._process_cmd(Words(line[1:]))
+ elif not line[0].isspace(): me._process_episode(Words(line))
+ else: me._process_chapter(Words(line))
+ me._pl.done_season()
+
+ def done(me):
+ discs = set()
+ for name, vdir in me._vdirs.items():
+ if not me._series[name].wantedp: continue
+ for s in vdir.seasons.values():
+ for d in s.episodes.values():
+ discs.add(d)
+ for sfdir in me._sfdirs.values():
+ for d in sfdir.episodes.values():
+ discs.add(d)
+ for d in sorted(discs, key = lambda d: d.fn):
+ if d.neps is not None and d.neps != d.nuses:
+ raise ExpectedError("disc `%s' has %d episodes, used %d times" %
+ (d.fn, d.neps, d.nuses))
+ return me._pl
+
+op = OP.OptionParser \
+ (usage = "%prog [-Dc] [-L NAME] [-M DEPS] [-d CACHE] [-o OUT] [-s SERIES] EPLS\n"
+ "%prog -i -d CACHE",
+ description = "Generate M3U playlists from an episode list.")
+op.add_option("-D", "--dump",
+ dest = "dump", action = "store_true", default = False,
+ help = "Dump playlist in machine-readable form")
+op.add_option("-L", "--list-name", metavar = "NAME",
+ dest = "list_name", type = "str", default = None,
+ help = "Set the playlist name")
+op.add_option("-M", "--make-deps", metavar = "DEPS",
+ dest = "deps", type = "str", default = None,
+ help = "Write a `make' fragment for dependencies")
+op.add_option("-c", "--chapters",
+ dest = "chaptersp", action = "store_true", default = False,
+ help = "Output individual chapter names")
+op.add_option("-i", "--init-db",
+ dest = "initdbp", action = "store_true", default = False,
+ help = "Initialize the database")
+op.add_option("-d", "--database", metavar = "CACHE",
+ dest = "database", type = "str", default = None,
+ help = "Set filename for cache database")
+op.add_option("-o", "--output", metavar = "OUT",
+ dest = "output", type = "str", default = None,
+ help = "Write output playlist to OUT")
+op.add_option("-O", "--fake-output", metavar = "OUT",
+ dest = "fakeout", type = "str", default = None,
+ help = "Pretend output goes to OUT for purposes of `-M'")
+op.add_option("-s", "--series", metavar = "SERIES",
+ dest = "series", type = "str", default = None,
+ help = "Output only the listed SERIES (comma-separated)")