1+ # pylint: disable=logging-fstring-interpolation, global-statement, missing-function-docstring, missing-module-docstring, broad-exception-caught
12import os
3+ import subprocess
24from pathlib import Path
5+ import shutil
6+ import logging
7+ import sys
38
4- def convert (filename ):
9+ # Full path to vgmstream-cli.exe, usually in your FModel's Output Directory
10+ # Example Path
11+ VGMSTREAM = Path (r"C:/FModel/Output/.data/vgmstream-cli.exe" )
512
6- my_file = open ("./txtp/" + filename , "r" )
7- data = my_file .read ()
13+ # ==================================================================================================
814
9- data_into_list = data .replace ('\n ' , ' ' ).split (" " )
15+ # Logs
16+ MAIN_LOG = "conversion_main.log"
17+ FAILED_LOG = "conversion_errors.log"
1018
19+ # Setup main logging
20+ logging .basicConfig (
21+ level = logging .INFO ,
22+ format = "%(asctime)s [%(levelname)s] %(message)s" ,
23+ handlers = [
24+ logging .FileHandler (MAIN_LOG , mode = "w" , encoding = "utf-8" ),
25+ logging .StreamHandler (),
26+ ],
27+ )
1128
12- for i in range (len (data_into_list )):
13- if data_into_list [i ].startswith ('wem' ):
1429
15- wavname = "./txtp/" + data_into_list [i ].split ('.' )[0 ] + '.wav'
30+ # Set up subfolders
31+ currentDirectory = Path (os .path .dirname (os .path .abspath (__file__ )))
32+ (currentDirectory / "out" ).mkdir (exist_ok = True )
33+ (currentDirectory / "txtp" / "wem" ).mkdir (parents = True , exist_ok = True )
1634
17- if os .path .isfile (wavname ):
18- os .rename (wavname , "./out/" + filename .split ('.' )[0 ] + '_' + str (i ) + '.wav' )
35+ # Check if vgmstream exists
36+ if not VGMSTREAM .exists ():
37+ logging .critical (
38+ f"vgmstream-cli.exe not found at { VGMSTREAM } . It should have been downloaded by FModel. Edit the script if necessary to change the path."
39+ )
40+ sys .exit (1 )
41+ else :
42+ logging .info (f"Using vgmstream-cli.exe at { VGMSTREAM } " )
43+
44+ # Setup failed conversion logging (Will overwrite each run, could be replaced with RotatingFileHandler but needs script changes)
45+ failed_logger = logging .getLogger ("failed" )
46+ failed_handler = logging .FileHandler (FAILED_LOG , mode = "w" , encoding = "utf-8" )
47+ failed_handler .setLevel (logging .ERROR )
48+ failed_logger .addHandler (failed_handler )
49+ failed_logger .propagate = False
50+
51+ # Counters for summary
52+ total_wems = 0
53+ converted_count = 0
54+ skipped_count = 0
55+ failed_count = 0
56+
57+
58+ # Step 1: Convert all .wem files into ./out_temp/wem/ (flat), mapping to digit folders
59+ def wem_to_wav (input_root , temp_root ):
60+ global converted_count , skipped_count , failed_count
61+ input_root = Path (input_root )
62+ temp_wem_root = Path (temp_root ) / "wem"
63+
64+ # CLEAN temp folder
65+ if temp_wem_root .exists ():
66+ shutil .rmtree (temp_wem_root )
67+ temp_wem_root .mkdir (parents = True , exist_ok = True )
68+
69+ wav_filename_to_digit_folder = {}
70+
71+ for folder , _ , files in os .walk (input_root ):
72+ folder_path = Path (folder )
73+
74+ # If we are in root (txtp/wem) use "root" as folder name
75+ digit_folder = "root" if folder_path == input_root else folder_path .name
76+
77+ for file in files :
78+ ext = Path (file ).suffix .lower ()
79+ base_name = Path (file ).stem
80+ wav_name = base_name + ".wav"
81+
82+ wem_path = folder_path / file
83+ wav_path = temp_wem_root / wav_name
84+ wav_filename_to_digit_folder [wav_name ] = digit_folder
85+
86+ final_out_path = Path ("out" ) / digit_folder / wav_name
87+ if wav_path .exists () or final_out_path .exists ():
88+ skipped_count += 1
89+ logging .info (f"Skipping existing WAV: { wav_path } or { final_out_path } " )
90+ continue
91+
92+ if ext == ".wem" :
93+ # Convert wem → wav
94+ logging .info (f"Converting: { wem_path } → { wav_path } " )
95+ result = subprocess .run (
96+ [str (VGMSTREAM ), "-o" , str (wav_path ), str (wem_path )],
97+ capture_output = True ,
98+ text = True ,
99+ check = False ,
100+ )
101+ if result .returncode != 0 or not wav_path .exists ():
102+ failed_count += 1
103+ logging .error (f"Conversion failed for { wem_path } : { result .stderr } " )
104+ failed_logger .error (str (wem_path ))
105+ else :
106+ converted_count += 1
107+ logging .info (f"Converted { wem_path } successfully" )
108+
109+ elif ext == ".wav" :
110+ # Copy pre-existing wav into temp for rename step
111+ try :
112+ shutil .copy2 (wem_path , wav_path )
113+ skipped_count += 1
114+ logging .info (
115+ f"Using existing WAV instead of converting: { wem_path } → { wav_path } "
116+ )
117+ except Exception as e :
118+ failed_count += 1
119+ logging .error (f"Failed to copy existing WAV { wem_path } : { e } " )
120+ failed_logger .error (str (wem_path ))
121+ return wav_filename_to_digit_folder
122+
123+
124+ # Step 2: Rename .wav files based on .txtp references
125+ def convert (filename , wav_root , out_root , mapping ):
126+ wav_root = Path (wav_root )
127+ out_root = Path (out_root )
128+ txtp_path = Path ("txtp" ) / filename
129+
130+ try :
131+ with open (txtp_path , "r" , encoding = "utf-8" ) as my_file :
132+ data = my_file .read ()
133+ except Exception as e :
134+ logging .error (f"Failed to read { txtp_path } : { e } " )
135+ return
136+
137+ tokens = data .replace ("\n " , " " ).split (" " )
138+
139+ for i , token in enumerate (tokens ):
140+ if token .startswith ("wem" ):
141+ wav_file_only = Path (token ).stem + ".wav"
142+ wavname = wav_root / wav_file_only
143+ digit_folder = mapping .get (wavname .name , "unknown" )
144+ out_folder = out_root / digit_folder
145+ out_folder .mkdir (parents = True , exist_ok = True )
146+ new_name = out_folder / f"{ filename .split ('.' )[0 ]} _{ i } .wav"
147+
148+ if new_name .exists ():
149+ logging .info (f"Skipping already renamed WAV: { new_name } " )
150+ continue
151+
152+ if wavname .exists ():
153+ try :
154+ shutil .move (str (wavname ), str (new_name ))
155+ logging .info (f"Renamed { wavname } → { new_name } " )
156+ except Exception as e :
157+ logging .error (f"Failed to rename { wavname } : { e } " )
19158 else :
20- print (wavname + " not found." )
159+ logging .warning (f"{ wavname } not found." )
160+
161+
162+ # Step 3: Retry failed conversions
163+ def retry_failed_conversions (temp_wav_root ):
164+ global converted_count , failed_count
165+ failed_path = Path (FAILED_LOG )
166+ if not failed_path .exists ():
167+ logging .info ("No failed conversions to retry." )
168+ return
169+
170+ logging .info ("Retrying failed conversions..." )
171+
172+ # Read and truncate the failed log for this retry
173+ with open (failed_path , "r+" , encoding = "utf-8" ) as f :
174+ failed_files = [line .strip () for line in f .readlines () if line .strip ()]
175+ f .seek (0 )
176+ f .truncate (0 )
177+
178+ new_failures = 0 # counter for files that fail again
179+
180+ for wem_path_str in failed_files :
181+ wem_path = Path (wem_path_str )
182+ wav_name = wem_path .stem + ".wav"
183+ wav_path = temp_wav_root / wav_name
184+
185+ if wav_path .exists ():
186+ logging .info (f"Skipping existing WAV: { wav_path } " )
187+ continue
188+
189+ logging .info (f"Retrying conversion: { wem_path } → { wav_path } " )
190+ result = subprocess .run (
191+ [str (VGMSTREAM ), "-o" , str (wav_path ), str (wem_path )],
192+ capture_output = True ,
193+ text = True ,
194+ check = False ,
195+ )
196+ if result .returncode != 0 or not wav_path .exists ():
197+ new_failures += 1
198+ logging .error (
199+ f"Conversion failed a 2nd time: { wem_path } . "
200+ "Either the .wem file is corrupt, broken, or there is no .txtp path for that file. "
201+ "Consider a manual approach or ask for help in the Discord."
202+ )
203+ failed_logger .error (str (wem_path ))
204+ else :
205+ # Count as converted only if it actually succeeds now
206+ converted_count += 1
207+ logging .info (f"Successfully converted on retry: { wem_path } " )
208+
209+ # Update failed_count to reflect files that truly failed after retry
210+ failed_count = new_failures
211+
212+
213+ # Main driver
214+ if __name__ == "__main__" :
215+ wem_root = Path ("txtp/wem" )
216+ wav_temp_root = Path ("out_temp" ) / "wem"
217+ out_root = Path ("out" )
218+
219+ logging .info ("Starting .wem → .wav conversion" )
220+ mapping = wem_to_wav (wem_root , Path ("out_temp" ))
21221
22- my_file .close ()
222+ logging .info ("Starting .wav renaming based on .txtp files" )
223+ txtp_files = [f for f in Path ("txtp" ).glob ("*.txtp" )]
224+ for file_path in txtp_files :
225+ convert (file_path .name , wav_temp_root , out_root , mapping )
23226
24- relevant_path = "./txtp/"
25- included_extensions = ['txtp' ]
26- file_names = [fn for fn in os .listdir (relevant_path )
27- if any (fn .endswith (ext ) for ext in included_extensions )]
227+ # Retry any failed conversions
228+ retry_failed_conversions (wav_temp_root )
28229
230+ # Clean up temp folder
231+ if wav_temp_root .parent .exists ():
232+ shutil .rmtree (wav_temp_root .parent )
233+ logging .info (f"Temporary folder { wav_temp_root .parent } deleted" )
29234
30- for file_name in file_names :
31- convert (file_name )
235+ # Final summary
236+ logging .info ("===================================" )
237+ logging .info (f"Total .wem files found: { total_wems } " )
238+ logging .info (f"Successfully converted: { converted_count } " )
239+ logging .info (f"Skipped (already exists): { skipped_count } " )
240+ logging .info (f"Failed conversions: { failed_count } " )
241+ logging .info ("Conversion and renaming complete" )
242+ logging .info ("===================================" )
0 commit comments