Compare commits
10 commits
970e63e19b
...
4d8c19db8f
Author | SHA1 | Date | |
---|---|---|---|
4d8c19db8f | |||
20af631a74 | |||
26f4cd96d4 | |||
ae91d13132 | |||
a15fb7604b | |||
355bd9d5d9 | |||
eded543071 | |||
20083dc749 | |||
43639955fb | |||
5b6ea201fe |
9 changed files with 168 additions and 35 deletions
10
dcc/check.py
10
dcc/check.py
|
@ -1,10 +0,0 @@
|
||||||
import dcc.config
|
|
||||||
import dcc.doom_base
|
|
||||||
import wand.display
|
|
||||||
import wand.image
|
|
||||||
|
|
||||||
class Check(dcc.doom_base.WadMap):
|
|
||||||
def take_action(self, parsed_args):
|
|
||||||
with wand.image.Image(filename=dcc.config.BaseThumbPath(parsed_args.wad, parsed_args.map)) as img:
|
|
||||||
print("Image is {}x{}.".format(img.width, img.height))
|
|
||||||
wand.display.display(img)
|
|
119
dcc/concat.py
Normal file
119
dcc/concat.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import av
|
||||||
|
import copy
|
||||||
|
import dcc.doom_base
|
||||||
|
import fractions
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
import wand.image
|
||||||
|
|
||||||
|
class Concat(dcc.doom_base.Wad):
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super().get_parser(prog_name)
|
||||||
|
parser.add_argument("start_map")
|
||||||
|
parser.add_argument("end_map")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
logging.basicConfig()
|
||||||
|
av.logging.set_level(av.logging.VERBOSE)
|
||||||
|
av.logging.restore_default_callback()
|
||||||
|
videos = self.fabricate.joinpath(parsed_args.wad).glob(f"{parsed_args.wad}_map*.mp4")
|
||||||
|
output = av.open(self.fabricate.joinpath(parsed_args.wad).joinpath(f"{parsed_args.wad}_maps{parsed_args.start_map}to{parsed_args.end_map}.mp4"), "w")
|
||||||
|
offset = 0
|
||||||
|
# We'd like to use the concat filter here and connect everything into a
|
||||||
|
# single filter graph... but it produces a "Resource temporarily
|
||||||
|
# unavailable" error when switching to inputs after the first. Presumably
|
||||||
|
# fixable, but it's easier to just make one graph per video and mux
|
||||||
|
# everything together at the end.
|
||||||
|
for v in sorted(videos):
|
||||||
|
# TODO: Support UDoom in literally any way.
|
||||||
|
if not (v.name >= f"{parsed_args.wad}_map{parsed_args.start_map}.mp4" and
|
||||||
|
v.name <= f"{parsed_args.wad}_map{parsed_args.end_map}.mp4"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
chunk = av.open(v)
|
||||||
|
ograph = av.filter.Graph()
|
||||||
|
sink = ograph.add("buffersink")
|
||||||
|
asink = ograph.add("abuffersink")
|
||||||
|
|
||||||
|
img = wand.image.Image(height=chunk.streams[0].height,width=chunk.streams[0].width)
|
||||||
|
mapstring = v.name[-6:-4]
|
||||||
|
text = self._config["map_names"][f"map{mapstring}"]
|
||||||
|
dcc.text.draw_text(img, f"MAP{mapstring}: {text}", font_size=120)
|
||||||
|
img.trim(reset_coords=True)
|
||||||
|
img.border("graya(25%, 25%)", 10, 10)
|
||||||
|
img.border(dcc.config.TEXT_STROKE_COLOR, 16, 16)
|
||||||
|
# for this to work... the image needs to have a width that's a multiple
|
||||||
|
# of 8. dude whyyyyyyy
|
||||||
|
padfactor=8
|
||||||
|
img.border("transparent", padfactor, 0)
|
||||||
|
img.crop(width=img.width-img.width%padfactor, height=img.height)
|
||||||
|
text_frame = av.video.frame.VideoFrame(img.width, img.height, format="rgba")
|
||||||
|
|
||||||
|
if len(output.streams.get()) == 0:
|
||||||
|
# TODO: less hardcoding.
|
||||||
|
output.add_stream("h264", rate=61440)
|
||||||
|
output.streams[0].extradata = copy.deepcopy(chunk.streams[0].extradata)
|
||||||
|
output.streams[0].height=1440
|
||||||
|
output.streams[0].width=2560
|
||||||
|
output.streams[0].profile="High"
|
||||||
|
output.streams[0].qmax = chunk.streams[0].qmax
|
||||||
|
output.streams[0].qmin = chunk.streams[0].qmin
|
||||||
|
output.streams[0].codec_context.gop_size=30
|
||||||
|
output.streams[0].codec_context.max_b_frames=2
|
||||||
|
output.streams[0].codec_context.framerate = fractions.Fraction(60,1)
|
||||||
|
output.streams[0].codec_context.pix_fmt="yuv420p"
|
||||||
|
output.streams[0].codec_context.bit_rate = chunk.streams[0].codec_context.bit_rate
|
||||||
|
output.add_stream("aac", rate=48000)
|
||||||
|
output.streams[1].extradata = copy.deepcopy(output.streams[1].extradata)
|
||||||
|
output.streams[1].rate=48000
|
||||||
|
output.streams[1].bit_rate=chunk.streams[1].bit_rate
|
||||||
|
src = ograph.add_buffer(template=chunk.streams[0], time_base=chunk.streams[0].time_base)
|
||||||
|
asrc = ograph.add_abuffer(template=chunk.streams[1], time_base=chunk.streams[1].time_base)
|
||||||
|
overlay = ograph.add_buffer(width=img.width, height=img.height, format="rgba", time_base=chunk.streams[0].time_base)
|
||||||
|
overlay_fo = ograph.add("fade", args="out:240:60")
|
||||||
|
overlay.link_to(overlay_fo, 0, 0)
|
||||||
|
composite = ograph.add("overlay", args="x=4:y=4")
|
||||||
|
src.link_to(composite, 0, 0)
|
||||||
|
overlay_fo.link_to(composite, 0, 1)
|
||||||
|
ifade = ograph.add("fade", args="in:0:60")
|
||||||
|
iafade = ograph.add("afade", args="in:{}:48000".format(offset*48000/1000000))
|
||||||
|
ofade = ograph.add("fade", args="out:{}:60".format((chunk.duration*60/1000000)-60))
|
||||||
|
oafade = ograph.add("afade", args="out:{}:48000".format(((offset+chunk.duration)*48000/1000000)-48000))
|
||||||
|
composite.link_to(ifade, 0, 0)
|
||||||
|
asrc.link_to(iafade, 0, 0)
|
||||||
|
ifade.link_to(ofade, 0, 0)
|
||||||
|
iafade.link_to(oafade, 0, 0)
|
||||||
|
ofade.link_to(sink, 0, 0)
|
||||||
|
oafade.link_to(asink, 0, 0)
|
||||||
|
|
||||||
|
ograph.configure()
|
||||||
|
for packet in chunk.demux():
|
||||||
|
if packet.dts is None:
|
||||||
|
continue
|
||||||
|
packet.dts += (offset * packet.time_base.denominator) / (packet.time_base.numerator * 1000000)
|
||||||
|
packet.pts += (offset * packet.time_base.denominator) / (packet.time_base.numerator * 1000000)
|
||||||
|
if packet.stream_index == 0: # TODO: robustness
|
||||||
|
for ifr in packet.decode():
|
||||||
|
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
|
||||||
|
text_frame.time_base = ifr.time_base
|
||||||
|
overlay.push(text_frame)
|
||||||
|
src.push(ifr)
|
||||||
|
ofr = sink.pull()
|
||||||
|
for p in output.streams[packet.stream_index].encode(ofr):
|
||||||
|
output.mux(p)
|
||||||
|
else:
|
||||||
|
for ifr in packet.decode():
|
||||||
|
asrc.push(ifr)
|
||||||
|
ofr = asink.pull()
|
||||||
|
for p in output.streams[packet.stream_index].encode(ofr):
|
||||||
|
output.mux(p)
|
||||||
|
offset += chunk.duration
|
||||||
|
chunk.close()
|
||||||
|
output.close()
|
||||||
|
|
|
@ -32,6 +32,10 @@ class Base(Command):
|
||||||
for d in ("iwads", "pwads", "demos", "fabricate"):
|
for d in ("iwads", "pwads", "demos", "fabricate"):
|
||||||
self._init_path(d)
|
self._init_path(d)
|
||||||
|
|
||||||
|
def run(self, parsed_args):
|
||||||
|
self.init_base(parsed_args)
|
||||||
|
self.take_action(parsed_args)
|
||||||
|
|
||||||
def _init_path(self, what):
|
def _init_path(self, what):
|
||||||
setattr(self, f"_{what}", self.doom.joinpath(self._config.get(what, what)))
|
setattr(self, f"_{what}", self.doom.joinpath(self._config.get(what, what)))
|
||||||
setattr(type(self), what, property(lambda self: getattr(self, f"_{what}")))
|
setattr(type(self), what, property(lambda self: getattr(self, f"_{what}")))
|
||||||
|
|
|
@ -3,6 +3,7 @@ from cliff.command import Command
|
||||||
import dcc.config
|
import dcc.config
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import tomlkit
|
import tomlkit
|
||||||
|
|
||||||
class Wad(dcc.config.Base):
|
class Wad(dcc.config.Base):
|
||||||
|
@ -17,12 +18,12 @@ class Wad(dcc.config.Base):
|
||||||
wcp = self.pwads.joinpath(self.wad).joinpath(self.config_name)
|
wcp = self.pwads.joinpath(self.wad).joinpath(self.config_name)
|
||||||
if wcp.exists():
|
if wcp.exists():
|
||||||
self._wad_config = tomlkit.toml_file.TOMLFile(wcp).read()
|
self._wad_config = tomlkit.toml_file.TOMLFile(wcp).read()
|
||||||
for k,v in self._wad_config.value.items():
|
self._config.update(self._wad_config.value)
|
||||||
print(k,v)
|
#for k,v in self._wad_config.value.items():
|
||||||
self._config.add(k,v)
|
#self._config.add(k,v)
|
||||||
|
|
||||||
def run(self, parsed_args):
|
def run(self, parsed_args):
|
||||||
self.wad_init(self, parsed_args)
|
self.wad_init(parsed_args)
|
||||||
self.take_action(parsed_args)
|
self.take_action(parsed_args)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -72,7 +73,7 @@ class WadMap(Wad):
|
||||||
def complevel(self):
|
def complevel(self):
|
||||||
complevel = self.pwads.joinpath(self.wad).joinpath("complevel")
|
complevel = self.pwads.joinpath(self.wad).joinpath("complevel")
|
||||||
if not complevel.exists():
|
if not complevel.exists():
|
||||||
raise Exception("No complevel set in PWAD dir {}.".format(pwadpath))
|
raise Exception("No complevel set for wad {}.".format(self.wad))
|
||||||
|
|
||||||
with io.open(complevel) as f:
|
with io.open(complevel) as f:
|
||||||
return f.read().strip()
|
return f.read().strip()
|
||||||
|
|
|
@ -22,7 +22,6 @@ class Fabricate(dcc.doom_base.WadMap):
|
||||||
list.append(options, f"{k}={v}")
|
list.append(options, f"{k}={v}")
|
||||||
if len(options) > 0:
|
if len(options) > 0:
|
||||||
options = ["-assign", ",".join(options)]
|
options = ["-assign", ",".join(options)]
|
||||||
print(options)
|
|
||||||
subprocess.run(command + self.dsda_preamble() + options +
|
subprocess.run(command + self.dsda_preamble() + options +
|
||||||
["-timedemo", self.demo_in_path()] +
|
["-timedemo", self.demo_in_path()] +
|
||||||
["-viddump", self.video_path()])
|
["-viddump", self.video_path()])
|
||||||
|
|
|
@ -17,7 +17,9 @@ class List(dcc.config.Base):
|
||||||
case "demos":
|
case "demos":
|
||||||
self.list(x.name for x in os.scandir(self.demos.joinpath(parsed_args.wad)) if x.name.endswith(".lmp"))
|
self.list(x.name for x in os.scandir(self.demos.joinpath(parsed_args.wad)) if x.name.endswith(".lmp"))
|
||||||
case "videos":
|
case "videos":
|
||||||
self.list(x.name for x in os.scandir(self.output.joinpath(parsed_args.wad)) if x.name.endswith(".mp4"))
|
self.list(x.name for x in os.scandir(self.fabricate.joinpath(parsed_args.wad)) if x.name.endswith(".mp4"))
|
||||||
|
case _:
|
||||||
|
raise Exception(f"unknown target {parsed_args.target}")
|
||||||
|
|
||||||
def list(self, gen):
|
def list(self, gen):
|
||||||
# TODO: fancy text?
|
# TODO: fancy text?
|
||||||
|
|
40
dcc/text.py
40
dcc/text.py
|
@ -3,32 +3,36 @@ import sys
|
||||||
import wand.drawing
|
import wand.drawing
|
||||||
import wand.image
|
import wand.image
|
||||||
|
|
||||||
|
def draw_text(img, text, font_size=64):
|
||||||
|
with wand.drawing.Drawing() as draw:
|
||||||
|
draw.font = dcc.config.FONT
|
||||||
|
draw.font_size=font_size
|
||||||
|
draw.fill_color=wand.color.Color(dcc.config.TEXT_FILL_COLOR)
|
||||||
|
draw.stroke_color=wand.color.Color(dcc.config.TEXT_STROKE_COLOR)
|
||||||
|
draw.stroke_width=font_size*5/32
|
||||||
|
draw.text_interline_spacing=-font_size/4
|
||||||
|
draw.text(5,int(draw.font_size)+5,text)
|
||||||
|
draw(img)
|
||||||
|
draw.stroke_color=wand.color.Color("none")
|
||||||
|
draw.stroke_width=0
|
||||||
|
draw.text(5,int(draw.font_size)+5,text)
|
||||||
|
draw(img)
|
||||||
|
|
||||||
|
|
||||||
class Text(dcc.doom_base.WadMap):
|
class Text(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("--nomap", action="store_true")
|
parser.add_argument("--nomap", action="store_true")
|
||||||
parser.add_argument("--demotype", default="UV-Max")
|
parser.add_argument("--demotype", default="UV-Max Demo")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
text = sys.stdin.read().rstrip()
|
text = sys.stdin.read().rstrip()
|
||||||
if not parsed_args.nomap:
|
if not parsed_args.nomap:
|
||||||
text = "MAP{}: {}".format(parsed_args.map, text)
|
text = "MAP{}: {}".format(parsed_args.map, text)
|
||||||
text = "{}\n{} Demo".format(text, parsed_args.demotype)
|
text = "{}\n{}".format(text, parsed_args.demotype)
|
||||||
with wand.image.Image(height=dcc.config.THUMB_HEIGHT,width=dcc.config.THUMB_WIDTH) as img:
|
with wand.image.Image(height=dcc.config.THUMB_HEIGHT,width=dcc.config.THUMB_WIDTH) as img:
|
||||||
with wand.drawing.Drawing() as draw:
|
draw_text(img, text)
|
||||||
draw.font = dcc.config.FONT
|
img.trim()
|
||||||
draw.font_size=64
|
img.reset_coords()
|
||||||
draw.fill_color=wand.color.Color(dcc.config.TEXT_FILL_COLOR)
|
img.save(filename=self.text_thumb_path())
|
||||||
draw.stroke_color=wand.color.Color(dcc.config.TEXT_STROKE_COLOR)
|
|
||||||
draw.stroke_width=10
|
|
||||||
draw.text_interline_spacing=-16
|
|
||||||
draw.text(5,int(draw.font_size),text)
|
|
||||||
draw(img)
|
|
||||||
draw.stroke_color=wand.color.Color("none")
|
|
||||||
draw.stroke_width=0
|
|
||||||
draw.text(5,int(draw.font_size),text)
|
|
||||||
draw(img)
|
|
||||||
img.trim()
|
|
||||||
img.reset_coords()
|
|
||||||
img.save(filename=self.text_thumb_path())
|
|
||||||
|
|
13
pyav_by8
Normal file
13
pyav_by8
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
try to create an image with a custom buffer, format rbga
|
||||||
|
image is 278x57
|
||||||
|
got 63384 bytes, need 63840 bytes
|
||||||
|
63384=278*57*4, checks out. why 63840?
|
||||||
|
factor 63384=2*2*2*3*19*139
|
||||||
|
factor 63840=2*2*2*2*2*3*5*7*19
|
||||||
|
factor 278=2*139
|
||||||
|
factor 57=3*19
|
||||||
|
factor 4=2*2
|
||||||
|
63840 doesn't have 139 as a factor... if we divide by the other two we get 2*2*2*5*7=280
|
||||||
|
is it padding an extra pixel on each side? no, reducing video frame width to 276 still demands 63840 bytes.
|
||||||
|
does stretching image to 280 work? yes! so it has to be a multiple of 5... or 10, or 20. and larger than the requested dimension.
|
||||||
|
after some further fiddling, it's 8.
|
1
setup.py
1
setup.py
|
@ -36,6 +36,7 @@ setup(
|
||||||
'eureka = dcc.eureka:Eureka',
|
'eureka = dcc.eureka:Eureka',
|
||||||
'ls = dcc.ls:List',
|
'ls = dcc.ls:List',
|
||||||
'configure = dcc.configure:Configure',
|
'configure = dcc.configure:Configure',
|
||||||
|
'concat = dcc.concat:Concat',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue