23 #include <AppConfig.h>
24 #include <juce_audio_basics/juce_audio_basics.h>
25 #include <juce_audio_devices/juce_audio_devices.h>
27 #include <QApplication>
35 #include <QHBoxLayout>
45 Frame::Frame(int64_t number,
int width,
int height, std::string color,
int samples,
int channels)
46 : audio(std::make_shared<
juce::AudioBuffer<float>>(channels, samples)),
47 number(number), capture_timestamp(std::numeric_limits<double>::quiet_NaN()), width(width), height(height),
48 pixel_ratio(1,1), color(color),
51 has_audio_data(false), has_image_data(false),
52 max_audio_sample(0), audio_is_increasing(true)
62 Frame::Frame(int64_t number,
int width,
int height, std::string color)
63 :
Frame::
Frame(number, width, height, color, 0, 2) {}
67 :
Frame::
Frame(number, 1, 1,
"#000000", samples, channels) {}
91 channels = other.channels;
93 height = other.height;
94 channel_layout = other.channel_layout;
97 sample_rate = other.sample_rate;
98 pixel_ratio =
Fraction(other.pixel_ratio.
num, other.pixel_ratio.
den);
100 max_audio_sample = other.max_audio_sample;
101 audio_is_increasing = other.audio_is_increasing;
104 image = std::make_shared<QImage>(*(other.image));
106 audio = std::make_shared<juce::AudioBuffer<float>>(*(other.
audio));
107 if (other.wave_image)
108 wave_image = std::make_shared<QImage>(*(other.wave_image));
124 if (!QApplication::instance()) {
127 static char* argv[1] = {NULL};
128 previewApp = std::make_shared<QApplication>(argc, argv);
132 std::shared_ptr<QImage> previewImage =
GetImage();
135 if (pixel_ratio.
num != 1 || pixel_ratio.
den != 1)
138 previewImage = std::make_shared<QImage>(previewImage->scaled(
139 previewImage->size().width(), previewImage->size().height() * pixel_ratio.
Reciprocal().
ToDouble(),
140 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
144 QWidget previewWindow;
145 previewWindow.setStyleSheet(
"background-color: #000000;");
150 previewLabel.setPixmap(QPixmap::fromImage(*previewImage));
151 previewLabel.setMask(QPixmap::fromImage(*previewImage).mask());
152 layout.addWidget(&previewLabel);
155 previewWindow.setLayout(&layout);
156 previewWindow.show();
161 std::shared_ptr<QImage>
Frame::GetWaveform(
int width,
int height,
int Red,
int Green,
int Blue,
int Alpha)
167 QVector<QPointF> lines;
168 QVector<QPointF> labels;
172 if (total_samples > 0)
175 int new_height = 200 *
audio->getNumChannels();
176 int height_padding = 20 * (
audio->getNumChannels() - 1);
177 int total_height = new_height + height_padding;
179 float zero_height = 1.0;
183 for (
int channel = 0; channel <
audio->getNumChannels(); channel++)
188 const float *samples =
audio->getReadPointer(channel);
193 float value = samples[sample] * 100.0;
197 if (value > -zero_height && value < 0.0) {
198 value = -zero_height;
199 }
else if (value > 0.0 && value < zero_height) {
204 lines.push_back(QPointF(X, Y));
205 lines.push_back(QPointF(X, Y - value));
209 labels.push_back(QPointF(5.0, Y - 5.0));
212 Y += (200 + height_padding);
217 wave_image = std::make_shared<QImage>(
218 total_width, total_height, QImage::Format_RGBA8888_Premultiplied);
219 wave_image->fill(QColor(0,0,0,0));
222 QPainter painter(wave_image.get());
226 pen.setColor(QColor(Red, Green, Blue, Alpha));
228 pen.setStyle(Qt::SolidLine);
232 painter.drawLines(lines);
238 wave_image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
239 wave_image->fill(QColor(QString::fromStdString(
"#000000")));
243 if (wave_image->width() != width || wave_image->height() != height) {
244 QImage scaled_wave_image = wave_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
245 wave_image = std::make_shared<QImage>(scaled_wave_image);
263 wave_image =
GetWaveform(width, height, Red, Green, Blue, Alpha);
266 return wave_image->constBits();
275 if (!QApplication::instance()) {
278 static char* argv[1] = {NULL};
279 previewApp = std::make_shared<QApplication>(argc, argv);
283 QWidget previewWindow;
284 previewWindow.setStyleSheet(
"background-color: #000000;");
289 previewLabel.setPixmap(QPixmap::fromImage(*wave_image));
290 previewLabel.setMask(QPixmap::fromImage(*wave_image).mask());
291 layout.addWidget(&previewLabel);
294 previewWindow.setLayout(&layout);
295 previewWindow.show();
307 return audio->getMagnitude(channel, sample, magnitude_range);
311 return audio->getMagnitude(sample, magnitude_range);
322 return buffer->getWritePointer(channel);
331 float *output = NULL;
332 int num_of_channels =
audio->getNumChannels();
336 output =
new float[num_of_channels * num_of_samples];
340 for (
int sample = 0; sample < num_of_samples; sample++)
342 for (
int channel = 0; channel < num_of_channels; channel++)
345 output[position] = buffer->getReadPointer(channel)[sample];
353 *sample_count = num_of_samples;
362 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
364 return audio->getNumChannels();
372 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
373 return max_audio_sample;
384 int64_t total_bytes = 0;
386 total_bytes +=
static_cast<int64_t
>(
387 width * height *
sizeof(char) * 4);
391 total_bytes += (sample_rate / 24.0) *
sizeof(
float);
407 return image->constBits();
419 return image->constScanLine(row);
423 bool Frame::CheckPixel(
int row,
int col,
int red,
int green,
int blue,
int alpha,
int threshold) {
424 int col_pos = col * 4;
425 if (!image || row < 0 || row >= (height - 1) ||
426 col_pos < 0 || col_pos >= (width - 1) ) {
431 const unsigned char* pixels =
GetPixels(row);
432 if (pixels[col_pos + 0] >= (red - threshold) && pixels[col_pos + 0] <= (red + threshold) &&
433 pixels[col_pos + 1] >= (green - threshold) && pixels[col_pos + 1] <= (green + threshold) &&
434 pixels[col_pos + 2] >= (blue - threshold) && pixels[col_pos + 2] <= (blue + threshold) &&
435 pixels[col_pos + 3] >= (alpha - threshold) && pixels[col_pos + 3] <= (alpha + threshold)) {
447 pixel_ratio.
num = num;
448 pixel_ratio.
den = den;
462 if (channels <= 0 || sample_rate <= 0 || fps.
num <= 0 || fps.
den <= 0)
return 0;
468 double previous_samples = (sample_rate * fps_rate) * (
number - 1);
469 double previous_samples_remainder = fmod(previous_samples, (
double)channels);
470 previous_samples -= previous_samples_remainder;
473 double total_samples = (sample_rate * fps_rate) *
number;
474 double total_samples_remainder = fmod(total_samples, (
double)channels);
475 total_samples -= total_samples_remainder;
479 int samples_per_frame = round(total_samples - previous_samples);
480 if (samples_per_frame < 0)
481 samples_per_frame = 0;
482 return samples_per_frame;
512 return channel_layout;
520 std::shared_ptr<QImage> previewImage =
GetImage();
523 if (pixel_ratio.
num != 1 || pixel_ratio.
den != 1)
526 previewImage = std::make_shared<QImage>(previewImage->scaled(
527 previewImage->size().width(), previewImage->size().height() * pixel_ratio.
Reciprocal().
ToDouble(),
528 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
532 if (fabs(scale) > 1.001 || fabs(scale) < 0.999)
535 previewImage = std::make_shared<QImage>(previewImage->scaled(
536 previewImage->size().width() * scale, previewImage->size().height() * scale,
537 Qt::KeepAspectRatio, Qt::SmoothTransformation));
541 previewImage->save(QString::fromStdString(
path), format.c_str(), quality);
545 void Frame::Thumbnail(std::string
path,
int new_width,
int new_height, std::string mask_path, std::string overlay_path,
546 std::string background_color,
bool ignore_aspect, std::string format,
int quality,
float rotate,
ScaleType scale_mode) {
549 auto thumbnail = std::make_shared<QImage>(
550 new_width, new_height, QImage::Format_RGBA8888_Premultiplied);
551 thumbnail->fill(QColor(QString::fromStdString(background_color)));
554 QPainter painter(thumbnail.get());
555 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing,
true);
558 std::shared_ptr<QImage> previewImage =
GetImage();
561 if (pixel_ratio.
num != 1 || pixel_ratio.
den != 1)
564 int aspect_width = previewImage->size().width();
565 int aspect_height = previewImage->size().height() * pixel_ratio.
Reciprocal().
ToDouble();
568 previewImage = std::make_shared<QImage>(previewImage->scaled(
569 aspect_width, aspect_height,
570 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
574 Qt::AspectRatioMode aspect_ratio_mode = Qt::KeepAspectRatio;
576 aspect_ratio_mode = Qt::IgnoreAspectRatio;
578 switch (scale_mode) {
580 aspect_ratio_mode = Qt::KeepAspectRatioByExpanding;
583 aspect_ratio_mode = Qt::IgnoreAspectRatio;
588 aspect_ratio_mode = Qt::KeepAspectRatio;
593 previewImage = std::make_shared<QImage>(previewImage->scaled(
594 new_width, new_height,
595 aspect_ratio_mode, Qt::SmoothTransformation));
598 int x = (new_width - previewImage->size().width()) / 2.0;
599 int y = (new_height - previewImage->size().height()) / 2.0;
600 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
604 QTransform transform;
605 float origin_x = previewImage->width() / 2.0;
606 float origin_y = previewImage->height() / 2.0;
607 transform.translate(origin_x, origin_y);
608 transform.rotate(rotate);
609 transform.translate(-origin_x,-origin_y);
610 painter.setTransform(transform);
613 painter.drawImage(x, y, *previewImage);
617 if (overlay_path !=
"") {
619 auto overlay = std::make_shared<QImage>();
620 overlay->load(QString::fromStdString(overlay_path));
623 overlay = std::make_shared<QImage>(
624 overlay->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
627 overlay = std::make_shared<QImage>(overlay->scaled(
628 new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
631 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
632 painter.drawImage(0, 0, *overlay);
637 if (mask_path !=
"") {
639 auto mask = std::make_shared<QImage>();
640 mask->load(QString::fromStdString(mask_path));
643 mask = std::make_shared<QImage>(
644 mask->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
647 mask = std::make_shared<QImage>(mask->scaled(
648 new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
651 mask->invertPixels();
654 unsigned char *pixels =
static_cast<unsigned char *
>(thumbnail->bits());
655 const unsigned char *mask_pixels =
static_cast<const unsigned char *
>(mask->constBits());
659 for (
int pixel = 0, byte_index=0; pixel < new_width * new_height; pixel++, byte_index+=4)
662 int gray_value = qGray(mask_pixels[byte_index], mask_pixels[byte_index] + 1, mask_pixels[byte_index] + 2);
663 int Frame_Alpha = pixels[byte_index + 3];
664 int Mask_Value = constrain(Frame_Alpha - gray_value);
667 pixels[byte_index + 3] = Mask_Value;
676 thumbnail->save(QString::fromStdString(
path), format.c_str(), quality);
680 int Frame::constrain(
int color_value)
685 else if (color_value > 255)
693 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
698 AddColor(QColor(QString::fromStdString(new_color)));
705 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
706 image = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
709 image->fill(new_color);
715 int new_width,
int new_height,
int bytes_per_pixel,
716 QImage::Format type,
const unsigned char *pixels_)
724 auto new_image = std::make_shared<QImage>(
726 new_width, new_height,
727 new_width * bytes_per_pixel,
729 (QImageCleanupFunction) &openshot::cleanUpBuffer,
743 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
747 if (image->format() != QImage::Format_RGBA8888_Premultiplied)
748 *image = image->convertToFormat(QImage::Format_RGBA8888_Premultiplied);
751 width = image->width();
752 height = image->height();
771 if (image == new_image || image->size() != new_image->size()) {
774 else if (new_image->format() != QImage::Format_RGBA8888_Premultiplied) {
775 new_image = std::make_shared<QImage>(
776 new_image->convertToFormat(QImage::Format_RGBA8888_Premultiplied));
783 const std::lock_guard<std::recursive_mutex> lock(addingImageMutex);
784 unsigned char *pixels = image->bits();
785 const unsigned char *new_pixels = new_image->constBits();
792 for (
int row = start; row < image->height(); row += 2) {
793 int offset = row * image->bytesPerLine();
794 memcpy(pixels + offset, new_pixels + offset, image->bytesPerLine());
798 height = image->height();
799 width = image->width();
808 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
811 audio->setSize(channels, length,
true,
true,
false);
812 channel_layout = layout;
816 max_audio_sample = length;
821 if (
audio && !audio_is_increasing && is_increasing) {
824 }
else if (
audio && audio_is_increasing && !is_increasing) {
828 audio_is_increasing = is_increasing;
832 void Frame::AddAudio(
bool replaceSamples,
int destChannel,
int destStartSample,
const float* source,
int numSamples,
float gainToApplyToSource = 1.0f) {
833 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
836 int destStartSampleAdjusted = max(destStartSample, 0);
839 int new_length = destStartSampleAdjusted + numSamples;
840 int new_channel_length =
audio->getNumChannels();
841 if (destChannel >= new_channel_length)
842 new_channel_length = destChannel + 1;
843 if (new_length >
audio->getNumSamples() || new_channel_length >
audio->getNumChannels())
844 audio->setSize(new_channel_length, new_length,
true,
true,
false);
848 audio->clear(destChannel, destStartSampleAdjusted, numSamples);
851 audio->addFrom(destChannel, destStartSampleAdjusted, source, numSamples, gainToApplyToSource);
855 if (new_length > max_audio_sample)
856 max_audio_sample = new_length;
859 audio_is_increasing =
true;
863 void Frame::ApplyGainRamp(
int destChannel,
int destStartSample,
int numSamples,
float initial_gain = 0.0f,
float final_gain = 1.0f)
865 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
868 audio->applyGainRamp(destChannel, destStartSample, numSamples, initial_gain, final_gain);
887 cv::Mat mat = cv::Mat(qimage->height(), qimage->width(), CV_8UC4, (uchar*)qimage->constBits(), qimage->bytesPerLine()).clone();
888 cv::Mat mat2 = cv::Mat(mat.rows, mat.cols, CV_8UC3 );
889 int from_to[] = { 0,0, 1,1, 2,2 };
890 cv::mixChannels( &mat, 1, &mat2, 1, from_to, 3 );
891 cv::cvtColor(mat2, mat2, cv::COLOR_RGB2BGR);
911 cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
912 QImage qimg((uchar*) img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
914 std::shared_ptr<QImage> imgIn = std::make_shared<QImage>(qimg.copy());
917 if (imgIn->format() != QImage::Format_RGBA8888_Premultiplied)
918 *imgIn = imgIn->convertToFormat(QImage::Format_RGBA8888_Premultiplied);
938 juce::AudioDeviceManager deviceManager;
939 juce::String error = deviceManager.initialise (
946 if (error.isNotEmpty()) {
947 cout <<
"Error on initialise(): " << error << endl;
950 juce::AudioSourcePlayer audioSourcePlayer;
951 deviceManager.addAudioCallback (&audioSourcePlayer);
953 std::unique_ptr<AudioBufferSource> my_source;
957 juce::TimeSliceThread my_thread(
"Audio buffer thread");
960 my_thread.startThread();
962 juce::AudioTransportSource transport1;
963 transport1.setSource (my_source.get(),
966 (
double) sample_rate,
967 audio->getNumChannels());
968 transport1.setPosition (0);
969 transport1.setGain(1.0);
973 juce::MixerAudioSource mixer;
974 mixer.addInputSource(&transport1,
false);
975 audioSourcePlayer.setSource (&mixer);
980 while (transport1.isPlaying())
982 cout <<
"playing" << endl;
983 std::this_thread::sleep_for(std::chrono::seconds(1));
986 cout <<
"DONE!!!" << endl;
989 transport1.setSource (0);
990 audioSourcePlayer.setSource (0);
991 my_thread.stopThread(500);
992 deviceManager.removeAudioCallback (&audioSourcePlayer);
993 deviceManager.closeAudioDevice();
994 deviceManager.removeAllChangeListeners();
995 deviceManager.dispatchPendingMessages();
997 cout <<
"End of Play()" << endl;
1005 const std::lock_guard<std::recursive_mutex> lock(addingAudioMutex);
1008 audio->setSize(channels, numSamples,
false,
true,
false);
1013 max_audio_sample = numSamples;
1016 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.
double capture_timestamp
Optional source capture timestamp in seconds for live capture frames.
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,...