Page load in progress

Ableton Live Set Real Export

Assume you want to be able to export your Ableton project's arrangement into a machine readable format to... do some things that machines are good at.
You start by opening a .als file in a text editor and - if your computer doesn't crash - get to see a lot of random characters.

Luckily, Ableton recently released a tool called Ableton Live Set Export.
It does exactly not what the name says:
The library only contains functionality for generating Ableton Live projects. It does not support reading or parsing Live Sets or other Ableton-generated files.
Sounds like Alice, doesn't it?

But the internet always has answers.
It turns out that .als is just a gz-compressed proprietary XML format - and that is pretty easy to use. So, step one, rename your .als to .als.gz, decompress it and save the file as .xml.

Step two, build a little Python (3.6) script - using the help of lxml - and get the data you want!

Caveats (as found so far)

Some weird things when working with Ableton live set files:
  • Time is measured in beats. This kind of makes sense, but is also really annoying if you want to interact with other software.

The code

from lxml import etree
import math

tree = etree.parse('ableton.xml')
root = tree.getroot()

project_bpm = float(root.find('.//Tempo/Manual').get('Value'))


def beats_to_seconds(beats, bpm):
    return beats * 60 / bpm


def seconds_to_time_string(seconds):
    milliseconds = round((seconds % 1) * 1000)
    seconds = round(seconds)
    minutes = math.floor(seconds / 60)
    hours = math.floor(minutes / 60)
    minutes = minutes % 60
    seconds = seconds % 60

    return f'{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}'


def beats_to_time_string(beats, bpm):
    return seconds_to_time_string(beats_to_seconds(beats, bpm))


tracks_xml = root.findall(".//AudioTrack")
tracks = {}
for track_xml in tracks_xml:
    track_id = track_xml.get('Id')
    track = {
        'id': track_id,
        'name': track_xml.find('Name/UserName').get('Value'),
        # it's handy to keep an XML element reference while debugging, but not necessary for 'production'
        '_xml': track_xml
    }

    clips_xml = track_xml.findall('.//AudioClip')
    clips = []
    for clip_xml in clips_xml:
        clip_path = '/' + '/'.join([path_xml.get('Dir') for path_xml in clip_xml.findall('.//SearchHint/PathHint/RelativePathElement')]) + '/'
        clip = {
            'name': clip_xml.find('Name').get('Value'),
            'file': clip_xml.find('SampleRef/FileRef/Name').get('Value'),
            'path': clip_path,
            'start_on_track': beats_to_seconds(float(clip_xml.find('CurrentStart').get('Value')), project_bpm),
            'end_on_track': beats_to_seconds(float(clip_xml.find('CurrentEnd').get('Value')), project_bpm),
            'start_in_file': beats_to_seconds(float(clip_xml.find('Loop/LoopStart').get('Value')), project_bpm),
            'end_in_file': beats_to_seconds(float(clip_xml.find('Loop/LoopEnd').get('Value')), project_bpm),
            # it's handy to keep an XML element reference while debugging, but not necessary for 'production'
            '_xml': clip_xml
        }
        clips.append(clip)

    track['clips'] = clips
    tracks[track_id] = track


# use this to find the IDs of the tracks you're interested in
# I don't think you can guess these from inside Ableton
# for track_id, track in tracks.items():
#     print(track_id, track['name'])

track_id_filter = ['144']
for track_id in track_id_filter:
    track = tracks[track_id]

    print("\n" + track['name'])

    for clip in track['clips']:
        print(f"{seconds_to_time_string(clip['start_on_track'])} - {seconds_to_time_string(clip['end_on_track'])} "
              f"| {clip['name']:20} | {clip['file']} ("
              f"{seconds_to_time_string(clip['start_in_file'])} - {seconds_to_time_string(clip['end_in_file'])})")

Sample output

I've redacted the actual values, but you get the idea:
TRACK NAME
00:00:00.000 - 00:00:02.500 | CLIP NAME          | FILE NAME (00:00:10.500 - 00:00:13.1000)
00:00:03.508 - 00:00:04.027 | CLIP NAME          | FILE NAME (00:02:59.625 - 00:03:00.144)
00:00:04.050 - 00:00:06.131 | CLIP NAME          | FILE NAME (00:00:21.479 - 00:00:24.560)
00:00:06.131 - 00:00:07.335 | CLIP NAME          | FILE NAME (00:00:09.459 - 00:00:11.663)
00:00:07.335 - 00:00:10.595 | CLIP NAME          | FILE NAME (00:00:28.572 - 00:00:30.831)
00:00:10.595 - 00:00:12.998 | CLIP NAME          | FILE NAME (00:00:15.439 - 00:00:18.842)
The first time range indicates the place within your arrangement, whereas the latter one (in brackets) refers to the portion of the source file being used.

Ableton, Ableton Live and some other words in this text are probably protected by copyright and therefore (somehow) belong to someone or something. Also, Ableton hasn't publicly encouraged people to hack there file format so I assume this isn't endorsed by them.