2023-10-17 15:28:43 +03:00
|
|
|
#!/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
|
2023-10-17 18:26:18 +03:00
|
|
|
from re import search
|
2023-10-17 15:28:43 +03:00
|
|
|
|
|
|
|
DFPWM_ENCODER_EXECUTABLE = "aucmp"
|
|
|
|
FFMPEG_EXECUTABLE = "ffmpeg"
|
2023-10-17 18:26:18 +03:00
|
|
|
FFPROBE_EXECUTABLE = "ffprobe"
|
2023-10-17 15:28:43 +03:00
|
|
|
|
|
|
|
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] = []
|
2023-10-17 18:26:18 +03:00
|
|
|
titles: list[bytes] = []
|
2023-10-17 15:28:43 +03:00
|
|
|
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:
|
2023-10-17 18:26:18 +03:00
|
|
|
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"))
|
2023-10-17 15:28:43 +03:00
|
|
|
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)
|
2023-10-17 16:03:16 +03:00
|
|
|
if i < len(titles):
|
2023-10-17 18:26:18 +03:00
|
|
|
print(f"{i:2d} {pos[0]} + {pos[1]} {name}")
|
2023-10-17 16:03:16 +03:00
|
|
|
written_bytes += fout.write(pos[0].to_bytes(4, "big"))
|
|
|
|
written_bytes += fout.write(pos[1].to_bytes(4, "big"))
|
2023-10-17 15:28:43 +03:00
|
|
|
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)")
|