22 #include <AppConfig.h>
23 #include <juce_audio_basics/juce_audio_basics.h>
24 #include <juce_audio_devices/juce_audio_devices.h>
26 #include <QApplication>
34 #include <QHBoxLayout>
44 Frame::Frame(int64_t number,
int width,
int height, std::string color,
int samples,
int channels)
45 : audio(std::make_shared<
juce::AudioBuffer<float>>(channels, samples)),
46 number(number), width(width), height(height),
47 pixel_ratio(1,1), color(color),
50 has_audio_data(false), has_image_data(false),
51 max_audio_sample(0), audio_is_increasing(true)
61 Frame::Frame(int64_t number,
int width,
int height, std::string color)
62 :
Frame::
Frame(number, width, height, color, 0, 2) {}
66 :
Frame::
Frame(number, 1, 1,
"#000000", samples, channels) {}
89 channels = other.channels;
91 height = other.height;
92 channel_layout = other.channel_layout;
95 sample_rate = other.sample_rate;
96 pixel_ratio =
Fraction(other.pixel_ratio.
num, other.pixel_ratio.
den);
98 max_audio_sample = other.max_audio_sample;
99 audio_is_increasing = other.audio_is_increasing;
102 image = std::make_shared<QImage>(*(other.image));
104 audio = std::make_shared<juce::AudioBuffer<float>>(*(other.
audio));
105 if (other.wave_image)
106 wave_image = std::make_shared<QImage>(*(other.wave_image));
122 if (!QApplication::instance()) {
125 static char* argv[1] = {NULL};
126 previewApp = std::make_shared<QApplication>(argc, argv);
130 std::shared_ptr<QImage> previewImage =
GetImage();
133 if (pixel_ratio.
num != 1 || pixel_ratio.
den != 1)
136 previewImage = std::make_shared<QImage>(previewImage->scaled(
137 previewImage->size().width(), previewImage->size().height() * pixel_ratio.
Reciprocal().
ToDouble(),
138 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
142 QWidget previewWindow;
143 previewWindow.setStyleSheet(
"background-color: #000000;");
148 previewLabel.setPixmap(QPixmap::fromImage(*previewImage));
149 previewLabel.setMask(QPixmap::fromImage(*previewImage).mask());
150 layout.addWidget(&previewLabel);
153 previewWindow.setLayout(&layout);
154 previewWindow.show();
159 std::shared_ptr<QImage>
Frame::GetWaveform(
int width,
int height,
int Red,
int Green,
int Blue,
int Alpha)
165 QVector<QPointF> lines;
166 QVector<QPointF> labels;
170 if (total_samples > 0)
173 int new_height = 200 *
audio->getNumChannels();
174 int height_padding = 20 * (
audio->getNumChannels() - 1);
175 int total_height = new_height + height_padding;
177 float zero_height = 1.0;
181 for (
int channel = 0; channel <
audio->getNumChannels(); channel++)
186 const float *samples =
audio->getReadPointer(channel);
191 float value = samples[sample] * 100.0;
195 if (value > -zero_height && value < 0.0) {
196 value = -zero_height;
197 }
else if (value > 0.0 && value < zero_height) {
202 lines.push_back(QPointF(X, Y));
203 lines.push_back(QPointF(X, Y - value));
207 labels.push_back(QPointF(5.0, Y - 5.0));
210 Y += (200 + height_padding);
215 wave_image = std::make_shared<QImage>(
216 total_width, total_height, QImage::Format_RGBA8888_Premultiplied);
217 wave_image->fill(QColor(0,0,0,0));
220 QPainter painter(wave_image.get());
224 pen.setColor(QColor(Red, Green, Blue, Alpha));
226 pen.setStyle(Qt::SolidLine);
230 painter.drawLines(lines);
236 wave_image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
237 wave_image->fill(QColor(QString::fromStdString(
"#000000")));
241 if (wave_image->width() != width || wave_image->height() != height) {
242 QImage scaled_wave_image = wave_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
243 wave_image = std::make_shared<QImage>(scaled_wave_image);
261 wave_image =
GetWaveform(width, height, Red, Green, Blue, Alpha);
264 return wave_image->constBits();
273 if (!QApplication::instance()) {
276 static char* argv[1] = {NULL};
277 previewApp = std::make_shared<QApplication>(argc, argv);
281 QWidget previewWindow;
282 previewWindow.setStyleSheet(
"background-color: #000000;");
287 previewLabel.setPixmap(QPixmap::fromImage(*wave_image));
288 previewLabel.setMask(QPixmap::fromImage(*wave_image).mask());
289 layout.addWidget(&previewLabel);
292 previewWindow.setLayout(&layout);
293 previewWindow.show();
305 return audio->getMagnitude(channel, sample, magnitude_range);
309 return audio->getMagnitude(sample, magnitude_range);
320 return buffer->getWritePointer(channel);
329 float *output = NULL;
330 int num_of_channels =
audio->getNumChannels();
334 output =
new float[num_of_channels * num_of_samples];
338 for (
int sample = 0; sample < num_of_samples; sample++)
340 for (
int channel = 0; channel < num_of_channels; channel++)
343 output[position] = buffer->getReadPointer(channel)[sample];
351 *sample_count = num_of_samples;
360 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
362 return audio->getNumChannels();
370 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
371 return max_audio_sample;
382 int64_t total_bytes = 0;
384 total_bytes +=
static_cast<int64_t
>(
385 width * height *
sizeof(char) * 4);
389 total_bytes += (sample_rate / 24.0) *
sizeof(
float);
405 return image->constBits();
417 return image->constScanLine(row);
421 bool Frame::CheckPixel(
int row,
int col,
int red,
int green,
int blue,
int alpha,
int threshold) {
422 int col_pos = col * 4;
423 if (!image || row < 0 || row >= (height - 1) ||
424 col_pos < 0 || col_pos >= (width - 1) ) {
429 const unsigned char* pixels =
GetPixels(row);
430 if (pixels[col_pos + 0] >= (red - threshold) && pixels[col_pos + 0] <= (red + threshold) &&
431 pixels[col_pos + 1] >= (green - threshold) && pixels[col_pos + 1] <= (green + threshold) &&
432 pixels[col_pos + 2] >= (blue - threshold) && pixels[col_pos + 2] <= (blue + threshold) &&
433 pixels[col_pos + 3] >= (alpha - threshold) && pixels[col_pos + 3] <= (alpha + threshold)) {
445 pixel_ratio.
num = num;
446 pixel_ratio.
den = den;
460 if (channels <= 0 || sample_rate <= 0 || fps.
num <= 0 || fps.
den <= 0)
return 0;
466 double previous_samples = (sample_rate * fps_rate) * (
number - 1);
467 double previous_samples_remainder = fmod(previous_samples, (
double)channels);
468 previous_samples -= previous_samples_remainder;
471 double total_samples = (sample_rate * fps_rate) *
number;
472 double total_samples_remainder = fmod(total_samples, (
double)channels);
473 total_samples -= total_samples_remainder;
477 int samples_per_frame = round(total_samples - previous_samples);
478 if (samples_per_frame < 0)
479 samples_per_frame = 0;
480 return samples_per_frame;
510 return channel_layout;
518 std::shared_ptr<QImage> previewImage =
GetImage();
521 if (pixel_ratio.
num != 1 || pixel_ratio.
den != 1)
524 previewImage = std::make_shared<QImage>(previewImage->scaled(
525 previewImage->size().width(), previewImage->size().height() * pixel_ratio.
Reciprocal().
ToDouble(),
526 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
530 if (fabs(scale) > 1.001 || fabs(scale) < 0.999)
533 previewImage = std::make_shared<QImage>(previewImage->scaled(
534 previewImage->size().width() * scale, previewImage->size().height() * scale,
535 Qt::KeepAspectRatio, Qt::SmoothTransformation));
539 previewImage->save(QString::fromStdString(
path), format.c_str(), quality);
543 void Frame::Thumbnail(std::string
path,
int new_width,
int new_height, std::string mask_path, std::string overlay_path,
544 std::string background_color,
bool ignore_aspect, std::string format,
int quality,
float rotate,
ScaleType scale_mode) {
547 auto thumbnail = std::make_shared<QImage>(
548 new_width, new_height, QImage::Format_RGBA8888_Premultiplied);
549 thumbnail->fill(QColor(QString::fromStdString(background_color)));
552 QPainter painter(thumbnail.get());
553 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing,
true);
556 std::shared_ptr<QImage> previewImage =
GetImage();
559 if (pixel_ratio.
num != 1 || pixel_ratio.
den != 1)
562 int aspect_width = previewImage->size().width();
563 int aspect_height = previewImage->size().height() * pixel_ratio.
Reciprocal().
ToDouble();
566 previewImage = std::make_shared<QImage>(previewImage->scaled(
567 aspect_width, aspect_height,
568 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
572 Qt::AspectRatioMode aspect_ratio_mode = Qt::KeepAspectRatio;
574 aspect_ratio_mode = Qt::IgnoreAspectRatio;
576 switch (scale_mode) {
578 aspect_ratio_mode = Qt::KeepAspectRatioByExpanding;
581 aspect_ratio_mode = Qt::IgnoreAspectRatio;
586 aspect_ratio_mode = Qt::KeepAspectRatio;
591 previewImage = std::make_shared<QImage>(previewImage->scaled(
592 new_width, new_height,
593 aspect_ratio_mode, Qt::SmoothTransformation));
596 int x = (new_width - previewImage->size().width()) / 2.0;
597 int y = (new_height - previewImage->size().height()) / 2.0;
598 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
602 QTransform transform;
603 float origin_x = previewImage->width() / 2.0;
604 float origin_y = previewImage->height() / 2.0;
605 transform.translate(origin_x, origin_y);
606 transform.rotate(rotate);
607 transform.translate(-origin_x,-origin_y);
608 painter.setTransform(transform);
611 painter.drawImage(x, y, *previewImage);
615 if (overlay_path !=
"") {
617 auto overlay = std::make_shared<QImage>();
618 overlay->load(QString::fromStdString(overlay_path));
621 overlay = std::make_shared<QImage>(
622 overlay->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
625 overlay = std::make_shared<QImage>(overlay->scaled(
626 new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
629 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
630 painter.drawImage(0, 0, *overlay);
635 if (mask_path !=
"") {
637 auto mask = std::make_shared<QImage>();
638 mask->load(QString::fromStdString(mask_path));
641 mask = std::make_shared<QImage>(
642 mask->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
645 mask = std::make_shared<QImage>(mask->scaled(
646 new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
649 mask->invertPixels();
652 unsigned char *pixels =
static_cast<unsigned char *
>(thumbnail->bits());
653 const unsigned char *mask_pixels =
static_cast<const unsigned char *
>(mask->constBits());
657 for (
int pixel = 0, byte_index=0; pixel < new_width * new_height; pixel++, byte_index+=4)
660 int gray_value = qGray(mask_pixels[byte_index], mask_pixels[byte_index] + 1, mask_pixels[byte_index] + 2);
661 int Frame_Alpha = pixels[byte_index + 3];
662 int Mask_Value = constrain(Frame_Alpha - gray_value);
665 pixels[byte_index + 3] = Mask_Value;
674 thumbnail->save(QString::fromStdString(
path), format.c_str(), quality);
678 int Frame::constrain(
int color_value)
683 else if (color_value > 255)
691 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
696 AddColor(QColor(QString::fromStdString(new_color)));
703 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
704 image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
707 image->fill(new_color);
713 int new_width,
int new_height,
int bytes_per_pixel,
714 QImage::Format type,
const unsigned char *pixels_)
722 auto new_image = std::make_shared<QImage>(
724 new_width, new_height,
725 new_width * bytes_per_pixel,
727 (QImageCleanupFunction) &openshot::cleanUpBuffer,
741 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
745 if (image->format() != QImage::Format_RGBA8888_Premultiplied)
746 *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied);
749 width = image->width();
750 height = image->height();
769 if (image == new_image || image->size() != new_image->size()) {
772 else if (new_image->format() != QImage::Format_RGBA8888_Premultiplied) {
773 new_image = std::make_shared<QImage>(
774 new_image->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
781 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
782 unsigned char *pixels = image->bits();
783 const unsigned char *new_pixels = new_image->constBits();
790 for (
int row = start; row < image->height(); row += 2) {
791 int offset = row * image->bytesPerLine();
792 memcpy(pixels + offset, new_pixels + offset, image->bytesPerLine());
796 height = image->height();
797 width = image->width();
806 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
809 audio->setSize(channels, length,
true,
true,
false);
810 channel_layout = layout;
814 max_audio_sample = length;
819 if (
audio && !audio_is_increasing && is_increasing) {
822 }
else if (
audio && audio_is_increasing && !is_increasing) {
826 audio_is_increasing = is_increasing;
830 void Frame::AddAudio(
bool replaceSamples,
int destChannel,
int destStartSample,
const float* source,
int numSamples,
float gainToApplyToSource = 1.0f) {
831 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
834 int destStartSampleAdjusted = max(destStartSample, 0);
837 int new_length = destStartSampleAdjusted + numSamples;
838 int new_channel_length =
audio->getNumChannels();
839 if (destChannel >= new_channel_length)
840 new_channel_length = destChannel + 1;
841 if (new_length >
audio->getNumSamples() || new_channel_length >
audio->getNumChannels())
842 audio->setSize(new_channel_length, new_length,
true,
true,
false);
846 audio->clear(destChannel, destStartSampleAdjusted, numSamples);
849 audio->addFrom(destChannel, destStartSampleAdjusted, source, numSamples, gainToApplyToSource);
853 if (new_length > max_audio_sample)
854 max_audio_sample = new_length;
857 audio_is_increasing =
true;
861 void Frame::ApplyGainRamp(
int destChannel,
int destStartSample,
int numSamples,
float initial_gain = 0.0f,
float final_gain = 1.0f)
863 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
866 audio->applyGainRamp(destChannel, destStartSample, numSamples, initial_gain, final_gain);
885 cv::Mat mat = cv::Mat(qimage->height(), qimage->width(), CV_8UC4, (uchar*)qimage->constBits(), qimage->bytesPerLine()).clone();
886 cv::Mat mat2 = cv::Mat(mat.rows, mat.cols, CV_8UC3 );
887 int from_to[] = { 0,0, 1,1, 2,2 };
888 cv::mixChannels( &mat, 1, &mat2, 1, from_to, 3 );
889 cv::cvtColor(mat2, mat2, cv::COLOR_RGB2BGR);
909 cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
910 QImage qimg((uchar*) img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
912 std::shared_ptr<QImage> imgIn = std::make_shared<QImage>(qimg.copy());
915 if (imgIn->format() != QImage::Format_RGBA8888_Premultiplied)
916 *imgIn = imgIn->convertToFormat(QImage::Format_RGBA8888_Premultiplied);
936 juce::AudioDeviceManager deviceManager;
937 juce::String error = deviceManager.initialise (
944 if (error.isNotEmpty()) {
945 cout <<
"Error on initialise(): " << error << endl;
948 juce::AudioSourcePlayer audioSourcePlayer;
949 deviceManager.addAudioCallback (&audioSourcePlayer);
951 std::unique_ptr<AudioBufferSource> my_source;
955 juce::TimeSliceThread my_thread(
"Audio buffer thread");
958 my_thread.startThread();
960 juce::AudioTransportSource transport1;
961 transport1.setSource (my_source.get(),
964 (
double) sample_rate,
965 audio->getNumChannels());
966 transport1.setPosition (0);
967 transport1.setGain(1.0);
971 juce::MixerAudioSource mixer;
972 mixer.addInputSource(&transport1,
false);
973 audioSourcePlayer.setSource (&mixer);
978 while (transport1.isPlaying())
980 cout <<
"playing" << endl;
981 std::this_thread::sleep_for(std::chrono::seconds(1));
984 cout <<
"DONE!!!" << endl;
987 transport1.setSource (0);
988 audioSourcePlayer.setSource (0);
989 my_thread.stopThread(500);
990 deviceManager.removeAudioCallback (&audioSourcePlayer);
991 deviceManager.closeAudioDevice();
992 deviceManager.removeAllChangeListeners();
993 deviceManager.dispatchPendingMessages();
995 cout <<
"End of Play()" << endl;
1003 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
1006 audio->setSize(channels, numSamples,
false,
true,
false);
1011 max_audio_sample = numSamples;
1014 audio_is_increasing =
true;
Header file for AudioBufferSource class.
Header file for AudioResampler class.
Header file for Frame class.
Header file for QtUtilities (compatibiity overlay)
This class is used to expose an AudioBuffer<float> as an AudioSource in JUCE.
This class represents a fraction.
int num
Numerator for the fraction.
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
int den
Denominator for the fraction.
This class represents a single frame of video (i.e. image & audio data)
Frame & operator=(const Frame &other)
Assignment operator.
std::shared_ptr< juce::AudioBuffer< float > > audio
std::shared_ptr< QImage > Mat2Qimage(cv::Mat img)
Convert OpenCV Mat to QImage.
void AddColor(int new_width, int new_height, std::string new_color)
Add (or replace) pixel data to the frame (based on a solid color)
const unsigned char * GetPixels()
Get pixel data (as packets)
void SetAudioDirection(bool is_increasing)
Set the direction of the audio buffer of this frame.
std::shared_ptr< QImage > GetWaveform(int width, int height, int Red, int Green, int Blue, int Alpha)
Get an audio waveform image.
void Save(std::string path, float scale, std::string format="PNG", int quality=100)
Save the frame image to the specified path. The image format can be BMP, JPG, JPEG,...
void Display()
Display the frame image to the screen (primarily used for debugging reasons)
int GetAudioChannelsCount()
Get number of audio channels.
cv::Mat Qimage2mat(std::shared_ptr< QImage > &qimage)
Convert Qimage to Mat.
bool has_image_data
This frame has been loaded with pixel data.
int GetWidth()
Get height of image.
int SampleRate()
Get the original sample rate of this frame's audio data.
void ResizeAudio(int channels, int length, int sample_rate, openshot::ChannelLayout channel_layout)
Resize audio container to hold more (or less) samples and channels.
void DeepCopy(const Frame &other)
Copy data and pointers from another Frame instance.
cv::Mat GetImageCV()
Get pointer to OpenCV Mat image object.
void ClearWaveform()
Clear the waveform image (and deallocate its memory)
void Play()
Play audio samples for this frame.
void Thumbnail(std::string path, int new_width, int new_height, std::string mask_path, std::string overlay_path, std::string background_color, bool ignore_aspect, std::string format="png", int quality=100, float rotate=0.0, ScaleType scale_mode=SCALE_FIT)
float * GetInterleavedAudioSamples(int *sample_count)
Get an array of sample data (all channels interleaved together), using any sample rate.
bool has_audio_data
This frame has been loaded with audio data.
int64_t GetBytes()
Get the size in bytes of this frame (rough estimate)
openshot::ChannelLayout ChannelsLayout()
void AddAudioSilence(int numSamples)
Add audio silence.
void DisplayWaveform()
Display the wave form.
bool CheckPixel(int row, int col, int red, int green, int blue, int alpha, int threshold)
Check a specific pixel color value (returns True/False)
float * GetAudioSamples(int channel)
Get an array of sample data (and optional reverse the sample values)
float GetAudioSample(int channel, int sample, int magnitude_range)
Get magnitude of range of samples (if channel is -1, return average of all channels for that sample)
virtual ~Frame()
Destructor.
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
std::shared_ptr< QImage > GetImage()
Get pointer to Qt QImage image object.
Frame()
Constructor - blank frame.
void AddImage(int new_width, int new_height, int bytes_per_pixel, QImage::Format type, const unsigned char *pixels_)
Add (or replace) pixel data to the frame.
void ApplyGainRamp(int destChannel, int destStartSample, int numSamples, float initial_gain, float final_gain)
Apply gain ramp (i.e. fading volume)
int GetAudioSamplesCount()
Get number of audio samples.
void AddAudio(bool replaceSamples, int destChannel, int destStartSample, const float *source, int numSamples, float gainToApplyToSource)
Add audio samples to a specific channel.
const unsigned char * GetWaveformPixels(int width, int height, int Red, int Green, int Blue, int Alpha)
Get an audio waveform image pixels.
int GetHeight()
Get height of image.
void SetPixelRatio(int num, int den)
Set Pixel Aspect Ratio.
void SetFrameNumber(int64_t number)
Set frame number.
juce::AudioBuffer< float > * GetAudioSampleBuffer()
int64_t number
This is the frame number (starting at 1)
void SetImageCV(cv::Mat _image)
Set pointer to OpenCV image object.
This namespace is the default namespace for all code in the openshot library.
ScaleType
This enumeration determines how clips are scaled to fit their parent container.
@ SCALE_FIT
Scale the clip until either height or width fills the canvas (with no cropping)
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
@ SCALE_NONE
Do not scale the clip.
ChannelLayout
This enumeration determines the audio channel layout (such as stereo, mono, 5 point surround,...