Advertisement
Guest User

Untitled

a guest
May 21st, 2023
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.07 KB | None | 0 0
  1. import os
  2. import re
  3. import struct
  4. import subprocess
  5. import sys
  6.  
  7. FLUIDSYNTH_LOCATION = "./fluidsynth/fluidsynth.exe"
  8. # gzdoom.sf2 sounds weird compared to gzdoom itself (synths too loud, drums too quiet);
  9. # if you don't have a particular soundfont in mind then Helion's works fine:
  10. # https://github.com/Helion-Engine/Helion/blob/master/Client/SoundFonts/Default.sf2
  11. SOUNDFONT_LOCATION = "./soundfont.sf2"
  12. ADJUST_MP3_VOLUME = True # see convert()
  13. OUTPUT_BASE_FOLDER = "./output"
  14.  
  15. MUSIC_NAME_MAP = {
  16.     "D_DM2TTL": "title",
  17.     "D_READ_M": "text screen",
  18.     "D_DM2INT": "intermission",
  19.     "D_RUNNIN": "MAP01",
  20.     "D_STALKS": "MAP02",
  21.     "D_COUNTD": "MAP03",
  22.     "D_BETWEE": "MAP04",
  23.     "D_DOOM": "MAP05",
  24.     "D_THE_DA": "MAP06",
  25.     "D_SHAWN": "MAP07",
  26.     "D_DDTBLU": "MAP08",
  27.     "D_IN_CIT": "MAP09",
  28.     "D_DEAD": "MAP10",
  29.     "D_STLKS2": "MAP11",
  30.     "D_THEDA2": "MAP12",
  31.     "D_DOOM2": "MAP13",
  32.     "D_DDTBL2": "MAP14",
  33.     "D_RUNNI2": "MAP15",
  34.     "D_DEAD2": "MAP16",
  35.     "D_STLKS3": "MAP17",
  36.     "D_ROMERO": "MAP18",
  37.     "D_SHAWN2": "MAP19",
  38.     "D_MESSAG": "MAP20",
  39.     "D_COUNT2": "MAP21",
  40.     "D_DDTBL3": "MAP22",
  41.     "D_AMPIE": "MAP23",
  42.     "D_THEDA3": "MAP24",
  43.     "D_ADRIAN": "MAP25",
  44.     "D_MESSG2": "MAP26",
  45.     "D_ROMER2": "MAP27",
  46.     "D_TENSE": "MAP28",
  47.     "D_SHAWN3": "MAP29",
  48.     "D_OPENIN": "MAP30",
  49.     "D_EVIL": "MAP31 & cast sequence",
  50.     "D_ULTIMA": "MAP32",
  51. }
  52.  
  53.  
  54. def get_wad_musics(wad_file: str) -> list[tuple[str, bytes]]:
  55.     musics: list[tuple[str, bytes]] = []
  56.     with open(wad_file, "rb") as f:
  57.         HEADER_SIZE = 12
  58.         DIRECTORY_ENTRY_SIZE = 16
  59.         magic_num, directory_entry_count, directory_offset = struct.unpack("<4sII", f.read(HEADER_SIZE))
  60.         assert magic_num in (b"IWAD", b"PWAD")
  61.  
  62.         lump_info = []
  63.         f.seek(directory_offset)
  64.         for _ in range(directory_entry_count):
  65.             lump_index, lump_size, lump_name_raw = struct.unpack("<II8s", f.read(DIRECTORY_ENTRY_SIZE))
  66.             lump_name = lump_name_raw.decode("ascii").rstrip("\0")
  67.             lump_info.append((lump_index, lump_size, lump_name))
  68.  
  69.         for lump_index, lump_size, lump_name in lump_info:
  70.             print(lump_name)
  71.             f.seek(lump_index)
  72.             lump_data = f.read(lump_size)
  73.             if lump_data[:4] == b'MThd': # TODO: get MUS as well
  74.                 musics.append((lump_name, lump_data))
  75.    
  76.     return musics
  77.  
  78.  
  79. def convert(midi_path: str, mp3_path: str) -> None:
  80.  
  81.     # render the midi
  82.     midi_proc = subprocess.run([
  83.         FLUIDSYNTH_LOCATION,
  84.         "--quiet",
  85.         "-T", "raw",
  86.         "-F", "-",
  87.         SOUNDFONT_LOCATION,
  88.         midi_path
  89.     ], stdout=subprocess.PIPE, check=True)
  90.     raw_audio = midi_proc.stdout
  91.  
  92.     # determine how much to boost volume if desired;
  93.     # we could use replaygain tags for personal listening but for internet stuff
  94.     # an adjustment should be made because it's pretty quiet by default
  95.     volume_diff: float = 0
  96.     if ADJUST_MP3_VOLUME:
  97.         volume_proc = subprocess.run([
  98.             "ffmpeg",
  99.             "-hide_banner",
  100.             "-f", "s32le",
  101.             "-i", "-",
  102.             "-filter:a", "volumedetect",
  103.             "-f", "null",
  104.             "-"
  105.         ], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, input=raw_audio, check=True)
  106.         volume_info = volume_proc.stderr
  107.         mean_volume = float(re.findall(rb"mean_volume: ([\d.-]+) dB", volume_info)[0])
  108.         # "ReplayGain nominally plays at -14 dB relative to full-scale leaving
  109.         # 14 dB of headroom for reproduction of dynamic material."
  110.         volume_diff = -14 - mean_volume
  111.  
  112.     # convert to mp3 with the volume adjustment
  113.     subprocess.run([
  114.         "ffmpeg",
  115.         "-hide_banner",
  116.         "-loglevel", "warning",
  117.         "-f", "s32le",
  118.         "-i", "-",
  119.         "-filter:a", f"volume={volume_diff}dB",
  120.         "-b:a", "192k",
  121.         "-y",
  122.         mp3_path
  123.     ], input=raw_audio, check=True)
  124.  
  125.  
  126. def ensure_folder_exists(path: str) -> None:
  127.     if not os.path.exists(path):
  128.         os.makedirs(path)
  129.  
  130.  
  131. def process_wads(wads: list[str]) -> None:
  132.     for fpath in wads:
  133.         fn = os.path.split(fpath)[-1]
  134.         fn_stub, ext = os.path.splitext(fn)
  135.         assert ext.lower() == ".wad"
  136.  
  137.         wad_musics = get_wad_musics(fpath)
  138.  
  139.         output_folder = os.path.join(OUTPUT_BASE_FOLDER, fn_stub)
  140.         midi_folder = os.path.join(output_folder, "midi")
  141.         mp3_folder = output_folder # os.path.join(output_folder, "mp3")
  142.         ensure_folder_exists(midi_folder)
  143.         ensure_folder_exists(mp3_folder)
  144.  
  145.         for name, data in wad_musics:
  146.             file_stub = MUSIC_NAME_MAP.get(name, name)
  147.             midi_path = os.path.join(midi_folder, f"{file_stub}.mid")
  148.             mp3_path = os.path.join(mp3_folder, f"{file_stub}.mp3")
  149.             print(f"{midi_path} ({name})")
  150.             with open(midi_path, "wb") as f2:
  151.                 f2.write(data)
  152.             convert(midi_path, mp3_path)
  153.  
  154.  
  155. if __name__ == "__main__":
  156.     process_wads([x for x in sys.argv[1:] if x.lower().endswith(".wad")])
  157.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement