Krotos Modules 3
Loading...
Searching...
No Matches
PresetBundler.cpp
Go to the documentation of this file.
1namespace krotos
2{
3 const String PresetBundler::PresetSubdirectory{"Presets"};
4 const String PresetBundler::AudioAssetsSubdirectory{"Audio Assets"};
5 const String PresetBundler::BundleFileExtension{".kspb"};
6
8
10 {
11 String presetString = preset.loadFileAsString();
12 ValueTree presetData = ValueTree::fromXml(presetString);
13 initialise(presetData);
14 }
15
16 PresetBundler::PresetBundler(ValueTree presetData) { initialise(presetData); }
17
18 void PresetBundler::initialise(ValueTree presetData)
19 {
20 if (presetData.isValid() == false)
21 {
22 throw PresetException("Invalid preset value tree", "initialisation");
23 }
25 m_presetFile = presetData;
27 clenseExportIDs(); // ensures filenames are compatible with windows OS
28 // set a unique root ID for the bundle build folder in case multiple instances are exporting
29 m_bundleRootId = "presetbundler" + Uuid().toString();
30 }
31
32 bool PresetBundler::createBundle(File outputLocation, String& errors)
33 {
34 bool bundleCreated(false);
35
36 const bool fileStructureExists(createBundleFileStructure());
37 if (fileStructureExists == false)
38 {
39 errors = "Failed to create bundle. Unable to make temp file " + getExportBundleRoot().getFullPathName();
40 return bundleCreated; // No need to continue from here
41 }
42 const bool bundleAssetsTooBig(getEstimatedAssetsSizeMB() > m_maxSizeMegaBytes);
43 if (bundleAssetsTooBig)
44 {
45 int bundleSize = getEstimatedAssetsSizeMB();
46 errors = "Audio files are too large - preset size over the size limit by " +
47 String(m_maxSizeMegaBytes - bundleSize) + "MB";
48 }
49 else
50 {
51 String anyMissingFiles = listMissingFiles();
52 if (anyMissingFiles.isNotEmpty())
53 {
54 errors = "Files used in this preset could not be found:\n" + anyMissingFiles;
55 // annoying but won't necessary break a preset
56 }
57
59 String exportedPresetFileName = outputLocation.getFileNameWithoutExtension() + m_presetFileExtension;
60 File exportPreset = copyPresetForExport(exportedPresetFileName);
61 processExportedPresetFile(exportPreset);
62
63 File bundleFile = buildBundleArchive(outputLocation.getFullPathName());
64 if (bundleFile.getFullPathName().isNotEmpty() && bundleFile.exists())
65 {
66 bundleCreated = true;
67 }
68 else
69 {
70 errors = "\nThe export bundle could not be created, "
71 "this might be caused by the chosen output folder becoming inaccessible or by lack of "
72 "available memory";
73 }
74 }
76
77 return bundleCreated;
78 }
79
80 File PresetBundler::openBundle(File bundleFile)
81 {
82 jassert(bundleFile.hasFileExtension(BundleFileExtension));
83 jassert(bundleFile.existsAsFile());
84
85 ZipFile bundleZip(bundleFile);
86 File bundleRoot = getSandboxDirectoryForBundle(bundleFile);
87 bundleRoot.deleteRecursively();
88 auto uncompressResult = bundleZip.uncompressTo(bundleRoot);
89 if (uncompressResult.failed())
90 {
91 throw PresetException("Krotos Studio was unable to open the bundle file.",
92 uncompressResult.getErrorMessage());
93 }
94 auto validBundle = validateBundleDirectory(bundleRoot);
95 if (!validBundle)
96 {
97 throw PresetException("The file does not appear to be a valid Krotos Studio preset bundle.", "openBundle");
98 }
99 return bundleRoot;
100 }
101
102 bool PresetBundler::validateBundleDirectory(File bundleDirectory)
103 {
104 bool validBundle(false);
105 if (bundleDirectory.isDirectory())
106 {
107 File presetFile = getPresetFromBundleDirectory(bundleDirectory);
108 validBundle |= presetFile.hasFileExtension(m_presetFileExtension);
109
110 File presetDir = bundleDirectory.getChildFile(PresetSubdirectory);
111 validBundle |= presetDir.isDirectory();
112
113 File audioAssetsDir = bundleDirectory.getChildFile(AudioAssetsSubdirectory);
114 validBundle |= audioAssetsDir.isDirectory();
115 }
116 return validBundle;
117 }
118
120 {
121 File presetFile = bundleRoot.getChildFile(PresetSubdirectory).findChildFiles(File::findFiles, true)[0];
122 jassert(presetFile.hasFileExtension(m_presetFileExtension));
123 return presetFile;
124 }
125
126 File PresetBundler::copyPresetForExport(String nameForPresetFile)
127 {
128 File presetDirectory(getExportBundleRoot().getChildFile(PresetSubdirectory));
129 File presetFileDestination(
130 getExportBundleRoot().getChildFile(PresetSubdirectory).getChildFile(nameForPresetFile));
131 if (presetDirectory.isDirectory())
132 {
133 m_presetFile.createXml()->writeTo(presetFileDestination);
134 }
135 return presetFileDestination;
136 }
137
139 {
140 const File audioAssetDirectory(getExportBundleRoot().getChildFile(AudioAssetsSubdirectory));
141 if (audioAssetDirectory.isDirectory())
142 {
143 for (auto asset : m_assetFilepaths)
144 {
145 const File fileSource(asset.assetPath);
146 const File fileDestination(audioAssetDirectory.getFullPathName() + File::getSeparatorChar() +
147 asset.assetID);
148 if (fileSource.existsAsFile())
149 {
150 if (fileDestination.getParentDirectory().createDirectory().wasOk())
151 {
152 fileSource.copyFileTo(fileDestination);
153 }
154 }
155 }
156 }
157 }
158
160 {
161 const File root(File::getSpecialLocation(File::SpecialLocationType::tempDirectory));
162 const File bundleRoot = root.getChildFile(m_bundleRootId);
163 return bundleRoot;
164 }
165
167 {
168 const File bundleRoot = getExportBundleRoot();
169 const File presetDir(bundleRoot.getChildFile(PresetSubdirectory));
170 const File audioAssetsDir(bundleRoot.getChildFile(AudioAssetsSubdirectory));
171 const bool structureOkay(presetDir.createDirectory().wasOk() && audioAssetsDir.createDirectory().wasOk());
172 return structureOkay;
173 }
174
176 {
177 const File bundleRoot(getExportBundleRoot());
178 if (bundleRoot.exists())
179 {
180 bundleRoot.deleteRecursively();
181 }
182 }
183
185 {
186 bool isAudioFile(false);
187 StringArray audioFileTypes;
188 audioFileTypes.addTokens(AssetManager::SupportedAudioTypes, ";", "\"");
189 for (auto type : audioFileTypes)
190 {
191 if (value.toLowerCase().endsWith(type))
192 {
193 isAudioFile = true;
194 break;
195 }
196 }
197 return isAudioFile;
198 }
199
201 {
202 int64 cumulativeSizeBytes(0);
203 for (auto asset : m_assetFilepaths)
204 {
205 File file(asset.assetPath);
206 cumulativeSizeBytes += file.getSize();
207 }
208 const int sizeInMB(cumulativeSizeBytes / (1024 * 1024));
209 return sizeInMB;
210 }
211
212 void PresetBundler::getPresetFilepaths(StringArray& listOfFiles, XmlElement kwidgetElement)
213 {
214 for (auto* kwidget : kwidgetElement.getChildIterator())
215 {
216 // KST-2636: hotfix to remove IRs from inclusion in exported preset bundles
217 if (kwidget->getStringAttribute(XmlType::Property::id).containsIgnoreCase(KwidgetFactory::KwidgetType::ConvolutionReverb))
218 {
219 continue;
220 }
221
222 // Some kwidgets will have child kwidgets
223 auto anotherKwidgetsElem = kwidget->getChildByName(XmlType::Tag::kwidgets);
224 if (anotherKwidgetsElem != nullptr)
225 {
226 getPresetFilepaths(listOfFiles, *anotherKwidgetsElem);
227 }
228 auto customParamsElem = kwidget->getChildByName(XmlType::Tag::customParams);
229 if (customParamsElem != nullptr)
230 {
231 findPathAttributesRecursive(listOfFiles, *customParamsElem);
232 }
233 }
234 }
235
236 void PresetBundler::findPathAttributesRecursive(StringArray& listOfFiles, XmlElement customParamsElement)
237 {
238 const int attributes = customParamsElement.getNumAttributes();
239 for (int i = 0; i < attributes; i++)
240 {
241 String value = customParamsElement.getAttributeValue(i);
242 if (isValueAudioFile(value))
243 {
244 // sometimes a value is multiple paths separated by newlines
245 if (isValueMultipleFiles(value))
246 {
247 StringArray values = StringArray::fromLines(value);
248 for (auto singleValue : values)
249 {
250 singleValue = convertFilepath(singleValue);
251 listOfFiles.addIfNotAlreadyThere(singleValue);
252 }
253 }
254 else
255 {
256 value = convertFilepath(value);
257 listOfFiles.addIfNotAlreadyThere(value);
258 }
259 }
260 }
261 // some custom parameters will have child tags i.e. SAMPLES
262 for (auto* customParamElem : customParamsElement.getChildIterator())
263 {
264 findPathAttributesRecursive(listOfFiles, *customParamElem);
265 }
266 }
267
268 std::vector<PresetBundler::AudioAsset> PresetBundler::getAssetsListFromPreset()
269 {
270 std::vector<AudioAsset> assetsFound;
271 StringArray audioAssetsPaths;
272 auto presetDataElem = m_presetFile.createXml();
273 if (presetDataElem != nullptr)
274 {
275 auto kwidgetsElem = presetDataElem->getChildByName(XmlType::Tag::kwidgets);
276 if (kwidgetsElem != nullptr)
277 {
278 getPresetFilepaths(audioAssetsPaths, *kwidgetsElem);
279 }
280
281 // assign asset IDs
282 int assetId(0);
283 for (String path : audioAssetsPaths)
284 {
285 AudioAsset newAsset;
286 newAsset.assetPath = path;
287 String slash = File::getSeparatorString();
288 newAsset.assetID = "Asset" + String(++assetId) + slash + File(path).getFileName();
289 assetsFound.push_back(newAsset);
290 }
291 }
292 return assetsFound;
293 }
294
296 {
297 if (exportPreset.existsAsFile())
298 {
299 auto presetDataElem = XmlDocument::parse(exportPreset);
300 auto kwidgetsElem = presetDataElem->getChildByName(XmlType::Tag::kwidgets);
301 if (kwidgetsElem != nullptr)
302 {
303 replacePaths(*kwidgetsElem);
304 presetDataElem->writeTo(exportPreset);
305 }
306 }
307 }
308
309 void PresetBundler::replacePaths(XmlElement& kwidgetElement)
310 {
311 for (auto* kwidgetIterator : kwidgetElement.getChildIterator())
312 {
313 // Some kwidgets will have child kwidgets
314 auto anotherKwidgetsElem = kwidgetIterator->getChildByName(XmlType::Tag::kwidgets);
315 if (anotherKwidgetsElem != nullptr)
316 {
317 replacePaths(*anotherKwidgetsElem);
318 }
319 auto customParamsElem = kwidgetIterator->getChildByName(XmlType::Tag::customParams);
320 if (customParamsElem != nullptr)
321 {
322 replacePathRecursive(*customParamsElem);
323 }
324 }
325 }
326 void PresetBundler::replacePathRecursive(XmlElement& customParamsElement)
327 {
328 const int attributes = customParamsElement.getNumAttributes();
329 for (int i = 0; i < attributes; i++)
330 {
331 String value = customParamsElement.getAttributeValue(i);
332 if (isValueAudioFile(value) == false)
333 {
334 continue;
335 }
336 for (auto asset : m_assetFilepaths)
337 {
338 const String attributeName = customParamsElement.getAttributeName(i);
339
341 {
342 customParamsElement.setAttribute(attributeName, asset.assetID);
343 break;
344 }
345 }
346 }
347 // some custom parameters will have child tags i.e. SAMPLES
348 for (auto* customParamChild : customParamsElement.getChildIterator())
349 {
350 replacePathRecursive(*customParamChild);
351 }
352 }
353
354 File PresetBundler::buildBundleArchive(String outputFilePath)
355 {
356 File archiveFile(outputFilePath);
357 if (archiveFile.exists())
358 {
359 archiveFile.deleteFile();
360 }
361 const int compressionLevel(0);
362 ZipFile::Builder zipBuilder;
363
364 auto files = File(getExportBundleRoot()).findChildFiles(File::findFiles, /*recurs*/ true);
365 for (auto file : files)
366 {
367 zipBuilder.addFile(file, compressionLevel, file.getRelativePathFrom(getExportBundleRoot()));
368 }
369
370 FileOutputStream archiveStream(archiveFile);
371 bool success = zipBuilder.writeToStream(archiveStream, nullptr);
372 return success ? archiveFile : File();
373 }
374
376 {
377 String missingAssets;
378 for (auto asset : m_assetFilepaths)
379 {
380 String filePath = AssetManager::convertFilePathString(asset.assetPath);
381 File file(filePath);
382 if (file.existsAsFile() == false)
383 {
384 missingAssets += filePath + "\n";
385 }
386 }
387 return missingAssets;
388 }
389
391 {
392 for (auto& asset : m_assetFilepaths)
393 {
394 String subfolder = asset.assetID.upToFirstOccurrenceOf(File::getSeparatorString(),
395 /*include slash*/ true, /*ignoreCase*/ false);
396 String filename = asset.assetID.fromLastOccurrenceOf(File::getSeparatorString(),
397 /*include slash*/ false, /*ignoreCase*/ false);
398 // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
399 asset.assetID = subfolder + File::createLegalFileName(filename);
400 }
401 }
402
403 void PresetBundler::setPresetFileExtension(String fileExtension) { m_presetFileExtension = fileExtension; }
404
406 {
407 return AssetManager::getUserDataDirectory().getFullPathName() + File::getSeparatorString() + "BundleImport";
408 }
409
411 {
412 return getSandboxDirectory().getChildFile(File::createLegalFileName(bundleFile.getFileNameWithoutExtension()));
413 }
414
416 {
417 return getSandboxDirectoryForBundle(bundleFile).getChildFile(AudioAssetsSubdirectory);
418 }
419
421 {
422 return getSandboxDirectoryForBundle(bundleFile).getChildFile(PresetSubdirectory);
423 }
424
425 bool PresetBundler::isABundlePreset(const File& preset)
426 {
427 return preset.isAChildOf(PresetBundler::getSandboxDirectory());
428 }
429
430 bool PresetBundler::isValueMultipleFiles(String value) { return value.containsChar('\n'); }
431
432 String PresetBundler::convertFilepath(String filepathFromPreset)
433 {
434 filepathFromPreset = AssetManager::convertFilePathString(filepathFromPreset);
435 if (File file(filepathFromPreset); file.existsAsFile() == false)
436 {
437 // If file does not exist, then check if user has a custom factory assets path
438 String subPath(filepathFromPreset.fromFirstOccurrenceOf("Factory Assets" + File::getSeparatorString(),
439 /*includeSubString*/ false, /*ignoreCase*/ false));
440 String customAssetFilepath(utils::StringsIntoPath(m_factoryAssetsDirectory, subPath));
441 File customFile(customAssetFilepath);
442 const String kafFile{".kaf"};
443 if (customFile.existsAsFile())
444 {
445 filepathFromPreset = customAssetFilepath;
446 }
447 // .kaf interoperability support
448 else if (customFile.withFileExtension(kafFile).existsAsFile())
449 {
450 filepathFromPreset = customFile.withFileExtension(kafFile).getFullPathName();
451 }
452 }
453 return filepathFromPreset;
454 }
455} // namespace krotos
static String convertFilePathString(const String &)
Definition AssetManager.cpp:583
static File getUserDataDirectory()
Returns the user data directory, this will be a named manufacturer / plugin subfolder under the File:...
Definition AssetManager.cpp:402
static const String SupportedAudioTypes
Definition AssetManager.h:112
static File getAssetDirectory()
Definition AssetManager.cpp:383
void copyAudioAssetsForExport()
Definition PresetBundler.cpp:138
static File getAudioAssetDirectoryForBundle(const File &bundleFile)
Returns the path to the audio assets directory for a particular bundle based on the name of the file ...
Definition PresetBundler.cpp:415
static const String AudioAssetsSubdirectory
Definition PresetBundler.h:144
static File getSandboxDirectory()
Returns the path to the sandbox directory where the preset bundles are temporarily stored.
Definition PresetBundler.cpp:405
void initialise(ValueTree presetData)
Definition PresetBundler.cpp:18
static File openBundle(File bundleArchive)
Extracts the contents of a bundle archive to the temporary "sandbox" directory.
Definition PresetBundler.cpp:80
static File getSandboxDirectoryForBundle(const File &bundleFile)
Returns the path to the sandbox directory for a particular bundle based on the name of the file being...
Definition PresetBundler.cpp:410
void getPresetFilepaths(StringArray &listOfFiles, XmlElement widgetElement)
Definition PresetBundler.cpp:212
static File getPresetFromBundleDirectory(File bundleDirectory)
Locates the preset file within a given bundle directory.
Definition PresetBundler.cpp:119
String m_bundleRootId
Definition PresetBundler.h:193
void cleanUpFileStructure()
Definition PresetBundler.cpp:175
std::vector< AudioAsset > m_assetFilepaths
Definition PresetBundler.h:195
int getEstimatedAssetsSizeMB()
Definition PresetBundler.cpp:200
void processExportedPresetFile(File exportPreset)
Definition PresetBundler.cpp:295
int m_maxSizeMegaBytes
Definition PresetBundler.h:196
File copyPresetForExport(String nameForPresetFile)
Definition PresetBundler.cpp:126
bool createBundle(File outputLocation, String &errors)
Definition PresetBundler.cpp:32
bool isValueMultipleFiles(String value)
Definition PresetBundler.cpp:430
void clenseExportIDs()
Definition PresetBundler.cpp:390
std::vector< AudioAsset > getAssetsListFromPreset()
Definition PresetBundler.cpp:268
void replacePaths(XmlElement &widgetElement)
Definition PresetBundler.cpp:309
PresetBundler(File preset)
Definition PresetBundler.cpp:9
static bool isABundlePreset(const File &preset)
Returns whether this preset file is part of a bundle. This is just a simple check for that the provid...
Definition PresetBundler.cpp:425
static const String BundleFileExtension
The file extension for the bundle archive.
Definition PresetBundler.h:47
String convertFilepath(String filepathFromPreset)
Definition PresetBundler.cpp:432
File buildBundleArchive(String outputDirectoryPath)
Definition PresetBundler.cpp:354
String listMissingFiles()
Definition PresetBundler.cpp:375
void findPathAttributesRecursive(StringArray &listOfFiles, XmlElement customParamsElement)
Definition PresetBundler.cpp:236
static bool validateBundleDirectory(File bundleDirectory)
Validates that a given directory is a valid bundle directory by checking it is a child file of the sa...
Definition PresetBundler.cpp:102
void replacePathRecursive(XmlElement &widgetElement)
Definition PresetBundler.cpp:326
static File getPresetDirectoryForBundle(const File &bundleFile)
Returns the path to the preset directory for a particular bundle based on the name of the file being ...
Definition PresetBundler.cpp:420
ValueTree m_presetFile
Definition PresetBundler.h:194
static String m_presetFileExtension
Definition PresetBundler.h:192
void setPresetFileExtension(String fileExtension)
Definition PresetBundler.cpp:403
String m_factoryAssetsDirectory
Definition PresetBundler.h:171
static const String PresetSubdirectory
Definition PresetBundler.h:142
bool isValueAudioFile(String value)
Definition PresetBundler.cpp:184
bool createBundleFileStructure()
Definition PresetBundler.cpp:166
File getExportBundleRoot()
Definition PresetBundler.cpp:159
An exception for preset manager.
Definition PresetManager.h:148
String StringsIntoPath(Args... args)
Joins multiple string arguments into a path string.
Definition helpers.h:25
Definition AirAbsorptionFilter.cpp:2
static const String ConvolutionReverb
Definition KwidgetFactory.h:15
Definition PresetBundler.h:50
String assetPath
Definition PresetBundler.h:52
String assetID
Definition PresetBundler.h:54
static const Identifier id
Definition XmlType.h:41
static const Identifier kwidgets
Definition XmlType.h:22
static const Identifier customParams
Definition XmlType.h:20