cc-stuff/tape-playlist.py

83 lines
3.6 KiB
Python

#!/usr/bin/env python3
# x-run: python3 % /mnt/windows/Users/user/Music/vgm/night-in-the-woods/2014-lost-constellation /tmp/lost-constellation.dfpwm
from tempfile import TemporaryDirectory
from subprocess import Popen, PIPE
from sys import argv
from pathlib import Path
from os.path import splitext
from math import ceil
from re import search
DFPWM_ENCODER_EXECUTABLE = "aucmp"
FFMPEG_EXECUTABLE = "ffmpeg"
FFPROBE_EXECUTABLE = "ffprobe"
DFPWM_SAMPLE_RATE = 6000
TAPE_SIZES = [ 2, 4, 8, 16, 32, 64, 128 ]
input_folder = Path(argv[1])
output_file = Path(argv[2])
with TemporaryDirectory(prefix="tapedrive") as tmpdir_str:
tmpdir = Path(tmpdir_str)
filelist: list[Path] = []
titles: list[bytes] = []
for i, name in enumerate(input_folder.rglob("*.mp3")):
if i >= 48:
print(f"more than 48 tracks, skipping {name}")
continue
encoded_file = tmpdir / (splitext(name.name)[0] + ".dfpwm")
filelist.append(encoded_file)
with encoded_file.open("wb") as fout:
with Popen([ FFPROBE_EXECUTABLE, name ], stderr=PIPE) as ffprobe:
metadata: str = ffprobe.stderr.read().decode() # type: ignore
if (match := search(r"title\s+: (.*)", metadata)):
titles.append(match.groups()[0].encode("ascii", errors="ignore"))
else:
titles.append(splitext(name.name)[0].encode("ascii", errors="ignore"))
with Popen([ DFPWM_ENCODER_EXECUTABLE ], stdout=fout, stdin=PIPE) as dfpwm:
with Popen([ FFMPEG_EXECUTABLE, "-i", name, "-f", "s8", "-ac", "1",
"-ar", "48k", "-" ], stdout=dfpwm.stdin) as ffmpeg:
ffmpeg.wait()
offset: int = 6000
positions: list[tuple[int, int]] = []
for file in filelist:
size = ceil(file.stat().st_size / DFPWM_SAMPLE_RATE) * DFPWM_SAMPLE_RATE
positions.append((offset, size))
offset += size
with output_file.open("wb") as fout:
print("Writing header...")
written_bytes: int = 0
for i in range(48):
name = (titles[i] if i < len(titles) else b"")[:117]
pos = positions[i] if i < len(titles) else (0, 0)
if i < len(titles):
print(f"{i:2d} {pos[0]} + {pos[1]} {name}")
written_bytes += fout.write(pos[0].to_bytes(4, "big"))
written_bytes += fout.write(pos[1].to_bytes(4, "big"))
written_bytes += fout.write(name)
written_bytes += fout.write(b"\x00" * (117 - len(name)))
if written_bytes != 6000:
print(f"!!! expected 6000 bytes to be written in header, but it's {written_bytes}")
print("Writing files...")
for i, (pos, file) in enumerate(zip(positions, filelist)):
print(f"Writing {file.name}")
if written_bytes != pos[0]:
print(f"!!! expected to be at {pos[0]}, rn at {written_bytes}")
with file.open("rb") as fin:
file_size: int = 0
while (chunk := fin.read(DFPWM_SAMPLE_RATE)):
file_size += fout.write(chunk)
written_bytes += file_size
padding_size = DFPWM_SAMPLE_RATE - (written_bytes % DFPWM_SAMPLE_RATE)
written_bytes += fout.write(b"\x00" * padding_size)
print("Use any of those tapes to store properly:")
for size in filter(lambda sz: sz * DFPWM_SAMPLE_RATE * 60 >= written_bytes, TAPE_SIZES):
print(f"{size} minutes ({size * DFPWM_SAMPLE_RATE * 60} bytes, {written_bytes * 100 / (size * DFPWM_SAMPLE_RATE * 60):7.3f}% used)")