Skip to content

Instantly share code, notes, and snippets.

@zeekay
Created December 12, 2014 21:14
Show Gist options
  • Save zeekay/3b29277c302e635a97a3 to your computer and use it in GitHub Desktop.
Save zeekay/3b29277c302e635a97a3 to your computer and use it in GitHub Desktop.
Compress ableton live projects using Flac.
#!/usr/bin/env python2
#
# minls
#
# Compress Ableton Live sets using Flac compression.
import argparse
import gzip
import os.path
import subprocess
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
class FileRef(object):
'''
An object representing an ALS FileRef element.
'''
uncompressed_exts = set(('wav', 'aif'))
def __init__(self, el):
self.el = el
self.exists = True
self.path = self.get_abspath()
@property
def is_uncompressed(self):
'''
Returns true if uncompressed file.
'''
return self.ext in self.uncompressed_exts
@property
def ext(self):
'''
Returns file extension.
'''
try:
return self.name.rsplit('.', 1)[1]
except IndexError:
return ''
@property
def name(self):
'''
Returns Name element's Value attribute.
'''
return self.el.find('Name').attrib['Value']
@name.setter
def name(self, value):
'''
Set Name element's Value attribute.
'''
self.el.find('Name').attrib['Value'] = value
def get_abspath(self):
'''
Returns absolute path if one can be found, otherwise relative path assembled from RelativePath children.
'''
rp = self.el.find('RelativePath')
rd = d = '/'.join(e.attrib['Dir'] for e in rp.getchildren()).lstrip('/')
# Go up at most 3 levels searching for absolute path to container
levels = 0
while not os.path.isdir(d):
d = '../' + d
levels += 1
if levels == 3:
self.exists = False
return '/'.join((rd, self.name))
return '/'.join((os.path.abspath(d), self.name))
class LiveSet(object):
'''
An object representing an Ableton Live set.
'''
def __init__(self, path):
self.path = path
self.etree = ET.parse(gzip.GzipFile(path))
def iter_files(self):
'''
Lazily yield all FileRef elements.
'''
for f in self.etree.iterfind('.//FileRef'):
f = FileRef(f)
if f.exists:
yield f
def iter_uncompressed_files(self):
'''
Lazily yield all uncompressed FileRef elements.
'''
for f in self.iter_files():
if f.is_uncompressed:
yield f
@property
def files(self):
'''
Wrapper around self.iter_files, returning all FileRefs.
'''
return list(self.iter_files())
@property
def uncompressed_files(self):
'''
Wrapper around self.iter_files, returning all FileRefs.
'''
return list(self.iter_uncompressed_files())
def save(self, path=None):
'''
Gzips and writes Live set to disk.
'''
if not path:
path = self.path
f = gzip.open(path, 'wb')
self.etree.write(f, encoding='UTF-8', xml_declaration=True)
f.close()
def compress_files(self, rm=False):
'''
Compress all uncompressed files in project with Flac.
'''
seen = set()
for f in als.iter_uncompressed_files():
print f.name
if not f.path in seen:
seen.add(f.path)
print 'Compressing %s...' % f.path
if subprocess.call(['flac', '-s', f.path]):
f.name = '.'.join([f.name.rsplit('.')[0], 'flac'])
if rm:
os.remove(f.path)
parser = argparse.ArgumentParser()
parser.add_argument('als', help='Ableton Live Set (.als)')
parser.add_argument('--rm', help='Remove uncompressed files after compression', action='store_true')
parser.set_defaults(rm=False)
if __name__ == '__main__':
args = parser.parse_args()
als = LiveSet(args.als)
als.compress_files(args.rm)
print 'Saving...'
als.save()
print 'Done!'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment