#!/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)")