Created
September 3, 2022 15:03
-
-
Save mrreband/dfb886943a04ac798b892b731d9444cf to your computer and use it in GitHub Desktop.
Restore Ableton Live Crashes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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