diff --git a/Makefile b/Makefile index 0b244ed..d471b52 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ install: - cp dist/pyinstaller/manylinux_2_39_x86_64/dcc /home/tynan/.local/bin/dcc + cp dist/pyinstaller/manylinux_2_39_x86_64/doomcc /home/tynan/.local/bin/dcc diff --git a/dcc/__main__.py b/dcc/__main__.py deleted file mode 100644 index dbb5b19..0000000 --- a/dcc/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -from dcc.main import main - -if __name__ == '__main__': - sys.exit(main(sys.argv[1:])) diff --git a/dcc/main.py b/dcc/main.py deleted file mode 100644 index 7fb436b..0000000 --- a/dcc/main.py +++ /dev/null @@ -1,72 +0,0 @@ -import sys - -from cliff.app import App -from cliff.commandmanager import CommandManager - -import dcc.concat -import dcc.configure -import dcc.dsda -import dcc.eureka -import dcc.extract -import dcc.fabricate -import dcc.fetch -import dcc.ls -import dcc.pb -import dcc.play -import dcc.put -import dcc.record -import dcc.rib -import dcc.ss -import dcc.text -import dcc.thumb - - -class DCC(App): - def __init__(self): - cm = CommandManager(None) - commands = { - "concat": dcc.concat.Concat, - "configure": dcc.configure.Configure, - "dsda": dcc.dsda.DSDA, - "eureka": dcc.eureka.Eureka, - "extract": dcc.extract.Extract, - "fabricate": dcc.fabricate.Fabricate, - "fetch": dcc.fetch.Fetch, - "ls": dcc.ls.List, - "ls demos": dcc.ls.ListDemos, - "ls videos": dcc.ls.ListVideos, - "pb": dcc.pb.PB, - "play": dcc.play.Play, - "put": dcc.put.Put, - "record": dcc.record.Record, - "rib": dcc.rib.RIB, - "ss": dcc.ss.SS, - "text": dcc.text.Text, - "thumb": dcc.thumb.Thumb, - } - for n, c in commands.items(): - cm.add_command(n, c) - super().__init__( - description="Doom Command Center", - version="0.0.1", - command_manager=cm, - deferred_help=True, - ) - - def initialize_app(self, argv): - pass - - def prepare_to_run_command(self, cmd): - pass - - def clean_up(self, cmd, result, err): - pass - - -def main(argv=sys.argv[1:]): - dcc = DCC() - return dcc.run(argv) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/dcc/__init__.py b/doomcc/__init__.py similarity index 100% rename from dcc/__init__.py rename to doomcc/__init__.py diff --git a/doomcc/__main__.py b/doomcc/__main__.py new file mode 100644 index 0000000..21747bf --- /dev/null +++ b/doomcc/__main__.py @@ -0,0 +1,5 @@ +import sys +from doomcc.main import main + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/dcc/concat.py b/doomcc/concat.py similarity index 79% rename from dcc/concat.py rename to doomcc/concat.py index 4be4705..03a106b 100644 --- a/dcc/concat.py +++ b/doomcc/concat.py @@ -1,6 +1,6 @@ import av import copy -import dcc.doom_base +import doomcc.doom_base import enum import fractions import io @@ -16,7 +16,7 @@ class State(enum.Enum): DONE = 3 -class Concat(dcc.doom_base.Wad): +class Concat(doomcc.doom_base.Wad): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("start_map") @@ -34,12 +34,10 @@ class Concat(dcc.doom_base.Wad): + f"to{parsed_args.end_map}" ) output = av.open( - self.fabricate.joinpath(parsed_args.wad).joinpath( - f"{fn_base}.mp4"), "w" + self.fabricate.joinpath(parsed_args.wad).joinpath(f"{fn_base}.mp4"), "w" ) summary_file = open( - self.fabricate.joinpath(parsed_args.wad).joinpath( - f"{fn_base}.txt"), "w" + self.fabricate.joinpath(parsed_args.wad).joinpath(f"{fn_base}.txt"), "w" ) self._offset = 0 @@ -66,11 +64,13 @@ class Concat(dcc.doom_base.Wad): start_time = self._offset / 1000000 text = self._add_chunk( videodir.joinpath(f"{parsed_args.wad}_map{idx}.mp4"), - output, not parsed_args.nooverlay + output, + not parsed_args.nooverlay, ) list.append( - summary, f"{text} {math.floor(start_time / 60):02}:" - + f"{math.floor(start_time % 60):02}" + summary, + f"{text} {math.floor(start_time / 60):02}:" + + f"{math.floor(start_time % 60):02}", ) if state == State.DONE: break @@ -82,8 +82,7 @@ class Concat(dcc.doom_base.Wad): def _add_chunk(self, v, output, overlay): chunk = av.open(v) - if not (len(chunk.streams.video) == 1 - and len(chunk.streams.audio) == 1): + if not (len(chunk.streams.video) == 1 and len(chunk.streams.audio) == 1): raise Exception( f"irregular chunk {v}: streams {chunk.streams} " + f"(expected 1 video & 1 audio)" @@ -96,16 +95,11 @@ class Concat(dcc.doom_base.Wad): text = "" if overlay: img = wand.image.Image( - height=chunk.streams[0].height, - width=chunk.streams[0].width + height=chunk.streams[0].height, width=chunk.streams[0].width ) mapstring = v.name[-6:-4] text = self._config["map_names"][f"map{mapstring}"] - self.draw_text( - img, - f"MAP{mapstring}: {text}", - font_size=120 - ) + self.draw_text(img, f"MAP{mapstring}: {text}", font_size=120) img.trim(reset_coords=True) img.border("graya(25%, 25%)", 10, 10) img.border(self.thumbnail_text_stroke, 16, 16) @@ -113,17 +107,14 @@ class Concat(dcc.doom_base.Wad): # multiple of 8. dude whyyyyyyy padfactor = 8 img.border("transparent", padfactor, 0) - img.crop( - width=img.width - img.width % padfactor, - height=img.height - ) + img.crop(width=img.width - img.width % padfactor, height=img.height) if len(output.streams.get()) == 0: # We can't use the input stream as a template here; it doesn't # have everything needed to do encoding and will fail # mysteriously later. vs = chunk.streams.video[0] - vr = int(vs.time_base.denominator/vs.time_base.numerator) + vr = int(vs.time_base.denominator / vs.time_base.numerator) ovs = output.add_stream("h264", rate=vr) ovs.extradata = copy.deepcopy(vs.extradata) ovs.height = vs.height @@ -145,12 +136,10 @@ class Concat(dcc.doom_base.Wad): oas.bit_rate = astr.bit_rate src = ograph.add_buffer( - template=chunk.streams.video[0], - time_base=chunk.streams.video[0].time_base + template=chunk.streams.video[0], time_base=chunk.streams.video[0].time_base ) asrc = ograph.add_abuffer( - template=chunk.streams.audio[0], - time_base=chunk.streams.audio[0].time_base + template=chunk.streams.audio[0], time_base=chunk.streams.audio[0].time_base ) # TODO: video fades are absolute relative to the input video; audio # fades need to have their timestamps offset by the position in the @@ -163,19 +152,18 @@ class Concat(dcc.doom_base.Wad): iafade_start = self._offset * sample_rate / 1000000 iafade = ograph.add("afade", args=f"in:{iafade_start}:{sample_rate}") oafade_start = ( - (self._offset + chunk.duration) * sample_rate / 1000000 - - sample_rate - ) + self._offset + chunk.duration + ) * sample_rate / 1000000 - sample_rate oafade = ograph.add("afade", args=f"out:{oafade_start}:{sample_rate}") if overlay: overlay = ograph.add_buffer( - width=img.width, height=img.height, - format="rgba", time_base=chunk.streams.video[0].time_base - ) - overlay_fo = ograph.add( - "fade", args=f"out:{4 * frame_rate}:{frame_rate}" + width=img.width, + height=img.height, + format="rgba", + time_base=chunk.streams.video[0].time_base, ) + overlay_fo = ograph.add("fade", args=f"out:{4 * frame_rate}:{frame_rate}") overlay.link_to(overlay_fo, 0, 0) composite = ograph.add("overlay", args="x=4:y=4") src.link_to(composite, 0, 0) @@ -194,9 +182,8 @@ class Concat(dcc.doom_base.Wad): for packet in chunk.demux(): if packet.dts is None: continue - pof = ( - (self._offset * packet.time_base.denominator) - / (packet.time_base.numerator * 1000000) + pof = (self._offset * packet.time_base.denominator) / ( + packet.time_base.numerator * 1000000 ) packet.dts += pof packet.pts += pof @@ -221,9 +208,7 @@ class Concat(dcc.doom_base.Wad): def _make_text_frame(self, img, ifr): # We need to give each frame its own memory it can own. - text_frame = av.video.frame.VideoFrame( - img.width, img.height, format="rgba" - ) + text_frame = av.video.frame.VideoFrame(img.width, img.height, format="rgba") text_frame.planes[0].update(img.make_blob(format="rgba")) text_frame.pts = ifr.pts text_frame.dts = ifr.dts diff --git a/dcc/config.py b/doomcc/config.py similarity index 84% rename from dcc/config.py rename to doomcc/config.py index 03243b6..9aae491 100644 --- a/dcc/config.py +++ b/doomcc/config.py @@ -12,13 +12,15 @@ class ConfigBase(object): self._doom = pathlib.Path(parsed_args.doom) self._config_name = parsed_args.config_name self._config = tomlkit.toml_file.TOMLFile( - self.doom.joinpath(self.config_name)).read() + self.doom.joinpath(self.config_name) + ).read() self._dsda = self._config.get("dsda") if self._dsda is None: raise Exception( "required key 'dsda' not set in config " - + f"{self.doom.joinpath(self.config_name)}.") + + f"{self.doom.joinpath(self.config_name)}." + ) for d in ("iwads", "pwads", "demos", "fabricate"): self._init_attr([d], d, fn=self.doom.joinpath) @@ -29,10 +31,7 @@ class ConfigBase(object): self._init_attr(["thumbnail", "text_fill"], "white") self._init_attr(["thumbnail", "text_stroke"], "red") self._init_attr(["thumbnail", "overlay_name"], "M_DOOM_scaled.png") - self._init_attr( - ["fetch", "mirror"], - "https://youfailit.net/pub/idgames" - ) + self._init_attr(["fetch", "mirror"], "https://youfailit.net/pub/idgames") def _init_attr(self, what, default, fn=lambda x: x): propname = "_".join(what) @@ -45,8 +44,7 @@ class ConfigBase(object): setattr(self, f"_{propname}", fn(val)) setattr( - type(self), propname, - property(lambda self: getattr(self, f"_{propname}")) + type(self), propname, property(lambda self: getattr(self, f"_{propname}")) ) @property @@ -65,10 +63,10 @@ class ConfigBase(object): def get_parser_func(toc): def add_common_args(self, prog_name): parser = super(toc, self).get_parser(prog_name) - parser.add_argument( - "--doom", default=pathlib.Path.home().joinpath("doom")) + parser.add_argument("--doom", default=pathlib.Path.home().joinpath("doom")) parser.add_argument("--config-name", default="config.toml") return parser + return add_common_args diff --git a/dcc/configure.py b/doomcc/configure.py similarity index 85% rename from dcc/configure.py rename to doomcc/configure.py index 31ed4b5..080f613 100644 --- a/dcc/configure.py +++ b/doomcc/configure.py @@ -1,9 +1,9 @@ -import dcc.doom_base +import doomcc.doom_base import omg import tomlkit.toml_file -class Configure(dcc.doom_base.Wad): +class Configure(doomcc.doom_base.Wad): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("--complevel", "--cl") @@ -22,12 +22,10 @@ class Configure(dcc.doom_base.Wad): wad = omg.WadIO(w) complevel = wad.read("COMPLVL").decode("ascii").strip() except Exception as e: - print( - f"Wad {w} likely has no lump COMPLVL (exception {e})") + print(f"Wad {w} likely has no lump COMPLVL (exception {e})") if complevel is None: complevel = input("Complevel? ") - doc = tomlkit.document() doc.add("complevel", complevel) if parsed_args.iwad is not None: diff --git a/dcc/doom_base.py b/doomcc/doom_base.py similarity index 79% rename from dcc/doom_base.py rename to doomcc/doom_base.py index aedae79..c12a867 100644 --- a/dcc/doom_base.py +++ b/doomcc/doom_base.py @@ -1,13 +1,13 @@ from abc import abstractmethod from cliff.command import Command -import dcc.config +import doomcc.config import io import os import re import tomlkit -class Wad(dcc.config.Base): +class Wad(doomcc.config.Base): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("wad") @@ -70,9 +70,8 @@ class Wad(dcc.config.Base): return [] def thumb_overlay_path(self): - return ( - self._ensure(self.fabricate.joinpath(self.wad)) - .joinpath(self.thumbnail_overlay_name) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath( + self.thumbnail_overlay_name ) @@ -129,55 +128,51 @@ class WadMap(Wad): def demo_in_path(self): candidates = [ - x for x in self.demos.joinpath(self.wad) - .glob(self._file_base("*.lmp")) + x for x in self.demos.joinpath(self.wad).glob(self._file_base("*.lmp")) ] if len(candidates) == 0: raise Exception( - "no suitable demo candidates for WAD {} MAP {} name {}." - .format(self.wad, self.map, self.name_string) + "no suitable demo candidates for WAD {} MAP {}{}.".format( + self.wad, + self.map, + f" name {self._name}" if self._name else "", + ) ) if len(candidates) == 1: return candidates[0] return sorted(filter(lambda s: re.search("-", str(s)), candidates))[-1] def dsda_text_path(self): - return ( - self._ensure(self.demos.joinpath(self.wad)) - .joinpath(self._file_base(".txt")) + return self._ensure(self.demos.joinpath(self.wad)).joinpath( + self._file_base(".txt") ) def video_path(self): - return ( - self._ensure(self.fabricate.joinpath(self.wad)) - .joinpath(self._file_base(".mp4")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath( + self._file_base(".mp4") ) def demo_out_path(self): - return ( - self._ensure(self.demos.joinpath(self.wad)) - .joinpath(self._file_base(".lmp")) + return self._ensure(self.demos.joinpath(self.wad)).joinpath( + self._file_base(".lmp") ) def target_bucket(self): return "doom/" + self._file_base(".lmp") def base_thumb_path(self): - return ( - self._ensure(self.fabricate.joinpath(self.wad)) - .joinpath(self._file_base("_base.png")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath( + self._file_base("_base.png") ) def text_thumb_path(self): - return ( - self._ensure(self.fabricate.joinpath(self.wad)) - .joinpath(self._file_base("_text.png")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath( + self._file_base("_text.png") ) def thumb_path(self): - return ( - self._ensure(self.fabricate.joinpath(self.wad)) - .joinpath(self._file_base("_thumb.png")) + return self._ensure(self.fabricate.joinpath(self.wad)).joinpath( + self._file_base("_thumb.png") ) def _file_base(self, ext): diff --git a/dcc/dsda.py b/doomcc/dsda.py similarity index 83% rename from dcc/dsda.py rename to doomcc/dsda.py index 8fb1e00..932d855 100644 --- a/dcc/dsda.py +++ b/doomcc/dsda.py @@ -1,5 +1,5 @@ -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import os import re import shutil @@ -7,7 +7,7 @@ import subprocess import zipfile -class DSDA(dcc.doom_base.WadMap): +class DSDA(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("-s", "--single", action="store_true") @@ -22,10 +22,9 @@ class DSDA(dcc.doom_base.WadMap): if shutil.which("xvfb-run") is not None: command = ["xvfb-run"] + command subprocess.run( - command + self.dsda_preamble(warp=False) + [ - "-fastdemo", dip, "-nosound", - "-skiptic", "-1", "-export_text_file" - ] + command + + self.dsda_preamble(warp=False) + + ["-fastdemo", dip, "-nosound", "-skiptic", "-1", "-export_text_file"] ) editor = "nano" if "EDITOR" in os.environ: @@ -38,7 +37,7 @@ class DSDA(dcc.doom_base.WadMap): else: fh1 = self.wad[0:2] + self.map if parsed_args.single: - fh1 = self.wad[0:min(len(self.wad), 4)] + fh1 = self.wad[0 : min(len(self.wad), 4)] fh2 = "" with open(dtp, mode="r") as f: for line in f: @@ -46,7 +45,7 @@ class DSDA(dcc.doom_base.WadMap): m = re.search("[^0-9]*([0-9]*):([0-9]*).[0-9]*", line) if m is None: continue - fh2 = m[1]+m[2] + fh2 = m[1] + m[2] if len(fh2) % 2 == 1: fh2 = "0" + fh2 break diff --git a/dcc/eureka.py b/doomcc/eureka.py similarity index 70% rename from dcc/eureka.py rename to doomcc/eureka.py index 4d7b9cd..8618287 100644 --- a/dcc/eureka.py +++ b/doomcc/eureka.py @@ -1,9 +1,9 @@ -import dcc.doom_base -import dcc.config +import doomcc.doom_base +import doomcc.config import os -class Eureka(dcc.doom_base.WadMap): +class Eureka(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("--main") @@ -24,7 +24,11 @@ class Eureka(dcc.doom_base.WadMap): if complevel == "11" or complevel == "21": port = "mbf" - os.execvp("eureka", - ["eureka"] + ["-iwad", iwad] + ["-w", parsed_args.map] - + ["-p", port] + [mw] + os.execvp( + "eureka", + ["eureka"] + + ["-iwad", iwad] + + ["-w", parsed_args.map] + + ["-p", port] + + [mw], ) diff --git a/dcc/extract.py b/doomcc/extract.py similarity index 79% rename from dcc/extract.py rename to doomcc/extract.py index 95228f7..d0412ea 100644 --- a/dcc/extract.py +++ b/doomcc/extract.py @@ -1,14 +1,14 @@ -import dcc.doom_base +import doomcc.doom_base import omg import numpy as np import wand.color import wand.image -class Extract(dcc.doom_base.Wad): +class Extract(doomcc.doom_base.Wad): def get_parser(self, prog_name): parser = super().get_parser(prog_name) - parser.add_argument('lump') + parser.add_argument("lump") return parser def take_action(self, parsed_args): @@ -24,8 +24,9 @@ class Extract(dcc.doom_base.Wad): ) as img: img.transparent_color(wand.color.Color("#ff00ff"), 0.0) img.save( - filename=self.fabricate.joinpath(parsed_args.wad) - .joinpath(parsed_args.lump + ".png") + filename=self.fabricate.joinpath(parsed_args.wad).joinpath( + parsed_args.lump + ".png" + ) ) return except Exception as e: @@ -35,6 +36,5 @@ class Extract(dcc.doom_base.Wad): ) print( - f"Lump {parsed_args.lump} not found in any wad in " - + f"{parsed_args.wad}" + f"Lump {parsed_args.lump} not found in any wad in " + f"{parsed_args.wad}" ) diff --git a/dcc/fabricate.py b/doomcc/fabricate.py similarity index 74% rename from dcc/fabricate.py rename to doomcc/fabricate.py index 8c279ef..36f81d5 100644 --- a/dcc/fabricate.py +++ b/doomcc/fabricate.py @@ -1,12 +1,12 @@ import contextlib -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import os import shutil import tempfile -class Fabricate(dcc.doom_base.WadMap): +class Fabricate(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("--fg", action="store_true") @@ -18,10 +18,12 @@ class Fabricate(dcc.doom_base.WadMap): command = [self.dsda] if not parsed_args.fg and shutil.which("xvfb-run") is not None: command = ["xvfb-run"] + command - os.execvp(command[0], - command + self.dsda_preamble() + os.execvp( + command[0], + command + + self.dsda_preamble() + ["-timedemo", self.demo_in_path()] - + ["-viddump", self.video_path()] + + ["-viddump", self.video_path()], ) def options_dict(self): diff --git a/dcc/fetch.py b/doomcc/fetch.py similarity index 76% rename from dcc/fetch.py rename to doomcc/fetch.py index fd53f55..3e5e411 100644 --- a/dcc/fetch.py +++ b/doomcc/fetch.py @@ -1,4 +1,4 @@ -import dcc.config +import doomcc.config import io import json import pathlib @@ -9,7 +9,7 @@ import urllib.request import zipfile -class Fetch(dcc.config.Base): +class Fetch(doomcc.config.Base): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("id_or_name") @@ -21,14 +21,12 @@ class Fetch(dcc.config.Base): idgames_id = self.search_idgames(parsed_args.id_or_name) reply = self.fetch_url( - "https://www.doomworld.com/idgames/api/" + - "api.php?action=get&id={}&out=json".format(idgames_id) + "https://www.doomworld.com/idgames/api/" + + "api.php?action=get&id={}&out=json".format(idgames_id) + ) + rpath = "/".join( + [self.fetch_mirror, reply["content"]["dir"], reply["content"]["filename"]] ) - rpath = "/".join([ - self.fetch_mirror, - reply["content"]["dir"], - reply["content"]["filename"] - ]) wad = reply["content"]["filename"][0:-4] with urllib.request.urlopen(rpath) as response: @@ -38,8 +36,8 @@ class Fetch(dcc.config.Base): # TODO: explicit error handling. Let users choose when >1 result. def search_idgames(self, wad): reply = self.fetch_url( - "https://www.doomworld.com/idgames/api/" + - "api.php?action=search&query={}&out=json".format(wad) + "https://www.doomworld.com/idgames/api/" + + "api.php?action=search&query={}&out=json".format(wad) ) if "content" not in reply: sys.exit(f"No WAD named {wad} found on idgames.") @@ -62,8 +60,5 @@ class Fetch(dcc.config.Base): if fetcher_path is None: raise Exception(f"Fetch util {fetcher} not found on PATH.") - proc = subprocess.run( - [fetcher_path, url], - capture_output=True, check=True - ) + proc = subprocess.run([fetcher_path, url], capture_output=True, check=True) return json.loads(proc.stdout) diff --git a/dcc/ls.py b/doomcc/ls.py similarity index 59% rename from dcc/ls.py rename to doomcc/ls.py index b7fb0ce..ffb8f70 100644 --- a/dcc/ls.py +++ b/doomcc/ls.py @@ -1,8 +1,8 @@ -import dcc.config +import doomcc.config import os -class List(dcc.config.ListerBase): +class List(doomcc.config.ListerBase): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("target") @@ -12,22 +12,19 @@ class List(dcc.config.ListerBase): match parsed_args.target: case "pwads": return ( - ("pwads",), sorted( - (x.name,) for x in os.scandir(self.pwads) if x.is_dir() - ) + ("pwads",), + sorted((x.name,) for x in os.scandir(self.pwads) if x.is_dir()), ) case "iwads": return ( - ("iwads",), sorted( - (x.name,) for x in - os.scandir(self.iwads) if x.is_file() - ) + ("iwads",), + sorted((x.name,) for x in os.scandir(self.iwads) if x.is_file()), ) case _: raise Exception(f"unknown target {parsed_args.target}") -class WadList(dcc.config.ListerBase): +class WadList(doomcc.config.ListerBase): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("wad") @@ -35,11 +32,12 @@ class WadList(dcc.config.ListerBase): def _get_results(self, name, base, wad, ext): return ( - (name,), sorted( - (x.name,) for x in - os.scandir(base.joinpath(wad)) + (name,), + sorted( + (x.name,) + for x in os.scandir(base.joinpath(wad)) if x.name.endswith(ext) - ) + ), ) @@ -50,6 +48,4 @@ class ListDemos(WadList): class ListVideos(WadList): def take_action(self, parsed_args): - return self._get_results( - "videos", self.fabricate, parsed_args.wad, ".mp4" - ) + return self._get_results("videos", self.fabricate, parsed_args.wad, ".mp4") diff --git a/doomcc/main.py b/doomcc/main.py new file mode 100644 index 0000000..745e1d7 --- /dev/null +++ b/doomcc/main.py @@ -0,0 +1,72 @@ +import sys + +from cliff.app import App +from cliff.commandmanager import CommandManager + +import doomcc.concat +import doomcc.configure +import doomcc.dsda +import doomcc.eureka +import doomcc.extract +import doomcc.fabricate +import doomcc.fetch +import doomcc.ls +import doomcc.pb +import doomcc.play +import doomcc.put +import doomcc.record +import doomcc.rib +import doomcc.ss +import doomcc.text +import doomcc.thumb + + +class DoomCC(App): + def __init__(self): + cm = CommandManager(None) + commands = { + "concat": doomcc.concat.Concat, + "configure": doomcc.configure.Configure, + "dsda": doomcc.dsda.DSDA, + "eureka": doomcc.eureka.Eureka, + "extract": doomcc.extract.Extract, + "fabricate": doomcc.fabricate.Fabricate, + "fetch": doomcc.fetch.Fetch, + "ls": doomcc.ls.List, + "ls demos": doomcc.ls.ListDemos, + "ls videos": doomcc.ls.ListVideos, + "pb": doomcc.pb.PB, + "play": doomcc.play.Play, + "put": doomcc.put.Put, + "record": doomcc.record.Record, + "rib": doomcc.rib.RIB, + "ss": doomcc.ss.SS, + "text": doomcc.text.Text, + "thumb": doomcc.thumb.Thumb, + } + for n, c in commands.items(): + cm.add_command(n, c) + super().__init__( + description="Doom Command Center", + version="0.0.1", + command_manager=cm, + deferred_help=True, + ) + + def initialize_app(self, argv): + pass + + def prepare_to_run_command(self, cmd): + pass + + def clean_up(self, cmd, result, err): + pass + + +def main(argv=sys.argv[1:]): + doomcc = DoomCC() + return doomcc.run(argv) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/dcc/pb.py b/doomcc/pb.py similarity index 58% rename from dcc/pb.py rename to doomcc/pb.py index 661d538..841aba7 100644 --- a/dcc/pb.py +++ b/doomcc/pb.py @@ -1,8 +1,8 @@ -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import os -class PB(dcc.doom_base.WadMap): +class PB(doomcc.doom_base.WadMap): def take_action(self, parsed_args): os.execvp("ffplay", ["ffplay", self.video_path()]) diff --git a/dcc/play.py b/doomcc/play.py similarity index 58% rename from dcc/play.py rename to doomcc/play.py index 5459b6e..ee135ca 100644 --- a/dcc/play.py +++ b/doomcc/play.py @@ -1,8 +1,8 @@ -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import os -class Play(dcc.doom_base.WadMap): +class Play(doomcc.doom_base.WadMap): def take_action(self, parsed_args): os.execv(self.dsda, [self.dsda] + self.dsda_preamble()) diff --git a/dcc/put.py b/doomcc/put.py similarity index 58% rename from dcc/put.py rename to doomcc/put.py index 59e1dbc..77215b4 100644 --- a/dcc/put.py +++ b/doomcc/put.py @@ -1,23 +1,22 @@ import boto3 -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base -class Put(dcc.doom_base.WadMap): +class Put(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) return parser # TODO: accept configuration for bucket name def take_action(self, parsed_args): - s3_client = boto3.client('s3') + s3_client = boto3.client("s3") demo = self.demo_in_path() bucket = self.target_bucket() print("Uploading {} to bucket {}.".format(demo, bucket)) s3_client.upload_file( - demo, 'yrriban', bucket, - ExtraArgs={ - 'ContentType': 'binary/octet-stream', - 'ACL': 'public-read' - } + demo, + "yrriban", + bucket, + ExtraArgs={"ContentType": "binary/octet-stream", "ACL": "public-read"}, ) diff --git a/dcc/record.py b/doomcc/record.py similarity index 54% rename from dcc/record.py rename to doomcc/record.py index 21a5bd5..55e5af2 100644 --- a/dcc/record.py +++ b/doomcc/record.py @@ -1,13 +1,13 @@ -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import os -class Record(dcc.doom_base.WadMap): +class Record(doomcc.doom_base.WadMap): def take_action(self, parsed_args): - os.execv(self.dsda, - [self.dsda] + self.dsda_preamble() + - ["-record", self.demo_out_path()] + os.execv( + self.dsda, + [self.dsda] + self.dsda_preamble() + ["-record", self.demo_out_path()], ) def options_dict(self): diff --git a/dcc/rib.py b/doomcc/rib.py similarity index 75% rename from dcc/rib.py rename to doomcc/rib.py index ffcc16a..6a456f7 100644 --- a/dcc/rib.py +++ b/doomcc/rib.py @@ -1,10 +1,10 @@ -import dcc.doom_base +import doomcc.doom_base import pathlib import os import time -class RIB(dcc.doom_base.WadMap): +class RIB(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("-s", "--secs_before", type=int, default=10) @@ -16,10 +16,10 @@ class RIB(dcc.doom_base.WadMap): demo = "" dt = 0 demodir = ( - pathlib.Path.home() / - ".dsda-doom" / - "dsda_doom_data" / - self.iwad_path.stem.lower() + pathlib.Path.home() + / ".dsda-doom" + / "dsda_doom_data" + / self.iwad_path.stem.lower() ) for w in self.load_order(): demodir = demodir.joinpath(w.stem.lower()) @@ -41,8 +41,10 @@ class RIB(dcc.doom_base.WadMap): + f"and map {parsed_args.map} (tried to look in {demodir})" ) - os.execv(self.dsda, - [self.dsda] + self.dsda_preamble(warp=False) + os.execv( + self.dsda, + [self.dsda] + + self.dsda_preamble(warp=False) + ["-playdemo", demo] - + ["-skiptic", str(-35 * parsed_args.secs_before)] + + ["-skiptic", str(-35 * parsed_args.secs_before)], ) diff --git a/dcc/ss.py b/doomcc/ss.py similarity index 81% rename from dcc/ss.py rename to doomcc/ss.py index de610ff..835dd00 100644 --- a/dcc/ss.py +++ b/doomcc/ss.py @@ -1,12 +1,12 @@ from tkinter import messagebox -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import sys import wand.display import wand.image -class SS(dcc.doom_base.WadMap): +class SS(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("-g", "--gravity", default="center") @@ -22,11 +22,11 @@ class SS(dcc.doom_base.WadMap): height = self.thumbnail_height with wand.image.Image(width=width, height=height, pseudo="x:") as img: img.reset_coords() - if (img.width < width or img.height < height): + if img.width < width or img.height < height: if not messagebox.askretrycancel( - title="DCC", - message=f"Image too small ({img.width}x{img.height})." + - "Try again?" + title="Doom Command Center", + message=f"Image too small ({img.width}x{img.height})." + + "Try again?", ): sys.exit("Gave up trying to select an image.") return False @@ -35,7 +35,7 @@ class SS(dcc.doom_base.WadMap): if not yolo: wand.display.display(img) accepted = messagebox.askyesnocancel( - title="DCC", message="Is this image acceptable?" + title="Doom Command Center", message="Is this image acceptable?" ) if accepted is None: sys.exit("Gave up on image verification") diff --git a/dcc/text.py b/doomcc/text.py similarity index 83% rename from dcc/text.py rename to doomcc/text.py index a93d72a..421f37d 100644 --- a/dcc/text.py +++ b/doomcc/text.py @@ -1,5 +1,5 @@ -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import sys import textwrap import wand.drawing @@ -30,9 +30,7 @@ def draw_text(self, img, text, font_size=64, wrap_dist=0.75): columns = len(wrapped_text) while columns > 0: columns -= 1 - wrapped_text = '\n'.join( - textwrap.wrap(wrapped_text, columns) - ) + wrapped_text = "\n".join(textwrap.wrap(wrapped_text, columns)) wrapped_width, _ = eval_metrics(wrapped_text) if wrapped_width <= target_width: break @@ -43,20 +41,20 @@ def draw_text(self, img, text, font_size=64, wrap_dist=0.75): ) textlines[idx] = wrapped_text - wrapped_text = '\n'.join(textlines) + wrapped_text = "\n".join(textlines) draw.text(5, int(draw.font_size) + 5, wrapped_text) draw(img) draw.stroke_color = wand.color.Color("none") draw.stroke_width = 0 - draw.text(5, int(draw.font_size)+5, wrapped_text) + draw.text(5, int(draw.font_size) + 5, wrapped_text) draw(img) -dcc.config.Base.draw_text = draw_text +doomcc.config.Base.draw_text = draw_text -class Text(dcc.doom_base.WadMap): +class Text(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("--nomap", action="store_true") @@ -74,12 +72,9 @@ class Text(dcc.doom_base.WadMap): text += "\n" text = "{}{}".format(text, parsed_args.demotype) with wand.image.Image( - height=self.thumbnail_height, - width=self.thumbnail_width + height=self.thumbnail_height, width=self.thumbnail_width ) as img: - self.draw_text( - img, text, wrap_dist=0.95 - ) + self.draw_text(img, text, wrap_dist=0.95) img.trim() img.reset_coords() img.save(filename=self.text_thumb_path()) @@ -91,7 +86,7 @@ class Text(dcc.doom_base.WadMap): map_names = self._config.get("map_names") if map_names is not None: text = map_names.get(f"map{mapnum}") - if text != "": + if text is not None: return text return input("Map Name? ") diff --git a/dcc/thumb.py b/doomcc/thumb.py similarity index 89% rename from dcc/thumb.py rename to doomcc/thumb.py index 01a0c39..5ee8265 100644 --- a/dcc/thumb.py +++ b/doomcc/thumb.py @@ -1,10 +1,10 @@ -import dcc.config -import dcc.doom_base +import doomcc.config +import doomcc.doom_base import wand.color import wand.image -class Thumb(dcc.doom_base.WadMap): +class Thumb(doomcc.doom_base.WadMap): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument("--index", action="store_true") @@ -17,7 +17,7 @@ class Thumb(dcc.doom_base.WadMap): overlay = self.thumb_overlay_path() with ( wand.image.Image(filename=base) as bi, - wand.color.Color("transparent") as tc + wand.color.Color("transparent") as tc, ): with wand.image.Image(filename=text) as ti: ti.border(tc, 5, 5) diff --git a/pyproject.toml b/pyproject.toml index 891d8db..520742e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] -name = "dcc" +name = "doomcc" version = "0.1.0" description = "Doom Command Center" authors = [ {name = "yrriban",email = "yrriban@gmail.com"} ] -license = {text = "MIT"} +license = "MIT" readme = "README.md" requires-python = ">=3.12" dependencies = [ @@ -27,10 +27,10 @@ requires = ["poetry-core>=2.0.0,<3.0.0"] build-backend = "poetry.core.masonry.api" [project.scripts] -dcc = "dcc.main:main" +doomcc = "doomcc.main:main" [tool.poetry-pyinstaller-plugin.scripts] -dcc = { source = "dcc/main.py", type = "onefile" } +doomcc = { source = "doomcc/main.py", type = "onefile" } [tool.poetry-pyinstaller-plugin.collect] all = ["cliff"] diff --git a/tests/doom_base_test.py b/tests/doom_base_test.py new file mode 100644 index 0000000..5510fee --- /dev/null +++ b/tests/doom_base_test.py @@ -0,0 +1,28 @@ +import doomcc.doom_base +import os +import pathlib +import pytest +import tempfile + +class Workbench: + pass + +def test_demo_in_path(): + w = Workbench() + w.demo_in_path = lambda: doomcc.doom_base.WadMap.demo_in_path(w) + w._file_base = lambda path: doomcc.doom_base.WadMap._file_base(w, path) + w.wad = pathlib.Path("scythe") + w.map = "01" + w.name_string = "_index" + w._name = "index" + with tempfile.TemporaryDirectory() as td: + w.demos = pathlib.Path(td) + with pytest.raises(Exception): + w.demo_in_path() + + dp = pathlib.Path(td).joinpath("scythe") + os.mkdir(dp) + dp.joinpath("scythe_map01_index.lmp").touch() + assert w.demo_in_path() == dp.joinpath("scythe_map01_index.lmp") + dp.joinpath("scythe_map01_index-00028.lmp").touch() + assert w.demo_in_path() == dp.joinpath("scythe_map01_index-00028.lmp") diff --git a/tests/play_test.py b/tests/play_test.py index 1fa3956..e778251 100644 --- a/tests/play_test.py +++ b/tests/play_test.py @@ -1,5 +1,5 @@ import cliff.app -import dcc.play +import doomcc.play import logging import os import mockito.matchers @@ -26,26 +26,30 @@ def test_play(expect): scp.mkdir() scp.joinpath("config.toml").touch() scp.joinpath("scythe.wad").touch() - dcc.play.Play.__init__ = lambda self: None - dcc.play.Play.get_epilog = lambda self: "" - rig = dcc.play.Play() + doomcc.play.Play.__init__ = lambda self: None + doomcc.play.Play.get_epilog = lambda self: "" + rig = doomcc.play.Play() rig._hooks = [] parser = rig.get_parser("test_play") parsed_args = parser.parse_args(args=["--doom", td, "scythe", "01"]) - with expect(os, times=1).execv( - tdp.joinpath("dsda-doom").joinpath("exe"), - [ + with ( + expect(os, times=1) + .execv( tdp.joinpath("dsda-doom").joinpath("exe"), - "-iwad", - tdp.joinpath("iwads").joinpath("DOOM2.WAD"), - "-file", - tdp.joinpath("pwads").joinpath("scythe").joinpath("scythe.wad"), - "-complevel", - "2", - "-skill", - "4", - "-warp", - "01", - ] - ).thenReturn(None): + [ + tdp.joinpath("dsda-doom").joinpath("exe"), + "-iwad", + tdp.joinpath("iwads").joinpath("DOOM2.WAD"), + "-file", + tdp.joinpath("pwads").joinpath("scythe").joinpath("scythe.wad"), + "-complevel", + "2", + "-skill", + "4", + "-warp", + "01", + ], + ) + .thenReturn(None) + ): assert rig.run(parsed_args) is None