implemented core audio recording support
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "audio/packedprocessor.h"
|
||||
#include "config/config.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -68,6 +69,19 @@ int OutputCallback(const void *input, void *output, unsigned long frameCount, co
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
int InputCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
|
||||
{
|
||||
FFmpegEncoder *f = static_cast<FFmpegEncoder*>(userData);
|
||||
|
||||
SampleBufferPtr s = SampleBuffer::Create();
|
||||
s->set_sample_count(frameCount);
|
||||
s->set_audio_params(f->params().audio_params());
|
||||
|
||||
f->WriteAudioData(f->params().audio_params(), false, reinterpret_cast<const uint8_t**>(&input), frameCount);
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &samples)
|
||||
{
|
||||
if (output_device_ == paNoDevice) {
|
||||
@@ -79,13 +93,7 @@ void AudioManager::PushToOutput(const AudioParams ¶ms, const QByteArray &sam
|
||||
|
||||
CloseOutputStream();
|
||||
|
||||
PaStreamParameters p;
|
||||
|
||||
p.channelCount = output_params_.channel_count();
|
||||
p.device = output_device_;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(output_params_.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(output_device_)->defaultLowOutputLatency;
|
||||
PaStreamParameters p = GetPortAudioParams(params, output_device_);
|
||||
|
||||
Pa_OpenStream(&output_stream_, nullptr, &p, output_params_.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, OutputCallback, output_buffer_);
|
||||
|
||||
@@ -176,6 +184,52 @@ void AudioManager::HardReset()
|
||||
Pa_Initialize();
|
||||
}
|
||||
|
||||
bool AudioManager::StartRecording(const QString &filename, const AudioParams ¶ms)
|
||||
{
|
||||
if (input_device_ == paNoDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EncodingParams encode_param;
|
||||
encode_param.EnableAudio(params, ExportCodec::kCodecMP3);
|
||||
encode_param.SetFilename(filename);
|
||||
|
||||
input_encoder_ = new FFmpegEncoder(encode_param);
|
||||
if (!input_encoder_->Open()) {
|
||||
qCritical() << "Failed to open encoder for recording";
|
||||
return false;
|
||||
}
|
||||
|
||||
PaStreamParameters p = GetPortAudioParams(params, input_device_);
|
||||
|
||||
if (Pa_OpenStream(&input_stream_, &p, nullptr, params.sample_rate(), paFramesPerBufferUnspecified, paNoFlag, InputCallback, input_encoder_) == paNoError) {
|
||||
if (Pa_StartStream(input_stream_) == paNoError) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
StopRecording();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioManager::StopRecording()
|
||||
{
|
||||
if (input_stream_) {
|
||||
if (Pa_IsStreamActive(input_stream_)) {
|
||||
Pa_StopStream(input_stream_);
|
||||
}
|
||||
Pa_CloseStream(input_stream_);
|
||||
|
||||
input_stream_ = nullptr;
|
||||
}
|
||||
|
||||
if (input_encoder_) {
|
||||
input_encoder_->Close();
|
||||
delete input_encoder_;
|
||||
input_encoder_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PaDeviceIndex AudioManager::FindConfigDeviceByName(bool is_output_device)
|
||||
{
|
||||
QString entry = is_output_device ? QStringLiteral("AudioOutput") : QStringLiteral("AudioInput");
|
||||
@@ -199,8 +253,23 @@ PaDeviceIndex AudioManager::FindDeviceByName(const QString &s, bool is_output_de
|
||||
return is_output_device ? Pa_GetDefaultOutputDevice() : Pa_GetDefaultInputDevice();
|
||||
}
|
||||
|
||||
PaStreamParameters AudioManager::GetPortAudioParams(const AudioParams ¶ms, PaDeviceIndex device)
|
||||
{
|
||||
PaStreamParameters p;
|
||||
|
||||
p.channelCount = params.channel_count();
|
||||
p.device = device;
|
||||
p.hostApiSpecificStreamInfo = nullptr;
|
||||
p.sampleFormat = GetPortAudioSampleFormat(params.format());
|
||||
p.suggestedLatency = Pa_GetDeviceInfo(device)->defaultLowOutputLatency;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
AudioManager::AudioManager() :
|
||||
output_stream_(nullptr)
|
||||
output_stream_(nullptr),
|
||||
input_stream_(nullptr),
|
||||
input_encoder_(nullptr)
|
||||
{
|
||||
#ifdef PA_HAS_JACK
|
||||
// PortAudio doesn't do a strcpy, so we need a const char that's readily accessible (i.e. not
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "audiovisualwaveform.h"
|
||||
#include "common/define.h"
|
||||
#include "codec/ffmpeg/ffmpegencoder.h"
|
||||
#include "render/audioparams.h"
|
||||
#include "render/audioplaybackcache.h"
|
||||
#include "render/previewaudiodevice.h"
|
||||
@@ -73,9 +74,15 @@ public:
|
||||
|
||||
void HardReset();
|
||||
|
||||
bool StartRecording(const QString &filename, const AudioParams ¶ms);
|
||||
|
||||
void StopRecording();
|
||||
|
||||
static PaDeviceIndex FindConfigDeviceByName(bool is_output_device);
|
||||
static PaDeviceIndex FindDeviceByName(const QString &s, bool is_output_device);
|
||||
|
||||
static PaStreamParameters GetPortAudioParams(const AudioParams &p, PaDeviceIndex device);
|
||||
|
||||
signals:
|
||||
void OutputNotify();
|
||||
|
||||
@@ -96,6 +103,8 @@ private:
|
||||
PreviewAudioDevice *output_buffer_;
|
||||
|
||||
PaDeviceIndex input_device_;
|
||||
PaStream *input_stream_;
|
||||
FFmpegEncoder *input_encoder_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -236,11 +236,6 @@ fail:
|
||||
|
||||
bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
{
|
||||
if (!InitializeResampleContext(audio)) {
|
||||
qCritical() << "Failed to initialize resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Create input buffer
|
||||
@@ -258,6 +253,25 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
}
|
||||
}
|
||||
|
||||
result = WriteAudioData(audio->audio_params(), true, const_cast<const uint8_t**>(input_data), input_sample_count);
|
||||
|
||||
if (input_data) {
|
||||
av_freep(&input_data[0]);
|
||||
av_freep(&input_data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **input_data, int input_sample_count)
|
||||
{
|
||||
if (!InitializeResampleContext(audio_params, planar)) {
|
||||
qCritical() << "Failed to initialize resample context";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
// Create output buffer
|
||||
int output_sample_count = input_sample_count ? swr_get_out_samples(audio_resample_ctx_, input_sample_count) : 102400;
|
||||
uint8_t** output_data = nullptr;
|
||||
@@ -308,11 +322,6 @@ bool FFmpegEncoder::WriteAudio(SampleBufferPtr audio)
|
||||
av_freep(&output_data);
|
||||
}
|
||||
|
||||
if (input_data) {
|
||||
av_freep(&input_data[0]);
|
||||
av_freep(&input_data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -774,7 +783,7 @@ void FFmpegEncoder::FlushCodecCtx(AVCodecContext *codec_ctx, AVStream* stream)
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
|
||||
bool FFmpegEncoder::InitializeResampleContext(const AudioParams &audio, bool planar)
|
||||
{
|
||||
if (audio_resample_ctx_) {
|
||||
return true;
|
||||
@@ -785,9 +794,9 @@ bool FFmpegEncoder::InitializeResampleContext(SampleBufferPtr audio)
|
||||
static_cast<int64_t>(audio_codec_ctx_->channel_layout),
|
||||
audio_codec_ctx_->sample_fmt,
|
||||
audio_codec_ctx_->sample_rate,
|
||||
static_cast<int64_t>(audio->audio_params().channel_layout()),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(audio->audio_params().format(), true),
|
||||
audio->audio_params().sample_rate(),
|
||||
static_cast<int64_t>(audio.channel_layout()),
|
||||
FFmpegUtils::GetFFmpegSampleFormat(audio.format(), planar),
|
||||
audio.sample_rate(),
|
||||
0,
|
||||
nullptr);
|
||||
if (!audio_resample_ctx_) {
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
|
||||
virtual bool WriteAudio(olive::SampleBufferPtr audio) override;
|
||||
|
||||
bool WriteAudioData(const AudioParams &audio_params, bool planar, const uint8_t **data, int input_sample_count);
|
||||
|
||||
virtual bool WriteSubtitle(const SubtitleBlock *sub_block) override;
|
||||
|
||||
virtual void Close() override;
|
||||
@@ -76,7 +78,7 @@ private:
|
||||
void FlushEncoders();
|
||||
void FlushCodecCtx(AVCodecContext* codec_ctx, AVStream *stream);
|
||||
|
||||
bool InitializeResampleContext(SampleBufferPtr audio);
|
||||
bool InitializeResampleContext(const AudioParams &audio, bool planar);
|
||||
|
||||
static const AVCodec *GetEncoder(ExportCodec::Codec c);
|
||||
|
||||
|
||||
+3
-3
@@ -245,14 +245,14 @@ UndoStack *Core::undo_stack()
|
||||
return &undo_stack_;
|
||||
}
|
||||
|
||||
void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder* parent)
|
||||
void Core::ImportFiles(const QStringList &urls, Folder* parent)
|
||||
{
|
||||
if (urls.isEmpty()) {
|
||||
QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import"));
|
||||
return;
|
||||
}
|
||||
|
||||
ProjectImportTask* pim = new ProjectImportTask(model, parent, urls);
|
||||
ProjectImportTask* pim = new ProjectImportTask(parent, urls);
|
||||
|
||||
if (!pim->GetFileCount()) {
|
||||
// No files to import
|
||||
@@ -364,7 +364,7 @@ void Core::DialogImportShow()
|
||||
// Get the selected folder in this panel
|
||||
Folder* folder = active_project_panel->GetSelectedFolder();
|
||||
|
||||
ImportFiles(files, active_project_panel->model(), folder);
|
||||
ImportFiles(files, folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ public:
|
||||
*
|
||||
* @param urls
|
||||
*/
|
||||
void ImportFiles(const QStringList& urls, ProjectViewModel *model, Folder *parent);
|
||||
void ImportFiles(const QStringList& urls, Folder *parent);
|
||||
|
||||
/**
|
||||
* @brief Get the currently active tool
|
||||
|
||||
@@ -24,6 +24,8 @@ set(OLIVE_SOURCES
|
||||
dialog/export/exportadvancedvideodialog.h
|
||||
dialog/export/exportaudiotab.cpp
|
||||
dialog/export/exportaudiotab.h
|
||||
dialog/export/exportformatcombobox.cpp
|
||||
dialog/export/exportformatcombobox.h
|
||||
dialog/export/exportsubtitlestab.cpp
|
||||
dialog/export/exportsubtitlestab.h
|
||||
dialog/export/exportvideotab.cpp
|
||||
|
||||
@@ -118,7 +118,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
row++;
|
||||
|
||||
preferences_layout->addWidget(new QLabel(tr("Format:")), row, 0);
|
||||
format_combobox_ = new QComboBox();
|
||||
format_combobox_ = new ExportFormatComboBox();
|
||||
preferences_layout->addWidget(format_combobox_, row, 1, 1, 3);
|
||||
|
||||
row++;
|
||||
@@ -193,33 +193,11 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
// Set default filename
|
||||
SetDefaultFilename();
|
||||
|
||||
// Populate combobox formats
|
||||
for (int i=0; i<ExportFormat::kFormatCount; i++) {
|
||||
QString format_name = ExportFormat::GetName(static_cast<ExportFormat::Format>(i));
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (int j=0; j<format_combobox_->count(); j++) {
|
||||
if (format_combobox_->itemText(j) > format_name) {
|
||||
format_combobox_->insertItem(j, format_name, i);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
format_combobox_->addItem(format_name, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
previously_selected_format_ = ExportFormat::kFormatMPEG4;
|
||||
SetCurrentFormat(ExportFormat::kFormatMPEG4);
|
||||
connect(format_combobox_,
|
||||
static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this,
|
||||
&ExportDialog::FormatChanged);
|
||||
FormatChanged(format_combobox_->currentIndex());
|
||||
format_combobox_->SetFormat(ExportFormat::kFormatMPEG4);
|
||||
connect(format_combobox_, &ExportFormatComboBox::FormatChanged, this, &ExportDialog::FormatChanged);
|
||||
FormatChanged(format_combobox_->GetFormat());
|
||||
|
||||
VideoParams vp = viewer_node_->GetVideoParams();
|
||||
AudioParams ap = viewer_node_->GetAudioParams();
|
||||
@@ -273,11 +251,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
|
||||
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
|
||||
}
|
||||
|
||||
ExportFormat::Format ExportDialog::GetSelectedFormat() const
|
||||
{
|
||||
return static_cast<ExportFormat::Format>(format_combobox_->currentData().toInt());
|
||||
}
|
||||
|
||||
rational ExportDialog::GetSelectedTimebase() const
|
||||
{
|
||||
return video_tab_->GetSelectedFrameRate().flipped();
|
||||
@@ -293,7 +266,7 @@ void ExportDialog::StartExport()
|
||||
|
||||
// Validate if the entered filename contains the correct extension (the extension is necessary
|
||||
// for both FFmpeg and OIIO to determine the output format)
|
||||
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(GetSelectedFormat()));
|
||||
QString necessary_ext = QStringLiteral(".%1").arg(ExportFormat::GetExtension(format_combobox_->GetFormat()));
|
||||
QString proposed_filename = filename_edit_->text().trimmed();
|
||||
|
||||
// If it doesn't, see if the user wants to append it automatically. If not, we don't abort the export.
|
||||
@@ -428,7 +401,7 @@ void ExportDialog::AddPreferencesTab(QWidget *inner_widget, const QString &title
|
||||
|
||||
void ExportDialog::BrowseFilename()
|
||||
{
|
||||
ExportFormat::Format f = GetSelectedFormat();
|
||||
ExportFormat::Format f = format_combobox_->GetFormat();
|
||||
|
||||
QString browsed_fn = QFileDialog::getSaveFileName(this,
|
||||
"",
|
||||
@@ -444,11 +417,10 @@ void ExportDialog::BrowseFilename()
|
||||
}
|
||||
}
|
||||
|
||||
void ExportDialog::FormatChanged(int index)
|
||||
void ExportDialog::FormatChanged(ExportFormat::Format current_format)
|
||||
{
|
||||
QString current_filename = filename_edit_->text().trimmed();
|
||||
QString previously_selected_ext = ExportFormat::GetExtension(previously_selected_format_);
|
||||
ExportFormat::Format current_format = static_cast<ExportFormat::Format>(format_combobox_->itemData(index).toInt());
|
||||
QString currently_selected_ext = ExportFormat::GetExtension(current_format);
|
||||
|
||||
// If the previous extension was added, remove it
|
||||
@@ -546,7 +518,7 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
AudioParams::kInternalFormat);
|
||||
|
||||
ExportParams params;
|
||||
params.set_encoder(Encoder::GetTypeFromFormat(GetSelectedFormat()));
|
||||
params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat()));
|
||||
params.SetFilename(filename_edit_->text().trimmed());
|
||||
params.SetExportLength(viewer_node_->GetLength());
|
||||
|
||||
@@ -594,16 +566,6 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
return params;
|
||||
}
|
||||
|
||||
void ExportDialog::SetCurrentFormat(ExportFormat::Format format)
|
||||
{
|
||||
for (int i=0; i<format_combobox_->count(); i++) {
|
||||
if (format_combobox_->itemData(i).toInt() == format) {
|
||||
format_combobox_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rational ExportDialog::GetExportLength() const
|
||||
{
|
||||
if (range_combobox_->currentIndex() == kRangeInToOut) {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "codec/exportcodec.h"
|
||||
#include "codec/exportformat.h"
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
#include "exportaudiotab.h"
|
||||
#include "exportsubtitlestab.h"
|
||||
#include "exportvideotab.h"
|
||||
@@ -43,8 +44,6 @@ class ExportDialog : public QDialog
|
||||
public:
|
||||
ExportDialog(ViewerOutput* viewer_node, QWidget* parent = nullptr);
|
||||
|
||||
ExportFormat::Format GetSelectedFormat() const;
|
||||
|
||||
rational GetSelectedTimebase() const;
|
||||
|
||||
protected:
|
||||
@@ -58,8 +57,6 @@ private:
|
||||
|
||||
ExportParams GenerateParams() const;
|
||||
|
||||
void SetCurrentFormat(ExportFormat::Format format);
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
ExportFormat::Format previously_selected_format_;
|
||||
@@ -82,7 +79,7 @@ private:
|
||||
|
||||
ViewerWidget* preview_viewer_;
|
||||
QLineEdit* filename_edit_;
|
||||
QComboBox* format_combobox_;
|
||||
ExportFormatComboBox* format_combobox_;
|
||||
|
||||
ExportVideoTab* video_tab_;
|
||||
ExportAudioTab* audio_tab_;
|
||||
@@ -98,7 +95,7 @@ private:
|
||||
private slots:
|
||||
void BrowseFilename();
|
||||
|
||||
void FormatChanged(int index);
|
||||
void FormatChanged(ExportFormat::Format current_format);
|
||||
|
||||
void ResolutionChanged();
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ class ExportAudioTab : public QWidget
|
||||
public:
|
||||
ExportAudioTab(QWidget* parent = nullptr);
|
||||
|
||||
int SetFormat(ExportFormat::Format format);
|
||||
|
||||
QComboBox* codec_combobox() const
|
||||
{
|
||||
return codec_combobox_;
|
||||
@@ -59,6 +57,9 @@ public:
|
||||
return bit_rate_slider_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
int SetFormat(ExportFormat::Format format);
|
||||
|
||||
private:
|
||||
QComboBox* codec_combobox_;
|
||||
SampleRateComboBox* sample_rate_combobox_;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportformatcombobox.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) :
|
||||
QComboBox(parent)
|
||||
{
|
||||
// Populate combobox formats
|
||||
for (int i=0; i<ExportFormat::kFormatCount; i++) {
|
||||
ExportFormat::Format f = static_cast<ExportFormat::Format>(i);
|
||||
|
||||
switch (mode) {
|
||||
case kShowAllFormats:
|
||||
break;
|
||||
case kShowAudioOnly:
|
||||
if (!ExportFormat::GetVideoCodecs(f).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case kShowVideoOnly:
|
||||
if (!ExportFormat::GetAudioCodecs(f).isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
QString format_name = ExportFormat::GetName(f);
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
// Sort formats alphabetically
|
||||
for (int j=0; j<count(); j++) {
|
||||
if (itemText(j) > format_name) {
|
||||
insertItem(j, format_name, i);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
addItem(format_name, i);
|
||||
}
|
||||
}
|
||||
|
||||
connect(this, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ExportFormatComboBox::HandleIndexChange);
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::SetFormat(ExportFormat::Format fmt)
|
||||
{
|
||||
for (int i=0; i<count(); i++) {
|
||||
if (itemData(i).toInt() == fmt) {
|
||||
setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExportFormatComboBox::HandleIndexChange(int index)
|
||||
{
|
||||
emit FormatChanged(static_cast<ExportFormat::Format>(itemData(index).toInt()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTFORMATCOMBOBOX_H
|
||||
#define EXPORTFORMATCOMBOBOX_H
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include "codec/exportformat.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class ExportFormatComboBox : public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Mode {
|
||||
kShowAllFormats,
|
||||
kShowAudioOnly,
|
||||
kShowVideoOnly
|
||||
};
|
||||
|
||||
ExportFormatComboBox(Mode mode, QWidget *parent = nullptr);
|
||||
ExportFormatComboBox(QWidget *parent = nullptr) :
|
||||
ExportFormatComboBox(kShowAllFormats, parent)
|
||||
{}
|
||||
|
||||
ExportFormat::Format GetFormat() const
|
||||
{
|
||||
return static_cast<ExportFormat::Format>(currentData().toInt());
|
||||
}
|
||||
|
||||
signals:
|
||||
void FormatChanged(ExportFormat::Format fmt);
|
||||
|
||||
public slots:
|
||||
void SetFormat(ExportFormat::Format fmt);
|
||||
|
||||
private slots:
|
||||
void HandleIndexChange(int index);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXPORTFORMATCOMBOBOX_H
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "audio/audiomanager.h"
|
||||
#include "config/config.h"
|
||||
#include "dialog/export/exportaudiotab.h"
|
||||
#include "dialog/export/exportformatcombobox.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -87,12 +89,24 @@ PreferencesAudioTab::PreferencesAudioTab()
|
||||
|
||||
row++;
|
||||
|
||||
input_layout->addWidget(new QLabel(tr("Recording Mode:"), this), row, 0);
|
||||
QGroupBox *recording_group = new QGroupBox(tr("Recording"));
|
||||
input_layout->addWidget(recording_group, row, 0, 1, 2);
|
||||
|
||||
recording_combobox_ = new QComboBox();
|
||||
recording_combobox_->addItem(tr("Mono"));
|
||||
recording_combobox_->addItem(tr("Stereo"));
|
||||
input_layout->addWidget(recording_combobox_, row, 1);
|
||||
QVBoxLayout *recording_layout = new QVBoxLayout(recording_group);
|
||||
|
||||
QHBoxLayout *fmt_layout = new QHBoxLayout();
|
||||
recording_layout->addLayout(fmt_layout);
|
||||
|
||||
fmt_layout->addWidget(new QLabel(tr("Format:")));
|
||||
|
||||
ExportFormatComboBox *fmt_combo = new ExportFormatComboBox(ExportFormatComboBox::kShowAudioOnly);
|
||||
fmt_combo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
fmt_layout->addWidget(fmt_combo);
|
||||
|
||||
ExportAudioTab *audio_recording_options = new ExportAudioTab();
|
||||
recording_layout->addWidget(audio_recording_options);
|
||||
|
||||
connect(fmt_combo, &ExportFormatComboBox::FormatChanged, audio_recording_options, &ExportAudioTab::SetFormat);
|
||||
}
|
||||
|
||||
QHBoxLayout* refresh_layout = new QHBoxLayout();
|
||||
|
||||
@@ -405,7 +405,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
|
||||
}
|
||||
|
||||
// Trigger an import
|
||||
Core::instance()->ImportFiles(urls, this, static_cast<Folder*>(drop_item));
|
||||
Core::instance()->ImportFiles(urls, static_cast<Folder*>(drop_item));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
***/
|
||||
|
||||
#include "sequenceviewer.h"
|
||||
#include "panel/timeline/timeline.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -29,6 +30,12 @@ SequenceViewerPanel::SequenceViewerPanel(QWidget *parent) :
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
void SequenceViewerPanel::StartCapture(const TimeRange &time, const Track::Reference &track)
|
||||
{
|
||||
TimelinePanel *tp = static_cast<TimelinePanel *>(sender());
|
||||
static_cast<ViewerWidget*>(GetTimeBasedWidget())->StartCapture(tp->timeline_widget(), time, track);
|
||||
}
|
||||
|
||||
void SequenceViewerPanel::Retranslate()
|
||||
{
|
||||
ViewerPanel::Retranslate();
|
||||
|
||||
@@ -31,6 +31,9 @@ class SequenceViewerPanel : public ViewerPanel
|
||||
public:
|
||||
SequenceViewerPanel(QWidget* parent);
|
||||
|
||||
public slots:
|
||||
void StartCapture(const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
protected:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) :
|
||||
Retranslate();
|
||||
|
||||
connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged);
|
||||
connect(tw, &TimelineWidget::RequestCaptureStart, this, &TimelinePanel::RequestCaptureStart );
|
||||
}
|
||||
|
||||
void TimelinePanel::SplitAtPlayhead()
|
||||
|
||||
@@ -112,6 +112,8 @@ protected:
|
||||
signals:
|
||||
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
|
||||
|
||||
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -30,9 +30,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
ProjectImportTask::ProjectImportTask(ProjectViewModel *model, Folder *folder, const QStringList &filenames) :
|
||||
ProjectImportTask::ProjectImportTask(Folder *folder, const QStringList &filenames) :
|
||||
command_(nullptr),
|
||||
model_(model),
|
||||
folder_(folder)
|
||||
{
|
||||
foreach (const QString& f, filenames) {
|
||||
@@ -117,6 +116,9 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
|
||||
|
||||
// Create undoable command that adds the items to the model
|
||||
AddItemToFolder(folder, footage, parent_command);
|
||||
|
||||
// Add to vector
|
||||
imported_footage_.push_back(footage);
|
||||
} else {
|
||||
// Add to list so we can tell the user about it later
|
||||
invalid_files_.append(file_info.absoluteFilePath());
|
||||
@@ -220,7 +222,7 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i
|
||||
void ProjectImportTask::AddItemToFolder(Folder *folder, Node *item, MultiUndoCommand *command)
|
||||
{
|
||||
// Create undoable command that adds the items to the model
|
||||
Project* project = model_->project();
|
||||
Project* project = folder->project();
|
||||
|
||||
NodeAddCommand* nac = new NodeAddCommand(project, item);
|
||||
nac->PushToThread(project->thread());
|
||||
|
||||
@@ -34,7 +34,7 @@ class ProjectImportTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectImportTask(ProjectViewModel* model, Folder* folder, const QStringList& filenames);
|
||||
ProjectImportTask(Folder* folder, const QStringList& filenames);
|
||||
|
||||
const int& GetFileCount() const;
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
return !invalid_files_.isEmpty();
|
||||
}
|
||||
|
||||
const QVector<Footage*> &GetImportedFootage() const { return imported_footage_; }
|
||||
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
|
||||
@@ -71,8 +73,6 @@ private:
|
||||
|
||||
MultiUndoCommand* command_;
|
||||
|
||||
ProjectViewModel* model_;
|
||||
|
||||
Folder* folder_;
|
||||
|
||||
QFileInfoList filenames_;
|
||||
@@ -83,6 +83,8 @@ private:
|
||||
|
||||
QList<QString> image_sequence_ignore_files_;
|
||||
|
||||
QVector<Footage*> imported_footage_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -156,6 +156,10 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
|
||||
SetAudioVideoDragButtonsVisible(false);
|
||||
|
||||
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &PlaybackControls::TimecodeChanged);
|
||||
|
||||
play_blink_timer_ = new QTimer(this);
|
||||
play_blink_timer_->setInterval(500);
|
||||
connect(play_blink_timer_, &QTimer::timeout, this, &PlaybackControls::PlayBlink);
|
||||
}
|
||||
|
||||
void PlaybackControls::SetTimecodeEnabled(bool enabled)
|
||||
@@ -231,10 +235,20 @@ void PlaybackControls::UpdateIcons()
|
||||
audio_drag_btn_->setIcon(icon::Audio);
|
||||
}
|
||||
|
||||
void PlaybackControls::SetButtonRecordingState(QPushButton *btn, bool on)
|
||||
{
|
||||
btn->setStyleSheet(on ? QStringLiteral("background: red;") : QString());
|
||||
}
|
||||
|
||||
void PlaybackControls::TimecodeChanged()
|
||||
{
|
||||
// Update end time
|
||||
SetEndTime(end_time_);
|
||||
}
|
||||
|
||||
void PlaybackControls::PlayBlink()
|
||||
{
|
||||
SetButtonRecordingState(play_btn_, play_btn_->styleSheet().isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,23 @@ public slots:
|
||||
|
||||
void ShowPlayButton();
|
||||
|
||||
void StartPlayBlink()
|
||||
{
|
||||
play_blink_timer_->start();
|
||||
SetButtonRecordingState(play_btn_, true);
|
||||
}
|
||||
|
||||
void StopPlayBlink()
|
||||
{
|
||||
play_blink_timer_->stop();
|
||||
SetButtonRecordingState(play_btn_, false);
|
||||
}
|
||||
|
||||
void SetPauseButtonRecordingState(bool on)
|
||||
{
|
||||
SetButtonRecordingState(pause_btn_, on);
|
||||
}
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when "Go to Start" is clicked
|
||||
@@ -108,6 +125,8 @@ protected:
|
||||
private:
|
||||
void UpdateIcons();
|
||||
|
||||
static void SetButtonRecordingState(QPushButton *btn, bool on);
|
||||
|
||||
QWidget* lower_left_container_;
|
||||
QWidget* lower_right_container_;
|
||||
|
||||
@@ -129,9 +148,13 @@ private:
|
||||
|
||||
QStackedWidget* playpause_stack_;
|
||||
|
||||
QTimer *play_blink_timer_;
|
||||
|
||||
private slots:
|
||||
void TimecodeChanged();
|
||||
|
||||
void PlayBlink();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "dialog/speedduration/speeddurationdialog.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
#include "node/project/serializer/serializer.h"
|
||||
#include "task/project/import/import.h"
|
||||
#include "tool/add.h"
|
||||
#include "tool/beam.h"
|
||||
#include "tool/edit.h"
|
||||
@@ -788,6 +789,35 @@ void TimelineWidget::ShowSpeedDurationDialogForSelectedClips()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track)
|
||||
{
|
||||
ProjectImportTask task(GetConnectedNode()->project()->root(), {filename});
|
||||
task.Start();
|
||||
|
||||
MultiUndoCommand *import_command = task.GetCommand();
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
|
||||
|
||||
if (task.GetImportedFootage().empty()) {
|
||||
qCritical() << "Failed to import recorded audio file" << filename;
|
||||
} else {
|
||||
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index());
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
||||
{
|
||||
foreach (TimelineAndTrackView* tview, views_) {
|
||||
tview->view()->EnableRecordingOverlay(coord);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::DisableRecordingOverlay()
|
||||
{
|
||||
foreach (TimelineAndTrackView* tview, views_) {
|
||||
tview->view()->DisableRecordingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command)
|
||||
{
|
||||
for (int i=0;i<Track::kCount;i++) {
|
||||
|
||||
@@ -99,6 +99,12 @@ public:
|
||||
|
||||
void ShowSpeedDurationDialogForSelectedClips();
|
||||
|
||||
void RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
void EnableRecordingOverlay(const TimelineCoordinate &coord);
|
||||
|
||||
void DisableRecordingOverlay();
|
||||
|
||||
/**
|
||||
* @brief Timelines should always be connected to sequences
|
||||
*/
|
||||
@@ -261,6 +267,8 @@ public:
|
||||
signals:
|
||||
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
|
||||
|
||||
void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert)
|
||||
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, int track_offset)
|
||||
{
|
||||
DraggedFootageData refs;
|
||||
|
||||
@@ -181,10 +181,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
|
||||
refs.append({f, f->GetEnabledStreamsAsReferences()});
|
||||
}
|
||||
|
||||
PlaceAt(refs, start, insert);
|
||||
PlaceAt(refs, start, insert, track_offset);
|
||||
}
|
||||
|
||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert)
|
||||
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset)
|
||||
{
|
||||
dragged_footage_ = footage;
|
||||
|
||||
@@ -192,7 +192,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
|
||||
return;
|
||||
}
|
||||
|
||||
PrepGhosts(start, 0);
|
||||
PrepGhosts(start, track_offset);
|
||||
DropGhosts(insert);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ public:
|
||||
|
||||
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
|
||||
|
||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert);
|
||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert);
|
||||
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, int track_offset = 0);
|
||||
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0);
|
||||
|
||||
enum DropWithoutSequenceBehavior {
|
||||
kDWSAsk,
|
||||
|
||||
@@ -1,11 +1,88 @@
|
||||
#include "record.h"
|
||||
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
RecordTool::RecordTool(TimelineWidget *parent) :
|
||||
BeamTool(parent)
|
||||
BeamTool(parent),
|
||||
ghost_(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RecordTool::MousePress(TimelineViewMouseEvent *event)
|
||||
{
|
||||
const Track::Reference& track = event->GetTrack();
|
||||
|
||||
// Check if track is locked
|
||||
Track* t = parent()->GetTrackFromReference(track);
|
||||
if (t && t->IsLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (t->type() != Track::kAudio) {
|
||||
// We only support audio tracks here
|
||||
return;
|
||||
}
|
||||
|
||||
drag_start_point_ = ValidatedCoordinate(event->GetCoordinates(true)).GetFrame();
|
||||
|
||||
ghost_ = new TimelineViewGhostItem();
|
||||
ghost_->SetIn(drag_start_point_);
|
||||
ghost_->SetOut(drag_start_point_);
|
||||
ghost_->SetTrack(track);
|
||||
parent()->AddGhost(ghost_);
|
||||
|
||||
snap_points_.push_back(drag_start_point_);
|
||||
}
|
||||
|
||||
void RecordTool::MouseMove(TimelineViewMouseEvent *event)
|
||||
{
|
||||
if (!ghost_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate movement
|
||||
rational movement = event->GetFrame() - drag_start_point_;
|
||||
|
||||
// Validation: Ensure in point never goes below 0
|
||||
if (movement < -ghost_->GetIn()) {
|
||||
movement = -ghost_->GetIn();
|
||||
}
|
||||
|
||||
// Snap movement
|
||||
bool snapped;
|
||||
|
||||
if (Core::instance()->snapping()) {
|
||||
snapped = parent()->SnapPoint(snap_points_, &movement);
|
||||
} else {
|
||||
snapped = false;
|
||||
}
|
||||
|
||||
// Make adjustment
|
||||
if (!movement) {
|
||||
ghost_->SetInAdjustment(0);
|
||||
ghost_->SetOutAdjustment(0);
|
||||
} else if (movement > 0) {
|
||||
ghost_->SetInAdjustment(0);
|
||||
ghost_->SetOutAdjustment(movement);
|
||||
} else if (movement < 0) {
|
||||
ghost_->SetInAdjustment(movement);
|
||||
ghost_->SetOutAdjustment(0);
|
||||
}
|
||||
|
||||
Q_UNUSED(snapped)
|
||||
}
|
||||
|
||||
void RecordTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
{
|
||||
if (ghost_) {
|
||||
emit parent()->RequestCaptureStart(TimeRange(ghost_->GetAdjustedIn(), ghost_->GetAdjustedOut()), ghost_->GetTrack());
|
||||
parent()->ClearGhosts();
|
||||
snap_points_.clear();
|
||||
ghost_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,18 @@ class RecordTool : public BeamTool
|
||||
{
|
||||
public:
|
||||
RecordTool(TimelineWidget* parent);
|
||||
|
||||
virtual void MousePress(TimelineViewMouseEvent *event) override;
|
||||
virtual void MouseMove(TimelineViewMouseEvent *event) override;
|
||||
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
|
||||
|
||||
protected:
|
||||
void MouseMoveInternal(const rational& cursor_frame, bool outwards);
|
||||
|
||||
TimelineViewGhostItem* ghost_;
|
||||
|
||||
rational drag_start_point_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -348,6 +348,16 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
|
||||
track_y + GetTrackHeight(track_index));
|
||||
}
|
||||
|
||||
// Draw recording overlay
|
||||
if (recording_overlay_ && recording_coord_.GetTrack().type() == connected_track_list_->type()) {
|
||||
painter->setPen(QPen(Qt::red, 2));
|
||||
painter->setBrush(QColor(255, 128, 128));
|
||||
|
||||
int x = TimeToScene(recording_coord_.GetFrame());
|
||||
painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()),
|
||||
TimeToScene(GetTime()) - x, GetTrackHeight(recording_coord_.GetTrack().index()));
|
||||
}
|
||||
|
||||
// Draw standard TimelineViewBase things (such as playhead)
|
||||
super::drawForeground(painter, rect);
|
||||
}
|
||||
@@ -364,6 +374,7 @@ void TimelineView::ToolChangedEvent(Tool::Item tool)
|
||||
case Tool::kAdd:
|
||||
case Tool::kTransition:
|
||||
case Tool::kZoom:
|
||||
case Tool::kRecord :
|
||||
setCursor(Qt::CrossCursor);
|
||||
break;
|
||||
case Tool::kTrackSelect:
|
||||
@@ -745,6 +756,19 @@ void TimelineView::SetTransitionOverlay(ClipBlock *out, ClipBlock *in)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineView::EnableRecordingOverlay(const TimelineCoordinate &coord)
|
||||
{
|
||||
recording_overlay_ = true;
|
||||
recording_coord_ = coord;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::DisableRecordingOverlay()
|
||||
{
|
||||
recording_overlay_ = false;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
int TimelineView::SceneToTrack(double y)
|
||||
{
|
||||
int track = -1;
|
||||
|
||||
@@ -56,6 +56,8 @@ public:
|
||||
|
||||
void SetBeamCursor(const TimelineCoordinate& coord);
|
||||
void SetTransitionOverlay(ClipBlock *out, ClipBlock *in);
|
||||
void EnableRecordingOverlay(const TimelineCoordinate &coord);
|
||||
void DisableRecordingOverlay();
|
||||
|
||||
void SetSelectionList(QHash<Track::Reference, TimeRangeList>* s)
|
||||
{
|
||||
@@ -157,6 +159,9 @@ private:
|
||||
|
||||
QMap<TimelineMarker*, QRectF> clip_marker_rects_;
|
||||
|
||||
bool recording_overlay_;
|
||||
TimelineCoordinate recording_coord_;
|
||||
|
||||
private slots:
|
||||
void TrackListChanged();
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
color_menu_enabled_(true),
|
||||
time_changed_from_timer_(false),
|
||||
prequeuing_video_(false),
|
||||
prequeuing_audio_(0)
|
||||
prequeuing_audio_(0),
|
||||
record_armed_(false),
|
||||
recording_(false)
|
||||
{
|
||||
// Set up main layout
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
@@ -158,6 +160,10 @@ void ViewerWidget::TimeChangedEvent(const rational &time)
|
||||
PauseInternal();
|
||||
}
|
||||
|
||||
if (record_armed_) {
|
||||
DisarmRecording();
|
||||
}
|
||||
|
||||
controls_->SetTime(time);
|
||||
waveform_view_->SetTime(time);
|
||||
|
||||
@@ -377,6 +383,17 @@ void ViewerWidget::SetGizmos(Node *node)
|
||||
display_widget_->SetGizmos(node);
|
||||
}
|
||||
|
||||
void ViewerWidget::StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track)
|
||||
{
|
||||
SetTimeAndSignal(time.in());
|
||||
ArmForRecording();
|
||||
|
||||
recording_filename_ = QStringLiteral("/home/matt/Desktop/ass.mp3");
|
||||
recording_callback_ = source;
|
||||
recording_range_ = time;
|
||||
recording_track_ = track;
|
||||
}
|
||||
|
||||
FramePtr ViewerWidget::DecodeCachedImage(const QString &cache_path, const QByteArray& hash, const rational& time)
|
||||
{
|
||||
FramePtr frame = FrameHashCache::LoadCacheFrame(cache_path, hash);
|
||||
@@ -428,6 +445,18 @@ void ViewerWidget::DecrementPrequeuedAudio()
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::ArmForRecording()
|
||||
{
|
||||
controls_->StartPlayBlink();
|
||||
record_armed_ = true;
|
||||
}
|
||||
|
||||
void ViewerWidget::DisarmRecording()
|
||||
{
|
||||
controls_->StopPlayBlink();
|
||||
record_armed_ = false;
|
||||
}
|
||||
|
||||
void ViewerWidget::QueueNextAudioBuffer()
|
||||
{
|
||||
rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_);
|
||||
@@ -595,13 +624,20 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
viewer->auto_cacher_.SetAudioPaused(true);
|
||||
}
|
||||
|
||||
// Disarm recording if armed
|
||||
if (record_armed_) {
|
||||
DisarmRecording();
|
||||
}
|
||||
|
||||
// If the playhead is beyond the end, restart at 0
|
||||
rational last_frame = GetConnectedNode()->GetLength() - timebase();
|
||||
if (!in_to_out_only && GetTime() >= last_frame) {
|
||||
if (speed > 0) {
|
||||
SetTimeAndSignal(0);
|
||||
} else {
|
||||
SetTimeAndSignal(last_frame);
|
||||
if (!recording_) {
|
||||
rational last_frame = GetConnectedNode()->GetLength() - timebase();
|
||||
if (!in_to_out_only && GetTime() >= last_frame) {
|
||||
if (speed > 0) {
|
||||
SetTimeAndSignal(0);
|
||||
} else {
|
||||
SetTimeAndSignal(last_frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,6 +693,15 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
|
||||
void ViewerWidget::PauseInternal()
|
||||
{
|
||||
if (recording_) {
|
||||
AudioManager::instance()->StopRecording();
|
||||
recording_ = false;
|
||||
controls_->SetPauseButtonRecordingState(false);
|
||||
|
||||
recording_callback_->DisableRecordingOverlay();
|
||||
recording_callback_->RecordingCallback(recording_filename_, recording_range_, recording_track_);
|
||||
}
|
||||
|
||||
if (IsPlaying()) {
|
||||
playback_speed_ = 0;
|
||||
controls_->ShowPlayButton();
|
||||
@@ -1121,6 +1166,17 @@ void ViewerWidget::Play(bool in_to_out_only)
|
||||
} else {
|
||||
in_to_out_only = false;
|
||||
}
|
||||
} else if (record_armed_) {
|
||||
if (AudioManager::instance()->StartRecording(recording_filename_, GetConnectedNode()->GetAudioParams())) {
|
||||
recording_ = true;
|
||||
controls_->SetPauseButtonRecordingState(true);
|
||||
recording_callback_->EnableRecordingOverlay(TimelineCoordinate(recording_range_.in(), recording_track_));
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Audio Recording"), tr("Failed to start audio recording"));
|
||||
return;
|
||||
}
|
||||
|
||||
DisarmRecording();
|
||||
}
|
||||
|
||||
PlayInternal(1, in_to_out_only);
|
||||
@@ -1205,7 +1261,13 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
|
||||
rational min_time, max_time;
|
||||
|
||||
if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) {
|
||||
if (recording_ && recording_range_.out() != recording_range_.in()) {
|
||||
|
||||
// Limit recording range if applicable
|
||||
min_time = recording_range_.in();
|
||||
max_time = recording_range_.out();
|
||||
|
||||
} else if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) {
|
||||
|
||||
// If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea
|
||||
min_time = GetConnectedNode()->GetTimelinePoints()->workarea()->in();
|
||||
@@ -1229,8 +1291,9 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
bool end_of_line = false;
|
||||
bool play_after_pause = false;
|
||||
|
||||
if ((playback_speed_ < 0 && current_time <= min_time)
|
||||
|| (playback_speed_ > 0 && current_time >= max_time)) {
|
||||
if ((!recording_ || recording_range_.out() != recording_range_.in())
|
||||
&& ((playback_speed_ < 0 && current_time <= min_time)
|
||||
|| (playback_speed_ > 0 && current_time >= max_time))) {
|
||||
|
||||
// Determine which timestamp we tripped
|
||||
rational tripped_time;
|
||||
@@ -1245,7 +1308,7 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
// or restart playback
|
||||
end_of_line = true;
|
||||
|
||||
if (Config::Current()[QStringLiteral("Loop")].toBool()) {
|
||||
if (Config::Current()[QStringLiteral("Loop")].toBool() && !recording_) {
|
||||
|
||||
// If we're looping, jump to the other side of the workarea and continue
|
||||
time_to_set = (tripped_time == min_time) ? max_time : min_time;
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "viewerwindow.h"
|
||||
#include "widget/playbackcontrols/playbackcontrols.h"
|
||||
#include "widget/timebased/timebasedwidget.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -87,6 +88,8 @@ public:
|
||||
|
||||
void SetGizmos(Node* node);
|
||||
|
||||
void StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
public slots:
|
||||
void Play(bool in_to_out_only);
|
||||
|
||||
@@ -205,6 +208,10 @@ private:
|
||||
|
||||
void DecrementPrequeuedAudio();
|
||||
|
||||
void ArmForRecording();
|
||||
|
||||
void DisarmRecording();
|
||||
|
||||
QStackedWidget* stack_;
|
||||
|
||||
ViewerSizer* sizer_;
|
||||
@@ -255,6 +262,13 @@ private:
|
||||
|
||||
static QVector<ViewerWidget*> instances_;
|
||||
|
||||
bool record_armed_;
|
||||
bool recording_;
|
||||
TimelineWidget *recording_callback_;
|
||||
TimeRange recording_range_;
|
||||
Track::Reference recording_track_;
|
||||
QString recording_filename_;
|
||||
|
||||
private slots:
|
||||
void PlaybackTimerUpdate();
|
||||
|
||||
|
||||
@@ -555,6 +555,7 @@ TimelinePanel* MainWindow::AppendTimelinePanel()
|
||||
connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime);
|
||||
connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime);
|
||||
connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime);
|
||||
connect(panel, &TimelinePanel::RequestCaptureStart, sequence_viewer_panel_, &SequenceViewerPanel::StartCapture);
|
||||
connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged);
|
||||
connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
||||
connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime);
|
||||
|
||||
Reference in New Issue
Block a user