mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2025-08-15 10:27:46 +00:00
Add --video_mode HGR|DHGR option to support both video modes uniformly.
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
import movie
|
import movie
|
||||||
|
import video
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Transcode videos to ][Vision format.')
|
description='Transcode videos to ][Vision format.')
|
||||||
@@ -23,6 +24,11 @@ parser.add_argument(
|
|||||||
help='Allows skipping frames of input video to lower effective output '
|
help='Allows skipping frames of input video to lower effective output '
|
||||||
'frame rate, which may give better quality for some videos.'
|
'frame rate, which may give better quality for some videos.'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--video_mode', type=str, choices=video.Mode.__members__.keys(),
|
||||||
|
help='Video display mode to encode for (HGR/DHGR)'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
filename = args.input
|
filename = args.input
|
||||||
@@ -30,7 +36,8 @@ def main(args):
|
|||||||
filename,
|
filename,
|
||||||
every_n_video_frames=args.every_n_video_frames,
|
every_n_video_frames=args.every_n_video_frames,
|
||||||
audio_normalization=args.audio_normalization,
|
audio_normalization=args.audio_normalization,
|
||||||
max_bytes_out = 1024. * 1024 * args.max_output_mb
|
max_bytes_out=1024. * 1024 * args.max_output_mb,
|
||||||
|
video_mode=video.Mode[args.video_mode]
|
||||||
)
|
)
|
||||||
|
|
||||||
print("Input frame rate = %f" % m.video.input_frame_rate)
|
print("Input frame rate = %f" % m.video.input_frame_rate)
|
||||||
|
@@ -13,15 +13,17 @@ class Movie:
|
|||||||
self, filename: str,
|
self, filename: str,
|
||||||
every_n_video_frames: int = 1,
|
every_n_video_frames: int = 1,
|
||||||
audio_normalization: float = None,
|
audio_normalization: float = None,
|
||||||
max_bytes_out: int = None
|
max_bytes_out: int = None,
|
||||||
|
video_mode: video.Mode = video.Mode.HGR,
|
||||||
):
|
):
|
||||||
self.filename = filename # type: str
|
self.filename = filename # type: str
|
||||||
self.every_n_video_frames = every_n_video_frames # type: int
|
self.every_n_video_frames = every_n_video_frames # type: int
|
||||||
self.max_bytes_out = max_bytes_out # type: int
|
self.max_bytes_out = max_bytes_out # type: int
|
||||||
|
self.video_mode = video_mode # type: video.Mode
|
||||||
|
|
||||||
self.audio = audio.Audio(
|
self.audio = audio.Audio(
|
||||||
filename, normalization=audio_normalization) # type: audio.Audio
|
filename, normalization=audio_normalization) # type: audio.Audio
|
||||||
self.video = video.Video(filename) # type: video.Video
|
self.video = video.Video(filename, mode=video_mode) # type: video.Video
|
||||||
|
|
||||||
self.stream_pos = 0 # type: int
|
self.stream_pos = 0 # type: int
|
||||||
|
|
||||||
@@ -44,8 +46,10 @@ class Movie:
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
video_frames = self.video.frames()
|
video_frames = self.video.frames()
|
||||||
|
main_seq = None
|
||||||
|
aux_seq = None
|
||||||
|
|
||||||
yield opcodes.Header(mode=video.Mode.DHGR)
|
yield opcodes.Header(mode=self.video_mode)
|
||||||
|
|
||||||
for au in self.audio.audio_stream():
|
for au in self.audio.audio_stream():
|
||||||
self.cycles += self.audio.cycles_per_tick
|
self.cycles += self.audio.cycles_per_tick
|
||||||
@@ -56,9 +60,11 @@ class Movie:
|
|||||||
print("Starting frame %d" % self.video.frame_number)
|
print("Starting frame %d" % self.video.frame_number)
|
||||||
main_seq = self.video.encode_frame(
|
main_seq = self.video.encode_frame(
|
||||||
main, self.video.memory_map, self.video.update_priority)
|
main, self.video.memory_map, self.video.update_priority)
|
||||||
aux_seq = self.video.encode_frame(
|
|
||||||
aux, self.video.aux_memory_map,
|
if aux:
|
||||||
self.video.aux_update_priority)
|
aux_seq = self.video.encode_frame(
|
||||||
|
aux, self.video.aux_memory_map,
|
||||||
|
self.video.aux_update_priority)
|
||||||
|
|
||||||
# au has range -15 .. 16 (step=1)
|
# au has range -15 .. 16 (step=1)
|
||||||
# Tick cycles are units of 2
|
# Tick cycles are units of 2
|
||||||
@@ -95,10 +101,13 @@ class Movie:
|
|||||||
if socket_pos >= 2044:
|
if socket_pos >= 2044:
|
||||||
# 2 op_ack address bytes + 2 payload bytes from ACK must
|
# 2 op_ack address bytes + 2 payload bytes from ACK must
|
||||||
# terminate 2K stream frame
|
# terminate 2K stream frame
|
||||||
|
if self.video_mode == video.Mode.DHGR:
|
||||||
|
# Flip-flop between MAIN and AUX banks
|
||||||
|
self.aux_memory_bank = not self.aux_memory_bank
|
||||||
|
|
||||||
yield from self._emit_bytes(opcodes.Ack(self.aux_memory_bank))
|
yield from self._emit_bytes(opcodes.Ack(self.aux_memory_bank))
|
||||||
assert self.stream_pos % 2048 == 0, self.stream_pos % 2048
|
assert self.stream_pos % 2048 == 0, self.stream_pos % 2048
|
||||||
# Flip-flop between MAIN and AUX banks
|
|
||||||
self.aux_memory_bank = not self.aux_memory_bank
|
|
||||||
yield from self._emit_bytes(op)
|
yield from self._emit_bytes(op)
|
||||||
|
|
||||||
yield from self.done()
|
yield from self.done()
|
||||||
|
@@ -112,7 +112,7 @@ class Ack(Opcode):
|
|||||||
def emit_data(self) -> Iterator[int]:
|
def emit_data(self) -> Iterator[int]:
|
||||||
# Flip $C054 or $C055 soft-switches to steer subsequent writes to
|
# Flip $C054 or $C055 soft-switches to steer subsequent writes to
|
||||||
# MAIN/AUX screen memory
|
# MAIN/AUX screen memory
|
||||||
yield 0x54 if self.aux_active else 0x55
|
yield 0x55 if self.aux_active else 0x54
|
||||||
# Dummy byte to pad out TCP frame
|
# Dummy byte to pad out TCP frame
|
||||||
yield 0xff
|
yield 0xff
|
||||||
|
|
||||||
|
@@ -32,8 +32,10 @@ class Video:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
filename: str,
|
filename: str,
|
||||||
|
mode: Mode = Mode.HGR
|
||||||
):
|
):
|
||||||
self.filename = filename # type: str
|
self.filename = filename # type: str
|
||||||
|
self.mode = mode # type: Mode
|
||||||
|
|
||||||
self._reader = skvideo.io.FFmpegReader(filename)
|
self._reader = skvideo.io.FFmpegReader(filename)
|
||||||
|
|
||||||
@@ -51,13 +53,14 @@ class Video:
|
|||||||
# Initialize empty screen
|
# Initialize empty screen
|
||||||
self.memory_map = screen.MemoryMap(
|
self.memory_map = screen.MemoryMap(
|
||||||
screen_page=1) # type: screen.MemoryMap
|
screen_page=1) # type: screen.MemoryMap
|
||||||
self.aux_memory_map = screen.MemoryMap(
|
if self.mode == mode.DHGR:
|
||||||
screen_page=1) # type: screen.MemoryMap
|
self.aux_memory_map = screen.MemoryMap(
|
||||||
|
screen_page=1) # type: screen.MemoryMap
|
||||||
|
|
||||||
# Accumulates pending edit weights across frames
|
# Accumulates pending edit weights across frames
|
||||||
self.update_priority = np.zeros((32, 256), dtype=np.int64)
|
self.update_priority = np.zeros((32, 256), dtype=np.int64)
|
||||||
|
if self.mode == mode.DHGR:
|
||||||
self.aux_update_priority = np.zeros((32, 256), dtype=np.int64)
|
self.aux_update_priority = np.zeros((32, 256), dtype=np.int64)
|
||||||
|
|
||||||
def tick(self, cycles: int) -> bool:
|
def tick(self, cycles: int) -> bool:
|
||||||
if cycles > (self.cycles_per_frame * self.frame_number):
|
if cycles > (self.cycles_per_frame * self.frame_number):
|
||||||
@@ -117,30 +120,65 @@ class Video:
|
|||||||
|
|
||||||
q = queue.Queue(maxsize=10)
|
q = queue.Queue(maxsize=10)
|
||||||
|
|
||||||
|
def _hgr_decode(_idx, _frame):
|
||||||
|
outfile = "%s/%08dC.BIN" % (frame_dir, _idx)
|
||||||
|
bmpfile = "%s/%08d.bmp" % (frame_dir, _idx)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.stat(outfile)
|
||||||
|
except FileNotFoundError:
|
||||||
|
_frame = _frame.resize((280, 192), resample=Image.LANCZOS)
|
||||||
|
_frame.save(bmpfile)
|
||||||
|
|
||||||
|
# TODO: parametrize palette
|
||||||
|
subprocess.call([
|
||||||
|
"/usr/local/bin/bmp2dhr", bmpfile, "hgr",
|
||||||
|
"P0", # Kegs32 RGB Color palette(for //gs playback)
|
||||||
|
"D9" # Buckels dither
|
||||||
|
])
|
||||||
|
|
||||||
|
os.remove(bmpfile)
|
||||||
|
|
||||||
|
_main = np.fromfile(outfile, dtype=np.uint8)
|
||||||
|
|
||||||
|
return _main, None
|
||||||
|
|
||||||
|
def _dhgr_decode(_idx, _frame):
|
||||||
|
mainfile = "%s/%08d.BIN" % (frame_dir, _idx)
|
||||||
|
auxfile = "%s/%08d.AUX" % (frame_dir, _idx)
|
||||||
|
|
||||||
|
bmpfile = "%s/%08d.bmp" % (frame_dir, _idx)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.stat(mainfile)
|
||||||
|
os.stat(auxfile)
|
||||||
|
except FileNotFoundError:
|
||||||
|
_frame = _frame.resize((280, 192), resample=Image.LANCZOS)
|
||||||
|
_frame.save(bmpfile)
|
||||||
|
|
||||||
|
# TODO: parametrize palette
|
||||||
|
subprocess.call([
|
||||||
|
"/usr/local/bin/bmp2dhr", bmpfile, "dhgr",
|
||||||
|
"P0", # Kegs32 RGB Color palette (for //gs playback)
|
||||||
|
"A", # Output separate .BIN and .AUX files
|
||||||
|
"D9" # Buckels dither
|
||||||
|
])
|
||||||
|
|
||||||
|
os.remove(bmpfile)
|
||||||
|
|
||||||
|
_main = np.fromfile(mainfile, dtype=np.uint8)
|
||||||
|
_aux = np.fromfile(auxfile, dtype=np.uint8)
|
||||||
|
|
||||||
|
return _main, _aux
|
||||||
|
|
||||||
def worker():
|
def worker():
|
||||||
"""Invoke bmp2dhr to encode input image frames and push to queue."""
|
"""Invoke bmp2dhr to encode input image frames and push to queue."""
|
||||||
for _idx, _frame in enumerate(self._frame_grabber()):
|
for _idx, _frame in enumerate(self._frame_grabber()):
|
||||||
mainfile = "%s/%08d.BIN" % (frame_dir, _idx)
|
if self.mode == Mode.DHGR:
|
||||||
auxfile = "%s/%08d.AUX" % (frame_dir, _idx)
|
res = _dhgr_decode(_idx, _frame)
|
||||||
|
else:
|
||||||
bmpfile = "%s/%08d.bmp" % (frame_dir, _idx)
|
res = _hgr_decode(_idx, _frame)
|
||||||
|
q.put(res)
|
||||||
try:
|
|
||||||
os.stat(mainfile)
|
|
||||||
os.stat(auxfile)
|
|
||||||
except FileNotFoundError:
|
|
||||||
_frame = _frame.resize((280, 192), resample=Image.LANCZOS)
|
|
||||||
_frame.save(bmpfile)
|
|
||||||
|
|
||||||
subprocess.call(
|
|
||||||
["/usr/local/bin/bmp2dhr", bmpfile, "dhgr", "P0", "A",
|
|
||||||
"D9"])
|
|
||||||
|
|
||||||
os.remove(bmpfile)
|
|
||||||
|
|
||||||
main = np.fromfile(mainfile, dtype=np.uint8)
|
|
||||||
aux = np.fromfile(auxfile, dtype=np.uint8)
|
|
||||||
q.put((main, aux))
|
|
||||||
|
|
||||||
q.put((None, None))
|
q.put((None, None))
|
||||||
|
|
||||||
@@ -148,14 +186,19 @@ class Video:
|
|||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
main, aux = q.get()
|
main, aux = q.get()
|
||||||
if main is None:
|
if main is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
yield (
|
main_map = screen.FlatMemoryMap(
|
||||||
screen.FlatMemoryMap(screen_page=1, data=main).to_memory_map(),
|
screen_page=1, data=main).to_memory_map()
|
||||||
screen.FlatMemoryMap(screen_page=1, data=aux).to_memory_map()
|
if aux is None:
|
||||||
)
|
aux_map = None
|
||||||
|
else:
|
||||||
|
aux_map = screen.FlatMemoryMap(
|
||||||
|
screen_page=1, data=aux).to_memory_map()
|
||||||
|
yield (main_map, aux_map)
|
||||||
q.task_done()
|
q.task_done()
|
||||||
|
|
||||||
t.join()
|
t.join()
|
||||||
|
Reference in New Issue
Block a user