Last active
August 29, 2015 14:19
-
-
Save monomadic/6e5abbc5ef22a86583f7 to your computer and use it in GitHub Desktop.
ableton_tools
This file contains 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
#!/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