Major refactor to make handling name/path manipulation easier.

Most of this is now in doom_base.py, which stores wad/map/name, exposes them as
properties, and also takes care of most common tasks needed for building
command lines and such.
This commit is contained in:
yrriban 2025-05-13 05:46:26 -04:00
parent 85d3686f1c
commit 2438995093
11 changed files with 113 additions and 100 deletions

View file

@ -22,46 +22,6 @@ TEXT_STROKE_COLOR="srgb(176,0,0)"
MIRROR="https://youfailit.net/pub/idgames" # NYC MIRROR="https://youfailit.net/pub/idgames" # NYC
def DsdaPreamble(wad, mapstr):
args = ["-iwad", IwadPath(wad)]
pwadpath = PWADS.joinpath(wad)
wads = sorted(pwadpath.glob('*.wad', case_sensitive=False))
if len(wads) > 0:
args = args + ["-file"] + wads
dehs = sorted(pwadpath.glob('*.deh', case_sensitive=False))
if len(dehs) > 0:
args = args + ["-deh"] + dehs
complevel = pwadpath.joinpath("complevel")
if not complevel.exists():
raise Exception("No complevel set in PWAD dir {}.".format(pwadpath))
with io.open(complevel) as f:
args = args + ["-complevel", Complevel(wad)]
args = args + ["-skill", "4"]
args = args + ["-warp", mapstr] # TODO: UDoom
return args
def DemoInPath(wad, mapstr, name=None):
candidates = [x for x in DEMOS.joinpath(wad).glob("{}_map{}{}*.lmp".format(wad, mapstr, NameString(name)))]
if len(candidates) == 0:
raise Exception("no suitable demo candidates for WAD {} MAP {}.".format(wad, mapstr))
if len(candidates) == 1:
return candidates[0]
return sorted(filter(lambda s : re.search("-", str(s)), candidates))[-1]
def DsdaTextPath(wad, mapstr):
return Ensure(DEMOS.joinpath(wad)).joinpath("{}_map{}.txt".format(wad, mapstr))
def DemoOutPath(wad, mapstr, name=None):
return Ensure(DEMOS.joinpath(wad)).joinpath(DemoName(wad, mapstr, name))
def DemoName(wad, mapstr, name=None):
return "{}_map{}{}.lmp".format(wad, mapstr, NameString(name))
def Complevel(wad): def Complevel(wad):
complevel = PwadPath(wad).joinpath("complevel") complevel = PwadPath(wad).joinpath("complevel")
if not complevel.exists(): if not complevel.exists():
@ -79,27 +39,4 @@ def IwadPath(wad):
return iwad return iwad
def PwadPath(wad): def PwadPath(wad):
return Ensure(PWADS.joinpath(wad)) return PWADS.joinpath(wad)
def BaseThumbPath(wad, mapstr, name=None):
return Ensure(OUTPUT.joinpath(wad)).joinpath("{}_map{}{}_base.png".format(wad, mapstr, NameString(name)))
def MDoomPath(wad):
return Ensure(OUTPUT.joinpath(wad)).joinpath("M_DOOM_scaled.png")
def TextThumbPath(wad, mapstr, name=None):
return Ensure(OUTPUT.joinpath(wad)).joinpath("{}_map{}_text.png".format(wad, mapstr, NameString(name)))
def ThumbPath(wad, mapstr, name=None):
return Ensure(OUTPUT.joinpath(wad)).joinpath("{}_map{}_thumb.png".format(wad, mapstr, NameString(name)))
def VideoPath(wad, mapstr, name=None):
return Ensure(OUTPUT.joinpath(wad)).joinpath("{}_map{}{}.mp4".format(wad, mapstr, NameString(name)))
def NameString(name):
return "" if name is None else "_" + name
def Ensure(path):
if not path.exists():
os.mkdir(path)
return path

View file

@ -1,4 +1,7 @@
from abc import abstractmethod
from cliff.command import Command from cliff.command import Command
import dcc.config
import io
class WadMap(Command): class WadMap(Command):
def get_parser(self, prog_name): def get_parser(self, prog_name):
@ -7,3 +10,88 @@ class WadMap(Command):
parser.add_argument('map') parser.add_argument('map')
parser.add_argument('-n','--name','--demo_name') parser.add_argument('-n','--name','--demo_name')
return parser return parser
def run(self, parsed_args):
self._wad = parsed_args.wad
self._map = parsed_args.map
self._name = parsed_args.name
self.take_action(parsed_args)
@abstractmethod
def take_action(self, parsed_args):
pass
@property
def wad(self):
return self._wad
@property
def map(self):
return self._map
@property
def name_string(self):
return "" if self._name is None else "_" + self._name
def dsda_preamble(self):
args = ["-iwad", dcc.config.IwadPath(self.wad)]
pwadpath = dcc.config.PWADS.joinpath(self.wad)
wads = sorted(pwadpath.glob('*.wad', case_sensitive=False))
if len(wads) > 0:
args = args + ["-file"] + wads
dehs = sorted(pwadpath.glob('*.deh', case_sensitive=False))
if len(dehs) > 0:
args = args + ["-deh"] + dehs
complevel = pwadpath.joinpath("complevel")
if not complevel.exists():
raise Exception("No complevel set in PWAD dir {}.".format(pwadpath))
with io.open(complevel) as f:
args = args + ["-complevel", dcc.config.Complevel(self.wad)]
args = args + ["-skill", "4"]
args = args + ["-warp", self.map]
return args
def demo_in_path(self):
candidates = [x for x in dcc.config.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))
if len(candidates) == 1:
return candidates[0]
return sorted(filter(lambda s : re.search("-", str(s)), candidates))[-1]
def dsda_text_path(self):
return _ensure(dcc.config.DEMOS.joinpath(self.wad)).joinpath(self._file_base(".txt"))
def video_path(self):
return _ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(self._file_base(".mp4"))
def demo_out_path(self):
return _ensure(dcc.config.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 _ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(_file_base("_base.png"))
def m_doom_path(self):
return _ensure(OUTPUT.joinpath(self.wad)).joinpath("M_DOOM_scaled.png")
def text_thumb_path(self):
return _ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(_file_base("_text.png"))
def thumb_path(self):
return _ensure(dcc.config.OUTPUT.joinpath(self.wad)).joinpath(_file_base("_thumb.png"))
def _file_base(self, ext):
return "{}_map{}{}{}".format(self.wad, self.map, self.name_string, ext)
def _ensure(path):
if not path.exists():
os.mkdir(path)
return path

View file

@ -14,15 +14,14 @@ class DSDA(dcc.doom_base.WadMap):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
dip = dcc.config.DemoInPath(parsed_args.wad, parsed_args.map, parsed_args.name) dip = self.demo_in_path()
dtp = dcc.config.DsdaTextPath(parsed_args.wad, parsed_args.map, parsed_args.name) dtp = self.dsda_text_path()
if not dtp.exists(): if not dtp.exists():
command = [dcc.config.DSDA] command = [dcc.config.DSDA]
if shutil.which("xvfb-run") is not None: if shutil.which("xvfb-run") is not None:
command = ["xvfb-run"] + command command = ["xvfb-run"] + command
# TODO: negative tics should seek from the end, but this doesn't seem to work. # TODO: negative tics should seek from the end, but this doesn't seem to work.
subprocess.run(command + subprocess.run(command + self.dsda_preamble() +
dcc.config.DsdaPreamble(parsed_args.wad, parsed_args.map) +
["-fastdemo", dip, "-nosound", "-skiptic", "999999999", "-export_text_file"]) ["-fastdemo", dip, "-nosound", "-skiptic", "999999999", "-export_text_file"])
editor = "nano" editor = "nano"
if "EDITOR" in os.environ: if "EDITOR" in os.environ:
@ -31,11 +30,11 @@ class DSDA(dcc.doom_base.WadMap):
if parsed_args.abbreviation: if parsed_args.abbreviation:
fh1 = parsed_args.abbreviation fh1 = parsed_args.abbreviation
if not parsed_args.single: if not parsed_args.single:
fh1 += parsed_args.map fh1 += self.map
else: else:
fh1 = parsed_args.wad[0:2] + parsed_args.map fh1 = self.wad[0:2] + self.map
if parsed_args.single: if parsed_args.single:
fh1 = parsed_args.wad[0:min(len(parsed_args.wad), 4)] fh1 = self.wad[0:min(len(self.wad), 4)]
fh2 = "" fh2 = ""
with open(dtp, mode="r") as f: with open(dtp, mode="r") as f:
for line in f: for line in f:
@ -52,6 +51,6 @@ class DSDA(dcc.doom_base.WadMap):
# TODO: demo names other than uv-max. # TODO: demo names other than uv-max.
fnf = fh1 + "-" + fh2 + ".zip" fnf = fh1 + "-" + fh2 + ".zip"
with zipfile.ZipFile(dcc.config.DEMOS.joinpath(parsed_args.wad).joinpath(fnf), mode="w") as zf: with zipfile.ZipFile(dcc.config.DEMOS.joinpath(self.wad).joinpath(fnf), mode="w") as zf:
zf.write(dip, arcname=dip.name) zf.write(dip, arcname=dip.name)
zf.write(dtp, arcname=dtp.name) zf.write(dtp, arcname=dtp.name)

View file

@ -17,7 +17,6 @@ class Fabricate(dcc.doom_base.WadMap):
command = [dcc.config.DSDA] command = [dcc.config.DSDA]
if not parsed_args.fg and shutil.which("xvfb-run") is not None: if not parsed_args.fg and shutil.which("xvfb-run") is not None:
command = ["xvfb-run"] + command command = ["xvfb-run"] + command
subprocess.run(command + subprocess.run(command + self.dsda_preamble() +
dcc.config.DsdaPreamble(parsed_args.wad, parsed_args.map) + ["-timedemo", self.demo_in_path()] +
["-timedemo", dcc.config.DemoInPath(parsed_args.wad, parsed_args.map, parsed_args.name)] + ["-viddump", self.video_path()])
["-viddump", dcc.config.VideoPath(parsed_args.wad, parsed_args.map, parsed_args.name)])

View file

@ -4,4 +4,4 @@ import subprocess
class PB(dcc.doom_base.WadMap): class PB(dcc.doom_base.WadMap):
def take_action(self, parsed_args): def take_action(self, parsed_args):
subprocess.run(["ffplay", dcc.config.VideoPath(parsed_args.wad, parsed_args.map, parsed_args.name)]) subprocess.run(["ffplay", self.video_path()])

View file

@ -4,4 +4,4 @@ import subprocess
class Play(dcc.doom_base.WadMap): class Play(dcc.doom_base.WadMap):
def take_action(self, parsed_args): def take_action(self, parsed_args):
subprocess.run([dcc.config.DSDA] + dcc.config.DsdaPreamble(parsed_args.wad, parsed_args.map)) subprocess.run([dcc.config.DSDA] + self.dsda_preamble())

View file

@ -5,22 +5,13 @@ import dcc.doom_base
class Put(dcc.doom_base.WadMap): class Put(dcc.doom_base.WadMap):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super().get_parser(prog_name) parser = super().get_parser(prog_name)
parser.add_argument("-d", "--dsda", action="store_true")
parser.add_argument("-s", "--single", action="store_true")
return parser return parser
# TODO: accept configuration for bucket name # TODO: accept configuration for bucket name
def take_action(self, parsed_args): def take_action(self, parsed_args):
s3_client = boto3.client('s3') s3_client = boto3.client('s3')
demo = dcc.config.DemoInPath(parsed_args.wad, parsed_args.map, parsed_args.name) demo = self.demo_in_path()
bucket = dcc.config.DemoName(parsed_args.wad, parsed_args.map, parsed_args.name) bucket = self.target_bucket()
if parsed_args.dsda: print("Uploading {} to bucket {}.".format(demo, bucket))
pattern = ("{}{}".format(parsed_args.wad[0:2], parsed_args.map) if not parsed_args.single else parsed_args.wad[0:4]) + "*.zip" s3_client.upload_file(demo, 'yrriban', bucket,
zips = [x for x in dcc.config.DEMOS.joinpath(parsed_args.wad).glob(pattern)]
if len(zips) != 1:
raise Exception("Unable to identify exactly one suitable upload candidate (was {})".format(zips))
demo = zips[0]
bucket = "dsda/{}".format(demo.name)
print("Uploading {} to bucket doom/{}.".format(demo, bucket))
s3_client.upload_file(demo, 'yrriban', 'doom/' + bucket,
ExtraArgs={'ContentType': 'binary/octet-stream', 'ACL': 'public-read'}) ExtraArgs={'ContentType': 'binary/octet-stream', 'ACL': 'public-read'})

View file

@ -4,6 +4,5 @@ import subprocess
class Record(dcc.doom_base.WadMap): class Record(dcc.doom_base.WadMap):
def take_action(self, parsed_args): def take_action(self, parsed_args):
subprocess.run([dcc.config.DSDA] + subprocess.run([dcc.config.DSDA] + self.dsda_preamble() +
dcc.config.DsdaPreamble(parsed_args.wad, parsed_args.map) + ["-record", self.demo_out_path()])
["-record", dcc.config.DemoOutPath(parsed_args.wad, parsed_args.map, parsed_args.name)])

View file

@ -29,7 +29,7 @@ class SS(dcc.doom_base.WadMap):
sys.exit("Gave up on image verification") sys.exit("Gave up on image verification")
if not accepted: if not accepted:
return False return False
img.save(filename=dcc.config.BaseThumbPath(parsed_args.wad, parsed_args.map, parsed_args.name)) img.save(filename=self.base_thumb_path())
return True return True
while not try_screenshot(): while not try_screenshot():

View file

@ -31,4 +31,4 @@ class Text(dcc.doom_base.WadMap):
draw(img) draw(img)
img.trim() img.trim()
img.reset_coords() img.reset_coords()
img.save(filename=dcc.config.TextThumbPath(parsed_args.wad, parsed_args.map, parsed_args.name)) img.save(filename=self.text_thumb_path())

View file

@ -10,9 +10,9 @@ class Thumb(dcc.doom_base.WadMap):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
base = dcc.config.BaseThumbPath(parsed_args.wad, parsed_args.map, parsed_args.name) base = self.base_thumb_path()
text = dcc.config.TextThumbPath(parsed_args.wad, parsed_args.map, parsed_args.name) text = self.text_thumb_path()
mdoom = dcc.config.MDoomPath(parsed_args.wad) mdoom = self.m_doom_path()
with wand.image.Image(filename=base) as bi, wand.color.Color("transparent") as tc: with wand.image.Image(filename=base) as bi, wand.color.Color("transparent") as tc:
with wand.image.Image(filename=text) as ti: with wand.image.Image(filename=text) as ti:
ti.border(tc, 5, 5) ti.border(tc, 5, 5)
@ -27,4 +27,4 @@ class Thumb(dcc.doom_base.WadMap):
di.border(tc, 1, 1) di.border(tc, 1, 1)
bi.composite(di, gravity="north_east") bi.composite(di, gravity="north_east")
bi.save(filename=dcc.config.ThumbPath(parsed_args.wad, parsed_args.map, parsed_args.name)) bi.save(filename=self.thumb_path())