-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathfxp2aupreset.py
executable file
·201 lines (155 loc) · 7.21 KB
/
fxp2aupreset.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#!/usr/bin/env python
# vst to au preset convert
# Colin Barry / [email protected] / www.loomer.co.uk
#
# Based on the aupreset to fxp converter found here
# http://www.rawmaterialsoftware.com/viewtopic.php?f=8&t=8337
import argparse
import re
from os import path, listdir, getcwd, chdir
import sys
import fnmatch
from xml.dom import minidom
from base64 import b64encode
from vst2preset import vst2preset
# converts a four character identifier to an integer
def id_to_integer(id):
if (len(id) != 4):
print id, "should be exactly 4 characters length."
sys.exit(1)
return str((ord(id[0]) << 24) + (ord(id[1]) << 16) + (ord(id[2]) << 8) + ord(id[3]))
# converts an integer to a four character identifier
def integer_to_id(integer):
integer = int(integer)
return chr(integer>>24 & 0x7F) + chr(integer>>16 & 0x7F) + chr(integer>> 8 & 0x7F) + chr(integer & 0x7F)
# adds an element to an xml dom tree
def add_key_and_value(doc, keyname, value, value_type, parent_element):
key_element = doc.createElement("key")
key_element_text = doc.createTextNode(keyname);
key_element.appendChild(key_element_text)
parent_element.appendChild(key_element)
data_element = doc.createElement(value_type)
data_element_text = doc.createTextNode(value);
data_element.appendChild(data_element_text)
parent_element.appendChild(data_element)
# converts the passed fxp file, creating the equivalent aupreset.
def convert(filename, manufacturer, subtype, type, state_key):
print "Opening fxp preset file", path.abspath(filename)
# extract fxp structure
f = open(filename, 'rb')
fxp = vst2preset.parse(f.read())
f.close()
EXPECTED = 'FXP_OPAQUE_CHUNK'
if (fxp['fxMagic'] != EXPECTED):
print ".fxp preset is not in opaque chunk format {} (but {}), and so can not be converted.".format(EXPECTED, fxp['fxMagic'])
return
preset_name = path.splitext(filename)[0]
# convert data chunk to base64
base64data = b64encode(fxp['data']['chunk'])
# create the aupreset dom
# set the DOCTYPE
imp = minidom.DOMImplementation()
doctype = imp.createDocumentType(
qualifiedName='plist',
publicId="-//Apple//DTD PLIST 1.0//EN",
systemId="http://www.apple.com/DTDs/PropertyList-1.0.dtd",
)
doc = imp.createDocument(None, 'plist', doctype)
# create the plist element
nodeList = doc.getElementsByTagName("plist")
plist = nodeList.item(0)
plist.setAttribute("version", "1.0");
dict = doc.createElement("dict")
plist.appendChild(dict)
# create document nodes
add_key_and_value(doc, state_key, base64data, "data", dict);
add_key_and_value(doc, "manufacturer", manufacturer, "integer", dict);
add_key_and_value(doc, "name", preset_name, "string", dict);
add_key_and_value(doc, "subtype", subtype, "integer", dict);
add_key_and_value(doc, "type", type, "integer", dict);
add_key_and_value(doc, "version", "0", "integer", dict);
aupreset_name = preset_name + ".aupreset"
f = open(aupreset_name, "wb")
f.write(doc.toxml("utf-8"))
f.close()
print "Created", path.abspath(aupreset_name)
print
def au_param_from_preset_dict(param, d):
return d[param].childNodes[0].data
def au_parameters_from_example(example_file):
with open(example_file, 'r') as fp:
parsed_example = minidom.parse(fp)
# Convert list of XML keys and values into proper dict for easier handling
aupreset_keys = filter(
lambda x: x.nodeType == minidom.Node.ELEMENT_NODE,
parsed_example.documentElement.getElementsByTagName('dict')[0]
.getElementsByTagName('key'))
# nextSibling twice to skip over newline text nodes
aupreset_dict = dict({ (k.childNodes[0].data, k.nextSibling.nextSibling)
for k in aupreset_keys })
au_type = au_param_from_preset_dict('type', aupreset_dict)
au_subtype = au_param_from_preset_dict('subtype', aupreset_dict)
au_manufacturer = au_param_from_preset_dict('manufacturer', aupreset_dict)
data_elements = (parsed_example.documentElement
.getElementsByTagName('dict')[0]
.getElementsByTagName('data'))
state_key = None
if len(data_elements) > 1:
# previousSibling twice to skip over newline text nodes
state_key = (data_elements[1].previousSibling.previousSibling
.childNodes[0].data)
print ("Guessing '%s' for state key, use --state_key to override" %
(state_key))
else:
print "Couldn't infer state key from example"
return (au_type, au_subtype, au_manufacturer, state_key)
DESCRIPTION="""
.fxp to .aupreset converter"
Original By: Colin Barry / [email protected]
Modifications By: Alex Rasmussen / [email protected]
"""
def get_arguments(parser):
args = parser.parse_args()
if (args.example is not None):
args.type, args.subtype, args.manufacturer, args.state_key = (
au_parameters_from_example(args.example))
print "\ngiven example \"{}\" corresponds to:\n".format(args.example)
print "{} --type {} --subtype {} --manufacturer {} --state_key {} \"{}\"\n".format(sys.argv[0], integer_to_id(args.type), integer_to_id(args.subtype), integer_to_id(args.manufacturer), args.state_key, args.path);
else:
for attr in ['type', 'subtype', 'manufacturer']:
if getattr(args, attr) is None:
sys.exit("ERROR: Must provide a %s or an example aupreset for "
"the instrument" % (attr))
args.type = id_to_integer(args.type)
args.subtype = id_to_integer(args.subtype)
args.manufacturer = id_to_integer(args.manufacturer)
if args.state_key is None:
sys.exit("ERROR: Must provide a state key or an example aupreset for "
"the instrument from which we can infer one")
return args
def main():
parser = argparse.ArgumentParser(description=DESCRIPTION)
parser.add_argument('path', help='path to the directory of .fxp '
'and .fxb files to convert')
parser.add_argument('--type', '-t', help="the four-character type code for "
"the preset's audio unit")
parser.add_argument('--subtype', '-s', help="the four character subtype "
"code for the preset's audio unit file")
parser.add_argument('--manufacturer', '-m', help="the four character "
"manufacturer code for the preset's audio unit file")
parser.add_argument("--state_key", '-k', help="the key in the aupreset that "
"stores the preset's state")
parser.add_argument('--example', '-x', help='an example .aupreset file '
'from which to infer type, subtype, manufacturer and '
'state key')
args = get_arguments(parser)
# enumerate all the .fxp files in the current directory
chdir(args.path)
fxpFileList = fnmatch.filter(listdir(args.path), '*.[Ff][Xx][BbPp]')
if (len(fxpFileList) == 0):
print "No .fxp or .fxb files found in '%s'" % (getcwd())
for fname in fxpFileList:
convert(fname, args.manufacturer, args.subtype, args.type,
args.state_key)
if __name__ == "__main__":
sys.exit(main())