From 1455a0d1671527bd2a11866479aa95239abaac64 Mon Sep 17 00:00:00 2001 From: jakob Date: Fri, 1 Nov 2024 23:43:24 +0100 Subject: [PATCH] [api][feature][file] Add ID & Save preset as remix The idea is to highlight AVS's remix culture by creating metadata to both machine-readably identify presets globally, as well as keep track of any presets the current preset was based on. This could, in the future, enable a database of presets to trace inheritance connections among other things. To that end: - Add an "ID" parameter to the Root config that gets randomly assigned a UUID on first creation of a new preset. The ID does not change when the preset is saved, it's more of a "project id". - Add a "Based On" list parameter to the Root config that can record previous presets with ID, date, name and authors. - Add a `remix()` method to the Root effect, which switches some metadata and assigns a new ID and initial date. - Add an `as_remix` parameter to both `avs_preset_set()` and `avs_preset_save()` API functions. Setting this flag invokes the `remix()` method on the current Root before saving. While not strictly related, also in this commit: - Split the Root config's "Date" field into initial date and last- edited date, to reflect both start of the preset and most recent version. - When creating a new preset give it a semi-random name of the format "Untitled Preset xxxxxxxxxx", where xx..xx is a 10-digit random alphanumeric suffix. --- avs/vis_avs/avs.cpp | 11 +++++---- avs/vis_avs/avs.h | 8 ++++--- avs/vis_avs/e_root.cpp | 28 ++++++++++++++++++++++- avs/vis_avs/e_root.h | 48 +++++++++++++++++++++++++++++++++++++--- avs/vis_avs/instance.cpp | 10 ++++++--- 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/avs/vis_avs/avs.cpp b/avs/vis_avs/avs.cpp index ffeefa6..5881d8e 100644 --- a/avs/vis_avs/avs.cpp +++ b/avs/vis_avs/avs.cpp @@ -161,20 +161,23 @@ bool avs_preset_set(AVS_Handle avs, const char* preset) { return instance->preset_load(preset, false); } AVS_API -bool avs_preset_save(AVS_Handle avs, const char* file_path, bool indent) { +bool avs_preset_save(AVS_Handle avs, + const char* file_path, + bool as_remix, + bool indent) { AVS_Instance* instance = get_instance_from_handle(avs); if (instance == nullptr) { return false; } - return instance->preset_save_file(file_path, indent); + return instance->preset_save_file(file_path, as_remix, indent); } AVS_API -const char* avs_preset_get(AVS_Handle avs, bool indent) { +const char* avs_preset_get(AVS_Handle avs, bool as_remix, bool indent) { AVS_Instance* instance = get_instance_from_handle(avs); if (instance == nullptr) { return nullptr; } - return instance->preset_save(indent); + return instance->preset_save(as_remix, indent); } AVS_API bool avs_preset_set_legacy(AVS_Handle avs, diff --git a/avs/vis_avs/avs.h b/avs/vis_avs/avs.h index 1b6432e..e820d35 100644 --- a/avs/vis_avs/avs.h +++ b/avs/vis_avs/avs.h @@ -266,7 +266,9 @@ bool avs_preset_load(AVS_Handle avs, const char* file_path); bool avs_preset_set(AVS_Handle avs, const char* preset); /** - * Save the currently loaded preset to the given file path. If `indent` is `true`, the + * Save the currently loaded preset to the given file path. If `as_remix` is `true`, the + * preset's metadata will be refreshed, a new ID assigned and some of the old preset's + * metadata appended to the history list for the new preset. If `indent` is `true`, the * JSON will be indented with 4 spaces in the file. Otherwise it will be a single line. * * If `file_path` does not end in ".avs" it will be appended. Setting `file_path` to a @@ -274,7 +276,7 @@ bool avs_preset_set(AVS_Handle avs, const char* preset); * * Returns `true` on success or `false` if saving fails for some reason. */ -bool avs_preset_save(AVS_Handle avs, const char* file_path, bool indent); +bool avs_preset_save(AVS_Handle avs, const char* file_path, bool as_remix, bool indent); /** * Return the currently loaded preset's JSON string. If `indent` is `true`, the JSON @@ -289,7 +291,7 @@ bool avs_preset_save(AVS_Handle avs, const char* file_path, bool indent); * * Returns `NULL` on error. */ -const char* avs_preset_get(AVS_Handle avs, bool indent); +const char* avs_preset_get(AVS_Handle avs, bool as_remix, bool indent); /** * Load a preset from a legacy binary buffer. diff --git a/avs/vis_avs/e_root.cpp b/avs/vis_avs/e_root.cpp index d495e7f..9a1f745 100644 --- a/avs/vis_avs/e_root.cpp +++ b/avs/vis_avs/e_root.cpp @@ -18,9 +18,24 @@ (data[pos] | (data[pos + 1] << 8) | (data[pos + 2] << 16) | (data[pos + 3] << 24)) constexpr Parameter Root_Info::author_parameters[]; +constexpr Parameter Root_Info::remix_parameters[]; constexpr Parameter Root_Info::parameters[]; -E_Root::E_Root(AVS_Instance* avs) : Configurable_Effect(avs), buffers_saved(false) {} +std::string random_preset_name_slug() { + static const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + std::string slug; + for (int i = 0; i < 10; i++) { + slug += charset[rand() % (sizeof(charset) - 1)]; + } + return slug; +} + +E_Root::E_Root(AVS_Instance* avs) : Configurable_Effect(avs), buffers_saved(false) { + this->config.date_init = current_date_str(); + this->config.name = "Untitled Preset " + random_preset_name_slug(); + log_info("Name: %s", this->config.name.c_str()); + this->config.id = uuid4(); +} E_Root::~E_Root() { for (int i = 0; i < NBUF; i++) { free(this->buffers[i]); @@ -30,6 +45,17 @@ E_Root::~E_Root() { } } +void E_Root::remix() { + Root_RemixParent_Config remix_parent; + remix_parent.id = this->config.id; + remix_parent.name = this->config.name; + remix_parent.date = this->config.date_last; + remix_parent.authors = this->config.authors; + this->config.based_on.push_back(remix_parent); + this->config.id = uuid4(); + this->config.date_init = current_date_str(); +} + int E_Root::render(char visdata[2][2][576], int is_beat, int* framebuffer, diff --git a/avs/vis_avs/e_root.h b/avs/vis_avs/e_root.h index ddc8508..06532ec 100644 --- a/avs/vis_avs/e_root.h +++ b/avs/vis_avs/e_root.h @@ -11,11 +11,21 @@ struct Root_Author_Config : public Effect_Config { std::string name; }; +struct Root_BasedOn_Config : public Effect_Config { + std::string id; + std::string name; + std::string date; + std::vector authors; +}; + struct Root_Config : public Effect_Config { bool clear = false; std::string name; - std::string date; + std::string date_init; + std::string date_last; std::vector authors; + std::string id; + std::vector based_on; }; struct Root_Info : public Effect_Info { @@ -34,13 +44,36 @@ struct Root_Info : public Effect_Info { "Role", "'author', 'remixer', 'editor', 'curator' - or anything else")}; - static constexpr uint32_t num_parameters = 4; + static constexpr uint32_t num_remix_parameters = 4; + static constexpr Parameter remix_parameters[num_remix_parameters] = { + P_STRING(offsetof(Root_BasedOn_Config, id), "ID", "UUID of the predecessor"), + P_STRING(offsetof(Root_BasedOn_Config, name), + "Name", + "Name of the predecessor"), + P_STRING(offsetof(Root_BasedOn_Config, date), + "Date", + "Last-edited date of the predecessor"), + P_LIST(offsetof(Root_BasedOn_Config, authors), + "Authors", + author_parameters, + num_author_parameters, + 0, + 0, + "Authors of the predecessor"), + }; + + static constexpr uint32_t num_parameters = 7; static constexpr Parameter parameters[num_parameters] = { P_BOOL(offsetof(Root_Config, clear), "Clear", "Clear the screen for every new frame"), P_STRING(offsetof(Root_Config, name), "Name", "Name of the preset"), - P_STRING(offsetof(Root_Config, date), "Date", "Date of the preset"), + P_STRING(offsetof(Root_Config, date_init), + "Date Initial", + "Date of preset's first save"), + P_STRING(offsetof(Root_Config, date_last), + "Date Last", + "Date of preset's latest save"), P_LIST(offsetof(Root_Config, authors), "Authors", author_parameters, @@ -48,6 +81,14 @@ struct Root_Info : public Effect_Info { 0, 0, "Authors of the preset"), + P_STRING(offsetof(Root_Config, id), "ID", "UUID of the preset"), + P_LIST(offsetof(Root_Config, based_on), + "Based On", + remix_parameters, + num_remix_parameters, + 0, + 0, + "Previous presets used to create this one"), }; virtual bool can_have_child_components() const { return true; } @@ -69,6 +110,7 @@ class E_Root : public Configurable_Effect { virtual void load_legacy(unsigned char* data, int len); virtual int save_legacy(unsigned char* data); virtual E_Root* clone() { return new E_Root(*this); } + void remix(); int64_t get_num_renders() { return this->children.size(); } void start_buffer_context(); void end_buffer_context(); diff --git a/avs/vis_avs/instance.cpp b/avs/vis_avs/instance.cpp index 013faec..b024a9d 100644 --- a/avs/vis_avs/instance.cpp +++ b/avs/vis_avs/instance.cpp @@ -177,8 +177,8 @@ bool AVS_Instance::preset_load_legacy(const uint8_t* preset, return false; } -bool AVS_Instance::preset_save_file(const char* file_path, bool indent) { - auto preset_str = this->preset_save(indent); +bool AVS_Instance::preset_save_file(const char* file_path, bool as_remix, bool indent) { + auto preset_str = this->preset_save(as_remix, indent); FILE* fp = fopen(file_path, "wb"); if (fp != nullptr) { fwrite(preset_str, 1, strlen(preset_str), fp); @@ -210,10 +210,14 @@ int AVS_Instance::preset_save_file_legacy(const char* file_path) { return result; } -const char* AVS_Instance::preset_save(bool indent) { +const char* AVS_Instance::preset_save(bool as_remix, bool indent) { json j; j["preset format version"] = "0"; j["avs version"] = "2.81.4"; + if (as_remix) { + this->root.remix(); + } + this->root.config.date_last = current_date_str(); try { j.update(this->root.save()); j.erase("effect");