Skip to content

Instantly share code, notes, and snippets.

@mrreband
Created September 3, 2022 15:03
Show Gist options
  • Save mrreband/dfb886943a04ac798b892b731d9444cf to your computer and use it in GitHub Desktop.
Save mrreband/dfb886943a04ac798b892b731d9444cf to your computer and use it in GitHub Desktop.
Restore Ableton Live Crashes
import os
import shutil
from typing import List, Dict
def prepare_crash(file_date: str,
source_cfg_file: str,
source_undo_folder: str,
source_basefiles_folder: str,
target_path: str,
overwrite: bool=True):
"""
Prepare files for recovery, as described here:
https://help.ableton.com/hc/en-us/articles/115001878844-Recovering-a-Set-manually-after-a-crash
:param file_date: crash datetime taken from the cfg file prefix
:param source_cfg_file: path to CrashRecoverInfo.cfg file
:param source_undo_folder: path to `{file_date}_Undo` folder
:param source_basefiles_folder: path to `{file_date}_BaseFiles` folder
:param target_path: target path write prepared files to
:param overwrite: switch to overwrite target path, default true
"""
restore_folder = os.path.join(target_path, file_date)
if overwrite and os.path.exists(restore_folder):
shutil.rmtree(restore_folder)
os.makedirs(restore_folder)
target_cfg_file = os.path.join(restore_folder, "CrashRecoveryInfo.cfg")
shutil.copy2(source_cfg_file, target_cfg_file)
target_undo_folder = os.path.join(restore_folder, "Undo")
shutil.copytree(source_undo_folder, target_undo_folder)
target_basefiles_folder = os.path.join(restore_folder, "BaseFiles")
shutil.copytree(source_basefiles_folder, target_basefiles_folder)
als_files = [file for file in os.listdir(target_basefiles_folder) if file.endswith(".als")]
print(f"{file_date}: restored {als_files}")
return als_files
def prepare_crashes(root_path: str, target_path: str) -> Dict[str, List[str]]:
"""
Prepare multiple crashes for recovery, as described here:
https://help.ableton.com/hc/en-us/articles/115001878844-Recovering-a-Set-manually-after-a-crash
:param root_path: path to the folder with the crashes (may be f"{ableton_path}/Crash")
:type root_path: str
:param target_path: path to store prepared crash files, before loading into ableton
:type target_path: str
:return: dictionary with key = the prepared folder path, value = list of als files in the crash
:rtype: Dict[str, List[str]]
"""
assert os.path.abspath(root_path) != os.path.abspath(target_path) # non-destructive failsafe
cfg_files = [file for file in os.listdir(root_path) if file.endswith("cfg")]
restored = dict()
for cfg_file_name in cfg_files:
file_date = cfg_file_name.strip("_CrashRecoveryInfo.cfg")
undo_folder_name = f"{file_date}_Undo"
basefiles_folder_name = f"{file_date}_BaseFiles"
cfg_file_path = os.path.join(root_path, cfg_file_name)
undo_folder_path = os.path.join(root_path, undo_folder_name)
basefiles_folder_path = os.path.join(root_path, basefiles_folder_name)
assert os.path.exists(undo_folder_path)
assert os.path.exists(basefiles_folder_path)
als_files = prepare_crash(file_date=file_date,
source_cfg_file=cfg_file_path,
source_undo_folder=undo_folder_path,
source_basefiles_folder=basefiles_folder_path,
target_path=target_path)
restored[file_date] = als_files
return restored
def get_most_recent_sessions(restored: dict) -> Dict[str, str]:
"""
If there are duplicate als files in the restored sessions, identify the most recent one.
:param restored: dictionary from prepare_crashes - key = datetime prefix, value = list of als files
:return: dictionary with key = als filename, value = most recent datetime prefix
:rtype: Dict[str, str]
"""
most_recent = dict()
distinct_als_files = list(set([file.lower() for files in restored.values() for file in files]))
for als_file in distinct_als_files:
relevant_keys = [k for k in restored.keys() if als_file in [f.lower() for f in restored[k]]]
latest_date = max(relevant_keys)
most_recent[als_file] = latest_date
return most_recent
def write_recovery_script(restored: dict, target_path: str, ableton_folder: str):
"""
Write a shell script with commands to copy prepared files to the ableton folder
Only one crash can be restored at a time, so all commands are commented out.
The user can then uncomment one, then run `. ./restore.sh`, then open Live
:param restored: dictionary of prepared crashes
:param target_path: where to create restore.sh
:param ableton_folder: where to copy the restored files to
"""
target_script_path = os.path.join(target_path, "restore.sh")
with open(target_script_path, "w") as script:
script.write('# Ableton can only handle one crash at a time\n')
script.write('# Uncomment one copy statement, then run the script, then open Ableton Live.\n')
script.write('# You should be presented with the familiar "would you like to recover your work" prompt\n')
script.write("\n")
for date, als_files in restored.items():
script.write(f"# cp -Rv {date}/* '{ableton_folder}' # {als_files} \n")
if __name__ == "__main__":
root_path = "./raw_crashes"
target_path = "./output"
ableton_folder = "/Users/mr/Library/Preferences/Ableton/Live 11.1.6"
restored = prepare_crashes(root_path=root_path, target_path=target_path)
most_recent = get_most_recent_sessions(restored=restored)
write_recovery_script(restored=restored, target_path=target_path, ableton_folder=ableton_folder)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment