A src/FfmpegManager.cpp => src/FfmpegManager.cpp +170 -0
@@ 0,0 1,170 @@
+#include "FfmpegManager.h"
+#include "PathUtil.h"
+
+void FfmpegManager::saveGif(string filename, PNGRenderer *pngRenderer) {
+
+ if (isRunning) return;
+
+ vector<string> 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<string> 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
A src/FfmpegManager.h => src/FfmpegManager.h +60 -0
@@ 0,0 1,60 @@
+#pragma once
+
+#include <string>
+#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<void> cancelButton;
+ ofParameter<string> statusLabel;
+ string ffmpegCommand = "ffmpeg";
+ ofxGuiButton *cancelButtonRef;
+ ofxGuiContainer *container = nullptr;
+ ThreadedSystemCall processCall;
+ ofEvent<void> completedEventGif;
+ ofEvent<void> 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
M src/PNGRenderer.cpp => src/PNGRenderer.cpp +81 -30
@@ 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
M src/PNGRenderer.h => src/PNGRenderer.h +19 -1
@@ 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<string> spaceBufferLabel;
@@ 37,6 42,8 @@ public:
ofParameter<int> FPS;
ofParameter<void> encodeMp4Button;
ofParameter<void> encodeGifButton;
+ ofParameter<void> exportToWebButton;
+ ofParameter<void> 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<bool> preview;
ofParameter<void> saveButton;
ofParameter<void> 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();
};
M src/Parameters/TextureParameter.cpp => src/Parameters/TextureParameter.cpp +1 -1
@@ 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);
}
}
A src/PathUtil.cpp => src/PathUtil.cpp +58 -0
@@ 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
A src/PathUtil.h => src/PathUtil.h +12 -0
@@ 0,0 1,12 @@
+#pragma once
+
+#include <string>
+
+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
M src/ShaderChain.cpp => src/ShaderChain.cpp +100 -334
@@ 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);
}
}
M src/ShaderChain.h => src/ShaderChain.h +13 -17
@@ 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<ShaderPass*> passes;
- float time;
- ofParameter<bool> isRunning;
+ preset currentPreset;
+
+ ofParameter<bool> 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();
};
M src/ShaderPass.cpp => src/ShaderPass.cpp +4 -2
@@ 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();
}
A src/ThreadedSystemCall.cpp => src/ThreadedSystemCall.cpp +11 -0
@@ 0,0 1,11 @@
+#include "ThreadedSystemCall.h"
+
+ThreadedSystemCall::ThreadedSystemCall() {
+ waitForThread(false);
+}
+
+void ThreadedSystemCall::systemCall(std::vector<std::string> commands, ofEvent<void> *completionEvent) {
+ this->commands = commands;
+ this->completionEvent = completionEvent;
+ startThread();
+}<
\ No newline at end of file
A src/ThreadedSystemCall.h => src/ThreadedSystemCall.h +32 -0
@@ 0,0 1,32 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "ofMain.h"
+
+class ThreadedSystemCall : public ofThread {
+
+public:
+ ThreadedSystemCall();
+ void systemCall(std::vector<std::string> commands, ofEvent<void> *completionEvent);
+
+ void threadedFunction() {
+ if (isThreadRunning()) {
+ for (string command : commands) {
+ system(command.c_str());
+ }
+ ofNotifyEvent(*completionEvent);
+ stopThread();
+ }
+ }
+
+ ~ThreadedSystemCall() {
+ stopThread();
+ waitForThread(false);
+ }
+
+private:
+ std::vector<std::string> commands;
+ ofEvent<void> *completionEvent;
+};<
\ No newline at end of file
M src/gui/PassesGui.cpp => src/gui/PassesGui.cpp +3 -4
@@ 10,12 10,11 @@ PassesGui::~PassesGui() {
}
-void PassesGui::Setup(std::vector<ShaderPass*> *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<string> text;
- text.set(passes->at(i)->displayName);
+ text.set(currentPreset->passes.at(i)->displayName);
passButtons->add(text);
}
}
M src/gui/PassesGui.h => src/gui/PassesGui.h +2 -2
@@ 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<ShaderPass*> *passes);
+ void setup(preset *currentPreset);
ofxGuiContainer *panel;
private:
ofxGui gui;
- std::vector<ShaderPass*> *passes;
};
M src/gui/credits.h => src/gui/credits.h +1 -1
@@ 6,7 6,7 @@
class credits {
public:
credits();
- bool enabled;
+ bool enabled = false;
void draw();
private:
M src/ofApp.cpp => src/ofApp.cpp +1 -1
@@ 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);
}
A src/preset.cpp => src/preset.cpp +172 -0
@@ 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
A src/preset.h => src/preset.h +49 -0
@@ 0,0 1,49 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include "ShaderPass.h"
+#include "RenderStruct.h"
+class preset {
+
+public:
+ string name;
+ string presetFilePath;
+
+ // Without the full path or .json file extension
+ string presetDisplayName;
+
+ vector<ShaderPass*> 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
A src/webExport.cpp => src/webExport.cpp +247 -0
@@ 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 += "<a href=\"" + currentPreset->passes[i]->filePath + ".frag\">" + currentPreset->passes[i]->displayName + "</a>";
+
+ if (i != currentPreset->passes.size() - 1) {
+ passesString += " -> ";
+ }
+ }
+
+ std::map<string, string> 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<string, string> 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<std::string, std::string> 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<string, string>::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<div class=\"button\">\n\t<div id=\"buttonText\">\n\t\t<a href=\"" + linkString + "\"><img src=\"" + imageString + "\"/></a>\n\t</div>\n</div>\n";
+ }
+
+ std::map<string, string> 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 += "<a href=\"" + shaderLink + ".frag\">" + shaderName + "</a>";
+
+ if (j != presetJson["data"].size() - 1) {
+ passesString += " -> ";
+ }
+ }
+ cout << "Passes string: " << passesString << endl;
+
+ std::map<string, string> 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
A src/webExport.h => src/webExport.h +19 -0
@@ 0,0 1,19 @@
+#pragma once
+
+#include <string>
+
+#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<std::string, std::string> 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