Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ asciidoc:
# Custom text replacements. Built-in ones can be found here:
# https://github.com/asciidoctor/asciidoctor/blob/917d3800a08a8f283a8d05beb08bb75af1673de5/lib/asciidoctor.rb#L391
attributes:
# Increase max table of content nesting levels from 2 to 3 https://docs.antora.org/antora/latest/page/page-attributes/#access-attributes-from-ui-template
page-toclevels: 3@
# Zero-width joiner (U+200D) to avoid being split across lines
zero-width-join: ‍
# Uses zero-width joiner (U+200D) to avoid being split across lines
cpp: C‍+‍+
# Use backslash without it escaping things in other situations
literal-backslash: \
# To keep paths from breaking across lines
NoBreakBackslash: ‍\‍
NoBreakBackslash: ‍\‍
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"structs",
"subfolder",
"subfolders",
"toclevels",
"Tolgee",
"Treelo",
"uasset",
Expand Down
245 changes: 228 additions & 17 deletions modules/ROOT/attachments/Development/SatisfactoryAudioRenamer/convert.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,242 @@
# pylint: disable=logging-fstring-interpolation, global-statement, missing-function-docstring, missing-module-docstring, broad-exception-caught
import os
import subprocess
from pathlib import Path
import shutil
import logging
import sys

def convert(filename):
# Full path to vgmstream-cli.exe, usually in your FModel's Output Directory
# Example Path
VGMSTREAM = Path(r"C:/FModel/Output/.data/vgmstream-cli.exe")

my_file = open("./txtp/" + filename, "r")
data = my_file.read()
# ==================================================================================================

data_into_list = data.replace('\n', ' ').split(" ")
# Logs
MAIN_LOG = "conversion_main.log"
FAILED_LOG = "conversion_errors.log"

# Setup main logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(MAIN_LOG, mode="w", encoding="utf-8"),
logging.StreamHandler(),
],
)

for i in range(len(data_into_list)):
if data_into_list[i].startswith('wem'):

wavname = "./txtp/" + data_into_list[i].split('.')[0] + '.wav'
# Set up subfolders
currentDirectory = Path(os.path.dirname(os.path.abspath(__file__)))
(currentDirectory / "out").mkdir(exist_ok=True)
(currentDirectory / "txtp" / "wem").mkdir(parents=True, exist_ok=True)

if os.path.isfile(wavname):
os.rename(wavname, "./out/" + filename.split('.')[0] + '_' + str(i) + '.wav')
# Check if vgmstream exists
if not VGMSTREAM.exists():
logging.critical(
f"vgmstream-cli.exe not found at {VGMSTREAM}. It should have been downloaded by FModel. Edit the script if necessary to change the path."
)
sys.exit(1)
else:
logging.info(f"Using vgmstream-cli.exe at {VGMSTREAM}")

# Setup failed conversion logging (Will overwrite each run, could be replaced with RotatingFileHandler but needs script changes)
failed_logger = logging.getLogger("failed")
failed_handler = logging.FileHandler(FAILED_LOG, mode="w", encoding="utf-8")
failed_handler.setLevel(logging.ERROR)
failed_logger.addHandler(failed_handler)
failed_logger.propagate = False

# Counters for summary
total_wems = 0
converted_count = 0
skipped_count = 0
failed_count = 0


# Step 1: Convert all .wem files into ./out_temp/wem/ (flat), mapping to digit folders
def wem_to_wav(input_root, temp_root):
global converted_count, skipped_count, failed_count
input_root = Path(input_root)
temp_wem_root = Path(temp_root) / "wem"

# CLEAN temp folder
if temp_wem_root.exists():
shutil.rmtree(temp_wem_root)
temp_wem_root.mkdir(parents=True, exist_ok=True)

wav_filename_to_digit_folder = {}

for folder, _, files in os.walk(input_root):
folder_path = Path(folder)

# If we are in root (txtp/wem) use "root" as folder name
digit_folder = "root" if folder_path == input_root else folder_path.name

for file in files:
ext = Path(file).suffix.lower()
base_name = Path(file).stem
wav_name = base_name + ".wav"

wem_path = folder_path / file
wav_path = temp_wem_root / wav_name
wav_filename_to_digit_folder[wav_name] = digit_folder

final_out_path = Path("out") / digit_folder / wav_name
if wav_path.exists() or final_out_path.exists():
skipped_count += 1
logging.info(f"Skipping existing WAV: {wav_path} or {final_out_path}")
continue

if ext == ".wem":
# Convert wem → wav
logging.info(f"Converting: {wem_path} → {wav_path}")
result = subprocess.run(
[str(VGMSTREAM), "-o", str(wav_path), str(wem_path)],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0 or not wav_path.exists():
failed_count += 1
logging.error(f"Conversion failed for {wem_path}: {result.stderr}")
failed_logger.error(str(wem_path))
else:
converted_count += 1
logging.info(f"Converted {wem_path} successfully")

elif ext == ".wav":
# Copy pre-existing wav into temp for rename step
try:
shutil.copy2(wem_path, wav_path)
skipped_count += 1
logging.info(
f"Using existing WAV instead of converting: {wem_path} → {wav_path}"
)
except Exception as e:
failed_count += 1
logging.error(f"Failed to copy existing WAV {wem_path}: {e}")
failed_logger.error(str(wem_path))
return wav_filename_to_digit_folder


# Step 2: Rename .wav files based on .txtp references
def convert(filename, wav_root, out_root, mapping):
wav_root = Path(wav_root)
out_root = Path(out_root)
txtp_path = Path("txtp") / filename

try:
with open(txtp_path, "r", encoding="utf-8") as my_file:
data = my_file.read()
except Exception as e:
logging.error(f"Failed to read {txtp_path}: {e}")
return

tokens = data.replace("\n", " ").split(" ")

for i, token in enumerate(tokens):
if token.startswith("wem"):
wav_file_only = Path(token).stem + ".wav"
wavname = wav_root / wav_file_only
digit_folder = mapping.get(wavname.name, "unknown")
out_folder = out_root / digit_folder
out_folder.mkdir(parents=True, exist_ok=True)
new_name = out_folder / f"{filename.split('.')[0]}_{i}.wav"

if new_name.exists():
logging.info(f"Skipping already renamed WAV: {new_name}")
continue

if wavname.exists():
try:
shutil.move(str(wavname), str(new_name))
logging.info(f"Renamed {wavname} → {new_name}")
except Exception as e:
logging.error(f"Failed to rename {wavname}: {e}")
else:
print(wavname + " not found.")
logging.warning(f"{wavname} not found.")


# Step 3: Retry failed conversions
def retry_failed_conversions(temp_wav_root):
global converted_count, failed_count
failed_path = Path(FAILED_LOG)
if not failed_path.exists():
logging.info("No failed conversions to retry.")
return

logging.info("Retrying failed conversions...")

# Read and truncate the failed log for this retry
with open(failed_path, "r+", encoding="utf-8") as f:
failed_files = [line.strip() for line in f.readlines() if line.strip()]
f.seek(0)
f.truncate(0)

new_failures = 0 # counter for files that fail again

for wem_path_str in failed_files:
wem_path = Path(wem_path_str)
wav_name = wem_path.stem + ".wav"
wav_path = temp_wav_root / wav_name

if wav_path.exists():
logging.info(f"Skipping existing WAV: {wav_path}")
continue

logging.info(f"Retrying conversion: {wem_path} → {wav_path}")
result = subprocess.run(
[str(VGMSTREAM), "-o", str(wav_path), str(wem_path)],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0 or not wav_path.exists():
new_failures += 1
logging.error(
f"Conversion failed a 2nd time: {wem_path}. "
"Either the .wem file is corrupt, broken, or there is no .txtp path for that file. "
"Consider a manual approach or ask for help in the Discord."
)
failed_logger.error(str(wem_path))
else:
# Count as converted only if it actually succeeds now
converted_count += 1
logging.info(f"Successfully converted on retry: {wem_path}")

# Update failed_count to reflect files that truly failed after retry
failed_count = new_failures


# Main driver
if __name__ == "__main__":
wem_root = Path("txtp/wem")
wav_temp_root = Path("out_temp") / "wem"
out_root = Path("out")

logging.info("Starting .wem → .wav conversion")
mapping = wem_to_wav(wem_root, Path("out_temp"))

my_file.close()
logging.info("Starting .wav renaming based on .txtp files")
txtp_files = [f for f in Path("txtp").glob("*.txtp")]
for file_path in txtp_files:
convert(file_path.name, wav_temp_root, out_root, mapping)

relevant_path = "./txtp/"
included_extensions = ['txtp']
file_names = [fn for fn in os.listdir(relevant_path)
if any(fn.endswith(ext) for ext in included_extensions)]
# Retry any failed conversions
retry_failed_conversions(wav_temp_root)

# Clean up temp folder
if wav_temp_root.parent.exists():
shutil.rmtree(wav_temp_root.parent)
logging.info(f"Temporary folder {wav_temp_root.parent} deleted")

for file_name in file_names:
convert(file_name)
# Final summary
logging.info("===================================")
logging.info(f"Total .wem files found: {total_wems}")
logging.info(f"Successfully converted: {converted_count}")
logging.info(f"Skipped (already exists): {skipped_count}")
logging.info(f"Failed conversions: {failed_count}")
logging.info("Conversion and renaming complete")
logging.info("===================================")
Loading