kopia lustrzana https://github.com/jameshball/osci-render
Add further support and add a timeline for animatable files
rodzic
7e43db6c66
commit
1b4b826763
|
@ -4,23 +4,28 @@
|
|||
FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, OscirenderAudioProcessorEditor& editor) : audioProcessor(p), pluginEditor(editor) {
|
||||
setText("Frame Settings");
|
||||
|
||||
addAndMakeVisible(animate);
|
||||
addAndMakeVisible(sync);
|
||||
if (!juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||
addAndMakeVisible(animate);
|
||||
addAndMakeVisible(sync);
|
||||
addAndMakeVisible(offsetLabel);
|
||||
addAndMakeVisible(offsetBox);
|
||||
|
||||
offsetLabel.setText("Start Frame", juce::dontSendNotification);
|
||||
offsetBox.setJustification(juce::Justification::left);
|
||||
|
||||
offsetLabel.setTooltip("Offsets the animation's start point by a specified number of frames.");
|
||||
} else {
|
||||
audioProcessor.animationSyncBPM->setValueNotifyingHost(false);
|
||||
addAndMakeVisible(timeline);
|
||||
}
|
||||
addAndMakeVisible(rateLabel);
|
||||
addAndMakeVisible(rateBox);
|
||||
addAndMakeVisible(offsetLabel);
|
||||
addAndMakeVisible(offsetBox);
|
||||
addAndMakeVisible(invertImage);
|
||||
addAndMakeVisible(threshold);
|
||||
addAndMakeVisible(stride);
|
||||
|
||||
offsetLabel.setTooltip("Offsets the animation's start point by a specified number of frames.");
|
||||
|
||||
rateLabel.setText("Frames per Second", juce::dontSendNotification);
|
||||
rateBox.setJustification(juce::Justification::left);
|
||||
|
||||
offsetLabel.setText("Start Frame", juce::dontSendNotification);
|
||||
offsetBox.setJustification(juce::Justification::left);
|
||||
|
||||
update();
|
||||
|
||||
|
@ -39,12 +44,14 @@ FrameSettingsComponent::FrameSettingsComponent(OscirenderAudioProcessor& p, Osci
|
|||
stride.slider.onValueChange = [this]() {
|
||||
audioProcessor.imageStride->setValue(stride.slider.getValue());
|
||||
};
|
||||
|
||||
|
||||
audioProcessor.animationRate->addListener(this);
|
||||
audioProcessor.animationOffset->addListener(this);
|
||||
audioProcessor.animationSyncBPM->addListener(this);
|
||||
}
|
||||
|
||||
FrameSettingsComponent::~FrameSettingsComponent() {
|
||||
audioProcessor.animationSyncBPM->removeListener(this);
|
||||
audioProcessor.animationOffset->removeListener(this);
|
||||
audioProcessor.animationRate->removeListener(this);
|
||||
}
|
||||
|
@ -52,16 +59,23 @@ FrameSettingsComponent::~FrameSettingsComponent() {
|
|||
void FrameSettingsComponent::resized() {
|
||||
auto area = getLocalBounds().withTrimmedTop(20).reduced(20);
|
||||
double rowHeight = 20;
|
||||
|
||||
auto timelineArea = juce::JUCEApplicationBase::isStandaloneApp() ? area.removeFromBottom(30) : juce::Rectangle<int>();
|
||||
|
||||
auto toggleBounds = area.removeFromTop(rowHeight);
|
||||
auto toggleBounds = juce::JUCEApplicationBase::isStandaloneApp() ? juce::Rectangle<int>() : area.removeFromTop(rowHeight);
|
||||
auto toggleWidth = juce::jmin(area.getWidth() / 3, 150);
|
||||
|
||||
auto firstColumn = area.removeFromLeft(220);
|
||||
|
||||
if (animated) {
|
||||
animate.setBounds(toggleBounds.removeFromLeft(toggleWidth));
|
||||
sync.setBounds(toggleBounds.removeFromLeft(toggleWidth));
|
||||
if (juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||
timeline.setBounds(timelineArea);
|
||||
} else {
|
||||
animate.setBounds(toggleBounds.removeFromLeft(toggleWidth));
|
||||
sync.setBounds(toggleBounds.removeFromLeft(toggleWidth));
|
||||
}
|
||||
|
||||
double rowSpace = 10;
|
||||
auto firstColumn = area.removeFromLeft(220);
|
||||
|
||||
firstColumn.removeFromTop(rowSpace);
|
||||
|
||||
|
@ -70,13 +84,19 @@ void FrameSettingsComponent::resized() {
|
|||
rateBox.setBounds(animateBounds.removeFromLeft(60));
|
||||
firstColumn.removeFromTop(rowSpace);
|
||||
|
||||
animateBounds = firstColumn.removeFromTop(rowHeight);
|
||||
offsetLabel.setBounds(animateBounds.removeFromLeft(140));
|
||||
offsetBox.setBounds(animateBounds.removeFromLeft(60));
|
||||
if (!juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||
animateBounds = firstColumn.removeFromTop(rowHeight);
|
||||
offsetLabel.setBounds(animateBounds.removeFromLeft(140));
|
||||
offsetBox.setBounds(animateBounds.removeFromLeft(60));
|
||||
}
|
||||
}
|
||||
|
||||
if (image) {
|
||||
invertImage.setBounds(toggleBounds.removeFromLeft(toggleWidth));
|
||||
if (juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||
invertImage.setBounds(firstColumn.removeFromTop(rowHeight));
|
||||
} else {
|
||||
invertImage.setBounds(toggleBounds.removeFromLeft(toggleWidth));
|
||||
}
|
||||
|
||||
auto secondColumn = area;
|
||||
secondColumn.removeFromTop(5);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "components/DoubleTextBox.h"
|
||||
#include "components/EffectComponent.h"
|
||||
#include "components/SwitchButton.h"
|
||||
#include "components/AnimationTimelineComponent.h"
|
||||
|
||||
class OscirenderAudioProcessorEditor;
|
||||
class FrameSettingsComponent : public juce::GroupComponent, public juce::AudioProcessorParameter::Listener, juce::AsyncUpdater {
|
||||
|
@ -32,6 +33,7 @@ private:
|
|||
juce::Label offsetLabel{ "Offset","Offset" };
|
||||
DoubleTextBox rateBox{ audioProcessor.animationRate->min, audioProcessor.animationRate->max };
|
||||
DoubleTextBox offsetBox{ audioProcessor.animationOffset->min, audioProcessor.animationRate->max };
|
||||
AnimationTimelineComponent timeline{audioProcessor};
|
||||
|
||||
jux::SwitchButton invertImage{audioProcessor.invertImage};
|
||||
EffectComponent threshold{*audioProcessor.imageThreshold};
|
||||
|
|
|
@ -155,6 +155,7 @@ OscirenderAudioProcessor::OscirenderAudioProcessor() : CommonAudioProcessor(Buse
|
|||
booleanParameters.push_back(midiEnabled);
|
||||
booleanParameters.push_back(inputEnabled);
|
||||
booleanParameters.push_back(animateFrames);
|
||||
booleanParameters.push_back(loopAnimation);
|
||||
booleanParameters.push_back(animationSyncBPM);
|
||||
booleanParameters.push_back(invertImage);
|
||||
|
||||
|
@ -537,26 +538,26 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
|
|||
|
||||
|
||||
for (int sample = 0; sample < buffer.getNumSamples(); ++sample) {
|
||||
|
||||
// Update frame animation
|
||||
if (animateFrames->getValue()) {
|
||||
if (animationSyncBPM->getValue()) {
|
||||
animationTime = playTimeBeats;
|
||||
if (animateFrames->getBoolValue()) {
|
||||
if (juce::JUCEApplicationBase::isStandaloneApp()) {
|
||||
animationFrame = animationFrame + sTimeSec * animationRate->getValueUnnormalised();
|
||||
} else if (animationSyncBPM->getValue()) {
|
||||
animationFrame = playTimeBeats * animationRate->getValueUnnormalised() + animationOffset->getValueUnnormalised();
|
||||
} else {
|
||||
animationTime = playTimeSeconds;
|
||||
animationFrame = playTimeSeconds * animationRate->getValueUnnormalised() + animationOffset->getValueUnnormalised();
|
||||
}
|
||||
|
||||
juce::SpinLock::ScopedLockType lock1(parsersLock);
|
||||
juce::SpinLock::ScopedLockType lock2(effectsLock);
|
||||
if (currentFile >= 0 && sounds[currentFile]->parser->isAnimatable) {
|
||||
int animFrame = (int)(animationTime * animationRate->getValueUnnormalised() + animationOffset->getValueUnnormalised());
|
||||
auto lineArt = sounds[currentFile]->parser->getLineArt();
|
||||
auto img = sounds[currentFile]->parser->getImg();
|
||||
if (lineArt != nullptr) {
|
||||
lineArt->setFrame(animFrame);
|
||||
} else if (img != nullptr) {
|
||||
img->setFrame(animFrame);
|
||||
int totalFrames = sounds[currentFile]->parser->getNumFrames();
|
||||
if (loopAnimation->getBoolValue()) {
|
||||
animationFrame = std::fmod(animationFrame, totalFrames);
|
||||
} else {
|
||||
animationFrame = juce::jlimit(0.0, (double) totalFrames - 1, animationFrame.load());
|
||||
}
|
||||
sounds[currentFile]->parser->setFrame(animationFrame);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@ public:
|
|||
IntParameter* voices = new IntParameter("Voices", "voices", VERSION_HINT, 4, 1, 16);
|
||||
|
||||
BooleanParameter* animateFrames = new BooleanParameter("Animate", "animateFrames", VERSION_HINT, true, "Enables animation for files that have multiple frames, such as GIFs or Line Art.");
|
||||
BooleanParameter* loopAnimation = new BooleanParameter("Loop Animation", "loopAnimation", VERSION_HINT, true, "Loops the animation. If disabled, the animation will stop at the last frame.");
|
||||
BooleanParameter* animationSyncBPM = new BooleanParameter("Sync To BPM", "animationSyncBPM", VERSION_HINT, false, "Synchronises the animation's framerate with the BPM of your DAW.");
|
||||
FloatParameter* animationRate = new FloatParameter("Animation Rate", "animationRate", VERSION_HINT, 30, -1000, 1000);
|
||||
FloatParameter* animationOffset = new FloatParameter("Animation Offset", "animationOffset", VERSION_HINT, 0, -10000, 10000);
|
||||
|
@ -174,7 +175,7 @@ public:
|
|||
)
|
||||
);
|
||||
|
||||
double animationTime = 0.f;
|
||||
std::atomic<double> animationFrame = 0.f;
|
||||
|
||||
std::shared_ptr<WobbleEffect> wobbleEffect = std::make_shared<WobbleEffect>(*this);
|
||||
|
||||
|
|
|
@ -30,6 +30,10 @@ void SettingsComponent::resized() {
|
|||
area.removeFromRight(5);
|
||||
area.removeFromTop(5);
|
||||
area.removeFromBottom(5);
|
||||
|
||||
if (area.getWidth() <= 0 || area.getHeight() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
juce::Component dummy;
|
||||
juce::Component dummy2;
|
||||
|
@ -57,7 +61,7 @@ void SettingsComponent::resized() {
|
|||
auto dummyBounds = dummy.getBounds();
|
||||
|
||||
if (effectSettings != nullptr) {
|
||||
effectSettings->setBounds(dummyBounds.removeFromBottom(150));
|
||||
effectSettings->setBounds(dummyBounds.removeFromBottom(160));
|
||||
dummyBounds.removeFromBottom(pluginEditor.RESIZER_BAR_SIZE);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
#include "AnimationTimelineComponent.h"
|
||||
#include "../PluginProcessor.h"
|
||||
|
||||
AnimationTimelineComponent::AnimationTimelineComponent(OscirenderAudioProcessor& processor)
|
||||
: audioProcessor(processor)
|
||||
{
|
||||
setOpaque(false);
|
||||
|
||||
addAndMakeVisible(slider);
|
||||
slider.setSliderStyle(juce::Slider::SliderStyle::LinearHorizontal);
|
||||
slider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
|
||||
slider.setOpaque(false);
|
||||
slider.setRange(0, 1, 0.001);
|
||||
slider.setColour(juce::Slider::ColourIds::thumbColourId, juce::Colours::black);
|
||||
|
||||
slider.onValueChange = [this]() {
|
||||
juce::SpinLock::ScopedLockType sl(audioProcessor.parsersLock);
|
||||
int currentFileIndex = audioProcessor.getCurrentFileIndex();
|
||||
if (currentFileIndex < 0) return;
|
||||
auto parser = audioProcessor.parsers[currentFileIndex];
|
||||
if (parser != nullptr) {
|
||||
audioProcessor.animationFrame = slider.getValue() * (parser->getNumFrames() - 1);
|
||||
parser->setFrame((int) audioProcessor.animationFrame);
|
||||
}
|
||||
};
|
||||
|
||||
addChildComponent(playButton);
|
||||
addChildComponent(pauseButton);
|
||||
addAndMakeVisible(stopButton);
|
||||
addAndMakeVisible(repeatButton);
|
||||
|
||||
// Set up button behavior
|
||||
playButton.onClick = [this]() {
|
||||
audioProcessor.animateFrames->setValueNotifyingHost(true);
|
||||
playButton.setVisible(false);
|
||||
pauseButton.setVisible(true);
|
||||
};
|
||||
|
||||
pauseButton.onClick = [this]() {
|
||||
audioProcessor.animateFrames->setValueNotifyingHost(false);
|
||||
playButton.setVisible(true);
|
||||
pauseButton.setVisible(false);
|
||||
};
|
||||
|
||||
repeatButton.onClick = [this]() {
|
||||
audioProcessor.loopAnimation->setValueNotifyingHost(repeatButton.getToggleState());
|
||||
};
|
||||
|
||||
stopButton.onClick = [this]() {
|
||||
audioProcessor.animateFrames->setValueNotifyingHost(false);
|
||||
playButton.setVisible(true);
|
||||
pauseButton.setVisible(false);
|
||||
slider.setValue(0, juce::sendNotification);
|
||||
};
|
||||
|
||||
setup();
|
||||
startTimer(20);
|
||||
}
|
||||
|
||||
AnimationTimelineComponent::~AnimationTimelineComponent()
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void AnimationTimelineComponent::timerCallback()
|
||||
{
|
||||
juce::SpinLock::ScopedLockType sl(audioProcessor.parsersLock);
|
||||
int currentFileIndex = audioProcessor.getCurrentFileIndex();
|
||||
if (currentFileIndex < 0) return;
|
||||
auto parser = audioProcessor.parsers[currentFileIndex];
|
||||
if (parser == nullptr) return;
|
||||
int totalFrames = parser->getNumFrames();
|
||||
double frame = std::fmod(audioProcessor.animationFrame, totalFrames);
|
||||
slider.setValue(frame / (totalFrames - 1), juce::dontSendNotification);
|
||||
}
|
||||
|
||||
void AnimationTimelineComponent::setup()
|
||||
{
|
||||
// Get the current file parser
|
||||
juce::SpinLock::ScopedLockType sl(audioProcessor.parsersLock);
|
||||
int currentFileIndex = audioProcessor.getCurrentFileIndex();
|
||||
|
||||
bool hasAnimatableContent = false;
|
||||
|
||||
if (currentFileIndex >= 0) {
|
||||
auto parser = audioProcessor.parsers[currentFileIndex];
|
||||
|
||||
if (parser->isAnimatable) {
|
||||
hasAnimatableContent = true;
|
||||
int totalFrames = parser->getNumFrames();
|
||||
int currentFrame = parser->getCurrentFrame();
|
||||
|
||||
// Update the slider position without triggering the callback
|
||||
if (totalFrames > 1) {
|
||||
slider.setValue(static_cast<double>(currentFrame) / (totalFrames - 1), juce::dontSendNotification);
|
||||
} else {
|
||||
slider.setValue(0, juce::dontSendNotification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update visibility of components
|
||||
slider.setVisible(hasAnimatableContent);
|
||||
repeatButton.setVisible(hasAnimatableContent);
|
||||
stopButton.setVisible(hasAnimatableContent);
|
||||
|
||||
if (hasAnimatableContent) {
|
||||
playButton.setVisible(!audioProcessor.animateFrames->getBoolValue());
|
||||
pauseButton.setVisible(audioProcessor.animateFrames->getBoolValue());
|
||||
} else {
|
||||
playButton.setVisible(false);
|
||||
pauseButton.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTimelineComponent::update()
|
||||
{
|
||||
setup();
|
||||
}
|
||||
|
||||
void AnimationTimelineComponent::resized()
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
auto playPauseBounds = r.removeFromLeft(25);
|
||||
playButton.setBounds(playPauseBounds);
|
||||
pauseButton.setBounds(playPauseBounds);
|
||||
stopButton.setBounds(r.removeFromLeft(25));
|
||||
|
||||
repeatButton.setBounds(r.removeFromRight(25));
|
||||
|
||||
slider.setBounds(r);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <JuceHeader.h>
|
||||
#include "../PluginProcessor.h"
|
||||
#include "../LookAndFeel.h"
|
||||
#include "../parser/FileParser.h"
|
||||
#include "SvgButton.h"
|
||||
|
||||
class AnimationTimelineComponent : public juce::Component, public juce::Timer
|
||||
{
|
||||
public:
|
||||
AnimationTimelineComponent(OscirenderAudioProcessor& processor);
|
||||
~AnimationTimelineComponent() override;
|
||||
|
||||
void resized() override;
|
||||
void update();
|
||||
void timerCallback() override;
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
|
||||
juce::Slider slider;
|
||||
SvgButton playButton{"Play", BinaryData::play_svg, juce::Colours::white, juce::Colours::white};
|
||||
SvgButton pauseButton{"Pause", BinaryData::pause_svg, juce::Colours::white, juce::Colours::white};
|
||||
SvgButton stopButton{"Stop", BinaryData::stop_svg, juce::Colours::white, juce::Colours::white};
|
||||
SvgButton repeatButton{"Repeat", BinaryData::repeat_svg, juce::Colours::white, Colours::accentColor, audioProcessor.loopAnimation};
|
||||
|
||||
void setup();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AnimationTimelineComponent)
|
||||
};
|
|
@ -18,13 +18,15 @@ public:
|
|||
static std::vector<std::vector<Line>> parseBinaryFrames(char* data, int dataLength);
|
||||
|
||||
static std::vector<Line> generateFrame(juce::Array < juce::var> objects, double focalLength);
|
||||
|
||||
int numFrames = 0;
|
||||
int frameNumber = 0;
|
||||
private:
|
||||
static std::vector<std::vector<Line>> epicFail();
|
||||
static double makeDouble(int64_t data);
|
||||
static void makeChars(int64_t data, char* chars);
|
||||
static std::vector<std::vector<OsciPoint>> reorderVertices(std::vector<std::vector<OsciPoint>> vertices);
|
||||
static std::vector<Line> assembleFrame(std::vector<std::vector<std::vector<OsciPoint>>> allVertices, std::vector<std::vector<double>> allMatrices, double focalLength);
|
||||
int frameNumber = 0;
|
||||
std::vector<std::vector<Line>> frames;
|
||||
int numFrames = 0;
|
||||
|
||||
};
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
#include "../CommonPluginEditor.h"
|
||||
|
||||
ImageParser::ImageParser(OscirenderAudioProcessor& p, juce::String extension, juce::MemoryBlock image) : audioProcessor(p) {
|
||||
// Set up the temporary file
|
||||
temp = std::make_unique<juce::TemporaryFile>();
|
||||
juce::File file = temp->getFile();
|
||||
juce::File file = temp.getFile();
|
||||
|
||||
{
|
||||
juce::FileOutputStream output(file);
|
||||
|
@ -142,18 +140,17 @@ void ImageParser::processVideoFile(juce::File& file) {
|
|||
}
|
||||
|
||||
bool ImageParser::loadAllVideoFrames(const juce::File& file, const juce::File& ffmpegFile) {
|
||||
juce::String altCmd = "\"" + ffmpegFile.getFullPathName() + "\" -i \"" + file.getFullPathName() +
|
||||
"\" -hide_banner 2>&1";
|
||||
juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\" -i \"" + file.getFullPathName() + "\" -hide_banner 2>&1";
|
||||
|
||||
ffmpegProcess.start(altCmd);
|
||||
ffmpegProcess.start(cmd);
|
||||
|
||||
char altBuf[2048];
|
||||
memset(altBuf, 0, sizeof(altBuf));
|
||||
size_t altSize = ffmpegProcess.read(altBuf, sizeof(altBuf) - 1);
|
||||
char buf[2048];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
size_t size = ffmpegProcess.read(buf, sizeof(buf) - 1);
|
||||
ffmpegProcess.close();
|
||||
|
||||
if (altSize > 0) {
|
||||
juce::String output(altBuf, altSize);
|
||||
if (size > 0) {
|
||||
juce::String output(buf, size);
|
||||
|
||||
// Look for resolution in format "1920x1080"
|
||||
std::regex resolutionRegex(R"((\d{2,5})x(\d{2,5}))");
|
||||
|
@ -167,10 +164,23 @@ bool ImageParser::loadAllVideoFrames(const juce::File& file, const juce::File& f
|
|||
}
|
||||
}
|
||||
|
||||
// If still no dimensions, use defaults
|
||||
// If still no dimensions or dimensions are too large, use reasonable defaults
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = 640;
|
||||
height = 360;
|
||||
width = 320;
|
||||
height = 240;
|
||||
} else {
|
||||
// Downscale large videos to improve performance
|
||||
const int MAX_DIMENSION = 512;
|
||||
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
||||
float aspectRatio = static_cast<float>(width) / height;
|
||||
if (width > height) {
|
||||
width = MAX_DIMENSION;
|
||||
height = static_cast<int>(width / aspectRatio);
|
||||
} else {
|
||||
height = MAX_DIMENSION;
|
||||
width = static_cast<int>(height * aspectRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now prepare for frame reading
|
||||
|
@ -185,28 +195,36 @@ bool ImageParser::loadAllVideoFrames(const juce::File& file, const juce::File& f
|
|||
// Cap the number of frames to prevent excessive memory usage
|
||||
const int MAX_FRAMES = 10000;
|
||||
|
||||
// Start ffmpeg process to read frames
|
||||
juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\" -i \"" + file.getFullPathName() +
|
||||
"\" -f rawvideo -pix_fmt gray -v error -stats pipe:1";
|
||||
// Determine available hardware acceleration options
|
||||
#if JUCE_MAC
|
||||
// Try to use videotoolbox on macOS
|
||||
juce::String hwAccel = " -hwaccel videotoolbox";
|
||||
#elif JUCE_WINDOWS
|
||||
// Try to use DXVA2 on Windows
|
||||
juce::String hwAccel = " -hwaccel dxva2";
|
||||
#else
|
||||
juce::String hwAccel = "";
|
||||
#endif
|
||||
|
||||
// Start ffmpeg process to read frames with optimizations:
|
||||
// - Use hardware acceleration if available
|
||||
// - Lower resolution with scale filter
|
||||
// - Use multiple threads for faster processing
|
||||
// - Use gray colorspace directly to avoid extra conversion
|
||||
cmd = "\"" + ffmpegFile.getFullPathName() + "\"" +
|
||||
hwAccel +
|
||||
" -i \"" + file.getFullPathName() + "\"" +
|
||||
" -threads 8" + // Use 8 threads for processing
|
||||
" -vf \"scale=" + juce::String(width) + ":" + juce::String(height) + "\"" + // Scale to target size
|
||||
" -f rawvideo -pix_fmt gray" + // Output format
|
||||
" -v error" + // Only show errors
|
||||
" pipe:1"; // Output to stdout
|
||||
|
||||
ffmpegProcess.start(cmd);
|
||||
|
||||
if (!ffmpegProcess.isRunning()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read all frames into memory
|
||||
int framesRead = 0;
|
||||
|
||||
// Flag to indicate which frames to save (first, middle, last)
|
||||
bool shouldSaveFrame = false;
|
||||
|
||||
// Create debug directory in user documents
|
||||
juce::File debugDir = juce::File::getSpecialLocation(juce::File::userDocumentsDirectory).getChildFile("osci-render-debug");
|
||||
if (!debugDir.exists()) {
|
||||
debugDir.createDirectory();
|
||||
}
|
||||
|
||||
while (framesRead < MAX_FRAMES) {
|
||||
size_t bytesRead = ffmpegProcess.read(frameBuffer.data(), frameBuffer.size());
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ public:
|
|||
|
||||
void setFrame(int index);
|
||||
OsciPoint getSample();
|
||||
int getNumFrames() { return frames.size(); }
|
||||
int getCurrentFrame() const { return frameIndex; }
|
||||
|
||||
private:
|
||||
void findNearestNeighbour(int searchRadius, float thresholdPow, int stride, bool invert);
|
||||
|
@ -47,7 +49,7 @@ private:
|
|||
// Video processing fields
|
||||
ReadProcess ffmpegProcess;
|
||||
bool isVideo = false;
|
||||
std::unique_ptr<juce::TemporaryFile> temp;
|
||||
juce::TemporaryFile temp;
|
||||
std::vector<uint8_t> frameBuffer;
|
||||
int videoFrameSize = 0;
|
||||
|
||||
|
|
|
@ -152,12 +152,6 @@ OsciPoint FileParser::nextSample(lua_State*& L, LuaVariables& vars) {
|
|||
return OsciPoint();
|
||||
}
|
||||
|
||||
void FileParser::closeLua(lua_State*& L) {
|
||||
if (lua != nullptr) {
|
||||
lua->close(L);
|
||||
}
|
||||
}
|
||||
|
||||
bool FileParser::isSample() {
|
||||
return sampleSource;
|
||||
}
|
||||
|
@ -201,3 +195,29 @@ std::shared_ptr<ImageParser> FileParser::getImg() {
|
|||
std::shared_ptr<WavParser> FileParser::getWav() {
|
||||
return wav;
|
||||
}
|
||||
|
||||
int FileParser::getNumFrames() {
|
||||
if (gpla != nullptr) {
|
||||
return gpla->numFrames;
|
||||
} else if (img != nullptr) {
|
||||
return img->getNumFrames();
|
||||
}
|
||||
return 1; // Default to 1 frame for non-animatable content
|
||||
}
|
||||
|
||||
int FileParser::getCurrentFrame() {
|
||||
if (gpla != nullptr) {
|
||||
return gpla->frameNumber;
|
||||
} else if (img != nullptr) {
|
||||
return img->getCurrentFrame();
|
||||
}
|
||||
return 0; // Default to frame 0 for non-animatable content
|
||||
}
|
||||
|
||||
void FileParser::setFrame(int frame) {
|
||||
if (gpla != nullptr) {
|
||||
gpla->setFrame(frame);
|
||||
} else if (img != nullptr) {
|
||||
img->setFrame(frame);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,16 @@ public:
|
|||
void parse(juce::String fileId, juce::String fileName, juce::String extension, std::unique_ptr<juce::InputStream> stream, juce::Font font);
|
||||
std::vector<std::unique_ptr<Shape>> nextFrame();
|
||||
OsciPoint nextSample(lua_State*& L, LuaVariables& vars);
|
||||
void closeLua(lua_State*& L);
|
||||
|
||||
bool isSample();
|
||||
bool isActive();
|
||||
void disable();
|
||||
void enable();
|
||||
|
||||
|
||||
int getNumFrames();
|
||||
int getCurrentFrame();
|
||||
void setFrame(int frame);
|
||||
|
||||
std::shared_ptr<WorldObject> getObject();
|
||||
std::shared_ptr<SvgParser> getSvg();
|
||||
std::shared_ptr<TextParser> getText();
|
||||
|
|
|
@ -152,6 +152,10 @@
|
|||
file="Source/components/AboutComponent.cpp"/>
|
||||
<FILE id="vDlOTn" name="AboutComponent.h" compile="0" resource="0"
|
||||
file="Source/components/AboutComponent.h"/>
|
||||
<FILE id="AAXWW6" name="AnimationTimelineComponent.cpp" compile="1"
|
||||
resource="0" file="Source/components/AnimationTimelineComponent.cpp"/>
|
||||
<FILE id="ppzO8J" name="AnimationTimelineComponent.h" compile="0" resource="0"
|
||||
file="Source/components/AnimationTimelineComponent.h"/>
|
||||
<FILE id="xxiMAy" name="AudioPlayerComponent.cpp" compile="1" resource="0"
|
||||
file="Source/components/AudioPlayerComponent.cpp"/>
|
||||
<FILE id="DSvDMv" name="AudioPlayerComponent.h" compile="0" resource="0"
|
||||
|
@ -218,6 +222,7 @@
|
|||
file="Source/concurrency/BufferConsumer.h"/>
|
||||
<FILE id="L9aCHY" name="readerwritercircularbuffer.h" compile="0" resource="0"
|
||||
file="Source/concurrency/readerwritercircularbuffer.h"/>
|
||||
<FILE id="noScYw" name="ReadProcess.h" compile="0" resource="0" file="Source/concurrency/ReadProcess.h"/>
|
||||
<FILE id="aat2Je" name="WriteProcess.h" compile="0" resource="0" file="Source/concurrency/WriteProcess.h"/>
|
||||
</GROUP>
|
||||
<GROUP id="{A3E24187-62A5-AB8D-8837-14043B89A640}" name="gpla">
|
||||
|
|
Ładowanie…
Reference in New Issue