From 7b98b34b043f45237132f78d106e6260b08075a4 Mon Sep 17 00:00:00 2001 From: James H Ball Date: Fri, 29 Nov 2024 22:28:38 +0100 Subject: [PATCH] Add pop-up to download ffmpeg if not already downloaded --- Source/PluginEditor.h | 3 ++ Source/SosciPluginEditor.h | 3 ++ Source/components/DownloaderComponent.cpp | 50 +++++++++++++++++++++++ Source/components/DownloaderComponent.h | 43 +++++++++++++++++++ Source/visualiser/VisualiserComponent.cpp | 15 ++++--- Source/visualiser/VisualiserComponent.h | 35 +++++++++++++++- osci-render.jucer | 3 +- sosci.jucer | 8 +++- 8 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 Source/components/DownloaderComponent.cpp create mode 100644 Source/components/DownloaderComponent.h diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 560045f..99bd26a 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -45,6 +45,9 @@ private: OscirenderAudioProcessor& audioProcessor; juce::File applicationFolder = juce::File::getSpecialLocation(juce::File::SpecialLocationType::userApplicationDataDirectory) +#if JUCE_MAC + .getChildFile("Application Support") +#endif .getChildFile("osci-render"); public: diff --git a/Source/SosciPluginEditor.h b/Source/SosciPluginEditor.h index 7eed136..a0f8486 100644 --- a/Source/SosciPluginEditor.h +++ b/Source/SosciPluginEditor.h @@ -28,6 +28,9 @@ private: SosciAudioProcessor& audioProcessor; juce::File applicationFolder = juce::File::getSpecialLocation(juce::File::SpecialLocationType::userApplicationDataDirectory) +#if JUCE_MAC + .getChildFile("Application Support") +#endif .getChildFile("osci-render"); public: OscirenderLookAndFeel lookAndFeel; diff --git a/Source/components/DownloaderComponent.cpp b/Source/components/DownloaderComponent.cpp new file mode 100644 index 0000000..3d6f132 --- /dev/null +++ b/Source/components/DownloaderComponent.cpp @@ -0,0 +1,50 @@ +#include "DownloaderComponent.h" + +DownloaderComponent::DownloaderComponent(juce::URL url, juce::File file, juce::String title, juce::Component* parent) : juce::ThreadWithProgressWindow(title, true, true, 1000, juce::String(), parent), url(url), file(file) { + if (url.toString(false).endsWithIgnoreCase(".gz")) { + uncompressOnFinish = true; + this->file = file.getSiblingFile(file.getFileName() + ".gz"); + } +} + +void DownloaderComponent::run() { + downloader = std::make_unique(url, file, this, taskLock, task); + downloader->startThread(); + while (!threadShouldExit()) { + { + juce::CriticalSection::ScopedTryLockType lock(taskLock); + if (lock.isLocked() && task != nullptr) { + if (task->isFinished()) { + return; + } + } + } + wait(100); + } +} + +void DownloaderComponent::threadComplete(bool userPressedCancel) { + if (!userPressedCancel && uncompressOnFinish && task->isFinished() && !task->hadError()) { + juce::FileInputStream input(file); + juce::GZIPDecompressorInputStream decompressedInput(&input, false, juce::GZIPDecompressorInputStream::gzipFormat); + juce::File uncompressedFile = file.getSiblingFile(file.getFileNameWithoutExtension()); + juce::FileOutputStream output(uncompressedFile); + if (output.writeFromInputStream(decompressedInput, -1) < 1) { + uncompressedFile.deleteFile(); + } else { + uncompressedFile.setExecutePermission(true); + } + } +} + +void DownloaderComponent::finished(juce::URL::DownloadTask* task, bool success) { + notify(); +} + +void DownloaderComponent::progress(juce::URL::DownloadTask* task, juce::int64 bytesDownloaded, juce::int64 totalLength) { + setProgress((float)bytesDownloaded / (float)totalLength); +} + +void DownloaderComponent::download() { + launchThread(); +} diff --git a/Source/components/DownloaderComponent.h b/Source/components/DownloaderComponent.h new file mode 100644 index 0000000..8ecfc95 --- /dev/null +++ b/Source/components/DownloaderComponent.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +class DownloaderThread : public juce::Thread { +public: + DownloaderThread(juce::URL url, juce::File file, juce::URL::DownloadTaskListener* listener, juce::CriticalSection& taskLock, std::unique_ptr& task) : juce::Thread("Downloader Thread"), url(url), file(file), listener(listener), taskLock(taskLock), task(task) {}; + + void run() override { + juce::CriticalSection::ScopedLockType lock(taskLock); + task = url.downloadToFile(file, juce::URL::DownloadTaskOptions().withListener(listener)); + }; + +private: + juce::URL url; + juce::File file; + juce::CriticalSection& taskLock; + juce::URL::DownloadTaskListener* listener; + std::unique_ptr& task; +}; + +class DownloaderComponent : public juce::ThreadWithProgressWindow, public juce::URL::DownloadTaskListener { +public: + DownloaderComponent(juce::URL url, juce::File file, juce::String title, juce::Component* parent); + + void download(); + void run() override; + void threadComplete(bool userPressedCancel) override; + void finished(juce::URL::DownloadTask* task, bool success) override; + void progress(juce::URL::DownloadTask* task, juce::int64 bytesDownloaded, juce::int64 totalLength) override; + +private: + + juce::URL url; + juce::File file; + juce::CriticalSection taskLock; + std::unique_ptr task; + std::unique_ptr downloader; + + bool uncompressOnFinish = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DownloaderComponent) +}; diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 99ab119..6ad00c1 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -11,7 +11,7 @@ #include "TexturedFragmentShader.glsl" #include "TexturedVertexShader.glsl" -VisualiserComponent::VisualiserComponent(juce::File ffmpegPath, std::function& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent, bool visualiserOnly) : ffmpegPath(ffmpegPath), haltRecording(haltRecording), settings(settings), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), threadManager), parent(parent) { +VisualiserComponent::VisualiserComponent(juce::File ffmpegFile, std::function& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent, bool visualiserOnly) : ffmpegFile(ffmpegFile), haltRecording(haltRecording), settings(settings), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), threadManager), parent(parent) { haltRecording = [this] { setRecording(false); @@ -170,20 +170,25 @@ void VisualiserComponent::setFullScreen(bool fullScreen) {} void VisualiserComponent::setRecording(bool recording) { if (recording) { + if (!ffmpegFile.exists()) { + ffmpegDownloader.download(); + record.setToggleState(false, juce::NotificationType::sendNotification); + return; + } juce::TemporaryFile tempFile = juce::TemporaryFile(".mp4"); juce::String resolution = std::to_string(renderTexture.width) + "x" + std::to_string(renderTexture.height); - juce::String cmd = ffmpegPath.getFullPathName() + - " -r " + juce::String(FRAME_RATE) + " -f rawvideo -pix_fmt rgba -s " + resolution + " -i - -threads 0 -preset fast -y -pix_fmt yuv420p -crf " + juce::String(21) + " -vf vflip " + tempFile.getFile().getFullPathName(); + juce::String cmd = "\"" + ffmpegFile.getFullPathName() + + "\" -r " + juce::String(FRAME_RATE) + " -f rawvideo -pix_fmt rgba -s " + resolution + " -i - -threads 0 -preset fast -y -pix_fmt yuv420p -crf " + juce::String(21) + " -vf vflip \"" + tempFile.getFile().getFullPathName() + "\""; // open pipe to ffmpeg's stdin in binary write mode #if JUCE_WINDOWS ffmpeg = _popen(cmd.toStdString().c_str(), "wb"); #else ffmpeg = popen(cmd.toStdString().c_str(), "w"); +#endif if (ffmpeg == nullptr) { DBG("popen failed: " + juce::String(std::strerror(errno))); } -#endif framePixels.resize(renderTexture.width * renderTexture.height * 4); setPaused(false); } else if (ffmpeg != nullptr) { @@ -223,7 +228,7 @@ void VisualiserComponent::resized() { void VisualiserComponent::popoutWindow() { setRecording(false); record.setEnabled(false); - auto visualiser = new VisualiserComponent(ffmpegPath, haltRecording, threadManager, settings, this); + auto visualiser = new VisualiserComponent(ffmpegFile, haltRecording, threadManager, settings, this); visualiser->settings.setLookAndFeel(&getLookAndFeel()); visualiser->openSettings = openSettings; visualiser->closeSettings = closeSettings; diff --git a/Source/visualiser/VisualiserComponent.h b/Source/visualiser/VisualiserComponent.h index 6bede89..c98d5e0 100644 --- a/Source/visualiser/VisualiserComponent.h +++ b/Source/visualiser/VisualiserComponent.h @@ -8,6 +8,7 @@ #include "VisualiserSettings.h" #include "../components/StopwatchComponent.h" #include "../img/qoixx.hpp" +#include "../components/DownloaderComponent.h" #define FILE_RENDER_DUMMY 0 #define FILE_RENDER_PNG 1 @@ -28,7 +29,7 @@ struct Texture { class VisualiserWindow; class VisualiserComponent : public juce::Component, public AudioBackgroundThread, public juce::MouseListener, public juce::OpenGLRenderer, public juce::AsyncUpdater { public: - VisualiserComponent(juce::File ffmpegPath, std::function& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent = nullptr, bool visualiserOnly = false); + VisualiserComponent(juce::File ffmpegFile, std::function& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent = nullptr, bool visualiserOnly = false); ~VisualiserComponent() override; std::function openSettings; @@ -81,7 +82,37 @@ private: long numFrames = 0; std::vector framePixels; FILE* ffmpeg = nullptr; - juce::File ffmpegPath; + juce::File ffmpegFile; + juce::String ffmpegURL = juce::String("https://github.com/eugeneware/ffmpeg-static/releases/download/b6.0/") + +#if JUCE_WINDOWS + #if JUCE_64BIT + "ffmpeg-win32-x64" + #elif JUCE_32BIT + "ffmpeg-win32-ia32" + #endif +#elif JUCE_MAC + #if JUCE_ARM + "ffmpeg-darwin-arm64" + #elif JUCE_INTEL + "ffmpeg-darwin-x64" + #endif +#elif JUCE_LINUX + #if JUCE_ARM + #if JUCE_64BIT + "ffmpeg-linux-arm64" + #elif JUCE_32BIT + "ffmpeg-linux-arm" + #endif + #elif JUCE_INTEL + #if JUCE_64BIT + "ffmpeg-linux-x64" + #elif JUCE_32BIT + "ffmpeg-linux-ia32" + #endif + #endif +#endif + + ".gz"; + DownloaderComponent ffmpegDownloader{ffmpegURL, ffmpegFile, "Downloading ffmpeg...", this}; Semaphore renderingSemaphore{0}; diff --git a/osci-render.jucer b/osci-render.jucer index 26f4f35..4d84ee0 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -5,7 +5,8 @@ pluginManufacturer="jameshball" aaxIdentifier="sh.ball.oscirender" cppLanguageStandard="20" projectLineFeed=" " headerPath="./include" version="2.3.0" companyName="James H Ball" companyWebsite="https://osci-render.com" - companyEmail="james@ball.sh" defines="NOMINMAX=1" pluginAUMainType="'aumf'"> + companyEmail="james@ball.sh" defines="NOMINMAX=1 INTERNET_FLAG_NO_AUTO_REDIRECT=0" + pluginAUMainType="'aumf'"> diff --git a/sosci.jucer b/sosci.jucer index 6399d9b..44aa73e 100644 --- a/sosci.jucer +++ b/sosci.jucer @@ -5,8 +5,8 @@ aaxIdentifier="sh.ball.sosci" cppLanguageStandard="20" projectLineFeed=" " headerPath="./include" version="1.0.0" companyName="James H Ball" companyWebsite="https://osci-render.com" companyEmail="james@ball.sh" - defines="NOMINMAX=1" pluginManufacturerCode="Jhba" pluginCode="Sosc" - pluginAUMainType="'aufx'"> + defines="NOMINMAX=1 INTERNET_FLAG_NO_AUTO_REDIRECT=0" pluginManufacturerCode="Jhba" + pluginCode="Sosc" pluginAUMainType="'aufx'"> @@ -67,6 +67,10 @@ file="Source/components/AboutComponent.cpp"/> + +