#!/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 DFPWM_ENCODER_EXECUTABLE = "aucmp" FFMPEG_EXECUTABLE = "ffmpeg" 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] = [] 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([ 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 titles: list[bytes] = [] positions: list[tuple[int, int]] = [] for file in filelist: titles.append(file.name.removesuffix(".dfpwm").encode("ascii", errors="replace")) 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) written_bytes += fout.write(pos[0].to_bytes(4, "little")) written_bytes += fout.write(pos[0].to_bytes(4, "little")) 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)")