Skip to content

Instantly share code, notes, and snippets.

@monomadic
Last active August 29, 2015 14:19
Show Gist options
  • Save monomadic/6e5abbc5ef22a86583f7 to your computer and use it in GitHub Desktop.
Save monomadic/6e5abbc5ef22a86583f7 to your computer and use it in GitHub Desktop.
ableton_tools
#!/usr/bin/env ruby
require 'nokogiri'
require 'fileutils'
require 'zlib'
require 'colorize'
INPUT_FILE = ARGV[0]
PROJECT_NAME = (ARGV[1].nil? || ARGV[1].empty?) ? nil : ARGV[1] # dirty but necessary unforch. ruby sucks.
OUTPUT_DIR = "./_output"
abort "Usage: advtool.rb <ableton live file> <project name>" if INPUT_FILE.nil?
abort "File not found." unless File.exist?(INPUT_FILE)
XML_SEARCH_HINT = Nokogiri::XML::DocumentFragment.parse <<-EOXML
<SearchHint>
<PathHint>
<RelativePathElement Dir="." />
</PathHint>
</SearchHint>
EOXML
def write_dir_unless_exists(dir)
unless Dir.exist?(dir)
puts "[!] Writing directory: #{dir}".yellow
FileUtils.mkdir_p dir
end
end
class AbletonDeviceNode
def initialize(device)
@device_xml = device
end
def device_name
@device_xml.css('Name')[0]['Value']
end
def to_xml
@device_xml
end
end
class ADVFile
def initialize(input_file, project_name=nil)
@input_file_path = input_file
@input_file = input_file.split('/').last
@project_name = project_name || @input_file.split('.')[0...-1].join('.')
@sample_dir = "#{@project_name} samples"
@output_dir = "./#{@project_name}" || OUTPUT_DIR
to_s
read_file
write_dir_unless_exists("#{@output_dir}/#{@sample_dir}")
make_directory_references_relative
scrub_file_hash
end
def project_name
@project_name
end
def devices
@xml_file.css('Device').map do |device|
AbletonDeviceNode.new(device)
end
end
def device_chain_xml
@xml_file.css('Device Chain Devices')
end
def to_s
puts "[i] Input File Path: #{@input_file_path}"
puts "[i] Local File: #{@input_file}"
puts "[i] Project Name: #{@project_name}"
puts "[i] Samples Directory: #{@sample_dir}"
puts "\n"
end
def read_file
@fp = File.open(INPUT_FILE, 'r')
# deflate file
begin
@deflated_file = Zlib::GzipReader.new(@fp)
rescue
puts "[!] File already deflated. Continuing...".yellow
@fp.rewind
@deflated_file = @fp
end
# deserialise XML
@xml_file = Nokogiri.XML(@deflated_file.read)
if @xml_file.nil?
abort "[!] File is not a valid XML file."
end
puts "[i] Read: #{@fp.path}".green
end
# changes directory to find samples
def make_directory_references_relative
puts "[!] No relativepath element found.".red if @xml_file.xpath('//RelativePath').count == 0
@xml_file.xpath('//HasRelativePath').each do |hasrelpath|
rel = hasrelpath.attributes["Value"]
hasrelpath["Value"] = true
end
@xml_file.xpath('//RelativePathType').each do |relativepathtype|
rel = relativepathtype.attributes["Value"]
relativepathtype["Value"] = 1
end
# detect match patches
@xml_file.css('MxDPatchRef > FileRef').each do |maxpatchref|
max_file = maxpatchref.css('Name').first['Value']
puts "[!] Max patch detected: #{max_file}. Please copy this file manually.".yellow
end
# copy samples
@xml_file.css('SampleRef > FileRef, MxDFullFileDrop > FileRef').each do |sampleref|
sample_file = sampleref.css('Name').first['Value']
sample_dir = File.dirname(@fp.path) + '/' + sampleref.css('RelativePathElement').map{|pe| pe.attributes['Dir']}.join('/')
unless File.exist?(sample_dir)
sample_dir = '/' + sampleref.css('SearchHint PathHint RelativePathElement').map{|pe| pe.attributes['Dir']}.join('/')
end
sample_absolute_file = sample_dir + '/' + sample_file
target_directory = "#{@output_dir}/#{@sample_dir}"
if File.exist?(sample_absolute_file)
sample_filename = sample_absolute_file.split('/').last
unless File.exist?(target_directory + '/' + sample_filename)
FileUtils.cp(sample_absolute_file, target_directory)
puts "[+] Copied: #{sample_filename}".green
end
else
puts "[!] Sample missing: #{sample_absolute_file}".red
@show_copy_notice = true
end
end
@xml_file.xpath('//RelativePath').each do |relativepath|
samples_dir = Nokogiri::XML::DocumentFragment.parse <<-EOSMPDIR
<RelativePath>
<RelativePathElement Dir=\"#{@sample_dir}\" />
</RelativePath>
EOSMPDIR
relativepath.replace(samples_dir)
end
# find referenced files
@xml_file.xpath('//SearchHint').each do |searchhint|
searchhint.replace(XML_SEARCH_HINT)
end
if @show_copy_notice == true
puts "[!] This file contains sample references that are no longer valid. Ableton deals with this by finding them within the local project of the file itself instead (terrible idea). Try opening and re-saving the preset (for example to the current project directory, forcing it to copy samples) and running this script again.".yellow
end
end
def scrub_file_hash
@xml_file.xpath('//Data').each do |databit|
databit.content = ''
end
end
def write(filename="#{@output_dir}/#{@input_file}")
outfile = File.new(filename, 'w')
outfile.write(@xml_file)
outfile.close
puts "[+] File written: #{filename}".green
end
end
adv_file = ADVFile.new(INPUT_FILE, PROJECT_NAME)
adv_file.write
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment