Bunding of media files contained in snapshots.

This commit is contained in:
Robin E. R. Davies
2025-06-20 15:52:51 -04:00
parent 7d26a73922
commit 7474c2531d
5 changed files with 283 additions and 74 deletions
+18 -10
View File
@@ -20,12 +20,9 @@
#pragma once #pragma once
#include <iostream> #include <iostream>
#include <cstdint> #include <cstdint>
#include <boost/utility/string_view.hpp>
#include <boost/format.hpp>
#include <iomanip> #include <iomanip>
#include <type_traits> #include <type_traits>
#include <sstream> #include <sstream>
#include "HtmlHelper.hpp"
#include <cctype> #include <cctype>
#include <cmath> #include <cmath>
#include <map> #include <map>
@@ -35,6 +32,9 @@
#include <stdexcept> #include <stdexcept>
#include <chrono> #include <chrono>
#include <optional> #include <optional>
#include <string_view>
#include <string.h>
#include <stdexcept>
#define DECLARE_JSON_MAP(CLASSNAME) \ #define DECLARE_JSON_MAP(CLASSNAME) \
static pipedal::json_map::storage_type<CLASSNAME> jmap static pipedal::json_map::storage_type<CLASSNAME> jmap
@@ -228,7 +228,7 @@ namespace pipedal
reference(const char *name, MEMBER_TYPE CLASS::*member_pointer) reference(const char *name, MEMBER_TYPE CLASS::*member_pointer)
{ {
return new json_member_reference<CLASS, MEMBER_TYPE>(name, member_pointer); return new json_member_reference<CLASS, MEMBER_TYPE>(name, member_pointer);
}; }
template <typename CLASS, typename MEMBER_TYPE, typename ENUM_CONVERTER> template <typename CLASS, typename MEMBER_TYPE, typename ENUM_CONVERTER>
static json_enum_member_reference<CLASS, MEMBER_TYPE> * static json_enum_member_reference<CLASS, MEMBER_TYPE> *
enum_reference( enum_reference(
@@ -237,14 +237,20 @@ namespace pipedal
const ENUM_CONVERTER *converter) const ENUM_CONVERTER *converter)
{ {
return new json_enum_member_reference<CLASS, MEMBER_TYPE>(name, member_pointer, converter); return new json_enum_member_reference<CLASS, MEMBER_TYPE>(name, member_pointer, converter);
}; }
template <typename CLASS, typename MEMBER_TYPE> template <typename CLASS, typename MEMBER_TYPE>
static json_conditional_member_reference<CLASS, MEMBER_TYPE> * static json_conditional_member_reference<CLASS, MEMBER_TYPE> *
conditional_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer, typename JsonConditionFunction<CLASS, MEMBER_TYPE>::Pointer condition) conditional_reference(const char *name, MEMBER_TYPE CLASS::*member_pointer, typename JsonConditionFunction<CLASS, MEMBER_TYPE>::Pointer condition)
{ {
return new json_conditional_member_reference<CLASS, MEMBER_TYPE>(name, member_pointer, condition); return new json_conditional_member_reference<CLASS, MEMBER_TYPE>(name, member_pointer, condition);
}; }
// for cases where the name is not a valid C++ identifier.
#define JSON_MAP_DICTIONARY_REFERENCE(class,name, variable) \
json_map::reference(name,&class::variable##_),
}; };
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@@ -254,7 +260,7 @@ namespace pipedal
{ {
template <class TYPE> template <class TYPE>
static std::true_type test(decltype(TYPE::jmap) *) { return std::true_type(); }; static std::true_type test(decltype(TYPE::jmap) *) { return std::true_type(); }
template <class TYPE> template <class TYPE>
static std::false_type test(...); static std::false_type test(...);
@@ -268,7 +274,7 @@ namespace pipedal
{ {
template <class TYPE> template <class TYPE>
static std::true_type test() { return std::true_type(); }; static std::true_type test() { return std::true_type(); }
template <class TYPE> template <class TYPE>
static std::false_type test(...); static std::false_type test(...);
@@ -320,7 +326,7 @@ namespace pipedal
{ {
os << text; os << text;
} }
using string_view = boost::string_view; using string_view = std::string_view;
json_writer(std::ostream &os, bool compressed = true, bool allowNaN = false) json_writer(std::ostream &os, bool compressed = true, bool allowNaN = false)
: os(os), compressed(compressed), allowNaN_(allowNaN), indent_level(0) : os(os), compressed(compressed), allowNaN_(allowNaN), indent_level(0)
{ {
@@ -1049,6 +1055,7 @@ namespace pipedal
if (peek() == ']') if (peek() == ']')
{ {
c = get(); c = get();
(void)c;
break; break;
} }
T item; T item;
@@ -1057,6 +1064,7 @@ namespace pipedal
if (peek() == ',') if (peek() == ',')
{ {
c = get(); c = get();
(void)c;
} }
} }
*value = std::move(result); *value = std::move(result);
@@ -1068,7 +1076,7 @@ namespace pipedal
{ {
template <typename TYPE, typename ARG> template <typename TYPE, typename ARG>
static std::true_type test(decltype(json_reader(std::cin).read((ARG *)nullptr)) *v) { return std::true_type(); }; static std::true_type test(decltype(json_reader(std::cin).read((ARG *)nullptr)) *v) { return std::true_type(); }
template <typename TYPE, typename ARG> template <typename TYPE, typename ARG>
static std::false_type test(...); static std::false_type test(...);
+3 -2
View File
@@ -32,6 +32,7 @@
#include "AlsaDriver.hpp" #include "AlsaDriver.hpp"
#include <iomanip> #include <iomanip>
#include <chrono> #include <chrono>
#include <thread>
using namespace pipedal; using namespace pipedal;
@@ -213,11 +214,11 @@ public:
audioDriver->Activate(); audioDriver->Activate();
sleep(3); // let audio stabilize. std::this_thread::sleep_for(std::chrono::milliseconds(3000));
this->SetXruns(0); this->SetXruns(0);
sleep(7); // run for a bit. std::this_thread::sleep_for(std::chrono::milliseconds(7000));
audioDriver->Deactivate(); audioDriver->Deactivate();
audioDriver->Close(); audioDriver->Close();
+261 -61
View File
@@ -42,6 +42,45 @@ static std::string ToString(const std::vector<uint8_t> &value)
return std::string(start, end); return std::string(start, end);
} }
static std::string ToJsonAtomPath(const std::string &path) {
json_variant v = json_variant::make_object();;
auto obj = v.as_object();
(*obj)["otype_"] = json_variant("Path");
(*obj)["value"] = json_variant(path);
std::ostringstream ss;
json_writer writer(ss);
writer.write(v);
return ss.str();
}
static bool TryGetAtomPath(const std::string &atomJson, std::string *outPath)
{
if (atomJson.empty())
return false;
std::stringstream ss(atomJson);
json_reader reader(ss);
json_variant vProperty;
reader.read(&vProperty);
if (vProperty.is_object())
{
auto obj = vProperty.as_object();
if (obj->contains("otype_") &&
(*obj)["otype_"].as_string() == "Path")
{
if (obj->contains("value"))
{
std::string value = (*obj)["value"].as_string();
if (!value.empty())
{
*outPath = value;
return true;
}
}
}
}
return false;
}
static std::vector<uint8_t> ToBinary(const std::string &value) static std::vector<uint8_t> ToBinary(const std::string &value)
{ {
std::vector<uint8_t> result(value.length() + 1); std::vector<uint8_t> result(value.length() + 1);
@@ -65,6 +104,9 @@ public:
void LoadPresets(PiPedalModel &model, const std::string &presetJson) void LoadPresets(PiPedalModel &model, const std::string &presetJson)
{ {
pluginUploadDirectory = model.GetPluginUploadDirectory();
pluginUploadDirectoryString = pluginUploadDirectory.string();
BankFile bankFile; BankFile bankFile;
std::istringstream s(presetJson); std::istringstream s(presetJson);
@@ -73,12 +115,13 @@ public:
reader.read(&bankFile); reader.read(&bankFile);
GatherMediaPaths(model, bankFile); GatherMediaPaths(model, bankFile);
configFiles["bankFile.json"] = presetJson; configFiles["bankFile.json"] = presetJson;
pluginUploadDirectory = model.GetPluginUploadDirectory();
} }
void LoadPluginPresets(PiPedalModel &model, const std::string pluginPresetJson) void LoadPluginPresets(PiPedalModel &model, const std::string pluginPresetJson)
{ {
pluginUploadDirectory = model.GetPluginUploadDirectory();
pluginUploadDirectoryString = pluginUploadDirectory.string();
PluginPresets pluginPresets; PluginPresets pluginPresets;
std::istringstream s(pluginPresetJson); std::istringstream s(pluginPresetJson);
@@ -88,8 +131,6 @@ public:
GatherMediaPaths(model, pluginPresets); GatherMediaPaths(model, pluginPresets);
configFiles["pluginPresets.json"] = pluginPresetJson; configFiles["pluginPresets.json"] = pluginPresetJson;
pluginUploadDirectory = model.GetPluginUploadDirectory();
} }
virtual ~PresetBundleWriterImpl() noexcept; virtual ~PresetBundleWriterImpl() noexcept;
@@ -98,6 +139,10 @@ public:
private: private:
void AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name); void AddUsedPlugin(PiPedalModel &model, const std::string &pluginUri, const std::string &name);
std::string UnmapPath(const std::string &path);
std::string MapPath(const std::string &path);
bool IsValidMediaPath(const std::string &path);
void GatherMediaPaths(PiPedalModel &model, BankFile &bankFile); void GatherMediaPaths(PiPedalModel &model, BankFile &bankFile);
void GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPreset); void GatherMediaPaths(PiPedalModel &model, PluginPresets &pluginPreset);
@@ -107,6 +152,7 @@ private:
std::vector<std::map<std::string, std::string>> pluginMetadata; std::vector<std::map<std::string, std::string>> pluginMetadata;
std::filesystem::path pluginUploadDirectory; std::filesystem::path pluginUploadDirectory;
std::string pluginUploadDirectoryString;
}; };
void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath) void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath)
@@ -134,7 +180,14 @@ void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath)
std::filesystem::path sourcePath = this->pluginUploadDirectory / std::filesystem::path(mediaPath); std::filesystem::path sourcePath = this->pluginUploadDirectory / std::filesystem::path(mediaPath);
if (std::filesystem::exists(sourcePath)) if (std::filesystem::exists(sourcePath))
{ {
zipFile->WriteFile(zipName, sourcePath); if (IsValidMediaPath(sourcePath)) // paranoid guard against exfiltration of non-media files.
{
zipFile->WriteFile(zipName, sourcePath);
}
else
{
Lv2Log::warning(SS("Media file is not valid: " << sourcePath));
}
} }
else else
{ {
@@ -145,7 +198,6 @@ void PresetBundleWriterImpl::WriteToFile(const std::filesystem::path &filePath)
{ {
zipFile->WriteFile(SS(zipName << ".mdata"), metadataPath); zipFile->WriteFile(SS(zipName << ".mdata"), metadataPath);
} }
} }
zipFile->Close(); zipFile->Close();
} }
@@ -171,6 +223,54 @@ void PresetBundleWriterImpl::GatherMediaPaths(PiPedalModel &model, BankFile &ban
} }
} }
} }
for (const auto &property : plugin->pathProperties_)
{
std::string path;
if (TryGetAtomPath(property.second, &path))
{
path = UnmapPath(path);
if (!mediaPaths.contains(path))
{
mediaPaths.insert(std::move(path));
}
}
}
for (const auto &snapshot : pedalboard.snapshots())
{
if (snapshot)
{
for (const auto &value : snapshot->values_)
{
if (value.isEnabled_)
{
const Lv2PluginState &state = value.lv2State_;
for (const auto &v : state.values_)
{
if (v.second.atomType_ == LV2_ATOM__Path)
{
std::string path = ToString(v.second.value_);
if (!mediaPaths.contains(path))
{
mediaPaths.insert(std::move(path));
}
}
}
for (const auto &property : value.pathProperties_)
{
std::string path;
if (TryGetAtomPath(property.second, &path))
{
path = UnmapPath(path);
if (!mediaPaths.contains(path))
{
mediaPaths.insert(std::move(path));
}
}
}
}
}
}
}
} }
} }
} }
@@ -246,8 +346,8 @@ private:
{ {
return false; return false;
} }
std::string metadataZipFilename = SS(zipFileName <<".mdata"); std::string metadataZipFilename = SS(zipFileName << ".mdata");
std::filesystem::path metadataFilename = SS(filePath.string() <<".mdata"); std::filesystem::path metadataFilename = SS(filePath.string() << ".mdata");
if (!std::filesystem::exists(metadataFilename)) if (!std::filesystem::exists(metadataFilename))
{ {
@@ -256,7 +356,9 @@ private:
return true; return true;
} }
return false; return false;
} else { }
else
{
if (!zipFile->FileExists(metadataZipFilename)) if (!zipFile->FileExists(metadataZipFilename))
{ {
return false; return false;
@@ -267,6 +369,12 @@ private:
} }
void RenameMediaFileProperty(const std::string oldName, const std::string &newName); void RenameMediaFileProperty(const std::string oldName, const std::string &newName);
void RenameState(Lv2PluginState &state, const std::string oldName, const std::string &newName);
void RenamePedalboardItem(PedalboardItem *item, const std::string oldName, const std::string &newName);
void RenamePedalboard(Pedalboard &pedalboard, const std::string oldName, const std::string &newName);
void RenamePreset(PluginPreset &pedalboard, const std::string oldName, const std::string &newName);
void RenameSnapshot(Snapshot *snapshot, const std::string oldName, const std::string &newName);
std::filesystem::path pluginUploadDirectory; std::filesystem::path pluginUploadDirectory;
// BankFile bankFile; // BankFile bankFile;
ZipFileReader::ptr zipFile; ZipFileReader::ptr zipFile;
@@ -274,75 +382,137 @@ private:
BankFile bankFile; BankFile bankFile;
PluginPresets pluginPresets; PluginPresets pluginPresets;
}; };
void PresetBundleReaderImpl::RenameState(Lv2PluginState &state, const std::string oldName, const std::string &newName)
void PresetBundleReaderImpl::RenameMediaFileProperty(const std::string oldName, const std::string &newName)
{ {
for (auto &preset : bankFile.presets()) for (auto &value : state.values_)
{ {
Pedalboard &pedalboard = preset->preset(); if (value.second.atomType_ == LV2_ATOM__Path)
auto items = pedalboard.GetAllPlugins();
for (auto plugin : items)
{ {
Lv2PluginState &state = plugin->lv2State(); std::string path = ToString(value.second.value_);
for (auto &value : state.values_) if (path == oldName)
{ {
if (value.second.atomType_ == LV2_ATOM__Path) state.values_.at(value.first).value_ = ToBinary(newName);
{
std::string path = ToString(value.second.value_);
if (path == oldName)
{
state.values_.at(value.first).value_ = ToBinary(newName);
}
}
} }
std::string fullOldPath = (pluginUploadDirectory / oldName).string(); }
for (auto &property : plugin->pathProperties_) }
{ }
std::stringstream ss(property.second);
json_reader reader(ss);
json_variant vProperty;
reader.read(&vProperty);
if (vProperty.is_object())
{
auto obj = vProperty.as_object();
if (obj->contains("otype_") &&
(*obj)["otype_"].as_string() == "Path")
{
if (obj->contains("value"))
{
std::string value = (*obj)["value"].as_string();
if (value == fullOldPath)
{
(*obj)["value"] = (pluginUploadDirectory / newName).string();
std::ostringstream ss;
json_writer writer(ss);
writer.write(vProperty);
plugin->pathProperties_[property.first] = ss.str();
break;
}
}
}
void PresetBundleReaderImpl::RenamePedalboardItem(PedalboardItem *plugin, const std::string oldName, const std::string &newName)
{
Lv2PluginState &state = plugin->lv2State();
RenameState(state, oldName, newName);
std::vector<std::pair<std::string, std::string>> propertiesToUpdate;
std::string fullOldPath = (pluginUploadDirectory / oldName).string();
for (auto &property : plugin->pathProperties_)
{
std::stringstream ss(property.second);
json_reader reader(ss);
json_variant vProperty;
reader.read(&vProperty);
if (vProperty.is_object())
{
auto obj = vProperty.as_object();
if (obj->contains("otype_") &&
(*obj)["otype_"].as_string() == "Path")
{
if (obj->contains("value"))
{
std::string value = (*obj)["value"].as_string();
if (value == fullOldPath)
{
(*obj)["value"] = (pluginUploadDirectory / newName).string();
std::ostringstream ss;
json_writer writer(ss);
writer.write(vProperty);
propertiesToUpdate.push_back(std::make_pair(property.first, ss.str()));
}
} }
} }
} }
} }
for (auto preset : pluginPresets.presets_) for (auto &property : propertiesToUpdate)
{ {
Lv2PluginState state = preset.state_; plugin->pathProperties_[property.first] = property.second;
for (auto &value : state.values_) }
}
void PresetBundleReaderImpl::RenameSnapshot(Snapshot *snapshot, const std::string oldName, const std::string &newName)
{
std::string fullOldPath = (pluginUploadDirectory / oldName).string();
for (auto &value : snapshot->values_)
{
if (value.isEnabled_)
{ {
if (value.second.atomType_ == LV2_ATOM__Path) Lv2PluginState &state = value.lv2State_;
RenameState(state, oldName, newName);
std::vector<std::pair<std::string, std::string>> propertiesToUpdate;
// Check the path properties in the snapshot value.
for (auto &property : value.pathProperties_)
{ {
std::string path = ToString(value.second.value_); std::string path;
if (path == oldName) if (TryGetAtomPath(property.second,&path)) {
{ if (path == fullOldPath) {
state.values_.at(value.first).value_ = ToBinary(newName); propertiesToUpdate.push_back(std::make_pair(property.first,
ToJsonAtomPath((pluginUploadDirectory / newName).string())));
}
} }
} }
for (auto &property : propertiesToUpdate)
{
value.pathProperties_[property.first] = property.second;
}
} }
} }
} }
void PresetBundleReaderImpl::RenamePedalboard(Pedalboard &pedalboard, const std::string oldName, const std::string &newName)
{
auto items = pedalboard.GetAllPlugins();
for (auto pedalboardItem : items)
{
RenamePedalboardItem(pedalboardItem, oldName, newName);
}
for (std::shared_ptr<Snapshot> &snapshot : pedalboard.snapshots())
{
if (snapshot)
{
RenameSnapshot(snapshot.get(), oldName, newName);
}
}
}
void PresetBundleReaderImpl::RenamePreset(PluginPreset &preset, const std::string oldName, const std::string &newName)
{
Lv2PluginState state = preset.state_;
for (auto &value : state.values_)
{
if (value.second.atomType_ == LV2_ATOM__Path)
{
std::string path = ToString(value.second.value_);
if (path == oldName)
{
state.values_.at(value.first).value_ = ToBinary(newName);
}
}
}
}
void PresetBundleReaderImpl::RenameMediaFileProperty(const std::string oldName, const std::string &newName)
{
// snapshots.
// there should be a dictionary for remapped media files.
// there should be a set for saved media files.
for (auto &preset : bankFile.presets())
{
Pedalboard &pedalboard = preset->preset();
RenamePedalboard(pedalboard, oldName, newName);
}
for (auto &preset : pluginPresets.presets_)
{
RenamePreset(preset, oldName, newName);
}
}
PresetBundleReader::ptr PresetBundleReader::LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath) PresetBundleReader::ptr PresetBundleReader::LoadPresetsFile(PiPedalModel &model, const std::filesystem::path &filePath)
{ {
@@ -420,7 +590,7 @@ void PresetBundleReaderImpl::ExtractMediaFile(const std::string &zipFileName)
namespace fs = std::filesystem; namespace fs = std::filesystem;
if (zipFileName.starts_with("media/")) if (zipFileName.starts_with("media/"))
{ {
if (zipFileName.ends_with(".mdata")) if (zipFileName.ends_with(".mdata"))
{ {
return; return;
} }
@@ -530,3 +700,33 @@ void PresetBundleWriterImpl::AddUsedPlugin(PiPedalModel &model, const std::strin
} }
} }
} }
std::string PresetBundleWriterImpl::UnmapPath(const std::string &path)
{
if (path.starts_with(pluginUploadDirectoryString))
{
return path.substr(pluginUploadDirectory.string().length() + 1);
}
return path;
}
bool PresetBundleWriterImpl::IsValidMediaPath(const std::string &path)
{
if (path.starts_with(pluginUploadDirectoryString))
{
if (path.length() >= pluginUploadDirectoryString.length() + 1 &&
path[pluginUploadDirectoryString.length()] == '/')
{
return true;
;
}
}
return false;
}
std::string PresetBundleWriterImpl::MapPath(const std::string &path)
{
if (!path.starts_with("/"))
{
return pluginUploadDirectory / path;
}
return path;
}
+1
View File
@@ -38,6 +38,7 @@
#include "AudioFileMetadataReader.hpp" #include "AudioFileMetadataReader.hpp"
#include "AudioFiles.hpp" #include "AudioFiles.hpp"
#include "util.hpp" #include "util.hpp"
#include "HtmlHelper.hpp"
#define OLD_PRESET_EXTENSION ".piPreset" #define OLD_PRESET_EXTENSION ".piPreset"
#define PRESET_EXTENSION ".piPreset" #define PRESET_EXTENSION ".piPreset"
-1
View File
@@ -3,7 +3,6 @@ Numeric up-down buttons blown in light theme.
Tooltips on touch ui? Tooltips on touch ui?
pre-cue next decoder stream in big loops.
convolution reverb broken. convolution reverb broken.