From 5acda68a6cf2041107a59a4bdb8c0a908dd51752 Mon Sep 17 00:00:00 2001 From: Connor Bell Date: Fri, 4 Jun 2021 10:26:44 -0400 Subject: [PATCH] Added FFMPEG manager which is wrapped by a threaded system call so a dialogue can be shown during processing. Refactored preset structure to be exported to web. --- src/FfmpegManager.cpp | 170 +++++++++++ src/FfmpegManager.h | 60 ++++ src/PNGRenderer.cpp | 111 +++++-- src/PNGRenderer.h | 20 +- src/Parameters/TextureParameter.cpp | 2 +- src/PathUtil.cpp | 58 ++++ src/PathUtil.h | 12 + src/ShaderChain.cpp | 434 +++++++--------------------- src/ShaderChain.h | 30 +- src/ShaderPass.cpp | 6 +- src/ThreadedSystemCall.cpp | 11 + src/ThreadedSystemCall.h | 32 ++ src/gui/PassesGui.cpp | 7 +- src/gui/PassesGui.h | 4 +- src/gui/credits.h | 2 +- src/ofApp.cpp | 2 +- src/preset.cpp | 172 +++++++++++ src/preset.h | 49 ++++ src/webExport.cpp | 247 ++++++++++++++++ src/webExport.h | 19 ++ 20 files changed, 1055 insertions(+), 393 deletions(-) create mode 100644 src/FfmpegManager.cpp create mode 100644 src/FfmpegManager.h create mode 100644 src/PathUtil.cpp create mode 100644 src/PathUtil.h create mode 100644 src/ThreadedSystemCall.cpp create mode 100644 src/ThreadedSystemCall.h create mode 100644 src/preset.cpp create mode 100644 src/preset.h create mode 100644 src/webExport.cpp create mode 100644 src/webExport.h diff --git a/src/FfmpegManager.cpp b/src/FfmpegManager.cpp new file mode 100644 index 0000000..ef519f0 --- /dev/null +++ b/src/FfmpegManager.cpp @@ -0,0 +1,170 @@ +#include "FfmpegManager.h" +#include "PathUtil.h" + +void FfmpegManager::saveGif(string filename, PNGRenderer *pngRenderer) { + + if (isRunning) return; + + vector commands; + statusLabel.set("Saving Gif..."); + cancelButtonRef->setName("Cancel"); + cancelButtonRef->setEnabled(true); + container->setEnabled(true); + + int totalFrames = pngRenderer->FPS * pngRenderer->animduration; + string totalZerosString = to_string((int)floor(log10(((float)totalFrames))) + 1); + string fileWithoutExtension = pngRenderer->presetDisplayName; + string rendersDirectory = ofFilePath::getAbsolutePath(ofToDataPath("")) + PathUtil::getSlash() + "renders" + PathUtil::getSlash(); + string targetDirectory = rendersDirectory + fileWithoutExtension + PathUtil::getSlash(); + + string mkdirCommand = "mkdir \"" + targetDirectory + "\""; + commands.push_back(mkdirCommand); + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + string moveCommand = "move "; +#else + string moveCommand = "mv "; +#endif + + string moveFilesCommand = moveCommand + rendersDirectory + fileWithoutExtension + "_*.png " + targetDirectory; + commands.push_back(moveFilesCommand); + + string ffmpegCommand = this->ffmpegCommand + " -r " + to_string(pngRenderer->FPS) + " -v warning -start_number 0 -i \"" + targetDirectory + fileWithoutExtension + "_%0" + totalZerosString + "d.png\" -vf scale=500:-1:flags=lanczos,palettegen=stats_mode=diff:reserve_transparent=off:max_colors=" + to_string(pngRenderer->gifNumColors) + " -y " + "\"" + targetDirectory + PathUtil::getSlash() + "palette.png" + "\""; + commands.push_back(ffmpegCommand); + cout << ffmpegCommand << endl; + string targetFilename = targetDirectory + fileWithoutExtension + ".gif"; + targetFilename = PathUtil::createUniqueFilePath(targetFilename); + + int resX = (float)pngRenderer->resolutionX * pngRenderer->gifResolutionScale; + int resY = (float)pngRenderer->resolutionY * pngRenderer->gifResolutionScale; + + string compressionCommand = this->ffmpegCommand + " -v warning -thread_queue_size 512 -start_number 0 -i \"" + targetDirectory + fileWithoutExtension + "_%0" + totalZerosString + "d.png\" -i \"" + targetDirectory + "palette.png\" -r 30 -lavfi scale=" + to_string(resX) + ":" + to_string(resY) + ":flags=\"lanczos [x]; [x][1:v] paletteuse\" -y \"" + targetFilename + "\""; + commands.push_back(compressionCommand); + + mostRecentSavedFile = targetFilename; + isRunning = true; + processCall.systemCall(commands, &completedEventGif); +} + +void FfmpegManager::saveVideo(string filename, string soundFilePath, PNGRenderer *pngRenderer) { + + if (isRunning) return; + cancelButtonRef->setName("Cancel"); + cancelButtonRef->setEnabled(true); + vector commands; + statusLabel.setName("Saving mp4..."); + + container->setEnabled(true); + + string f = filename; + + int totalFrames = pngRenderer->FPS * pngRenderer->animduration; + string rendersDirectory = PathUtil::getSlash() + "renders" + PathUtil::getSlash(); + string outputFilename = ofFilePath::getAbsolutePath(ofToDataPath("")) + rendersDirectory + f; + string mkdirCommand = "mkdir " + outputFilename; + commands.push_back(mkdirCommand); + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + string moveCommand = "move "; +#else + string moveCommand = "mv "; +#endif + + string moveFilesCommand = moveCommand + outputFilename + "_*.png " + outputFilename; + commands.push_back(moveFilesCommand); + + outputFilename = outputFilename + PathUtil::getSlash() + f; + + cout << "Creating mp4 " << outputFilename << endl; + string outputMp4Filename = outputFilename + ".mp4"; + outputMp4Filename = PathUtil::createUniqueFilePath(outputMp4Filename); + string fpsString = to_string(pngRenderer->FPS); + string totalZerosString = to_string((int)floor(log10(((float)totalFrames))) + 1); + + string ffmpegCommand = this->ffmpegCommand + " -r " + fpsString + " -f image2 -s 1080x1920 -i \"" + outputFilename + "_%0" + totalZerosString + "d.png\" -vcodec libx264 -crf 18 -pix_fmt yuv420p " + outputMp4Filename; + commands.push_back(ffmpegCommand); + + cout << ffmpegCommand << endl; + + mostRecentSavedFile = outputMp4Filename; + + // Add soundfile to video if it exists + if (soundFilePath.length() > 0) { + string outputMp4AudioFilename = outputFilename + "_audio.mp4"; + outputMp4AudioFilename = PathUtil::createUniqueFilePath(outputMp4AudioFilename); + string addSoundCommand = this->ffmpegCommand + " -i \"" + outputMp4Filename + "\" -i \"" + soundFilePath + "\" -vcodec copy -acodec aac -shortest " + outputMp4AudioFilename; + commands.push_back(addSoundCommand); + outputMp4Filename = outputMp4AudioFilename; + } + + // Loop the video if required + if (pngRenderer->numLoops > 1) { + string inputFileText = ""; + for (unsigned int i = 0; i < pngRenderer->numLoops; i++) { + inputFileText += "file '" + outputMp4Filename + "''\n"; + } + + ofstream file; + file.open("list.txt"); + file << inputFileText; + file.close(); + + string outputLoopedFilename = outputFilename + "_looped.mp4"; + outputLoopedFilename = PathUtil::createUniqueFilePath(outputLoopedFilename); + + string loopffmpegCommand = this->ffmpegCommand + " -f concat -safe 0 -i list.txt -c copy " + outputLoopedFilename; + commands.push_back(loopffmpegCommand); + + outputMp4Filename = outputLoopedFilename; + } + + isRunning = true; + processCall.systemCall(commands, &completedEventMp4); +} + +// Moves files in current directory to a newly created directory with the target file name +void FfmpegManager::moveFilesIntoSubdir(string filenamePrefix) { + string mkdirCommand = "mkdir " + filenamePrefix; + system(mkdirCommand.c_str()); + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + string moveCommand = "move "; +#else + string moveCommand = "mv "; +#endif + + string moveFilesCommand = moveCommand + filenamePrefix + "_*.png " + filenamePrefix; + cout << moveFilesCommand << endl; + system(moveFilesCommand.c_str()); +} + +void FfmpegManager::cancelButtonPressed() { + container->setEnabled(false); + isRunning = false; +} + +void FfmpegManager::onCompletedGif() { + // Check for video writing success + cancelButtonRef->setName("Done"); + cancelButtonRef->setEnabled(true); + if (PathUtil::fileExists(mostRecentSavedFile)) { + updateStatusText("Gif saved to " + mostRecentSavedFile); + } + else { + updateStatusText("Error saving video to " + mostRecentSavedFile); + } + isRunning = false; +} + +void FfmpegManager::onCompletedMp4() { + // Check for video writing success + cancelButtonRef->setName("Done"); + cancelButtonRef->setEnabled(true); + if (PathUtil::fileExists(mostRecentSavedFile)) { + updateStatusText("Video saved to " + mostRecentSavedFile); + } + else { + updateStatusText("Error saving video to " + mostRecentSavedFile); + } + isRunning = false; +} \ No newline at end of file diff --git a/src/FfmpegManager.h b/src/FfmpegManager.h new file mode 100644 index 0000000..5b24b1a --- /dev/null +++ b/src/FfmpegManager.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include "ofxJSON.h" +#include "ThreadedSystemCall.h" +#include "PNGRenderer.h" + +class FfmpegManager { +public: + + // Constructor that loads config which contains alternate ffmpeg path on macos + FfmpegManager(ofxGui *gui) { + + container = gui->addContainer(); + + container->add(statusLabel); + container->setEnabled(false); + cancelButton.addListener(this, &FfmpegManager::cancelButtonPressed); + cancelButtonRef = this->container->add(cancelButton.set("Cancel"), ofJson({ {"type", "fullsize"}, {"text-align", "center"} })); + this->container->setPosition(ofPoint(ofGetWidth() / 2 - this->container->getWidth() / 2, ofGetHeight() / 2 - this->container->getHeight() / 2)); + ofAddListener(this->completedEventGif, this, &FfmpegManager::onCompletedGif); + ofAddListener(this->completedEventMp4, this, &FfmpegManager::onCompletedMp4); + +#if __APPLE__ + std::string file = ofToDataPath("config.json"); + ofxJSONElement result; + + bool parsingSuccessful = result.open(file); + + if (parsingSuccessful) + { + ffmpegCommand = result["ffmpeg"].asString(); + } +#endif + } + + void saveVideo(string filename, string soundFilePath, PNGRenderer *pngRenderer); + void saveGif(string filename, PNGRenderer *pngRenderer); + +private: + ofParameter cancelButton; + ofParameter statusLabel; + string ffmpegCommand = "ffmpeg"; + ofxGuiButton *cancelButtonRef; + ofxGuiContainer *container = nullptr; + ThreadedSystemCall processCall; + ofEvent completedEventGif; + ofEvent completedEventMp4; + string mostRecentSavedFile; + bool isRunning = false; + + void cancelButtonPressed(); + void onCompletedGif(); + void onCompletedMp4(); + void moveFilesIntoSubdir(string filenamePrefix); + + void updateStatusText(string text) { + statusLabel.set(text, ""); + } +}; \ No newline at end of file diff --git a/src/PNGRenderer.cpp b/src/PNGRenderer.cpp index 201f504..1f26ecd 100755 --- a/src/PNGRenderer.cpp +++ b/src/PNGRenderer.cpp @@ -6,8 +6,6 @@ PNGRenderer::PNGRenderer(float animduration, int fps, glm::vec2 resolution) { this->animduration = animduration; - this->FPS = fps; - this->presetFilePath = ""; this->presetDisplayName = ""; this->currentFrame = 0; this->totalFrames = animduration * fps; @@ -17,10 +15,64 @@ PNGRenderer::PNGRenderer(float animduration, int fps, glm::vec2 resolution) { this->displayScaleParam = 1.0; this->renderedFrames = 1; this->frameskip = 1; - this->FPS = 30; this->numLoops = 1; this->numBlendFrames = 1; this->preview = false; + this->animduration.addListener(this, &PNGRenderer::animDurationUpdated); + numBlendFrames.addListener(this, &PNGRenderer::numBlendFramesUpdated); + resolutionX.addListener(this, &PNGRenderer::resolutionXUpdated); + resolutionY.addListener(this, &PNGRenderer::resolutionYUpdated); + frameskip.addListener(this, &PNGRenderer::frameskipUpdated); + displayScaleParam.addListener(this, &PNGRenderer::displayScaleParamUpdated); + exportToWebButton.addListener(this, &PNGRenderer::exportToWebButtonPressed); + updateAllHtmlButton.addListener(this, &PNGRenderer::updateAllHtmlButtonPressed); + FPS.addListener(this, &PNGRenderer::fpsUpdated); +} + +PNGRenderer::~PNGRenderer() { + /* + this->animduration.removeListener(this, &PNGRenderer::animDurationUpdated); + resolutionX.removeListener(this, &PNGRenderer::resolutionXUpdated); + resolutionY.removeListener(this, &PNGRenderer::resolutionYUpdated); + displayScaleParam.removeListener(this, &PNGRenderer::displayScaleParamUpdated); + frameskip.removeListener(this, &PNGRenderer::frameskipUpdated); + numBlendFrames.removeListener(this, &PNGRenderer::numBlendFramesUpdated); + */ +} + +void PNGRenderer::animDurationUpdated(float &val) { + if (presetRef != nullptr) + presetRef->animduration = val; +} + +void PNGRenderer::resolutionXUpdated(float &val) { + if (presetRef != nullptr) + presetRef->resolutionX = val; +} + +void PNGRenderer::resolutionYUpdated(float &val) { + if (presetRef != nullptr) + presetRef->resolutionY = val; +} + +void PNGRenderer::frameskipUpdated(int &val) { + if (presetRef != nullptr) + presetRef->frameskip = val; +} + +void PNGRenderer::displayScaleParamUpdated(float &val) { + if (presetRef != nullptr) + presetRef->displayScaleParam = val; +} + +void PNGRenderer::numBlendFramesUpdated(int &val) { + if (presetRef != nullptr) + presetRef->numBlendFrames = val; +} + +void PNGRenderer::fpsUpdated(int &val) { + if (presetRef != nullptr) + presetRef->fps = val; } void PNGRenderer::AddToGui(ofxGuiContainer *panel, ofxGuiContainer *statusLabelPanel, FFTManager *fft) { @@ -63,6 +115,8 @@ void PNGRenderer::AddToGui(ofxGuiContainer *panel, ofxGuiContainer *statusLabelP renderingMenu->add(preview.set("Preview", preview)); renderingMenu->add(saveScreenshotButton.set("Save Screenshot"), buttonStyling); saveFramesButton = renderingMenu->add(saveButton.set("Save Frames"), buttonStyling); + renderingMenu->add(exportToWebButton.set("Export to Web"), buttonStyling); + renderingMenu->add(updateAllHtmlButton.set("Update All HTML"), buttonStyling); panel->add(displayScaleParam.set("Display scale", displayScaleParam, 0.1, 5.0)); @@ -102,7 +156,6 @@ void PNGRenderer::WritePNG(ofFbo *buffer) { s += std::to_string(this->currentFrame); buffer->readToPixels(outputPixels); string destFilePath = this->renderDirectory + this->presetDisplayName.get() + "_" + s + ".png"; - cout << destFilePath << endl; ofSaveImage(outputPixels, destFilePath, OF_IMAGE_QUALITY_BEST); } @@ -130,32 +183,15 @@ void PNGRenderer::Start() { saveFramesButton->setName("Cancel"); } -void PNGRenderer::updatePath(string s) { - if (s == "") return; - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) - - replace(s.begin(), s.end(), '/', '\\'); - static const std::string slash = "\\"; -#else - static const std::string slash = "/"; -#endif - - string file = s.substr(s.find_last_of(slash) + 1); - - // add json extension if needed - int indexOfPeriod = file.find_last_of("."); - if (indexOfPeriod == std::string::npos) { - file += ".json"; - s += ".json"; - indexOfPeriod = file.find_last_of("."); - } - - string fileWithoutExtension = file.substr(0, indexOfPeriod); - - presetDisplayName.set(fileWithoutExtension); - presetDisplayNameLabel.set("Preset: " + presetDisplayName.get()); - presetFilePath = s; +void PNGRenderer::updateUI(preset *currentPreset) { + presetRef = currentPreset; + presetDisplayName.set(currentPreset->presetDisplayName); + presetDisplayNameLabel.set("Preset: " + currentPreset->presetDisplayName); + resolutionX = currentPreset->resolutionX; + resolutionY = currentPreset->resolutionY; + FPS = currentPreset->fps; + animduration = currentPreset->animduration; + numBlendFrames = currentPreset->numBlendFrames; } void PNGRenderer::UpdateResolution(int w, int h) { @@ -166,3 +202,18 @@ void PNGRenderer::UpdateResolution(int w, int h) { bool PNGRenderer::isMouseOverAnyMenu() { return fileMenu->ofxGuiElement::isMouseOver() || renderingMenu->ofxGuiElement::isMouseOver() || gifGroup->ofxGuiElement::isMouseOver() || vidGroup->ofxGuiElement::isMouseOver(); } + +void PNGRenderer::exportToWebButtonPressed() { + + string destFilePath = "web" + PathUtil::getSlash() + "thumbnails" + PathUtil::getSlash() + presetRef->presetDisplayName + ".png"; + screenshot(&(presetRef->passes[presetRef->getLastEnabledPassIndex()]->swapBuffer), destFilePath); + + webExport exporter; + exporter.exportPreset(presetRef); +} + + +void PNGRenderer::updateAllHtmlButtonPressed() { + webExport exporter; + exporter.updateAllHTML(); +} \ No newline at end of file diff --git a/src/PNGRenderer.h b/src/PNGRenderer.h index aab14ac..fa68161 100755 --- a/src/PNGRenderer.h +++ b/src/PNGRenderer.h @@ -4,10 +4,15 @@ #include "ofMain.h" #include "ofxGuiExtended2.h" #include "FFTManager.h" +#include "preset.h" +#include "webExport.h" class PNGRenderer { public: + + ~PNGRenderer(); + string renderDirectory = "renders/"; ofParameter spaceBufferLabel; @@ -37,6 +42,8 @@ public: ofParameter FPS; ofParameter encodeMp4Button; ofParameter encodeGifButton; + ofParameter exportToWebButton; + ofParameter updateAllHtmlButton; ofParameterGroup renderParameterGroup; ofParameterGroup pngSavingGroup; @@ -56,6 +63,8 @@ public: void AddToGui(ofxGuiContainer *panel, ofxGuiContainer *statusLabelPanel, FFTManager *fft); void UpdateResolution(int w, int h); void updatePath(string s); + void updateUI(preset *currentPreset); + ofParameter preview; ofParameter saveButton; ofParameter saveScreenshotButton; @@ -74,5 +83,14 @@ private: ofxGuiMenu* renderingMenu; ofxGuiMenu* gifGroup; ofxGuiMenu* vidGroup; - + preset *presetRef = nullptr; + void animDurationUpdated(float &val); + void resolutionXUpdated(float &val); + void resolutionYUpdated(float &val); + void displayScaleParamUpdated(float &val); + void numBlendFramesUpdated(int &val); + void frameskipUpdated(int &val); + void fpsUpdated(int &val); + void exportToWebButtonPressed(); + void updateAllHtmlButtonPressed(); }; diff --git a/src/Parameters/TextureParameter.cpp b/src/Parameters/TextureParameter.cpp index fdbaa84..1251712 100755 --- a/src/Parameters/TextureParameter.cpp +++ b/src/Parameters/TextureParameter.cpp @@ -150,6 +150,7 @@ void TextureParameter::updateTextureFromFile(string &s) { else if (extension == "mp4" || extension == "mov" || extension == "avi" || extension == "mkv") { updateToNewType(VideoFile); this->videoFile.load(s); + cout << "playing video " << s << endl; this->videoFile.setUseTexture(true); this->videoFile.play(); filePath = s; @@ -200,7 +201,6 @@ void TextureParameter::startOfflineRender() { this->videoFile.setPaused(true); this->videoFile.setFrame(0); - sleep(1); } } diff --git a/src/PathUtil.cpp b/src/PathUtil.cpp new file mode 100644 index 0000000..d871fe9 --- /dev/null +++ b/src/PathUtil.cpp @@ -0,0 +1,58 @@ +#include "PathUtil.h" +#include "ofMain.h" + +namespace PathUtil { + std::string createUniqueFilePath(std::string path) { + bool found = false; + auto pathWithoutExtension = path.substr(0, path.find_last_of(".")); + auto extension = path.substr(path.find_last_of(".") + 1); + int tries = 0; + + while (!found) { + ofFile file; + + std::string p = ""; + + if (tries == 0) { + p = pathWithoutExtension + "." + extension; + } + else { + p = pathWithoutExtension + to_string(tries) + "." + extension; + } + + file.open(p, ofFile::ReadWrite, false); + + if (!file.exists()) { + found = true; + path = p; + } + file.close(); + tries++; + } + return path; + } + + std::string getSlash() { +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + return "\\"; +#else + return "/"; +#endif + } + + bool fileExists(string path) { + ofFile file; + file.open(path, ofFile::ReadOnly, false); + bool res = file.exists(); + file.close(); + return res; + } + + int copyFile(std::string from, std::string to) { + std::ifstream src(from, std::ios::binary); + std::ofstream dst(to, std::ios::binary); + + dst << src.rdbuf(); + return 1; // all good! + } +}; \ No newline at end of file diff --git a/src/PathUtil.h b/src/PathUtil.h new file mode 100644 index 0000000..1b8f515 --- /dev/null +++ b/src/PathUtil.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace PathUtil { + + // Repeatedly check for existance of a path with an accumulating suffix, return the first one which doesn't have an existing file. + std::string createUniqueFilePath(std::string path); + std::string getSlash(); + bool fileExists(std::string path); + int copyFile(std::string from, std::string to); +}; \ No newline at end of file diff --git a/src/ShaderChain.cpp b/src/ShaderChain.cpp index 12dd067..2eee22a 100755 --- a/src/ShaderChain.cpp +++ b/src/ShaderChain.cpp @@ -1,12 +1,10 @@ #include "ShaderChain.h" #include "ofxSortableList.h" #include "RenderStruct.h" +#include "PathUtil.h" void ShaderChain::Setup(glm::vec2 res) { ofDisableArbTex(); -#if __APPLE__ - loadFfmpegPath(); -#endif this->passesGui = new PassesGui(); ofAddListener(passesGui->passButtons->elementRemoved, this, &ShaderChain::removed); ofAddListener(passesGui->passButtons->elementMoved, this, &ShaderChain::moved); @@ -18,6 +16,8 @@ void ShaderChain::Setup(glm::vec2 res) { this->guiGlobal = gui.addContainer(); this->guiGlobal->setPosition(ofPoint(0, 10)); this->statusContainer = gui.addContainer(); + + this->ffmpegManager = new FfmpegManager(&gui); ofColor transparentColor; transparentColor.a = 0.0; this->statusContainer->setBackgroundColor(transparentColor); @@ -44,7 +44,7 @@ void ShaderChain::Setup(glm::vec2 res) { this->time = 0.0; this->parameterPanel = gui.addContainer(); this->cumulativeShader.load("shaders/internal/vertex.vert","shaders/internal/cumulativeAdd.frag"); - this->renderStruct.passes = &this->passes; + this->renderStruct.passes = ¤tPreset.passes; this->renderStruct.time = 0.0; this->renderStruct.fft = &this->fft; this->renderStruct.vidGrabber = &this->vidGrabber; @@ -63,27 +63,16 @@ void ShaderChain::Setup(glm::vec2 res) { ShaderChain::~ShaderChain() { delete this->pngRenderer; - for (unsigned int i = 0; i < this->passes.size(); i++) { - ofRemoveListener(passes[i]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); - delete this->passes[i]; + delete this->ffmpegManager; + for (unsigned int i = 0; i < currentPreset.passes.size(); i++) { + ofRemoveListener(currentPreset.passes[i]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); + delete currentPreset.passes[i]; } ofRemoveListener(passesGui->passButtons->elementRemoved, this, &ShaderChain::removed); ofRemoveListener(passesGui->passButtons->elementMoved, this, &ShaderChain::moved); delete this->passesGui; } -void ShaderChain::loadFfmpegPath() { - std::string file = ofToDataPath("config.json"); - ofxJSONElement result; - - bool parsingSuccessful = result.open(file); - - if (parsingSuccessful) - { - ffmpegCommand = result["ffmpeg"].asString(); - } -} - void ShaderChain::openDefaultPreset() { ofFile file; @@ -112,45 +101,41 @@ void ShaderChain::UpdateResolutionIfChanged(bool force) { bool needsUpdate = force; - if (this->passes.size() > 0) { - if (this->passes[0]->targetResolution.x != this->pngRenderer->resolutionX || - this->passes[0]->targetResolution.y != this->pngRenderer->resolutionY) { + if (currentPreset.passes.size() > 0) { + if (currentPreset.passes[0]->targetResolution.x != pngRenderer->resolutionX || + currentPreset.passes[0]->targetResolution.y != pngRenderer->resolutionY) { needsUpdate = true; } } if (needsUpdate) { - ofSetFrameRate(pngRenderer->FPS); + ofSetFrameRate(currentPreset.fps); ofFloatColor black; - cumulativeBuffer.allocate(this->pngRenderer->resolutionX, this->pngRenderer->resolutionY, GL_RGBA32F); - cumulativeDrawBuffer.allocate(this->pngRenderer->resolutionX, this->pngRenderer->resolutionY, GL_RGBA32F); - cumulativeBufferSwap.allocate(this->pngRenderer->resolutionX, this->pngRenderer->resolutionY, GL_RGBA32F); + cumulativeBuffer.allocate(currentPreset.resolutionX, currentPreset.resolutionY, GL_RGBA32F); + cumulativeDrawBuffer.allocate(currentPreset.resolutionX, currentPreset.resolutionY, GL_RGBA32F); + cumulativeBufferSwap.allocate(currentPreset.resolutionX, currentPreset.resolutionY, GL_RGBA32F); cumulativeBufferSwap.clearColorBuffer(black); cumulativeBuffer.clearColorBuffer(black); - this->cumulativeRenderPlane.set(pngRenderer->resolutionX, pngRenderer->resolutionY, 2, 2); - this->cumulativeRenderPlane.setPosition({pngRenderer->resolutionX/2, pngRenderer->resolutionY/2, 0.0f}); + this->cumulativeRenderPlane.set(currentPreset.resolutionX, currentPreset.resolutionY, 2, 2); + this->cumulativeRenderPlane.setPosition({ currentPreset.resolutionX/2, currentPreset.resolutionY/2, 0.0f}); - for (unsigned int i = 0; i < this->passes.size(); i++) { - this->passes[i]->UpdateResolution(this->pngRenderer->resolutionX, this->pngRenderer->resolutionY); - } + currentPreset.updateResolution(currentPreset.resolutionX, currentPreset.resolutionY); } } void ShaderChain::BeginSaveFrames() { this->isRunning = true; - for (int i = 0; i < this->passes.size(); i++) { - passes[i]->startOfflineRender(); - } + currentPreset.startRender(); if (fft.currentState == InputStateSoundFile) { this->time = 0.0; ofFloatColor *black = new ofFloatColor(0.0, 0.0, 0.0, 0.0); - for (unsigned int i = 0; i < this->passes.size(); i++) { - if (passes[i]->lastBuffer.isAllocated()) { - passes[i]->lastBuffer.clearColorBuffer(*black); + for (unsigned int i = 0; i < currentPreset.passes.size(); i++) { + if (currentPreset.passes[i]->lastBuffer.isAllocated()) { + currentPreset.passes[i]->lastBuffer.clearColorBuffer(*black); } } delete black; @@ -161,14 +146,14 @@ void ShaderChain::BeginSaveFrames() { void ShaderChain::update() { float mouseX = ofMap((float)ofGetMouseX(), - ofGetWidth()/2.0-pngRenderer->resolutionX*0.5*pngRenderer->displayScaleParam, - ofGetWidth()/2.0+pngRenderer->resolutionX*0.5*pngRenderer->displayScaleParam, + ofGetWidth()/2.0- currentPreset.resolutionX*0.5*currentPreset.displayScaleParam, + ofGetWidth()/2.0+ currentPreset.resolutionX*0.5*currentPreset.displayScaleParam, 0.0, 1.0); float mouseY = ofMap((float)ofGetMouseY(), - ofGetHeight()/2.0-pngRenderer->resolutionY*0.5*pngRenderer->displayScaleParam, - ofGetHeight()/2.0+pngRenderer->resolutionY*0.5*pngRenderer->displayScaleParam, + ofGetHeight()/2.0- currentPreset.resolutionY*0.5*currentPreset.displayScaleParam, + ofGetHeight()/2.0+ currentPreset.resolutionY*0.5*currentPreset.displayScaleParam, 0.0, 1.0); @@ -185,21 +170,18 @@ void ShaderChain::update() { renderStruct.isMouseDown = isMouseDown; renderStruct.mousePosition = glm::vec2(mouseX, mouseY); renderStruct.isOfflineRendering = pngRenderer->isCapturing; - - for (int i = 0; i < this->passes.size(); i++) { - this->passes[i]->update(&renderStruct); - } + currentPreset.updatePreRender(&renderStruct); } void ShaderChain::draw() { bool capturingThisFrame = pngRenderer->isCapturing; renderStruct.frame = pngRenderer->currentFrame; - renderStruct.numBlendFrames = pngRenderer->numBlendFrames; + renderStruct.numBlendFrames = currentPreset.numBlendFrames; renderStruct.isScrubbing = scrubber.isScrubbing; UpdateResolutionIfChanged(false); ofClear(25); - if (this->passes.size() > 0) { + if (currentPreset.passes.size() > 0) { ofFloatColor black(0,0,0,1); ofColor red(255, 0, 0); @@ -207,7 +189,7 @@ void ShaderChain::draw() { if (capturingThisFrame && this->isRunning) { pngRenderer->Tick(); } - float deltaTime = 1. / (pngRenderer->FPS * pngRenderer->numBlendFrames); + float deltaTime = 1. / (currentPreset.fps * currentPreset.numBlendFrames); if (this->isRunning) { this->cumulativeBuffer.begin(); @@ -218,9 +200,9 @@ void ShaderChain::draw() { ofClear(0,0,0,255); this->cumulativeBufferSwap.end(); - float blendFactor = (1./pngRenderer->numBlendFrames); + float blendFactor = (1./ currentPreset.numBlendFrames); - for (unsigned int i = 0; i < pngRenderer->numBlendFrames; i++) { + for (unsigned int i = 0; i < currentPreset.numBlendFrames; i++) { if (capturingThisFrame) { this->time = this->time + deltaTime; @@ -229,24 +211,23 @@ void ShaderChain::draw() { } else { if (!scrubber.isScrubbing) { - this->time = pngRenderer->preview ? fmod(this->time + deltaTime, pngRenderer->animduration) : this->time + deltaTime; + this->time = pngRenderer->preview ? fmod(this->time + deltaTime, currentPreset.animduration) : this->time + deltaTime; } fft.Update(); } this->renderStruct.time = this->time; - - if (frame % pngRenderer->frameskip == 0) { + if (frame % currentPreset.frameskip == 0) { RenderPasses(); } this->cumulativeBuffer.begin(); this->cumulativeShader.begin(); ofClear(0, 0, 0, 255); - int idx = getLastEnabledPassIndex(); + int idx = currentPreset.getLastEnabledPassIndex(); this->cumulativeShader.setUniform1f("factor", blendFactor); this->cumulativeShader.setUniformTexture("_CumulativeTexture", this->cumulativeBufferSwap.getTexture(), 1); - this->cumulativeShader.setUniformTexture("_IncomingTexture", this->passes[idx]->buffer.getTexture(), 2); + this->cumulativeShader.setUniformTexture("_IncomingTexture", currentPreset.passes[idx]->buffer.getTexture(), 2); this->cumulativeDrawBuffer.draw(0,0); this->cumulativeShader.end(); this->cumulativeBuffer.end(); @@ -257,23 +238,23 @@ void ShaderChain::draw() { } } - int idx = this->passes.size()-1; - float x = ofGetWidth()/2.-this->pngRenderer->resolutionX*0.5*this->pngRenderer->displayScaleParam; - float y = ofGetHeight()/2.-this->pngRenderer->resolutionY*0.5*this->pngRenderer->displayScaleParam; - float w = this->pngRenderer->resolutionX*this->pngRenderer->displayScaleParam; - float h = this->pngRenderer->resolutionY*this->pngRenderer->displayScaleParam; + int idx = currentPreset.passes.size()-1; + float x = ofGetWidth()/2.- currentPreset.resolutionX*0.5*currentPreset.displayScaleParam; + float y = ofGetHeight()/2.- currentPreset.resolutionY*0.5*currentPreset.displayScaleParam; + float w = currentPreset.resolutionX*currentPreset.displayScaleParam; + float h = currentPreset.resolutionY*currentPreset.displayScaleParam; this->cumulativeBufferSwap.draw(x, y, w, h); scrubber.enabled = pngRenderer->preview; if (pngRenderer->preview) { - scrubber.drawWithTime(this->time, pngRenderer->animduration); + scrubber.drawWithTime(this->time, currentPreset.animduration); } - for (int i = 0; i < this->passes.size(); i++) { - if (this->passes[i]->hasError) { - ofDrawBitmapStringHighlight(this->passes[i]->shader.compilerError, 10, ofGetHeight()-100, black, red); + for (int i = 0; i < currentPreset.passes.size(); i++) { + if (currentPreset.passes[i]->hasError) { + ofDrawBitmapStringHighlight(currentPreset.passes[i]->shader.compilerError, 10, ofGetHeight()-150, black, red); break; } } @@ -284,8 +265,8 @@ void ShaderChain::draw() { // On finished if (!this->pngRenderer->isCapturing) { - for (int i = 0; i < this->passes.size(); i++) { - passes[i]->stopOfflineRender(); + for (int i = 0; i < currentPreset.passes.size(); i++) { + currentPreset.passes[i]->stopOfflineRender(); } this->isRunning = false; } @@ -295,35 +276,17 @@ void ShaderChain::draw() { creditsGui.draw(); } -int ShaderChain::getFirstEnabledPassIndex() { - int idx = 0; - for (int i = 0; i < this->passes.size(); i++) { - if (this->passes[i]->enabled) { - return i; - } - } - return 0; -} - -int ShaderChain::getLastEnabledPassIndex() { - for (int i = this->passes.size()-1; i >= 0; i--) { - if (this->passes[i]->enabled) { - return i; - } - } - return 0; -} - void ShaderChain::RenderPasses() { int lastRenderedPassIndex = -1; - for (int i = getFirstEnabledPassIndex(); i <= getLastEnabledPassIndex(); i++) { + + for (int i = currentPreset.getFirstEnabledPassIndex(); i <= currentPreset.getLastEnabledPassIndex(); i++) { if (lastRenderedPassIndex != -1) { - this->passes[i]->Render(&(passes[lastRenderedPassIndex]->swapBuffer), &renderStruct); + currentPreset.passes[i]->Render(&(currentPreset.passes[lastRenderedPassIndex]->swapBuffer), &renderStruct); } - if (this->passes[i]->enabled) { + if (currentPreset.passes[i]->enabled) { lastRenderedPassIndex = i; - this->passes[i]->Render(nullptr, &renderStruct); + currentPreset.passes[i]->Render(nullptr, &renderStruct); } } } @@ -359,17 +322,16 @@ void ShaderChain::KeyPressed(int key) { } void ShaderChain::SetupGui() { - cout << "setup gui" << endl; - - parameterPanel->clear(); - + + parameterPanel->clear(); textureInputSelectionView.passNames.clear(); - for (unsigned int i = 0; i < passes.size(); i++) { - textureInputSelectionView.passNames.push_back(passes[i]->displayName); + + for (unsigned int i = 0; i < currentPreset.passes.size(); i++) { + textureInputSelectionView.passNames.push_back(currentPreset.passes[i]->displayName); } - for (int i = 0; i < this->passes.size(); i++) { - this->passes[i]->AddToGui(parameterPanel, &textureInputSelectionView); + for (int i = 0; i < currentPreset.passes.size(); i++) { + currentPreset.passes[i]->AddToGui(parameterPanel, &textureInputSelectionView); } this->parameterPanel->setPosition(ofPoint(ofGetWidth()-this->parameterPanel->getWidth(), 10)); @@ -381,9 +343,9 @@ void ShaderChain::newMidiMessage(ofxMidiMessage& msg) { if (midiMapper.isShowing) { midiMapper.midiSet(msg.control); } else { - for (int i = 0; i < this->passes.size(); i++) { - for (int j = 0; j < this->passes[i]->params.size(); j++) { - this->passes[i]->params[j]->UpdateMidi(msg.control, msg.value); + for (int i = 0; i < currentPreset.passes.size(); i++) { + for (int j = 0; j < currentPreset.passes[i]->params.size(); j++) { + currentPreset.passes[i]->params[j]->UpdateMidi(msg.control, msg.value); } } } @@ -392,43 +354,18 @@ void ShaderChain::newMidiMessage(ofxMidiMessage& msg) { } void ShaderChain::ReadFromJson(std::string filepath) { - bool parsingSuccessful = result.open(filepath); - - for (int i = 0; i < this->passes.size(); i++) { - delete this->passes[i]; - } - this->passes.clear(); + bool parsingSuccessful = currentPreset.load(filepath); if (parsingSuccessful) { this->time = 0; - this->pngRenderer->updatePath(filepath); - - this->pngRenderer->resolutionX = result["res"]["x"].asFloat(); - this->pngRenderer->resolutionY = result["res"]["y"].asFloat(); - - if (result.isMember("duration")) { - this->pngRenderer->animduration.set(this->result["duration"].asFloat()); - } - if (result.isMember("fps")) { - this->pngRenderer->FPS.set(this->result["fps"].asFloat()); - } - - if (result.isMember("blend")) { - this->pngRenderer->numBlendFrames.set(this->result["blend"].asInt()); - } - - if (result.isMember("scale")) { - this->pngRenderer->displayScaleParam.set(this->result["scale"].asFloat()); - } + this->pngRenderer->updateUI(¤tPreset); - for (int i = 0; i < result["data"].size(); i++) { - ShaderPass *pass = new ShaderPass(); - pass->LoadFromJson(result["data"][i], this->pngRenderer->resolutionX, this->pngRenderer->resolutionY); - this->passes.push_back(pass); - ofAddListener(passes[passes.size()-1]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); + // Add listeners to the shader passes to update the gui. + for (int i = 0; i < this->currentPreset.passes.size(); i++) { + ofAddListener(this->currentPreset.passes[i]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); } - this->passesGui->Setup(&this->passes); + this->passesGui->setup(¤tPreset); SetupGui(); UpdateResolutionIfChanged(true); } @@ -453,10 +390,10 @@ void ShaderChain::LoadPassFromFile(string filepath) { auto relativeFileNameWithoutExtension = relativeFileName.substr(0,relativeFileName.find("frag")-1); ShaderPass *pass = new ShaderPass(relativeFileNameWithoutExtension, glm::vec2(this->pngRenderer->resolutionX,this->pngRenderer->resolutionY) ); pass->LoadJsonParametersFromLoadedShader(); - this->passes.push_back(pass); - ofAddListener(passes[passes.size()-1]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); + currentPreset.addPass(pass); + ofAddListener(currentPreset.passes[currentPreset.passes.size()-1]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); SetupGui(); - this->passesGui->Setup(&this->passes); + passesGui->setup(¤tPreset); UpdateResolutionIfChanged(true); } @@ -469,10 +406,10 @@ void ShaderChain::processFileInput(string filePath) { } else if (extension == "mp3") { fft.loadSoundFile(filePath); } else if (extension == "png" || extension == "jpeg" || extension == "jpg" || extension == "bmp" || extension == "mp4" || extension == "mov" || extension == "mkv") { - for (int i = 0; i < this->passes.size(); i++) { - for (int j = 0; j < this->passes[i]->params.size(); j++) { - if (this->passes[i]->params[j]->isMouseHoveredOver()) { - this->passes[i]->params[j]->handleInputFile(filePath); + for (int i = 0; i < currentPreset.passes.size(); i++) { + for (int j = 0; j < currentPreset.passes[i]->params.size(); j++) { + if (currentPreset.passes[i]->params[j]->isMouseHoveredOver()) { + currentPreset.passes[i]->params[j]->handleInputFile(filePath); } } } @@ -480,42 +417,23 @@ void ShaderChain::processFileInput(string filePath) { } void ShaderChain::WriteToJson() { - this->result.clear(); - this->result["res"]["x"] = (float)this->pngRenderer->resolutionX; - this->result["res"]["y"] = (float)this->pngRenderer->resolutionY; - - this->result["duration"] = (float)this->pngRenderer->animduration; - this->result["fps"] = (float)this->pngRenderer->FPS; - this->result["blend"] = (float)this->pngRenderer->numBlendFrames; - this->result["scale"] = (float)this->pngRenderer->displayScaleParam; - - for (int i = 0; i < this->passes.size(); i++) { - this->result["data"][i]["shaderName"] = this->passes[i]->filePath; - this->result["data"][i]["numPassesPerFrame"] = this->passes[i]->numPassesPerFrame; - this->result["data"][i]["scale"] = this->passes[i]->scale; - for (int j = 0; j < this->passes[i]->params.size(); j++) { - this->result["data"][i]["parameters"][j]["name"] = this->passes[i]->params[j]->uniform; - this->passes[i]->params[j]->UpdateJson((this->result["data"][i]["parameters"][j])); - } - } - - if (!this->result.save(pngRenderer->presetFilePath, true)) { - updateStatusText("Error saving " + this->pngRenderer->presetDisplayName.get()); + if (currentPreset.save()) { + updateStatusText("Saved " + this->pngRenderer->presetDisplayName.get()); } else { - updateStatusText("Saved " + this->pngRenderer->presetDisplayName.get()); + updateStatusText("Error saving " + this->pngRenderer->presetDisplayName.get()); } } void ShaderChain::removed(RemovedElementData& data) { - auto item = (passes.begin() + data.index); + auto item = (currentPreset.passes.begin() + data.index); ofRemoveListener((*item)->shaderUpdatedEvent, this, &ShaderChain::SetupGui); - passes.erase(item); + currentPreset.removePass(data.index); freeUnusedResources(); SetupGui(); } void ShaderChain::moved(MovingElementData &data) { - iter_swap(passes.begin() + data.old_index, passes.begin() + data.new_index); + currentPreset.swapPass(data.old_index, data.new_index); SetupGui(); } @@ -537,7 +455,8 @@ void ShaderChain::savePresetAsPressed() { ofFileDialogResult result = ofSystemSaveDialog(pngRenderer->presetDisplayName.get() + ".json", "Save preset"); if (result.bSuccess) { string path = result.getPath(); - pngRenderer->updatePath(path); + currentPreset.updatePath(path); + pngRenderer->updateUI(¤tPreset); WriteToJson(); updateStatusText("Saved preset"); } @@ -546,77 +465,7 @@ void ShaderChain::savePresetAsPressed() { } void ShaderChain::saveVideo(string outputFilename) { - string f = outputFilename; - - int totalFrames = pngRenderer->FPS * pngRenderer->animduration; - string rendersDirectory = ShaderChain::getSlash() + "renders" + ShaderChain::getSlash(); - - outputFilename = ofFilePath::getAbsolutePath( ofToDataPath("") ) + rendersDirectory + outputFilename; - - string mkdirCommand = "mkdir " + outputFilename; - system(mkdirCommand.c_str()); - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) - string moveCommand = "move "; -#else - string moveCommand = "mv "; -#endif - - string moveFilesCommand = moveCommand + outputFilename + "_*.png " + outputFilename; - cout << moveFilesCommand << endl; - system(moveFilesCommand.c_str()); - outputFilename = outputFilename + ShaderChain::getSlash() + f; - - cout << "Creating mp4 " << outputFilename << endl; - string outputMp4Filename = outputFilename + ".mp4"; - outputMp4Filename = createUniqueFilePath(outputMp4Filename); - string fpsString = to_string(pngRenderer->FPS); - string totalZerosString = to_string((int)floor(log10 (((float)totalFrames)))+1); - - string ffmpegCommand = this->ffmpegCommand + " -r " + fpsString + " -f image2 -s 1080x1920 -i \"" + outputFilename + "_%0" + totalZerosString + "d.png\" -vcodec libx264 -crf 18 -pix_fmt yuv420p " + outputMp4Filename; - - system(ffmpegCommand.c_str()); - - cout << ffmpegCommand << endl; - - if (fft.currentState == InputStateSoundFile) { - string outputMp4AudioFilename = outputFilename + "_audio.mp4"; - outputMp4AudioFilename = createUniqueFilePath(outputMp4AudioFilename); - string addSoundCommand = this->ffmpegCommand + " -i \"" + outputMp4Filename + "\" -i \"" + fft.soundFilePath + "\" -vcodec copy -acodec aac -shortest " + outputMp4AudioFilename; - system(addSoundCommand.c_str()); - outputMp4Filename = outputMp4AudioFilename; - } - - if (pngRenderer->numLoops > 1) { - string inputFileText = ""; - for (unsigned int i = 0; i < pngRenderer->numLoops; i++) { - inputFileText += "file '" + outputMp4Filename + "''\n"; - } - - ofstream file; - file.open("list.txt"); - file << inputFileText; - file.close(); - - string outputLoopedFilename = outputFilename + "_looped.mp4"; - outputLoopedFilename = createUniqueFilePath(outputLoopedFilename); - - ffmpegCommand = this->ffmpegCommand + " -f concat -safe 0 -i list.txt -c copy " + outputLoopedFilename; - system(ffmpegCommand.c_str()); - - //remove("list.txt"); - outputMp4Filename = outputLoopedFilename; - } - - - ofFile file; - file.open(outputMp4Filename, ofFile::ReadOnly, false); - if (file.exists()) { - updateStatusText("Video saved to " + outputMp4Filename ); - } else { - updateStatusText("Error saving video"); - } - file.close(); + ffmpegManager->saveVideo(outputFilename, fft.soundFilePath, pngRenderer); } void ShaderChain::updateStatusText(string s) { @@ -637,47 +486,7 @@ void ShaderChain::encodeMp4Pressed() { } void ShaderChain::encodeGifPressed() { - - int totalFrames = pngRenderer->FPS * pngRenderer->animduration; - string totalZerosString = to_string((int)floor(log10 (((float)totalFrames)))+1); - - string fileWithoutExtension = pngRenderer->presetDisplayName; - - string rendersDirectory = ofFilePath::getAbsolutePath( ofToDataPath("") ) + ShaderChain::getSlash() + "renders" + ShaderChain::getSlash(); - - string targetDirectory = rendersDirectory + fileWithoutExtension + ShaderChain::getSlash(); - system(("mkdir \"" + targetDirectory + "\"").c_str()); - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) - string moveCommand = "move "; -#else - string moveCommand = "mv "; -#endif - - string moveFilesCommand = moveCommand + rendersDirectory + fileWithoutExtension + "_*.png " + targetDirectory; - system(moveFilesCommand.c_str()); - - string ffmpegCommand = this->ffmpegCommand + " -r " + to_string(pngRenderer->FPS) + " -v warning -start_number 0 -i \"" + targetDirectory + fileWithoutExtension + "_%0" + totalZerosString + "d.png\" -vf scale=500:-1:flags=lanczos,palettegen=stats_mode=diff:reserve_transparent=off:max_colors=" + to_string(pngRenderer->gifNumColors) + " -y " + "\"" + targetDirectory + ShaderChain::getSlash() + "palette.png" + "\""; - - system(ffmpegCommand.c_str()); - - string targetFilename = targetDirectory + fileWithoutExtension + ".gif"; - targetFilename = createUniqueFilePath(targetFilename); - - int resX = (float)pngRenderer->resolutionX * pngRenderer->gifResolutionScale; - int resY = (float)pngRenderer->resolutionY * pngRenderer->gifResolutionScale; - - ffmpegCommand = this->ffmpegCommand + " -v warning -thread_queue_size 512 -start_number 0 -i \"" + targetDirectory + fileWithoutExtension + "_%0" + totalZerosString + "d.png\" -i \"" + targetDirectory + "palette.png\" -r 30 -lavfi scale="+to_string(resX)+":"+to_string(resY)+":flags=\"lanczos [x]; [x][1:v] paletteuse\" -y \"" + targetFilename + "\""; - system(ffmpegCommand.c_str()); - - ofFile file; - file.open(targetFilename, ofFile::ReadOnly, false); - if (file.exists()) { - updateStatusText("Gif saved to " + targetFilename); - } else { - updateStatusText("Error saving gif"); - } - file.close(); + ffmpegManager->saveGif(pngRenderer->presetDisplayName, pngRenderer); } void ShaderChain::toggleWebcam(bool &val) { @@ -703,17 +512,7 @@ void ShaderChain::stopWebcam() { } void ShaderChain::freeUnusedResources() { - bool needsWebam = false; - - for (int i = 0; i < this->passes.size(); i++) { - for (int j = 0; j < this->passes[i]->params.size(); j++) { - if (passes[i]->params[j]->getTextureSourceType() == Webcam) { - needsWebam = true; - } - } - } - - if (!needsWebam) { + if (!currentPreset.requiresWebcam()) { stopWebcam(); } } @@ -723,42 +522,10 @@ void ShaderChain::pauseResourcesForCurrentPlaybackState() { fft.setPaused(!this->isRunning); if (!pngRenderer->isCapturing) { - for (int i = 0; i < this->passes.size(); i++) { - for (int j = 0; j < this->passes[i]->params.size(); j++) { - passes[i]->params[j]->playbackDidToggleState(!this->isRunning); - } - } + currentPreset.updateIsRunning(!this->isRunning); } } -string ShaderChain::createUniqueFilePath(string path) { - bool found = false; - auto pathWithoutExtension = path.substr(0, path.find_last_of(".")); - auto extension = path.substr(path.find_last_of(".") + 1); - int tries = 0; - - while (!found) { - ofFile file; - - string p = ""; - - if (tries == 0) { - p = pathWithoutExtension + "." + extension; - } else { - p = pathWithoutExtension + to_string(tries) + "." + extension; - } - - file.open(p, ofFile::ReadWrite, false); - - if (!file.exists()) { - found = true; - path = p; - } - file.close(); - tries++; - } - return path; -} bool ShaderChain::mouseScrolled(ofMouseEventArgs & args) { if (parameterPanel->isMouseOver()) { @@ -788,23 +555,22 @@ void ShaderChain::newPresetButtonPressed() { string result = ofSystemTextBoxDialog("New Preset name", ""); result = ofToDataPath("presets/" + result); - for (int i = 0; i < this->passes.size(); i++) { - ofRemoveListener(this->passes[i]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); - delete this->passes[i]; + for (int i = 0; i < currentPreset.passes.size(); i++) { + ofRemoveListener(currentPreset.passes[i]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); + delete currentPreset.passes[i]; } - this->passes.clear(); - this->passesGui->Setup(&this->passes); + currentPreset.removeAllPasses(); + passesGui->setup(¤tPreset); SetupGui(); - pngRenderer->updatePath(result); + currentPreset.updatePath(result); + pngRenderer->updateUI(¤tPreset); isShowingFileDialogue = false; } } void ShaderChain::updateShaderJsonPressed() { - for (int i = 0; i < this->passes.size(); i++) { - passes[i]->updateShaderJson(); - } + currentPreset.saveShaderJson(); updateStatusText("Updated shader json"); } @@ -815,10 +581,10 @@ void ShaderChain::windowResized(int w, int h) { } void ShaderChain::shaderPassRightClicked(RightClickedElementData &data) { - if (data.index < this->passes.size()) { + if (data.index < currentPreset.passes.size()) { ofPoint p = data.position; p.y += passesGui->panel->getPosition().y; - passGui.showWithShaderPass(&gui, p, passes[data.index]); + passGui.showWithShaderPass(&gui, p, currentPreset.passes[data.index]); } } @@ -827,10 +593,10 @@ void ShaderChain::scrubberProgressed(float &val) { } void ShaderChain::saveScreenshot() { - if (this->passes.size() > 0) { + if (currentPreset.passes.size() > 0) { string destFilePath = pngRenderer->renderDirectory + "screenshot_" + pngRenderer->presetDisplayName.get() + ".png"; - destFilePath = createUniqueFilePath(destFilePath); - pngRenderer->screenshot(&(this->passes[getLastEnabledPassIndex()]->swapBuffer), destFilePath); + destFilePath = PathUtil::createUniqueFilePath(destFilePath); + pngRenderer->screenshot(&(currentPreset.passes[currentPreset.getLastEnabledPassIndex()]->swapBuffer), destFilePath); updateStatusText("Saved screenshot to " + destFilePath); } } diff --git a/src/ShaderChain.h b/src/ShaderChain.h index 74f8e1b..4225b9e 100755 --- a/src/ShaderChain.h +++ b/src/ShaderChain.h @@ -13,18 +13,23 @@ #include "shaderPassGui.h" #include "previewProgressScrubber.h" #include "credits.h" +#include "FfmpegManager.h" class ShaderChain: public ofxMidiListener { public: string defaultPresetPath = "presets/default.json"; - vector passes; - float time; - ofParameter isRunning; + preset currentPreset; + + ofParameter isRunning; ofxMidiIn midiIn; ofxJSONElement result; - bool isMouseDown; - ofFbo cumulativeBuffer; + + float time; + bool isMouseDown; + + // FBOS + ofFbo cumulativeBuffer; ofFbo cumulativeBufferSwap; ofFbo cumulativeDrawBuffer; @@ -46,16 +51,10 @@ public: void SetupMidi(); void dragEvent(ofDragInfo info); void windowResized(int w, int h); - static string getSlash() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) - return "\\"; -#else - return "/"; -#endif - } private: PNGRenderer *pngRenderer; + FfmpegManager *ffmpegManager; ofxGui gui; ofxGuiContainer *guiGlobal; ofxGuiContainer *statusContainer; @@ -68,8 +67,6 @@ private: ofVideoGrabber vidGrabber; previewProgressScrubber scrubber; - string ffmpegCommand = "ffmpeg"; - float mouseMoveSpeed = 10.0; bool showGui; @@ -102,12 +99,10 @@ private: void stopWebcam(); void freeUnusedResources(); void pauseResourcesForCurrentPlaybackState(); - string createUniqueFilePath(string path); bool mouseScrolled(ofMouseEventArgs & args); - void loadFfmpegPath(); void midiButtonPressed(); MidiMapper midiMapper; - + void newPresetButtonPressed(); void updateShaderJsonPressed(); void shaderPassRightClicked(RightClickedElementData &data); @@ -115,4 +110,5 @@ private: void scrubberProgressed(float &val); void saveScreenshot(); void showCredits(); + void webExportedPressed(); }; diff --git a/src/ShaderPass.cpp b/src/ShaderPass.cpp index e1d0848..ec1a26a 100755 --- a/src/ShaderPass.cpp +++ b/src/ShaderPass.cpp @@ -129,7 +129,6 @@ void ShaderPass::Render(ofFbo *previousBuffer, RenderStruct *renderStruct) { for (int i = 0; i < numPassesPerFrame; i++) { this->buffer.begin(); this->shader.begin(); - UpdateTime(renderStruct->time); if (previousBuffer != nullptr) { @@ -411,7 +410,10 @@ void ShaderPass::LoadFromJson(Json::Value &json, float width, float height) { std::string shaderName = json["shaderName"].asString(); if (json.isMember("numPassesPerFrame")) { this->numPassesPerFrame = json["numPassesPerFrame"].asInt(); - } + } + else { + numPassesPerFrame = 1; + } if (json.isMember("scale")) { this->scale = json["scale"].asFloat(); } diff --git a/src/ThreadedSystemCall.cpp b/src/ThreadedSystemCall.cpp new file mode 100644 index 0000000..a9a3f54 --- /dev/null +++ b/src/ThreadedSystemCall.cpp @@ -0,0 +1,11 @@ +#include "ThreadedSystemCall.h" + +ThreadedSystemCall::ThreadedSystemCall() { + waitForThread(false); +} + +void ThreadedSystemCall::systemCall(std::vector commands, ofEvent *completionEvent) { + this->commands = commands; + this->completionEvent = completionEvent; + startThread(); +} \ No newline at end of file diff --git a/src/ThreadedSystemCall.h b/src/ThreadedSystemCall.h new file mode 100644 index 0000000..7d91e21 --- /dev/null +++ b/src/ThreadedSystemCall.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include "ofMain.h" + +class ThreadedSystemCall : public ofThread { + +public: + ThreadedSystemCall(); + void systemCall(std::vector commands, ofEvent *completionEvent); + + void threadedFunction() { + if (isThreadRunning()) { + for (string command : commands) { + system(command.c_str()); + } + ofNotifyEvent(*completionEvent); + stopThread(); + } + } + + ~ThreadedSystemCall() { + stopThread(); + waitForThread(false); + } + +private: + std::vector commands; + ofEvent *completionEvent; +}; \ No newline at end of file diff --git a/src/gui/PassesGui.cpp b/src/gui/PassesGui.cpp index 75f20fb..7cb5b9b 100644 --- a/src/gui/PassesGui.cpp +++ b/src/gui/PassesGui.cpp @@ -10,12 +10,11 @@ PassesGui::~PassesGui() { } -void PassesGui::Setup(std::vector *passes) { +void PassesGui::setup(preset *currentPreset) { passButtons->clear(); - this->passes = passes; - for (int i = 0; i < passes->size(); i++) { + for (int i = 0; i < currentPreset->passes.size(); i++) { ofParameter text; - text.set(passes->at(i)->displayName); + text.set(currentPreset->passes.at(i)->displayName); passButtons->add(text); } } diff --git a/src/gui/PassesGui.h b/src/gui/PassesGui.h index 2139b99..806d64d 100644 --- a/src/gui/PassesGui.h +++ b/src/gui/PassesGui.h @@ -4,6 +4,7 @@ #include "ShaderPass.h" #include "ofxGuiExtended2.h" #include "ofxSortableList.h" +#include "preset.h" class PassesGui { public: @@ -14,10 +15,9 @@ public: ofxSortableList *passButtons; ofxGuiButton addPassButton; - void Setup(std::vector *passes); + void setup(preset *currentPreset); ofxGuiContainer *panel; private: ofxGui gui; - std::vector *passes; }; diff --git a/src/gui/credits.h b/src/gui/credits.h index 64f625a..c3e00ab 100644 --- a/src/gui/credits.h +++ b/src/gui/credits.h @@ -6,7 +6,7 @@ class credits { public: credits(); - bool enabled; + bool enabled = false; void draw(); private: diff --git a/src/ofApp.cpp b/src/ofApp.cpp index c2616b6..fb4c833 100755 --- a/src/ofApp.cpp +++ b/src/ofApp.cpp @@ -13,7 +13,7 @@ ofApp::~ofApp() { void ofApp::setup(){ glm::vec2 res = glm::vec2(480, 270); ofSetWindowShape(1920, 1080); - ofSetWindowPosition(0, 0); + ofSetWindowPosition(0, 60); ofSetWindowTitle("ShaderChain"); this->shaderChain.Setup(res); } diff --git a/src/preset.cpp b/src/preset.cpp new file mode 100644 index 0000000..d644ec7 --- /dev/null +++ b/src/preset.cpp @@ -0,0 +1,172 @@ +#include "preset.h" + +bool preset::load(string path) { + cout << "Loading " << path << endl; + bool parsingSuccessful = json.open(path); + + for (int i = 0; i < this->passes.size(); i++) { + delete this->passes[i]; + } + this->passes.clear(); + this->frameskip = 1; + if (parsingSuccessful) { + this->updatePath(path); + + this->resolutionX = json["res"]["x"].asFloat(); + this->resolutionY = json["res"]["y"].asFloat(); + + if (json.isMember("duration")) { + this->animduration = json["duration"].asFloat(); + } + if (json.isMember("fps")) { + this->fps = json["fps"].asFloat(); + } + + if (json.isMember("blend")) { + this->numBlendFrames = json["blend"].asInt(); + } + + if (json.isMember("scale")) { + this->displayScaleParam = json["scale"].asFloat(); + } + + for (int i = 0; i < json["data"].size(); i++) { + ShaderPass *pass = new ShaderPass(); + pass->LoadFromJson(json["data"][i], this->resolutionX, this->resolutionY); + this->passes.push_back(pass); +// ->>>>>> ofAddListener(passes[passes.size() - 1]->shaderUpdatedEvent, this, &ShaderChain::SetupGui); + } + } + + return parsingSuccessful; +} + +bool preset::save() { + json.clear(); + json["res"]["x"] = (float)resolutionX; + json["res"]["y"] = (float)resolutionY; + + json["duration"] = (float)animduration; + json["fps"] = (float)fps; + json["blend"] = (float)numBlendFrames; + json["scale"] = (float)displayScaleParam; + + for (int i = 0; i < passes.size(); i++) { + json["data"][i]["shaderName"] = passes[i]->filePath; + json["data"][i]["numPassesPerFrame"] = passes[i]->numPassesPerFrame; + json["data"][i]["scale"] = passes[i]->scale; + for (int j = 0; j < passes[i]->params.size(); j++) { + json["data"][i]["parameters"][j]["name"] = passes[i]->params[j]->uniform; + passes[i]->params[j]->UpdateJson((json["data"][i]["parameters"][j])); + } + } + + return json.save(presetFilePath, true); +} + +void preset::addPass(ShaderPass *pass) { + passes.push_back(pass); +} + +void preset::removePass(int index) { + auto item = (passes.begin() + index); + passes.erase(item); +} + +void preset::removeAllPasses() { + passes.clear(); +} + +void preset::swapPass(int from, int to) { + iter_swap(passes.begin() + from, passes.begin() + to); +} + +bool preset::requiresWebcam() { + for (int i = 0; i < this->passes.size(); i++) { + for (int j = 0; j < this->passes[i]->params.size(); j++) { + if (passes[i]->params[j]->getTextureSourceType() == Webcam) { + return true; + } + } + } + return false; +} + +void preset::updateIsRunning(bool isRunning) { + for (int i = 0; i < this->passes.size(); i++) { + for (int j = 0; j < this->passes[i]->params.size(); j++) { + passes[i]->params[j]->playbackDidToggleState(isRunning); + } + } +} + +void preset::saveShaderJson() { + for (int i = 0; i < passes.size(); i++) { + passes[i]->updateShaderJson(); + } +} + + +void preset::updateResolution(int width, int height) { + for (unsigned int i = 0; i < this->passes.size(); i++) { + passes[i]->UpdateResolution(width, height); + } +} + +void preset::updatePreRender(RenderStruct *renderStruct) { + for (int i = 0; i < passes.size(); i++) { + passes[i]->update(renderStruct); + } +} + +int preset::getFirstEnabledPassIndex() { + int idx = 0; + for (int i = 0; i < this->passes.size(); i++) { + if (this->passes[i]->enabled) { + return i; + } + } + return 0; +} + +int preset::getLastEnabledPassIndex() { + for (int i = this->passes.size() - 1; i >= 0; i--) { + if (this->passes[i]->enabled) { + return i; + } + } + return 0; +} + +void preset::startRender() { + for (int i = 0; i < passes.size(); i++) { + passes[i]->startOfflineRender(); + } +} + +void preset::updatePath(string s) { + if (s == "") return; + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + + replace(s.begin(), s.end(), '/', '\\'); + static const std::string slash = "\\"; +#else + static const std::string slash = "/"; +#endif + + string file = s.substr(s.find_last_of(slash) + 1); + + // add json extension if needed + int indexOfPeriod = file.find_last_of("."); + if (indexOfPeriod == std::string::npos) { + file += ".json"; + s += ".json"; + indexOfPeriod = file.find_last_of("."); + } + + string fileWithoutExtension = file.substr(0, indexOfPeriod); + + presetDisplayName = fileWithoutExtension; + presetFilePath = s; +} \ No newline at end of file diff --git a/src/preset.h b/src/preset.h new file mode 100644 index 0000000..77154ad --- /dev/null +++ b/src/preset.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include "ShaderPass.h" +#include "RenderStruct.h" +class preset { + +public: + string name; + string presetFilePath; + + // Without the full path or .json file extension + string presetDisplayName; + + vector passes; + + int resolutionX; + int resolutionY; + float displayScaleParam; + int frameskip; + int numLoops; + int numBlendFrames; + float animduration; + int fps; + + ofxJSONElement json; + bool load(string name); + bool save(); + void addPass(ShaderPass *pass); + void removePass(int index); + void removeAllPasses(); + void swapPass(int from, int to); + void updateIsRunning(bool isRunning); + bool requiresWebcam(); + + // Seralize the json in the header of the shader + void saveShaderJson(); + + void updateResolution(int width, int height); + void updatePreRender(RenderStruct *renderStruct); + void updatePath(string s); + + void startRender(); + + int getFirstEnabledPassIndex(); + int getLastEnabledPassIndex(); + +}; \ No newline at end of file diff --git a/src/webExport.cpp b/src/webExport.cpp new file mode 100644 index 0000000..cbcd556 --- /dev/null +++ b/src/webExport.cpp @@ -0,0 +1,247 @@ +#include "webExport.h" + +void webExport::exportPreset(preset *currentPreset) { + + string webTemplatePath = "data" + PathUtil::getSlash() + "web" + PathUtil::getSlash() + "template.html"; + string exportPath = "data" + PathUtil::getSlash() + "web" + PathUtil::getSlash(); + + string exportFolderPath = exportPath + PathUtil::getSlash() + currentPreset->presetDisplayName; + string exportFilePath = exportFolderPath + PathUtil::getSlash() + "index.html"; + + string mkdirCommand = "mkdir " + exportFolderPath; + system(mkdirCommand.c_str()); + + // Patch template to reference json + string wordToReplace = "test.json"; + string wordToReplaceWith = currentPreset->presetDisplayName + ".json"; + + string passesString = ""; + + for (int i = 0; i < currentPreset->passes.size(); i++) { + passesString += "passes[i]->filePath + ".frag\">" + currentPreset->passes[i]->displayName + ""; + + if (i != currentPreset->passes.size() - 1) { + passesString += " -> "; + } + } + + std::map words; + words.insert(std::make_pair(wordToReplace, wordToReplaceWith)); + words.insert(std::make_pair("###PASSES###", passesString)); + + copyFileReplacingText(webTemplatePath, exportFilePath, words); + + copyResources(exportPath, exportFolderPath, currentPreset); + exportMainIndex(currentPreset, exportPath); +} + +void webExport::createPatchedTemplate(string templatePath, string output) { + +} + +void webExport::copyResources(string exportPath, string exportFolderPath, preset *currentPreset) { + string exportPresetPath = exportFolderPath + PathUtil::getSlash() + currentPreset->presetDisplayName + ".json"; + string shaderFolderPath = exportFolderPath + PathUtil::getSlash() + "shaders"; + + // Make Shaders folder + string mkdirCommand = "mkdir " + shaderFolderPath; + system(mkdirCommand.c_str()); + + // Copy shaders into shaders/ dir + for (int i = 0; i < currentPreset->passes.size(); i++) { + + string sourcePath = "data" + PathUtil::getSlash() + currentPreset->passes[i]->filePath + ".frag"; + string destPath = exportFolderPath + PathUtil::getSlash() + currentPreset->passes[i]->filePath + ".frag"; + + cout << "copy " << sourcePath << " to " << destPath << endl; + + std::map words; + words.insert(std::make_pair("out vec4 outputColor;", "")); + words.insert(std::make_pair("outputColor", "gl_FragColor")); + words.insert(std::make_pair("uv", "vUv")); + words.insert(std::make_pair("uv", "vUv")); + words.insert(std::make_pair("in vec2 uv;", "varying vec2 uv;")); + words.insert(std::make_pair("in vec2 texCoord;", "varying vec2 texCoord;")); + words.insert(std::make_pair("texture(", "texture2D(")); + + words.insert(std::make_pair("#pragma include", "")); + + words.insert(std::make_pair("#version 150", "")); + + copyFileReplacingText(sourcePath, destPath, words); + } + + // Export preset json + PathUtil::copyFile(currentPreset->presetFilePath, exportPresetPath); +} + +void webExport::copyFileReplacingText(string inputFilePath, string outputFilePath, std::map words) { + ifstream in(inputFilePath); + ofstream out(outputFilePath); + if (!in) { + cerr << "Could not open " << inputFilePath << "\n"; + return; + } + + if (!out) { + cerr << "Could not open " << outputFilePath << "\n"; + return; + } + + string line; + while (getline(in, line)) + { + std::map::iterator it = words.begin(); + while (it != words.end()) { + size_t len = it->first.length(); + size_t pos = line.find(it->first); + + if (pos != string::npos) { + + // Replace text with include file + if (it->first == "#pragma include") { + + string replaceFilePath = "data" + PathUtil::getSlash() + line.substr(pos + it->first.length() + 1); + replaceFilePath.erase(std::remove(replaceFilePath.begin(), replaceFilePath.end(), '\"'), replaceFilePath.end()); + + ifstream includeFile(replaceFilePath); + string includeLine; + string includeFileString = ""; + + if (includeFile) { + while (getline(includeFile, includeLine)) + { + includeFileString += includeLine + '\n'; + } + } + else { + cerr << "Could not open include file " << replaceFilePath << "\n"; + } + + line.replace(pos, line.length(), includeFileString); + } + else { + line = replaceAll(line, it->first, it->second); + } + it++; + } + else { + it++; + } + } + + out << line << '\n'; + } +} + +void webExport::exportMainIndex(preset *currentPreset, string presetExportPath) { + ofxJSONElement json; + string manifestPath = "web/manifest.json"; + string templatePath = "data/web/template_index.html"; + string outputPath = "data/web/index.html"; + bool parsingSuccessful = json.open(manifestPath); + + if (parsingSuccessful) { + string patchString = ""; + ofxJSONElement latestJson; + latestJson["link"] = currentPreset->presetDisplayName; + + // Check to see if preset already exported + int presetIndex = json["data"].size(); + for (int i = 0; i < json["data"].size(); i++) { + if (json["data"][i]["link"].asString() == currentPreset->presetDisplayName) { + presetIndex = i; + break; + } + } + + json["data"][presetIndex] = latestJson; + + json.save(manifestPath, true); + + for (int i = 0; i < json["data"].size(); i++) { + string linkString = json["data"][i]["link"].asString(); + string imageString = "thumbnails/" + linkString + ".png"; + patchString += "\n
\n\t
\n\t\t\n\t
\n
\n"; + } + + std::map words; + words.insert(std::make_pair("###LINKS###", patchString)); + words.insert(std::make_pair("###TITLE###", json["title"].asString())); + words.insert(std::make_pair("###DESCRIPTION###", json["description"].asString())); + + copyFileReplacingText(templatePath, outputPath, words); + } + else { + cout << "Failed to load " << manifestPath << endl; + } +} + +void webExport::updateAllHTML() { + string manifestPath = "web/manifest.json"; + string webTemplatePath = "data" + PathUtil::getSlash() + "web" + PathUtil::getSlash() + "template.html"; + string exportPath = "data" + PathUtil::getSlash() + "web" + PathUtil::getSlash(); + + ofxJSONElement json; + cout << "Updating all html..." << endl; + // Iterate thru the manifest and recreate the HTML from template. + bool parsingSuccessful = json.open(manifestPath); + if (parsingSuccessful) { + cout << "Parsed Manifest..." << endl; + for (int i = 0; i < json["data"].size(); i++) { + + string dirName = json["data"][i]["link"].asString(); + cout << "Trying dir: " << dirName << endl; + ofDirectory dir("web/" + dirName); + if (dir.exists()) { + cout << "Dir exists: " << dirName << endl; + + // Open the preset (should be the same name as the dir) + string presetPath = "web/" + dirName + PathUtil::getSlash() + dirName + ".json"; + + if (PathUtil::fileExists(presetPath)) { + cout << "Preset exists: " << presetPath << endl; + + ofxJSONElement presetJson; + parsingSuccessful = presetJson.open(presetPath); + + string passesString = ""; + + for (int j = 0; j < presetJson["data"].size(); j++) { + string shaderLink = presetJson["data"][j]["shaderName"].asString(); + std::size_t shaderNameIdx = shaderLink.find_last_of("\\"); + string shaderName = shaderLink.substr(shaderNameIdx + 1); + + passesString += "" + shaderName + ""; + + if (j != presetJson["data"].size() - 1) { + passesString += " -> "; + } + } + cout << "Passes string: " << passesString << endl; + + std::map words; + words.insert(std::make_pair("test.json", dirName + ".json")); + words.insert(std::make_pair("###PASSES###", passesString)); + + if (parsingSuccessful) { + cout << "Parsed: " << presetPath << endl; + + string exportFilePath = "data" + PathUtil::getSlash() + "web" + PathUtil::getSlash() + dirName + PathUtil::getSlash() + "index.html"; + copyFileReplacingText(webTemplatePath, exportFilePath, words); + } + } + } + } + } +} + +// from https://stackoverflow.com/questions/2896600/how-to-replace-all-occurrences-of-a-character-in-string +std::string webExport::replaceAll(std::string str, const std::string& from, const std::string& to) { + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); // Handles case where 'to' is a substring of 'from' + } + return str; +} \ No newline at end of file diff --git a/src/webExport.h b/src/webExport.h new file mode 100644 index 0000000..4d85d20 --- /dev/null +++ b/src/webExport.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "preset.h" +#include "PathUtil.h" + +class webExport { +public: + void exportPreset(preset *currentPreset); + void updateAllHTML(); + +private: + void createPatchedTemplate(string templatePath, string output); + void copyResources(string exportPath, string exportFolderPath, preset *currentPreset); + void copyFileReplacingText(string inputFilePath, string outputFilePath, std::map words); + std::string replaceAll(std::string str, const std::string& from, const std::string& to); + void exportMainIndex(preset *currentPreset, string presetExportPath); +}; \ No newline at end of file -- 2.45.2