diff --git a/debug.cpp b/debug.cpp index 0c63079ef..1e64959d8 100644 --- a/debug.cpp +++ b/debug.cpp @@ -11,7 +11,7 @@ QDebug debug_out(&debug_file); void setup_debug() { #ifndef QT_DEBUG - debug_file.setFileName(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/debug_log"); + debug_file.setFileName(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/debug_log"); if (debug_file.open(QFile::WriteOnly)) { QString debug_intro = "Olive Session " + QString::number(QDateTime::currentMSecsSinceEpoch()); debug_file.write(debug_intro.toUtf8()); diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 44835dacd..7f4c433e0 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -57,6 +57,7 @@ enum ExportFormats { ExportDialog::ExportDialog(QWidget *parent) : QDialog(parent) { + setWindowTitle("Export \"" + sequence->name + "\""); setup_ui(); rangeCombobox->setCurrentIndex(0); @@ -478,7 +479,7 @@ void ExportDialog::export_action() { connect(et, SIGNAL(finished()), this, SLOT(render_thread_finished())); connect(et, SIGNAL(progress_changed(int, qint64)), this, SLOT(update_progress_bar(int, qint64))); - closeActiveClips(sequence, true); + closeActiveClips(sequence); mainWindow->autorecover_interval(); @@ -601,54 +602,41 @@ void ExportDialog::setup_ui() { videoGroupbox->setTitle("Video"); videoGroupbox->setFlat(false); videoGroupbox->setCheckable(true); - QGridLayout* gridLayout = new QGridLayout(videoGroupbox); - gridLayout->addWidget(new QLabel("Compression Type:"), 4, 0, 1, 1); - - compressionTypeCombobox = new QComboBox(videoGroupbox); - compressionTypeCombobox->addItem("Quality-based (Constant Rate Factor)"); - compressionTypeCombobox->addItem("File size-based (Two-Pass)"); - - gridLayout->addWidget(compressionTypeCombobox, 4, 1, 1, 1); - - gridLayout->addWidget(new QLabel("Codec:"), 0, 0, 1, 1); + QGridLayout* videoGridLayout = new QGridLayout(videoGroupbox); + videoGridLayout->addWidget(new QLabel("Codec:"), 0, 0, 1, 1); vcodecCombobox = new QComboBox(videoGroupbox); + videoGridLayout->addWidget(vcodecCombobox, 0, 1, 1, 1); - gridLayout->addWidget(vcodecCombobox, 0, 1, 1, 1); - - gridLayout->addWidget(new QLabel("Height:"), 2, 0, 1, 1); - - gridLayout->addWidget(new QLabel("Width:"), 1, 0, 1, 1); - - heightSpinbox = new QSpinBox(videoGroupbox); - heightSpinbox->setMaximum(16777216); - - gridLayout->addWidget(heightSpinbox, 2, 1, 1, 1); - + videoGridLayout->addWidget(new QLabel("Width:"), 1, 0, 1, 1); widthSpinbox = new QSpinBox(videoGroupbox); widthSpinbox->setMaximum(16777216); + videoGridLayout->addWidget(widthSpinbox, 1, 1, 1, 1); - gridLayout->addWidget(widthSpinbox, 1, 1, 1, 1); - - videobitrateSpinbox = new QDoubleSpinBox(videoGroupbox); - videobitrateSpinbox->setMaximum(100); - videobitrateSpinbox->setValue(2); - - gridLayout->addWidget(videobitrateSpinbox, 5, 1, 1, 1); - - videoBitrateLabel = new QLabel(videoGroupbox); - - gridLayout->addWidget(videoBitrateLabel, 5, 0, 1, 1); - - gridLayout->addWidget(new QLabel("Frame Rate:"), 3, 0, 1, 1); + videoGridLayout->addWidget(new QLabel("Height:"), 2, 0, 1, 1); + heightSpinbox = new QSpinBox(videoGroupbox); + heightSpinbox->setMaximum(16777216); + videoGridLayout->addWidget(heightSpinbox, 2, 1, 1, 1); + videoGridLayout->addWidget(new QLabel("Frame Rate:"), 3, 0, 1, 1); framerateSpinbox = new QDoubleSpinBox(videoGroupbox); framerateSpinbox->setMaximum(60); framerateSpinbox->setValue(0); + videoGridLayout->addWidget(framerateSpinbox, 3, 1, 1, 1); - gridLayout->addWidget(framerateSpinbox, 3, 1, 1, 1); + videoGridLayout->addWidget(new QLabel("Compression Type:"), 4, 0, 1, 1); + compressionTypeCombobox = new QComboBox(videoGroupbox); + compressionTypeCombobox->addItem("Quality-based (Constant Rate Factor)"); + compressionTypeCombobox->addItem("File size-based (Two-Pass)"); + videoGridLayout->addWidget(compressionTypeCombobox, 4, 1, 1, 1); + videoBitrateLabel = new QLabel(videoGroupbox); + videoGridLayout->addWidget(videoBitrateLabel, 5, 0, 1, 1); + videobitrateSpinbox = new QDoubleSpinBox(videoGroupbox); + videobitrateSpinbox->setMaximum(100); + videobitrateSpinbox->setValue(2); + videoGridLayout->addWidget(videobitrateSpinbox, 5, 1, 1, 1); verticalLayout->addWidget(videoGroupbox); @@ -656,69 +644,60 @@ void ExportDialog::setup_ui() { audioGroupbox->setTitle("Audio"); audioGroupbox->setCheckable(true); - QGridLayout* gridLayout_2 = new QGridLayout(audioGroupbox); - gridLayout_2->addWidget(new QLabel("Codec:"), 0, 0, 1, 1); + QGridLayout* audioGridLayout = new QGridLayout(audioGroupbox); + audioGridLayout->addWidget(new QLabel("Codec:"), 0, 0, 1, 1); acodecCombobox = new QComboBox(audioGroupbox); + audioGridLayout->addWidget(acodecCombobox, 0, 1, 1, 1); - gridLayout_2->addWidget(acodecCombobox, 0, 1, 1, 1); - + audioGridLayout->addWidget(new QLabel("Sampling Rate:"), 1, 0, 1, 1); samplingRateSpinbox = new QSpinBox(audioGroupbox); samplingRateSpinbox->setMaximum(96000); samplingRateSpinbox->setValue(0); + audioGridLayout->addWidget(samplingRateSpinbox, 1, 1, 1, 1); - gridLayout_2->addWidget(samplingRateSpinbox, 1, 1, 1, 1); - - gridLayout_2->addWidget(new QLabel("Sampling Rate:"), 1, 0, 1, 1); - - gridLayout_2->addWidget(new QLabel("Bitrate (Kbps/CBR):"), 3, 0, 1, 1); - + audioGridLayout->addWidget(new QLabel("Bitrate (Kbps/CBR):"), 3, 0, 1, 1); audiobitrateSpinbox = new QSpinBox(audioGroupbox); audiobitrateSpinbox->setMaximum(320); audiobitrateSpinbox->setValue(256); - - gridLayout_2->addWidget(audiobitrateSpinbox, 3, 1, 1, 1); - + audioGridLayout->addWidget(audiobitrateSpinbox, 3, 1, 1, 1); verticalLayout->addWidget(audioGroupbox); - QHBoxLayout* horizontalLayout_3 = new QHBoxLayout(); + QHBoxLayout* progressLayout = new QHBoxLayout(); progressBar = new QProgressBar(this); progressBar->setFormat("%p% (ETA: 0:00:00)"); progressBar->setEnabled(false); progressBar->setValue(0); - - horizontalLayout_3->addWidget(progressBar); + progressLayout->addWidget(progressBar); renderCancel = new QPushButton(this); renderCancel->setText("x"); renderCancel->setEnabled(false); renderCancel->setMaximumSize(QSize(20, 16777215)); connect(renderCancel, SIGNAL(clicked(bool)), this, SLOT(cancel_render())); + progressLayout->addWidget(renderCancel); - horizontalLayout_3->addWidget(renderCancel); + verticalLayout->addLayout(progressLayout); - - verticalLayout->addLayout(horizontalLayout_3); - - QHBoxLayout* horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->addStretch(); + QHBoxLayout* buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(); export_button = new QPushButton(this); export_button->setText("Export"); connect(export_button, SIGNAL(clicked(bool)), this, SLOT(export_action())); - horizontalLayout_2->addWidget(export_button); + buttonLayout->addWidget(export_button); cancel_button = new QPushButton(this); cancel_button->setText("Cancel"); connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject())); - horizontalLayout_2->addWidget(cancel_button); + buttonLayout->addWidget(cancel_button); - horizontalLayout_2->addStretch(); + buttonLayout->addStretch(); - verticalLayout->addLayout(horizontalLayout_2); + verticalLayout->addLayout(buttonLayout); connect(formatCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(format_changed(int))); connect(compressionTypeCombobox, SIGNAL(currentIndexChanged(int)), this, SLOT(comp_type_changed(int))); diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 6f4fae51e..c37db60c2 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include "project/footage.h" #include "project/media.h" @@ -13,59 +16,136 @@ #include "project/undo.h" MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : - QDialog(parent), - item(i) + QDialog(parent), + item(i) { + setWindowTitle("\"" + i->get_name() + "\" Properties"); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QGridLayout* grid = new QGridLayout(); setLayout(grid); - Footage* f = item->to_footage(); - if (f->video_tracks.size() > 0) { - interlacing_box = new QComboBox(); - interlacing_box->addItem("Auto (" + get_interlacing_name(f->video_tracks.at(0)->video_auto_interlacing) + ")"); - interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); - interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); - interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); + int row = 0; - interlacing_box->setCurrentIndex((f->video_tracks.at(0)->video_auto_interlacing == f->video_tracks.at(0)->video_interlacing) ? 0 : f->video_tracks.at(0)->video_interlacing + 1); + Footage* f = item->to_footage(); - grid->addWidget(new QLabel("Interlacing:"), 0, 0); - grid->addWidget(interlacing_box, 0, 1); - } + grid->addWidget(new QLabel("Tracks:"), row, 0, 1, 2); + row++; - name_box = new QLineEdit(item->get_name()); - grid->addWidget(new QLabel("Name:"), 1, 0); - grid->addWidget(name_box, 1, 1); + track_list = new QListWidget(); + for (int i=0;ivideo_tracks.size();i++) { + const FootageStream& fs = f->video_tracks.at(i); + QListWidgetItem* item = new QListWidgetItem("Video " + QString::number(fs.file_index) + ": " + QString::number(fs.video_width) + "x" + QString::number(fs.video_height) + " " + QString::number(fs.video_frame_rate) + "FPS"); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole+1, fs.file_index); + track_list->addItem(item); + } + for (int i=0;iaudio_tracks.size();i++) { + const FootageStream& fs = f->audio_tracks.at(i); + QListWidgetItem* item = new QListWidgetItem("Audio " + QString::number(fs.file_index) + ": " + QString::number(fs.audio_frequency) + "Hz " + QString::number(fs.audio_channels) + " channels"); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole+1, fs.file_index); + track_list->addItem(item); + } + grid->addWidget(track_list, row, 0, 1, 2); + row++; + + if (f->video_tracks.size() > 0) { + // frame conforming + grid->addWidget(new QLabel("Conform to Frame Rate:"), row, 0); + conform_fr = new QDoubleSpinBox(); + conform_fr->setMinimum(0.01); + conform_fr->setValue(f->video_tracks.at(0).video_frame_rate * f->speed); + grid->addWidget(conform_fr, row, 1); + + row++; + + // deinterlacing mode + interlacing_box = new QComboBox(); + interlacing_box->addItem("Auto (" + get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) + ")"); + interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); + interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); + interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); + + interlacing_box->setCurrentIndex((f->video_tracks.at(0).video_auto_interlacing == f->video_tracks.at(0).video_interlacing) ? 0 : f->video_tracks.at(0).video_interlacing + 1); + + grid->addWidget(new QLabel("Interlacing:"), row, 0); + grid->addWidget(interlacing_box, row, 1); + + row++; + } + + name_box = new QLineEdit(item->get_name()); + grid->addWidget(new QLabel("Name:"), row, 0); + grid->addWidget(name_box, row, 1); + row++; QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttons->setCenterButtons(true); - grid->addWidget(buttons, 2, 0, 1, 2); + grid->addWidget(buttons, row, 0, 1, 2); connect(buttons, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); } void MediaPropertiesDialog::accept() { + Footage* f = item->to_footage(); + ComboAction* ca = new ComboAction(); - //set interlacing - Footage* f = item->to_footage(); - if (interlacing_box->currentIndex() > 0) { - ca->append(new SetInt(&f->video_tracks.at(0)->video_interlacing, interlacing_box->currentIndex() - 1)); - } else { - ca->append(new SetInt(&f->video_tracks.at(0)->video_interlacing, f->video_tracks.at(0)->video_auto_interlacing)); + // set track enable + for (int i=0;icount();i++) { + QListWidgetItem* item = track_list->item(i); + const QVariant& data = item->data(Qt::UserRole+1); + if (!data.isNull()) { + int index = data.toInt(); + bool found = false; + for (int j=0;jvideo_tracks.size();j++) { + if (f->video_tracks.at(j).file_index == index) { + f->video_tracks[j].enabled = (item->checkState() == Qt::Checked); + found = true; + break; + } + } + if (!found) { + for (int j=0;jaudio_tracks.size();j++) { + if (f->audio_tracks.at(j).file_index == index) { + f->audio_tracks[j].enabled = (item->checkState() == Qt::Checked); + break; + } + } + } + } } - //set name - MediaRename* mr = new MediaRename(item, name_box->text()); + bool refresh_clips = false; + + // set interlacing + if (f->video_tracks.size() > 0) { + if (interlacing_box->currentIndex() > 0) { + ca->append(new SetInt(&f->video_tracks[0].video_interlacing, interlacing_box->currentIndex() - 1)); + } else { + ca->append(new SetInt(&f->video_tracks[0].video_interlacing, f->video_tracks.at(0).video_auto_interlacing)); + } + + // set frame rate conform + if (!qFuzzyCompare(conform_fr->value(), f->video_tracks.at(0).video_frame_rate)) { + ca->append(new SetDouble(&f->speed, f->speed, conform_fr->value()/f->video_tracks.at(0).video_frame_rate)); + refresh_clips = true; + } + } + + // set name + MediaRename* mr = new MediaRename(item, name_box->text()); ca->append(mr); ca->appendPost(new CloseAllClipsCommand()); - ca->appendPost(new UpdateFootageTooltip(item)); + ca->appendPost(new UpdateFootageTooltip(item)); + if (refresh_clips) ca->appendPost(new RefreshClips(item)); undo_stack.push(ca); - QDialog::accept(); + QDialog::accept(); } diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index 13f96c3d1..b5699dff7 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -7,17 +7,21 @@ struct Footage; class QComboBox; class QLineEdit; class Media; +class QListWidget; +class QDoubleSpinBox; class MediaPropertiesDialog : public QDialog { Q_OBJECT public: - MediaPropertiesDialog(QWidget *parent, Media* i); + MediaPropertiesDialog(QWidget *parent, Media* i); private: QComboBox* interlacing_box; - QLineEdit* name_box; - Media* item; + QLineEdit* name_box; + Media* item; + QListWidget* track_list; + QDoubleSpinBox* conform_fr; private slots: - void accept(); + void accept(); }; #endif // MEDIAPROPERTIESDIALOG_H diff --git a/dialogs/newsequencedialog.cpp b/dialogs/newsequencedialog.cpp index cfcd0678e..0451aa886 100644 --- a/dialogs/newsequencedialog.cpp +++ b/dialogs/newsequencedialog.cpp @@ -42,7 +42,7 @@ NewSequenceDialog::NewSequenceDialog(QWidget *parent, Media *existing) : break; } } - lineEdit->setText(existing_sequence->name); + sequence_name_edit->setText(existing_sequence->name); for (int i=0;icount();i++) { if (audio_frequency_combobox->itemData(i) == existing_sequence->audio_frequency) { audio_frequency_combobox->setCurrentIndex(i); @@ -59,14 +59,14 @@ NewSequenceDialog::~NewSequenceDialog() {} void NewSequenceDialog::set_sequence_name(const QString& s) { - lineEdit->setText(s); + sequence_name_edit->setText(s); } void NewSequenceDialog::create() { if (existing_sequence == NULL) { Sequence* s = new Sequence(); - s->name = lineEdit->text(); + s->name = sequence_name_edit->text(); s->width = width_numeric->value(); s->height = height_numeric->value(); s->frame_rate = frame_rate_combobox->currentData().toDouble(); @@ -82,7 +82,7 @@ void NewSequenceDialog::create() { double multiplier = frame_rate_combobox->currentData().toDouble() / existing_sequence->frame_rate; EditSequenceCommand* esc = new EditSequenceCommand(existing_item, existing_sequence); - esc->name = lineEdit->text(); + esc->name = sequence_name_edit->text(); esc->width = width_numeric->value(); esc->height = height_numeric->value(); esc->frame_rate = frame_rate_combobox->currentData().toDouble(); @@ -154,10 +154,10 @@ void NewSequenceDialog::setup_ui() { QWidget* widget = new QWidget(this); - QHBoxLayout* horizontalLayout_2 = new QHBoxLayout(widget); - horizontalLayout_2->setContentsMargins(0, 0, 0, 0); + QHBoxLayout* preset_layout = new QHBoxLayout(widget); + preset_layout->setContentsMargins(0, 0, 0, 0); - horizontalLayout_2->addWidget(new QLabel("Preset:")); + preset_layout->addWidget(new QLabel("Preset:")); preset_combobox = new QComboBox(widget); @@ -174,75 +174,65 @@ void NewSequenceDialog::setup_ui() { preset_combobox->addItem("Custom"); preset_combobox->setCurrentIndex(2); - horizontalLayout_2->addWidget(preset_combobox); + preset_layout->addWidget(preset_combobox); verticalLayout->addWidget(widget); - QGroupBox* groupBox = new QGroupBox(this); - groupBox->setTitle("Video"); + QGroupBox* videoGroupBox = new QGroupBox(this); + videoGroupBox->setTitle("Video"); - QGridLayout* gridLayout = new QGridLayout(groupBox); + QGridLayout* videoLayout = new QGridLayout(videoGroupBox); - height_numeric = new QSpinBox(groupBox); + videoLayout->addWidget(new QLabel("Width:"), 0, 0, 1, 1); + width_numeric = new QSpinBox(videoGroupBox); + width_numeric->setMaximum(9999); + width_numeric->setValue(1920); + videoLayout->addWidget(width_numeric, 0, 2, 1, 2); + + videoLayout->addWidget(new QLabel("Height:"), 1, 0, 1, 2); + height_numeric = new QSpinBox(videoGroupBox); height_numeric->setMaximum(9999); height_numeric->setValue(1080); + videoLayout->addWidget(height_numeric, 1, 2, 1, 2); - gridLayout->addWidget(height_numeric, 1, 2, 1, 2); + videoLayout->addWidget(new QLabel("Frame Rate:"), 2, 0, 1, 1); + frame_rate_combobox = new QComboBox(videoGroupBox); + frame_rate_combobox->addItem("10 FPS", 10.0); + frame_rate_combobox->addItem("12.5 FPS", 12.5); + frame_rate_combobox->addItem("15 FPS", 15.0); + frame_rate_combobox->addItem("23.976 FPS", 23.976); + frame_rate_combobox->addItem("24 FPS", 24.0); + frame_rate_combobox->addItem("25 FPS", 25.0); + frame_rate_combobox->addItem("29.97 FPS", 29.97); + frame_rate_combobox->addItem("30 FPS", 30.0); + frame_rate_combobox->addItem("50 FPS", 50.0); + frame_rate_combobox->addItem("59.94 FPS", 59.94); + frame_rate_combobox->addItem("60 FPS", 60.0); + frame_rate_combobox->setCurrentIndex(6); + videoLayout->addWidget(frame_rate_combobox, 2, 2, 1, 2); - width_numeric = new QSpinBox(groupBox); - width_numeric->setMaximum(9999); - width_numeric->setValue(1920); + videoLayout->addWidget(new QLabel("Pixel Aspect Ratio:"), 4, 0, 1, 1); + par_combobox = new QComboBox(videoGroupBox); + par_combobox->addItem("Square Pixels (1.0)"); + videoLayout->addWidget(par_combobox, 4, 2, 1, 2); - gridLayout->addWidget(width_numeric, 0, 2, 1, 2); - - gridLayout->addWidget(new QLabel("Width:"), 0, 0, 1, 1); - - gridLayout->addWidget(new QLabel("Pixel Aspect Ratio:"), 4, 0, 1, 1); - - gridLayout->addWidget(new QLabel("Interlacing:"), 6, 0, 1, 1); - - gridLayout->addWidget(new QLabel("Height:"), 1, 0, 1, 2); - - par_combobox = new QComboBox(groupBox); - par_combobox->addItem("Square Pixels (1.0)"); - - gridLayout->addWidget(par_combobox, 4, 2, 1, 2); - - interlacing_combobox = new QComboBox(groupBox); + videoLayout->addWidget(new QLabel("Interlacing:"), 6, 0, 1, 1); + interlacing_combobox = new QComboBox(videoGroupBox); interlacing_combobox->addItem("None (Progressive)"); - interlacing_combobox->addItem("Upper Field First"); - interlacing_combobox->addItem("Lower Field First"); +// interlacing_combobox->addItem("Upper Field First"); +// interlacing_combobox->addItem("Lower Field First"); + videoLayout->addWidget(interlacing_combobox, 6, 2, 1, 2); - gridLayout->addWidget(interlacing_combobox, 6, 2, 1, 2); + verticalLayout->addWidget(videoGroupBox); - frame_rate_combobox = new QComboBox(groupBox); - frame_rate_combobox->addItem("10 FPS", 10.0); - frame_rate_combobox->addItem("12.5 FPS", 12.5); - frame_rate_combobox->addItem("15 FPS", 15.0); - frame_rate_combobox->addItem("23.976 FPS", 23.976); - frame_rate_combobox->addItem("24 FPS", 24.0); - frame_rate_combobox->addItem("25 FPS", 25.0); - frame_rate_combobox->addItem("29.97 FPS", 29.97); - frame_rate_combobox->addItem("30 FPS", 30.0); - frame_rate_combobox->addItem("50 FPS", 50.0); - frame_rate_combobox->addItem("59.94 FPS", 59.94); - frame_rate_combobox->addItem("60 FPS", 60.0); - frame_rate_combobox->setCurrentIndex(6); + QGroupBox* audioGroupBox = new QGroupBox(this); + audioGroupBox->setTitle("Audio"); - gridLayout->addWidget(frame_rate_combobox, 2, 2, 1, 2); + QGridLayout* audioLayout = new QGridLayout(audioGroupBox); - gridLayout->addWidget(new QLabel("Frame Rate:"), 2, 0, 1, 1); + audioLayout->addWidget(new QLabel("Sample Rate: "), 0, 0, 1, 1); - verticalLayout->addWidget(groupBox); - - QGroupBox* groupBox_2 = new QGroupBox(this); - groupBox_2->setTitle("Audio"); - - QGridLayout* gridLayout_2 = new QGridLayout(groupBox_2); - - gridLayout_2->addWidget(new QLabel("Sample Rate: "), 0, 0, 1, 1); - - audio_frequency_combobox = new QComboBox(groupBox_2); + audio_frequency_combobox = new QComboBox(audioGroupBox); audio_frequency_combobox->addItem("22050 Hz", 22050); audio_frequency_combobox->addItem("24000 Hz", 24000); audio_frequency_combobox->addItem("32000 Hz", 32000); @@ -252,21 +242,21 @@ void NewSequenceDialog::setup_ui() { audio_frequency_combobox->addItem("96000 Hz", 96000); audio_frequency_combobox->setCurrentIndex(4); - gridLayout_2->addWidget(audio_frequency_combobox, 0, 1, 1, 1); + audioLayout->addWidget(audio_frequency_combobox, 0, 1, 1, 1); - verticalLayout->addWidget(groupBox_2); + verticalLayout->addWidget(audioGroupBox); - QWidget* widget_2 = new QWidget(this); - QHBoxLayout* horizontalLayout = new QHBoxLayout(widget_2); - horizontalLayout->setContentsMargins(0, 0, 0, 0); + QWidget* nameWidget = new QWidget(this); + QHBoxLayout* nameLayout = new QHBoxLayout(nameWidget); + nameLayout->setContentsMargins(0, 0, 0, 0); - horizontalLayout->addWidget(new QLabel("Name:")); + nameLayout->addWidget(new QLabel("Name:")); - lineEdit = new QLineEdit(widget_2); + sequence_name_edit = new QLineEdit(nameWidget); - horizontalLayout->addWidget(lineEdit); + nameLayout->addWidget(sequence_name_edit); - verticalLayout->addWidget(widget_2); + verticalLayout->addWidget(nameWidget); QDialogButtonBox* buttonBox = new QDialogButtonBox(this); buttonBox->setOrientation(Qt::Horizontal); diff --git a/dialogs/newsequencedialog.h b/dialogs/newsequencedialog.h index 1a5cf2042..56502d817 100644 --- a/dialogs/newsequencedialog.h +++ b/dialogs/newsequencedialog.h @@ -37,7 +37,7 @@ private: QComboBox* interlacing_combobox; QComboBox* frame_rate_combobox; QComboBox* audio_frequency_combobox; - QLineEdit* lineEdit; + QLineEdit* sequence_name_edit; }; #endif // NEWSEQUENCEDIALOG_H diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index ec0eb76f5..d558f1a12 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -14,6 +14,11 @@ #include #include #include +#include +#include +#include + +#include "debug.h" KeySequenceEditor::KeySequenceEditor(QWidget* parent, QAction* a) : QKeySequenceEdit(parent), action(a) { @@ -53,6 +58,7 @@ void PreferencesDialog::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* if (a->menu() != NULL) { setup_kbd_shortcut_worker(a->menu(), item); } else { + item->setData(0, Qt::UserRole + 1, reinterpret_cast(a)); key_shortcut_items.append(item); key_shortcut_actions.append(a); } @@ -94,6 +100,59 @@ void PreferencesDialog::save() { accept(); } +void PreferencesDialog::reset_default_shortcut() { + QList items = keyboard_tree->selectedItems(); + for (int i=0;iselectedItems().at(i); + const QVariant& data = item->data(0, Qt::UserRole + 1); + if (!data.isNull()) { + QAction* a = reinterpret_cast(data.value()); + QKeySequence ks(a->property("default").toString()); + static_cast(keyboard_tree->itemWidget(item, 1))->setKeySequence(ks); + } + } +} + +bool PreferencesDialog::refine_shortcut_list(const QString &s, QTreeWidgetItem* parent) { + if (parent == NULL) { + for (int i=0;itopLevelItemCount();i++) { + refine_shortcut_list(s, keyboard_tree->topLevelItem(i)); + } + } else { + parent->setExpanded(!s.isEmpty()); + + bool all_children_are_hidden = !s.isEmpty(); + + for (int i=0;ichildCount();i++) { + QTreeWidgetItem* item = parent->child(i); + if (item->childCount() > 0) { + all_children_are_hidden = refine_shortcut_list(s, item); + } else { + item->setHidden(false); + if (s.isEmpty()) { + all_children_are_hidden = false; + } else { + QString shortcut; + if (keyboard_tree->itemWidget(item, 1) != NULL) { + shortcut = static_cast(keyboard_tree->itemWidget(item, 1))->keySequence().toString(); + } + if (item->text(0).contains(s, Qt::CaseInsensitive) || shortcut.contains(s, Qt::CaseInsensitive)) { + all_children_are_hidden = false; + } else { + item->setHidden(true); + } + } + } + } + + if (parent->text(0).contains(s, Qt::CaseInsensitive)) all_children_are_hidden = false; + + parent->setHidden(all_children_are_hidden); + + return all_children_are_hidden; + } +} + void PreferencesDialog::setup_ui() { QVBoxLayout* verticalLayout = new QVBoxLayout(this); QTabWidget* tabWidget = new QTabWidget(this); @@ -136,17 +195,33 @@ void PreferencesDialog::setup_ui() { verticalLayout_2->addWidget(groupBox); tabWidget->addTab(tab_4, "Playback"); - QWidget* tab_3 = new QWidget(); - QHBoxLayout* horizontalLayout = new QHBoxLayout(tab_3); - horizontalLayout->setContentsMargins(0, 0, 0, 0); + + QWidget* shortcut_tab = new QWidget(); + + QVBoxLayout* shortcut_layout = new QVBoxLayout(shortcut_tab); + + QLineEdit* key_search_line = new QLineEdit(); + key_search_line->setPlaceholderText("Search for action or shortcut"); + connect(key_search_line, SIGNAL(textChanged(const QString &)), this, SLOT(refine_shortcut_list(const QString &))); + + shortcut_layout->addWidget(key_search_line); keyboard_tree = new QTreeWidget(); QTreeWidgetItem* tree_header = keyboard_tree->headerItem(); tree_header->setText(0, "Action"); tree_header->setText(1, "Shortcut"); - horizontalLayout->addWidget(keyboard_tree); + shortcut_layout->addWidget(keyboard_tree); - tabWidget->addTab(tab_3, "Keyboard"); + QHBoxLayout* reset_shortcut_layout = new QHBoxLayout(); + reset_shortcut_layout->addStretch(); + + reset_shortcut_button = new QPushButton("Reset to Default"); + reset_shortcut_layout->addWidget(reset_shortcut_button); + connect(reset_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(reset_default_shortcut())); + + shortcut_layout->addLayout(reset_shortcut_layout); + + tabWidget->addTab(shortcut_tab, "Keyboard"); verticalLayout->addWidget(tabWidget); diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index b0560a247..9e3031bc5 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -32,6 +32,8 @@ public: private slots: void save(); + void reset_default_shortcut(); + bool refine_shortcut_list(const QString &, QTreeWidgetItem* parent = NULL); private: void setup_ui(); @@ -46,6 +48,8 @@ private: QVector key_shortcut_actions; QVector key_shortcut_items; QVector key_shortcut_fields; + + QPushButton* reset_shortcut_button; }; #endif // PREFERENCESDIALOG_H diff --git a/dialogs/replaceclipmediadialog.cpp b/dialogs/replaceclipmediadialog.cpp index c1f042f22..5243cc71c 100644 --- a/dialogs/replaceclipmediadialog.cpp +++ b/dialogs/replaceclipmediadialog.cpp @@ -23,6 +23,8 @@ ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, Media *old_media QDialog(parent), media(old_media) { + setWindowTitle("Replace clips using \"" + old_media->get_name() + "\""); + resize(300, 400); QVBoxLayout* layout = new QVBoxLayout(); @@ -56,12 +58,10 @@ ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, Media *old_media setLayout(layout); tree->setModel(&project_model); - - //copy_tree(NULL, NULL); } void ReplaceClipMediaDialog::replace() { - QModelIndexList selected_items = panel_project->get_current_selected(); + QModelIndexList selected_items = tree->selectionModel()->selectedRows(); if (selected_items.size() != 1) { QMessageBox::critical(this, "No media selected", "Please select a media to replace with or click 'Cancel'.", QMessageBox::Ok); } else { diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index c48baacd3..ff4c52a14 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -335,7 +335,7 @@ void SpeedDialog::accept() { for (int i=0;iopen) close_clip(c); + if (c->open) close_clip(c, true); if (c->track >= 0 && maintain_pitch->checkState() != Qt::PartiallyChecked diff --git a/effects/internal/audionoiseeffect.cpp b/effects/internal/audionoiseeffect.cpp index bd0283a5b..effd65f6e 100644 --- a/effects/internal/audionoiseeffect.cpp +++ b/effects/internal/audionoiseeffect.cpp @@ -4,15 +4,15 @@ #include AudioNoiseEffect::AudioNoiseEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - amount_val = add_row("Amount:")->add_field(EFFECT_FIELD_DOUBLE, "amount"); + amount_val = add_row("Amount")->add_field(EFFECT_FIELD_DOUBLE, "amount"); amount_val->set_double_minimum_value(0); amount_val->set_double_maximum_value(100); - amount_val->set_double_default_value(20); + amount_val->set_double_default_value(20); - mix_val = add_row("Mix:")->add_field(EFFECT_FIELD_BOOL, "mix"); + mix_val = add_row("Mix")->add_field(EFFECT_FIELD_BOOL, "mix"); mix_val->set_bool_value(true); - srand(QDateTime::currentMSecsSinceEpoch()); + srand(QDateTime::currentMSecsSinceEpoch()); } void AudioNoiseEffect::process_audio(double timecode_start, double timecode_end, quint8 *samples, int nb_bytes, int) { diff --git a/effects/internal/cornerpineffect.cpp b/effects/internal/cornerpineffect.cpp index 96069b0bf..5be93b671 100644 --- a/effects/internal/cornerpineffect.cpp +++ b/effects/internal/cornerpineffect.cpp @@ -5,59 +5,59 @@ #include "debug.h" CornerPinEffect::CornerPinEffect(Clip *c, const EffectMeta *em) : Effect(c, em) { - enable_coords = true; + enable_coords = true; enable_shader = true; - EffectRow* top_left = add_row("Top Left:"); - top_left_x = top_left->add_field(EFFECT_FIELD_DOUBLE, "topleftx"); - top_left_y = top_left->add_field(EFFECT_FIELD_DOUBLE, "toplefty"); + EffectRow* top_left = add_row("Top Left"); + top_left_x = top_left->add_field(EFFECT_FIELD_DOUBLE, "topleftx"); + top_left_y = top_left->add_field(EFFECT_FIELD_DOUBLE, "toplefty"); - EffectRow* top_right = add_row("Top Right:"); - top_right_x = top_right->add_field(EFFECT_FIELD_DOUBLE, "toprightx"); - top_right_y = top_right->add_field(EFFECT_FIELD_DOUBLE, "toprighty"); + EffectRow* top_right = add_row("Top Right"); + top_right_x = top_right->add_field(EFFECT_FIELD_DOUBLE, "toprightx"); + top_right_y = top_right->add_field(EFFECT_FIELD_DOUBLE, "toprighty"); - EffectRow* bottom_left = add_row("Bottom Left:"); - bottom_left_x = bottom_left->add_field(EFFECT_FIELD_DOUBLE, "bottomleftx"); - bottom_left_y = bottom_left->add_field(EFFECT_FIELD_DOUBLE, "bottomlefty"); + EffectRow* bottom_left = add_row("Bottom Left"); + bottom_left_x = bottom_left->add_field(EFFECT_FIELD_DOUBLE, "bottomleftx"); + bottom_left_y = bottom_left->add_field(EFFECT_FIELD_DOUBLE, "bottomlefty"); - EffectRow* bottom_right = add_row("Bottom Right:"); - bottom_right_x = bottom_right->add_field(EFFECT_FIELD_DOUBLE, "bottomrightx"); - bottom_right_y = bottom_right->add_field(EFFECT_FIELD_DOUBLE, "bottomrighty"); + EffectRow* bottom_right = add_row("Bottom Right"); + bottom_right_x = bottom_right->add_field(EFFECT_FIELD_DOUBLE, "bottomrightx"); + bottom_right_y = bottom_right->add_field(EFFECT_FIELD_DOUBLE, "bottomrighty"); - perspective = add_row("Perspective:")->add_field(EFFECT_FIELD_BOOL, "perspective"); - perspective->set_bool_value(true); + perspective = add_row("Perspective")->add_field(EFFECT_FIELD_BOOL, "perspective"); + perspective->set_bool_value(true); - top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_left_gizmo->x_field1 = top_left_x; - top_left_gizmo->y_field1 = top_left_y; + top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_left_gizmo->x_field1 = top_left_x; + top_left_gizmo->y_field1 = top_left_y; - top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_right_gizmo->x_field1 = top_right_x; - top_right_gizmo->y_field1 = top_right_y; + top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_right_gizmo->x_field1 = top_right_x; + top_right_gizmo->y_field1 = top_right_y; - bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_left_gizmo->x_field1 = bottom_left_x; - bottom_left_gizmo->y_field1 = bottom_left_y; + bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_left_gizmo->x_field1 = bottom_left_x; + bottom_left_gizmo->y_field1 = bottom_left_y; - bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_right_gizmo->x_field1 = bottom_right_x; - bottom_right_gizmo->y_field1 = bottom_right_y; + bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_right_gizmo->x_field1 = bottom_right_x; + bottom_right_gizmo->y_field1 = bottom_right_y; vertPath = "cornerpin.vert"; fragPath = "cornerpin.frag"; } void CornerPinEffect::process_coords(double timecode, GLTextureCoords &coords, int data) { - coords.vertexTopLeftX += top_left_x->get_double_value(timecode); - coords.vertexTopLeftY += top_left_y->get_double_value(timecode); + coords.vertexTopLeftX += top_left_x->get_double_value(timecode); + coords.vertexTopLeftY += top_left_y->get_double_value(timecode); - coords.vertexTopRightX += top_right_x->get_double_value(timecode); - coords.vertexTopRightY += top_right_y->get_double_value(timecode); + coords.vertexTopRightX += top_right_x->get_double_value(timecode); + coords.vertexTopRightY += top_right_y->get_double_value(timecode); - coords.vertexBottomLeftX += bottom_left_x->get_double_value(timecode); - coords.vertexBottomLeftY += bottom_left_y->get_double_value(timecode); + coords.vertexBottomLeftX += bottom_left_x->get_double_value(timecode); + coords.vertexBottomLeftY += bottom_left_y->get_double_value(timecode); - coords.vertexBottomRightX += bottom_right_x->get_double_value(timecode); + coords.vertexBottomRightX += bottom_right_x->get_double_value(timecode); coords.vertexBottomRightY += bottom_right_y->get_double_value(timecode); } @@ -66,12 +66,12 @@ void CornerPinEffect::process_shader(double timecode, GLTextureCoords &coords) { glslProgram->setUniformValue("p1", (GLfloat) coords.vertexBottomRightX, (GLfloat) coords.vertexBottomRightY); glslProgram->setUniformValue("p2", (GLfloat) coords.vertexTopLeftX, (GLfloat) coords.vertexTopLeftY); glslProgram->setUniformValue("p3", (GLfloat) coords.vertexTopRightX, (GLfloat) coords.vertexTopRightY); - glslProgram->setUniformValue("perspective", perspective->get_bool_value(timecode)); + glslProgram->setUniformValue("perspective", perspective->get_bool_value(timecode)); } void CornerPinEffect::gizmo_draw(double timecode, GLTextureCoords &coords) { - top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); + top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); + bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); + bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); } diff --git a/effects/internal/paneffect.cpp b/effects/internal/paneffect.cpp index 0a7b25a58..fcb862192 100644 --- a/effects/internal/paneffect.cpp +++ b/effects/internal/paneffect.cpp @@ -9,13 +9,13 @@ #include "ui/collapsiblewidget.h" PanEffect::PanEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - EffectRow* pan_row = add_row("Pan:"); - pan_val = pan_row->add_field(EFFECT_FIELD_DOUBLE, "pan"); + EffectRow* pan_row = add_row("Pan"); + pan_val = pan_row->add_field(EFFECT_FIELD_DOUBLE, "pan"); pan_val->set_double_minimum_value(-100); - pan_val->set_double_maximum_value(100); + pan_val->set_double_maximum_value(100); // set defaults - pan_val->set_double_default_value(0); + pan_val->set_double_default_value(0); } void PanEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) { diff --git a/effects/internal/shakeeffect.cpp b/effects/internal/shakeeffect.cpp index 0687a71ad..92975fe8a 100644 --- a/effects/internal/shakeeffect.cpp +++ b/effects/internal/shakeeffect.cpp @@ -16,19 +16,19 @@ ShakeEffect::ShakeEffect(Clip *c, const EffectMeta *em) : Effect(c, em) { enable_coords = true; - EffectRow* intensity_row = add_row("Intensity:"); - intensity_val = intensity_row->add_field(EFFECT_FIELD_DOUBLE, "intensity"); + EffectRow* intensity_row = add_row("Intensity"); + intensity_val = intensity_row->add_field(EFFECT_FIELD_DOUBLE, "intensity"); intensity_val->set_double_minimum_value(0); - EffectRow* rotation_row = add_row("Rotation:"); - rotation_val = rotation_row->add_field(EFFECT_FIELD_DOUBLE, "rotation"); + EffectRow* rotation_row = add_row("Rotation"); + rotation_val = rotation_row->add_field(EFFECT_FIELD_DOUBLE, "rotation"); rotation_val->set_double_minimum_value(0); - EffectRow* frequency_row = add_row("Frequency:"); - frequency_val = frequency_row->add_field(EFFECT_FIELD_DOUBLE, "frequency"); + EffectRow* frequency_row = add_row("Frequency"); + frequency_val = frequency_row->add_field(EFFECT_FIELD_DOUBLE, "frequency"); frequency_val->set_double_minimum_value(0); - // set defaults + // set defaults intensity_val->set_double_default_value(25); rotation_val->set_double_default_value(10); frequency_val->set_double_default_value(5); diff --git a/effects/internal/solideffect.cpp b/effects/internal/solideffect.cpp index 0f888da61..512dccda6 100644 --- a/effects/internal/solideffect.cpp +++ b/effects/internal/solideffect.cpp @@ -21,36 +21,36 @@ SolidEffect::SolidEffect(Clip* c, const EffectMeta* em) : Effect(c, em) { enable_superimpose = true; - solid_type = add_row("Type:")->add_field(EFFECT_FIELD_COMBO, "type"); + solid_type = add_row("Type")->add_field(EFFECT_FIELD_COMBO, "type"); solid_type->add_combo_item("Solid Color", SOLID_TYPE_COLOR); solid_type->add_combo_item("SMPTE Bars", SOLID_TYPE_BARS); - solid_type->add_combo_item("Checkerboard", SOLID_TYPE_CHECKERBOARD); + solid_type->add_combo_item("Checkerboard", SOLID_TYPE_CHECKERBOARD); - opacity_field = add_row("Opacity:")->add_field(EFFECT_FIELD_DOUBLE, "opacity"); - opacity_field->set_double_minimum_value(0); - opacity_field->set_double_maximum_value(100); - opacity_field->set_double_default_value(100); + opacity_field = add_row("Opacity")->add_field(EFFECT_FIELD_DOUBLE, "opacity"); + opacity_field->set_double_minimum_value(0); + opacity_field->set_double_maximum_value(100); + opacity_field->set_double_default_value(100); - solid_color_field = add_row("Color:")->add_field(EFFECT_FIELD_COLOR, "color"); - solid_color_field->set_color_value(Qt::red); + solid_color_field = add_row("Color")->add_field(EFFECT_FIELD_COLOR, "color"); + solid_color_field->set_color_value(Qt::red); - checkerboard_size_field = add_row("Checkerboard Size:")->add_field(EFFECT_FIELD_DOUBLE, "checker_size"); - checkerboard_size_field->set_double_minimum_value(1); - checkerboard_size_field->set_double_default_value(10); + checkerboard_size_field = add_row("Checkerboard Size")->add_field(EFFECT_FIELD_DOUBLE, "checker_size"); + checkerboard_size_field->set_double_minimum_value(1); + checkerboard_size_field->set_double_default_value(10); - // hacky but eh - QComboBox* solid_type_combo = static_cast(solid_type->get_ui_element()); - connect(solid_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(ui_update(int))); - ui_update(solid_type_combo->currentIndex()); + // hacky but eh + QComboBox* solid_type_combo = static_cast(solid_type->get_ui_element()); + connect(solid_type_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(ui_update(int))); + ui_update(solid_type_combo->currentIndex()); - /*vertPath = ":/shaders/common.vert"; - fragPath = ":/shaders/solideffect.frag";*/ + /*vertPath = ":/shaders/common.vert"; + fragPath = ":/shaders/solideffect.frag";*/ } void SolidEffect::redraw(double timecode) { int w = img.width(); int h = img.height(); - int alpha = qRound(opacity_field->get_double_value(timecode)*2.55); + int alpha = qRound(opacity_field->get_double_value(timecode)*2.55); switch (solid_type->get_combo_data(timecode).toInt()) { case SOLID_TYPE_COLOR: { @@ -141,37 +141,37 @@ void SolidEffect::redraw(double timecode) { third_color.setAlpha(alpha); p.fillRect(QRect(bar_x, third_bar_y, third_bar_width, third_bar_height), third_color); } - } - break; - case SOLID_TYPE_CHECKERBOARD: - { - // draw checkboard - QPainter p(&img); - img.fill(Qt::transparent); + } + break; + case SOLID_TYPE_CHECKERBOARD: + { + // draw checkboard + QPainter p(&img); + img.fill(Qt::transparent); - int checker_width = qCeil(checkerboard_size_field->get_double_value(timecode)); - int checker_x, checker_y; - int checkerboard_size_w = qCeil(double(w)/checker_width); - int checkerboard_size_h = qCeil(double(h)/checker_width); + int checker_width = qCeil(checkerboard_size_field->get_double_value(timecode)); + int checker_x, checker_y; + int checkerboard_size_w = qCeil(double(w)/checker_width); + int checkerboard_size_h = qCeil(double(h)/checker_width); - QColor checker_odd(QColor(0, 0, 0, alpha)); - QColor checker_even(solid_color_field->get_color_value(timecode)); - checker_even.setAlpha(alpha); - QVector checker_color{checker_odd, checker_even}; + QColor checker_odd(QColor(0, 0, 0, alpha)); + QColor checker_even(solid_color_field->get_color_value(timecode)); + checker_even.setAlpha(alpha); + QVector checker_color{checker_odd, checker_even}; - for(int i = 0; i < checkerboard_size_w; i++){ - checker_x = checker_width*i; - for(int j = 0; j < checkerboard_size_h; j++){ - checker_y = checker_width*j; - p.fillRect(QRect(checker_x, checker_y, checker_width, checker_width), checker_color[(i + j)%2]); - } - } - } - break; - } + for(int i = 0; i < checkerboard_size_w; i++){ + checker_x = checker_width*i; + for(int j = 0; j < checkerboard_size_h; j++){ + checker_y = checker_width*j; + p.fillRect(QRect(checker_x, checker_y, checker_width, checker_width), checker_color[(i + j)%2]); + } + } + } + break; + } } void SolidEffect::ui_update(int i) { - solid_color_field->set_enabled(i == SOLID_TYPE_COLOR || i == SOLID_TYPE_CHECKERBOARD); - checkerboard_size_field->set_enabled(i == SOLID_TYPE_CHECKERBOARD); + solid_color_field->set_enabled(i == SOLID_TYPE_COLOR || i == SOLID_TYPE_CHECKERBOARD); + checkerboard_size_field->set_enabled(i == SOLID_TYPE_CHECKERBOARD); } diff --git a/effects/internal/texteffect.cpp b/effects/internal/texteffect.cpp index 66a6a6108..cb3ea3155 100644 --- a/effects/internal/texteffect.cpp +++ b/effects/internal/texteffect.cpp @@ -24,43 +24,43 @@ TextEffect::TextEffect(Clip *c, const EffectMeta* em) : Effect(c, em) { enable_superimpose = true; - enable_shader = true; + enable_shader = true; - text_val = add_row("Text:")->add_field(EFFECT_FIELD_STRING, "text", 2); + text_val = add_row("Text")->add_field(EFFECT_FIELD_STRING, "text", 2); - set_font_combobox = add_row("Font:")->add_field(EFFECT_FIELD_FONT, "font", 2); + set_font_combobox = add_row("Font")->add_field(EFFECT_FIELD_FONT, "font", 2); - size_val = add_row("Size:")->add_field(EFFECT_FIELD_DOUBLE, "size", 2); - size_val->set_double_minimum_value(0); + size_val = add_row("Size")->add_field(EFFECT_FIELD_DOUBLE, "size", 2); + size_val->set_double_minimum_value(0); - set_color_button = add_row("Color:")->add_field(EFFECT_FIELD_COLOR, "color", 2); + set_color_button = add_row("Color")->add_field(EFFECT_FIELD_COLOR, "color", 2); - EffectRow* alignment_row = add_row("Alignment:"); - halign_field = alignment_row->add_field(EFFECT_FIELD_COMBO, "halign"); + EffectRow* alignment_row = add_row("Alignment"); + halign_field = alignment_row->add_field(EFFECT_FIELD_COMBO, "halign"); halign_field->add_combo_item("Left", Qt::AlignLeft); halign_field->add_combo_item("Center", Qt::AlignHCenter); halign_field->add_combo_item("Right", Qt::AlignRight); - halign_field->add_combo_item("Justify", Qt::AlignJustify); + halign_field->add_combo_item("Justify", Qt::AlignJustify); - valign_field = alignment_row->add_field(EFFECT_FIELD_COMBO, "valign"); + valign_field = alignment_row->add_field(EFFECT_FIELD_COMBO, "valign"); valign_field->add_combo_item("Top", Qt::AlignTop); valign_field->add_combo_item("Center", Qt::AlignVCenter); - valign_field->add_combo_item("Bottom", Qt::AlignBottom); + valign_field->add_combo_item("Bottom", Qt::AlignBottom); - word_wrap_field = add_row("Word Wrap:")->add_field(EFFECT_FIELD_BOOL, "wordwrap", 2); + word_wrap_field = add_row("Word Wrap")->add_field(EFFECT_FIELD_BOOL, "wordwrap", 2); - outline_bool = add_row("Outline:")->add_field(EFFECT_FIELD_BOOL, "outline", 2); - outline_color = add_row("Outline Color:")->add_field(EFFECT_FIELD_COLOR, "outlinecolor", 2); - outline_width = add_row("Outline Width:")->add_field(EFFECT_FIELD_DOUBLE, "outlinewidth", 2); + outline_bool = add_row("Outline")->add_field(EFFECT_FIELD_BOOL, "outline", 2); + outline_color = add_row("Outline Color")->add_field(EFFECT_FIELD_COLOR, "outlinecolor", 2); + outline_width = add_row("Outline Width")->add_field(EFFECT_FIELD_DOUBLE, "outlinewidth", 2); outline_width->set_double_minimum_value(0); - shadow_bool = add_row("Shadow:")->add_field(EFFECT_FIELD_BOOL, "shadow", 2); - shadow_color = add_row("Shadow Color:")->add_field(EFFECT_FIELD_COLOR, "shadowcolor", 2); - shadow_distance = add_row("Shadow Distance:")->add_field(EFFECT_FIELD_DOUBLE, "shadowdistance", 2); + shadow_bool = add_row("Shadow")->add_field(EFFECT_FIELD_BOOL, "shadow", 2); + shadow_color = add_row("Shadow Color")->add_field(EFFECT_FIELD_COLOR, "shadowcolor", 2); + shadow_distance = add_row("Shadow Distance")->add_field(EFFECT_FIELD_DOUBLE, "shadowdistance", 2); shadow_distance->set_double_minimum_value(0); - shadow_softness = add_row("Shadow Softness:")->add_field(EFFECT_FIELD_DOUBLE, "shadowsoftness", 2); + shadow_softness = add_row("Shadow Softness")->add_field(EFFECT_FIELD_DOUBLE, "shadowsoftness", 2); shadow_softness->set_double_minimum_value(0); - shadow_opacity = add_row("Shadow Opacity:")->add_field(EFFECT_FIELD_DOUBLE, "shadowopacity", 2); + shadow_opacity = add_row("Shadow Opacity")->add_field(EFFECT_FIELD_DOUBLE, "shadowopacity", 2); shadow_opacity->set_double_minimum_value(0); shadow_opacity->set_double_maximum_value(100); @@ -78,13 +78,13 @@ TextEffect::TextEffect(Clip *c, const EffectMeta* em) : outline_width->set_double_default_value(20); outline_enable(false); - shadow_enable(false); + shadow_enable(false); connect(shadow_bool, SIGNAL(toggled(bool)), this, SLOT(shadow_enable(bool))); connect(outline_bool, SIGNAL(toggled(bool)), this, SLOT(outline_enable(bool))); - vertPath = "common.vert"; - fragPath = "dropshadow.frag"; + vertPath = "common.vert"; + fragPath = "dropshadow.frag"; } void TextEffect::redraw(double timecode) { @@ -170,7 +170,7 @@ void TextEffect::redraw(double timecode) { } path.addText(text_x, text_y, font, lines.at(i)); - } + } // draw outline int outline_width_val = outline_width->get_double_value(timecode); @@ -185,7 +185,7 @@ void TextEffect::redraw(double timecode) { // draw "master" text p.setPen(Qt::NoPen); p.setBrush(set_color_button->get_color_value(timecode)); - p.drawPath(path); + p.drawPath(path); } void TextEffect::shadow_enable(bool e) { diff --git a/effects/internal/timecodeeffect.cpp b/effects/internal/timecodeeffect.cpp index f09b7fecc..baba8c50d 100644 --- a/effects/internal/timecodeeffect.cpp +++ b/effects/internal/timecodeeffect.cpp @@ -24,82 +24,82 @@ #include "playback/playback.h" TimecodeEffect::TimecodeEffect(Clip *c, const EffectMeta* em) : - Effect(c, em) + Effect(c, em) { - enable_always_update = true; + enable_always_update = true; enable_superimpose = true; - EffectRow* tc_row = add_row("Timecode:"); - tc_select = tc_row->add_field(EFFECT_FIELD_COMBO, "tc_selector"); - tc_select->add_combo_item("Sequence", true); - tc_select->add_combo_item("Media", false); - tc_select->set_combo_index(0); + EffectRow* tc_row = add_row("Timecode"); + tc_select = tc_row->add_field(EFFECT_FIELD_COMBO, "tc_selector"); + tc_select->add_combo_item("Sequence", true); + tc_select->add_combo_item("Media", false); + tc_select->set_combo_index(0); - scale_val = add_row("Scale:")->add_field(EFFECT_FIELD_DOUBLE, "scale", 2); - scale_val->set_double_minimum_value(1); - scale_val->set_double_default_value(100); - scale_val->set_double_maximum_value(1000); + scale_val = add_row("Scale")->add_field(EFFECT_FIELD_DOUBLE, "scale", 2); + scale_val->set_double_minimum_value(1); + scale_val->set_double_default_value(100); + scale_val->set_double_maximum_value(1000); - color_val = add_row("Color:")->add_field(EFFECT_FIELD_COLOR, "color", 2); - color_val->set_color_value(Qt::white); + color_val = add_row("Color")->add_field(EFFECT_FIELD_COLOR, "color", 2); + color_val->set_color_value(Qt::white); - color_bg_val = add_row("Background Color:")->add_field(EFFECT_FIELD_COLOR, "bgcolor", 2); - color_bg_val->set_color_value(Qt::black); + color_bg_val = add_row("Background Color")->add_field(EFFECT_FIELD_COLOR, "bgcolor", 2); + color_bg_val->set_color_value(Qt::black); - bg_alpha = add_row("Background Opacity:")->add_field(EFFECT_FIELD_DOUBLE, "bgalpha", 2); - bg_alpha->set_double_minimum_value(0); - bg_alpha->set_double_maximum_value(100); - bg_alpha->set_double_default_value(50); + bg_alpha = add_row("Background Opacity")->add_field(EFFECT_FIELD_DOUBLE, "bgalpha", 2); + bg_alpha->set_double_minimum_value(0); + bg_alpha->set_double_maximum_value(100); + bg_alpha->set_double_default_value(50); - EffectRow* offset = add_row("Offset:"); - offset_x_val = offset->add_field(EFFECT_FIELD_DOUBLE, "offsetx"); - offset_y_val = offset->add_field(EFFECT_FIELD_DOUBLE, "offsety"); + EffectRow* offset = add_row("Offset"); + offset_x_val = offset->add_field(EFFECT_FIELD_DOUBLE, "offsetx"); + offset_y_val = offset->add_field(EFFECT_FIELD_DOUBLE, "offsety"); - prepend_text = add_row("Prepend:")->add_field(EFFECT_FIELD_STRING, "prepend", 2); + prepend_text = add_row("Prepend")->add_field(EFFECT_FIELD_STRING, "prepend", 2); } void TimecodeEffect::redraw(double timecode) { - if (tc_select->get_combo_data(timecode).toBool()){ - display_timecode = prepend_text->get_string_value(timecode) + frame_to_timecode(sequence->playhead, config.timecode_view, sequence->frame_rate);} - else { - double media_rate = parent_clip->getMediaFrameRate(); - display_timecode = prepend_text->get_string_value(timecode) + frame_to_timecode(timecode * media_rate, config.timecode_view, media_rate);} - img.fill(Qt::transparent); + if (tc_select->get_combo_data(timecode).toBool()){ + display_timecode = prepend_text->get_string_value(timecode) + frame_to_timecode(sequence->playhead, config.timecode_view, sequence->frame_rate);} + else { + double media_rate = parent_clip->getMediaFrameRate(); + display_timecode = prepend_text->get_string_value(timecode) + frame_to_timecode(timecode * media_rate, config.timecode_view, media_rate);} + img.fill(Qt::transparent); QPainter p(&img); - p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::Antialiasing); int width = img.width(); int height = img.height(); // set font font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias); - font.setFamily("Helvetica"); - font.setPixelSize(qCeil(scale_val->get_double_value(timecode)*.01*(height/10))); - p.setFont(font); + font.setFamily("Helvetica"); + font.setPixelSize(qCeil(scale_val->get_double_value(timecode)*.01*(height/10))); + p.setFont(font); QFontMetrics fm(font); - QPainterPath path; + QPainterPath path; - int text_x, text_y, rect_y, offset_x, offset_y; - int text_height = fm.height(); - int text_width = fm.width(display_timecode); - QColor background_color = color_bg_val->get_color_value(timecode); - int alpha_val = bg_alpha->get_double_value(timecode)*2.55; - background_color.setAlpha(alpha_val); + int text_x, text_y, rect_y, offset_x, offset_y; + int text_height = fm.height(); + int text_width = fm.width(display_timecode); + QColor background_color = color_bg_val->get_color_value(timecode); + int alpha_val = bg_alpha->get_double_value(timecode)*2.55; + background_color.setAlpha(alpha_val); - offset_x = int(offset_x_val->get_double_value(timecode)); - offset_y = int(offset_y_val->get_double_value(timecode)); + offset_x = int(offset_x_val->get_double_value(timecode)); + offset_y = int(offset_y_val->get_double_value(timecode)); - text_x = offset_x + (width/2) - (text_width/2); - text_y = offset_y + height - height/10; - rect_y = text_y + fm.descent()/2 - text_height; + text_x = offset_x + (width/2) - (text_width/2); + text_y = offset_y + height - height/10; + rect_y = text_y + fm.descent()/2 - text_height; - path.addText(text_x, text_y, font, display_timecode); + path.addText(text_x, text_y, font, display_timecode); - p.setPen(Qt::NoPen); - p.setBrush(background_color); - p.drawRect(QRect(text_x-fm.descent()/2, rect_y, text_width+fm.descent(), text_height)); - p.setBrush(color_val->get_color_value(timecode)); - p.drawPath(path); + p.setPen(Qt::NoPen); + p.setBrush(background_color); + p.drawRect(QRect(text_x-fm.descent()/2, rect_y, text_width+fm.descent(), text_height)); + p.setBrush(color_val->get_color_value(timecode)); + p.drawPath(path); } diff --git a/effects/internal/toneeffect.cpp b/effects/internal/toneeffect.cpp index bb0fb7ac8..b17478e40 100644 --- a/effects/internal/toneeffect.cpp +++ b/effects/internal/toneeffect.cpp @@ -9,21 +9,21 @@ #include "debug.h" ToneEffect::ToneEffect(Clip* c, const EffectMeta *em) : Effect(c, em), sinX(INT_MIN) { - type_val = add_row("Type:")->add_field(EFFECT_FIELD_COMBO, "type"); + type_val = add_row("Type")->add_field(EFFECT_FIELD_COMBO, "type"); type_val->add_combo_item("Sine", TONE_TYPE_SINE); - freq_val = add_row("Frequency:")->add_field(EFFECT_FIELD_DOUBLE, "frequency"); + freq_val = add_row("Frequency")->add_field(EFFECT_FIELD_DOUBLE, "frequency"); freq_val->set_double_minimum_value(20); freq_val->set_double_maximum_value(20000); freq_val->set_double_default_value(1000); - amount_val = add_row("Amount:")->add_field(EFFECT_FIELD_DOUBLE, "amount"); + amount_val = add_row("Amount")->add_field(EFFECT_FIELD_DOUBLE, "amount"); amount_val->set_double_minimum_value(0); amount_val->set_double_maximum_value(100); amount_val->set_double_default_value(25); - mix_val = add_row("Mix:")->add_field(EFFECT_FIELD_BOOL, "mix"); - mix_val->set_bool_value(true); + mix_val = add_row("Mix")->add_field(EFFECT_FIELD_BOOL, "mix"); + mix_val->set_bool_value(true); } void ToneEffect::process_audio(double timecode_start, double timecode_end, quint8 *samples, int nb_bytes, int) { diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index 42b54497c..ca65a4b9a 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -28,147 +28,162 @@ #define BLEND_MODE_MULTIPLY 2 #define BLEND_MODE_OVERLAY 3 -TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) { - enable_coords = true; +TransformEffect::TransformEffect(Clip* c, const EffectMeta* em) : Effect(c, em) { + enable_coords = true; - EffectRow* position_row = add_row("Position:"); - position_x = position_row->add_field(EFFECT_FIELD_DOUBLE, "posx"); // position X - position_y = position_row->add_field(EFFECT_FIELD_DOUBLE, "posy"); // position Y + EffectRow* position_row = add_row("Position"); + position_x = position_row->add_field(EFFECT_FIELD_DOUBLE, "posx"); // position X + position_y = position_row->add_field(EFFECT_FIELD_DOUBLE, "posy"); // position Y - EffectRow* scale_row = add_row("Scale:"); - scale_x = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scalex"); // scale X (and Y is uniform scale is selected) + EffectRow* scale_row = add_row("Scale"); + scale_x = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scalex"); // scale X (and Y is uniform scale is selected) scale_x->set_double_minimum_value(0); scale_x->set_double_maximum_value(3000); - scale_y = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scaley"); // scale Y (disabled if uniform scale is selected) + scale_y = scale_row->add_field(EFFECT_FIELD_DOUBLE, "scaley"); // scale Y (disabled if uniform scale is selected) scale_y->set_double_minimum_value(0); scale_y->set_double_maximum_value(3000); - EffectRow* uniform_scale_row = add_row("Uniform Scale:"); - uniform_scale_field = uniform_scale_row->add_field(EFFECT_FIELD_BOOL, "uniformscale"); // uniform scale option + EffectRow* uniform_scale_row = add_row("Uniform Scale"); + uniform_scale_field = uniform_scale_row->add_field(EFFECT_FIELD_BOOL, "uniformscale"); // uniform scale option - EffectRow* rotation_row = add_row("Rotation:"); - rotation = rotation_row->add_field(EFFECT_FIELD_DOUBLE, "rotation"); + EffectRow* rotation_row = add_row("Rotation"); + rotation = rotation_row->add_field(EFFECT_FIELD_DOUBLE, "rotation"); - EffectRow* anchor_point_row = add_row("Anchor Point:"); - anchor_x_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE, "anchorx"); // anchor point X - anchor_y_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE, "anchory"); // anchor point Y + EffectRow* anchor_point_row = add_row("Anchor Point"); + anchor_x_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE, "anchorx"); // anchor point X + anchor_y_box = anchor_point_row->add_field(EFFECT_FIELD_DOUBLE, "anchory"); // anchor point Y - EffectRow* opacity_row = add_row("Opacity:"); - opacity = opacity_row->add_field(EFFECT_FIELD_DOUBLE, "opacity"); // opacity + EffectRow* opacity_row = add_row("Opacity"); + opacity = opacity_row->add_field(EFFECT_FIELD_DOUBLE, "opacity"); // opacity opacity->set_double_minimum_value(0); opacity->set_double_maximum_value(100); - EffectRow* blend_mode_row = add_row("Blend Mode:"); - blend_mode_box = blend_mode_row->add_field(EFFECT_FIELD_COMBO, "blendmode"); // blend mode + EffectRow* blend_mode_row = add_row("Blend Mode"); + blend_mode_box = blend_mode_row->add_field(EFFECT_FIELD_COMBO, "blendmode"); // blend mode blend_mode_box->add_combo_item("Normal", BLEND_MODE_NORMAL); blend_mode_box->add_combo_item("Overlay", BLEND_MODE_OVERLAY); blend_mode_box->add_combo_item("Screen", BLEND_MODE_SCREEN); blend_mode_box->add_combo_item("Multiply", BLEND_MODE_MULTIPLY); - // set up gizmos - top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_left_gizmo->set_cursor(Qt::SizeFDiagCursor); - top_left_gizmo->x_field1 = scale_x; + // set up gizmos + top_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_left_gizmo->set_cursor(Qt::SizeFDiagCursor); + top_left_gizmo->x_field1 = scale_x; - top_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_center_gizmo->set_cursor(Qt::SizeVerCursor); - top_center_gizmo->y_field1 = scale_x; + top_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_center_gizmo->set_cursor(Qt::SizeVerCursor); + top_center_gizmo->y_field1 = scale_x; - top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - top_right_gizmo->set_cursor(Qt::SizeBDiagCursor); - top_right_gizmo->x_field1 = scale_x; + top_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); + top_right_gizmo->set_cursor(Qt::SizeBDiagCursor); + top_right_gizmo->x_field1 = scale_x; - bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_left_gizmo->set_cursor(Qt::SizeBDiagCursor); - bottom_left_gizmo->x_field1 = scale_x; + bottom_left_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_left_gizmo->set_cursor(Qt::SizeBDiagCursor); + bottom_left_gizmo->x_field1 = scale_x; - bottom_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_center_gizmo->set_cursor(Qt::SizeVerCursor); - bottom_center_gizmo->y_field1 = scale_x; + bottom_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_center_gizmo->set_cursor(Qt::SizeVerCursor); + bottom_center_gizmo->y_field1 = scale_x; - bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); - bottom_right_gizmo->set_cursor(Qt::SizeFDiagCursor); - bottom_right_gizmo->x_field1 = scale_x; + bottom_right_gizmo = add_gizmo(GIZMO_TYPE_DOT); + bottom_right_gizmo->set_cursor(Qt::SizeFDiagCursor); + bottom_right_gizmo->x_field1 = scale_x; - left_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - left_center_gizmo->set_cursor(Qt::SizeHorCursor); - left_center_gizmo->x_field1 = scale_x; + left_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + left_center_gizmo->set_cursor(Qt::SizeHorCursor); + left_center_gizmo->x_field1 = scale_x; - right_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); - right_center_gizmo->set_cursor(Qt::SizeHorCursor); - right_center_gizmo->x_field1 = scale_x; + right_center_gizmo = add_gizmo(GIZMO_TYPE_DOT); + right_center_gizmo->set_cursor(Qt::SizeHorCursor); + right_center_gizmo->x_field1 = scale_x; - anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); - anchor_gizmo->set_cursor(Qt::SizeAllCursor); - anchor_gizmo->x_field1 = anchor_x_box; - anchor_gizmo->y_field1 = anchor_y_box; - anchor_gizmo->x_field2 = position_x; - anchor_gizmo->y_field2 = position_y; + anchor_gizmo = add_gizmo(GIZMO_TYPE_TARGET); + anchor_gizmo->set_cursor(Qt::SizeAllCursor); + anchor_gizmo->x_field1 = anchor_x_box; + anchor_gizmo->y_field1 = anchor_y_box; + anchor_gizmo->x_field2 = position_x; + anchor_gizmo->y_field2 = position_y; - rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); - rotate_gizmo->color = Qt::green; - rotate_gizmo->set_cursor(Qt::SizeAllCursor); - rotate_gizmo->x_field1 = rotation; + rotate_gizmo = add_gizmo(GIZMO_TYPE_DOT); + rotate_gizmo->color = Qt::green; + rotate_gizmo->set_cursor(Qt::SizeAllCursor); + rotate_gizmo->x_field1 = rotation; - rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); - rect_gizmo->x_field1 = position_x; - rect_gizmo->y_field1 = position_y; + rect_gizmo = add_gizmo(GIZMO_TYPE_POLY); + rect_gizmo->x_field1 = position_x; + rect_gizmo->y_field1 = position_y; - connect(uniform_scale_field, SIGNAL(toggled(bool)), this, SLOT(toggle_uniform_scale(bool))); + connect(uniform_scale_field, SIGNAL(toggled(bool)), this, SLOT(toggle_uniform_scale(bool))); - // set defaults + // set defaults uniform_scale_field->set_bool_value(true); blend_mode_box->set_combo_index(0); + set = false; refresh(); } +void adjust_field(EffectField* field, double old_offset, double new_offset) { + if (field->keyframes.size() > 0) { + for (int i=0;ikeyframes.size();i++) { + field->keyframes[i].data = field->keyframes.at(i).data.toDouble() - old_offset + new_offset; + } + } else { + field->set_current_data(field->get_current_data().toDouble() - old_offset + new_offset); + } +} + void TransformEffect::refresh() { - if (parent_clip != NULL && parent_clip->sequence != NULL) { - double default_pos_x = parent_clip->sequence->width/2; - double default_pos_y = parent_clip->sequence->height/2; + if (parent_clip != NULL && parent_clip->sequence != NULL) { + double new_default_pos_x = parent_clip->sequence->width/2; + double new_default_pos_y = parent_clip->sequence->height/2; + + /*if (set) { + adjust_field(position_x, default_pos_x, new_default_pos_x); + adjust_field(position_y, default_pos_y, new_default_pos_y); + }*/ + + double default_pos_x = new_default_pos_x; + double default_pos_y = new_default_pos_y; position_x->set_double_default_value(default_pos_x); position_y->set_double_default_value(default_pos_y); scale_x->set_double_default_value(100); scale_y->set_double_default_value(100); - default_anchor_x = parent_clip->getWidth()/2; - default_anchor_y = parent_clip->getHeight()/2; + anchor_x_box->set_double_default_value(0); + anchor_y_box->set_double_default_value(0); + opacity->set_double_default_value(100); - if (default_anchor_x == 0) default_anchor_x = default_pos_x; - if (default_anchor_y == 0) default_anchor_y = default_pos_y; + double x_percent_multipler = 200.0 / parent_clip->sequence->width; + double y_percent_multipler = 200.0 / parent_clip->sequence->height; + top_left_gizmo->x_field_multi1 = -x_percent_multipler; + top_left_gizmo->y_field_multi1 = -y_percent_multipler; + top_center_gizmo->y_field_multi1 = -y_percent_multipler; + top_right_gizmo->x_field_multi1 = x_percent_multipler; + top_right_gizmo->y_field_multi1 = -y_percent_multipler; + bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; + bottom_left_gizmo->y_field_multi1 = y_percent_multipler; + bottom_center_gizmo->y_field_multi1 = y_percent_multipler; + bottom_right_gizmo->x_field_multi1 = x_percent_multipler; + bottom_right_gizmo->y_field_multi1 = y_percent_multipler; + left_center_gizmo->x_field_multi1 = -x_percent_multipler; + right_center_gizmo->x_field_multi1 = x_percent_multipler; + rotate_gizmo->x_field_multi1 = x_percent_multipler; - anchor_x_box->set_double_default_value(default_anchor_x); - anchor_y_box->set_double_default_value(default_anchor_y); - opacity->set_double_default_value(100); - - double x_percent_multipler = 200.0 / parent_clip->sequence->width; - double y_percent_multipler = 200.0 / parent_clip->sequence->height; - top_left_gizmo->x_field_multi1 = -x_percent_multipler; - top_left_gizmo->y_field_multi1 = -y_percent_multipler; - top_center_gizmo->y_field_multi1 = -y_percent_multipler; - top_right_gizmo->x_field_multi1 = x_percent_multipler; - top_right_gizmo->y_field_multi1 = -y_percent_multipler; - bottom_left_gizmo->x_field_multi1 = -x_percent_multipler; - bottom_left_gizmo->y_field_multi1 = y_percent_multipler; - bottom_center_gizmo->y_field_multi1 = y_percent_multipler; - bottom_right_gizmo->x_field_multi1 = x_percent_multipler; - bottom_right_gizmo->y_field_multi1 = y_percent_multipler; - left_center_gizmo->x_field_multi1 = -x_percent_multipler; - right_center_gizmo->x_field_multi1 = x_percent_multipler; - rotate_gizmo->x_field_multi1 = x_percent_multipler; + set = true; } } void TransformEffect::toggle_uniform_scale(bool enabled) { scale_y->set_enabled(!enabled); - top_center_gizmo->y_field1 = enabled ? scale_x : scale_y; - bottom_center_gizmo->y_field1 = enabled ? scale_x : scale_y; - top_left_gizmo->y_field1 = enabled ? NULL : scale_y; - top_right_gizmo->y_field1 = enabled ? NULL : scale_y; - bottom_left_gizmo->y_field1 = enabled ? NULL : scale_y; - bottom_right_gizmo->y_field1 = enabled ? NULL : scale_y; + top_center_gizmo->y_field1 = enabled ? scale_x : scale_y; + bottom_center_gizmo->y_field1 = enabled ? scale_x : scale_y; + top_left_gizmo->y_field1 = enabled ? NULL : scale_y; + top_right_gizmo->y_field1 = enabled ? NULL : scale_y; + bottom_left_gizmo->y_field1 = enabled ? NULL : scale_y; + bottom_right_gizmo->y_field1 = enabled ? NULL : scale_y; } void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int data) { @@ -176,8 +191,8 @@ void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, i glTranslatef(position_x->get_double_value(timecode)-(parent_clip->sequence->width/2), position_y->get_double_value(timecode)-(parent_clip->sequence->height/2), 0); // anchor point - int anchor_x_offset = (anchor_x_box->get_double_value(timecode)-default_anchor_x); - int anchor_y_offset = (anchor_y_box->get_double_value(timecode)-default_anchor_y); + int anchor_x_offset = (anchor_x_box->get_double_value(timecode)); + int anchor_y_offset = (anchor_y_box->get_double_value(timecode)); coords.vertexTopLeftX -= anchor_x_offset; coords.vertexTopRightX -= anchor_x_offset; coords.vertexBottomLeftX -= anchor_x_offset; @@ -188,51 +203,51 @@ void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, i coords.vertexBottomRightY -= anchor_y_offset; // rotation - glRotatef(rotation->get_double_value(timecode), 0, 0, 1); + glRotatef(rotation->get_double_value(timecode), 0, 0, 1); // scale float sx = scale_x->get_double_value(timecode)*0.01; float sy = (uniform_scale_field->get_bool_value(timecode)) ? sx : scale_y->get_double_value(timecode)*0.01; - glScalef(sx, sy, 1); + glScalef(sx, sy, 1); - // blend mode + // blend mode switch (blend_mode_box->get_combo_data(timecode).toInt()) { - case BLEND_MODE_NORMAL: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case BLEND_MODE_OVERLAY: + case BLEND_MODE_NORMAL: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case BLEND_MODE_OVERLAY: glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case BLEND_MODE_SCREEN: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - break; - case BLEND_MODE_MULTIPLY: + break; + case BLEND_MODE_SCREEN: + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); + break; + case BLEND_MODE_MULTIPLY: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); - break; - default: + break; + default: dout << "[ERROR] Invalid blend mode. This is a bug - please contact developers"; - } + } // opacity - float color[4]; - glGetFloatv(GL_CURRENT_COLOR, color); - glColor4f(1.0, 1.0, 1.0, color[3]*(opacity->get_double_value(timecode)*0.01)); + float color[4]; + glGetFloatv(GL_CURRENT_COLOR, color); + glColor4f(1.0, 1.0, 1.0, color[3]*(opacity->get_double_value(timecode)*0.01)); } void TransformEffect::gizmo_draw(double timecode, GLTextureCoords& coords) { - top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - top_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopLeftX, coords.vertexTopRightX, 0.5), lerp(coords.vertexTopLeftY, coords.vertexTopRightY, 0.5)); - top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - right_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopRightX, coords.vertexBottomRightX, 0.5), lerp(coords.vertexTopRightY, coords.vertexBottomRightY, 0.5)); - bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - bottom_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomRightX, coords.vertexBottomLeftX, 0.5), lerp(coords.vertexBottomRightY, coords.vertexBottomLeftY, 0.5)); - bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); - left_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomLeftX, coords.vertexTopLeftX, 0.5), lerp(coords.vertexBottomLeftY, coords.vertexTopLeftY, 0.5)); + top_left_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); + top_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopLeftX, coords.vertexTopRightX, 0.5), lerp(coords.vertexTopLeftY, coords.vertexTopRightY, 0.5)); + top_right_gizmo->world_pos[0] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); + right_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexTopRightX, coords.vertexBottomRightX, 0.5), lerp(coords.vertexTopRightY, coords.vertexBottomRightY, 0.5)); + bottom_right_gizmo->world_pos[0] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); + bottom_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomRightX, coords.vertexBottomLeftX, 0.5), lerp(coords.vertexBottomRightY, coords.vertexBottomLeftY, 0.5)); + bottom_left_gizmo->world_pos[0] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + left_center_gizmo->world_pos[0] = QPoint(lerp(coords.vertexBottomLeftX, coords.vertexTopLeftX, 0.5), lerp(coords.vertexBottomLeftY, coords.vertexTopLeftY, 0.5)); - rotate_gizmo->world_pos[0] = QPoint(lerp(top_center_gizmo->world_pos[0].x(), bottom_center_gizmo->world_pos[0].x(), -0.1), lerp(top_center_gizmo->world_pos[0].y(), bottom_center_gizmo->world_pos[0].y(), -0.1)); + rotate_gizmo->world_pos[0] = QPoint(lerp(top_center_gizmo->world_pos[0].x(), bottom_center_gizmo->world_pos[0].x(), -0.1), lerp(top_center_gizmo->world_pos[0].y(), bottom_center_gizmo->world_pos[0].y(), -0.1)); - rect_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); - rect_gizmo->world_pos[1] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); - rect_gizmo->world_pos[2] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); - rect_gizmo->world_pos[3] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); + rect_gizmo->world_pos[0] = QPoint(coords.vertexTopLeftX, coords.vertexTopLeftY); + rect_gizmo->world_pos[1] = QPoint(coords.vertexTopRightX, coords.vertexTopRightY); + rect_gizmo->world_pos[2] = QPoint(coords.vertexBottomRightX, coords.vertexBottomRightY); + rect_gizmo->world_pos[3] = QPoint(coords.vertexBottomLeftX, coords.vertexBottomLeftY); } diff --git a/effects/internal/transformeffect.h b/effects/internal/transformeffect.h index c198ffb0a..feb875ac6 100644 --- a/effects/internal/transformeffect.h +++ b/effects/internal/transformeffect.h @@ -8,37 +8,36 @@ class TransformEffect : public Effect { public: TransformEffect(Clip* c, const EffectMeta* em); void refresh(); - void process_coords(double timecode, GLTextureCoords& coords, int data); + void process_coords(double timecode, GLTextureCoords& coords, int data); - void gizmo_draw(double timecode, GLTextureCoords& coords); + void gizmo_draw(double timecode, GLTextureCoords& coords); public slots: void toggle_uniform_scale(bool enabled); private: - EffectField* position_x; - EffectField* position_y; - EffectField* scale_x; - EffectField* scale_y; - EffectField* uniform_scale_field; - EffectField* rotation; - EffectField* anchor_x_box; - EffectField* anchor_y_box; - EffectField* opacity; - EffectField* blend_mode_box; + EffectField* position_x; + EffectField* position_y; + EffectField* scale_x; + EffectField* scale_y; + EffectField* uniform_scale_field; + EffectField* rotation; + EffectField* anchor_x_box; + EffectField* anchor_y_box; + EffectField* opacity; + EffectField* blend_mode_box; - EffectGizmo* top_left_gizmo; - EffectGizmo* top_center_gizmo; - EffectGizmo* top_right_gizmo; - EffectGizmo* bottom_left_gizmo; - EffectGizmo* bottom_center_gizmo; - EffectGizmo* bottom_right_gizmo; - EffectGizmo* left_center_gizmo; - EffectGizmo* right_center_gizmo; - EffectGizmo* anchor_gizmo; - EffectGizmo* rotate_gizmo; - EffectGizmo* rect_gizmo; + EffectGizmo* top_left_gizmo; + EffectGizmo* top_center_gizmo; + EffectGizmo* top_right_gizmo; + EffectGizmo* bottom_left_gizmo; + EffectGizmo* bottom_center_gizmo; + EffectGizmo* bottom_right_gizmo; + EffectGizmo* left_center_gizmo; + EffectGizmo* right_center_gizmo; + EffectGizmo* anchor_gizmo; + EffectGizmo* rotate_gizmo; + EffectGizmo* rect_gizmo; - int default_anchor_x; - int default_anchor_y; + bool set; }; #endif // TRANSFORMEFFECT_H diff --git a/effects/internal/volumeeffect.cpp b/effects/internal/volumeeffect.cpp index 6a71d06a7..b7b18017b 100644 --- a/effects/internal/volumeeffect.cpp +++ b/effects/internal/volumeeffect.cpp @@ -9,12 +9,12 @@ #include "ui/collapsiblewidget.h" VolumeEffect::VolumeEffect(Clip* c, const EffectMeta *em) : Effect(c, em) { - EffectRow* volume_row = add_row("Volume:"); - volume_val = volume_row->add_field(EFFECT_FIELD_DOUBLE, "volume"); + EffectRow* volume_row = add_row("Volume"); + volume_val = volume_row->add_field(EFFECT_FIELD_DOUBLE, "volume"); volume_val->set_double_minimum_value(0); // set defaults - volume_val->set_double_default_value(100); + volume_val->set_double_default_value(100); } void VolumeEffect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) { diff --git a/io/config.cpp b/io/config.cpp index 2ecb744ba..e36e7b763 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -4,93 +4,98 @@ #include #include +#include "panels/project.h" +#include "panels/panels.h" + #include "debug.h" Config config; Config::Config() - : saved_layout(false), + : saved_layout(false), show_track_lines(true), - scroll_zooms(false), - edit_tool_selects_links(false), - edit_tool_also_seeks(false), - select_also_seeks(false), + scroll_zooms(false), + edit_tool_selects_links(false), + edit_tool_also_seeks(false), + select_also_seeks(false), paste_seeks(true), - img_seq_formats("jpg|jpeg|bmp|tiff|tif|psd|png|tga|jp2|gif"), + img_seq_formats("jpg|jpeg|bmp|tiff|tif|psd|png|tga|jp2|gif"), rectified_waveforms(false), - default_transition_length(30), - timecode_view(TIMECODE_DROP), - show_title_safe_area(false), - use_custom_title_safe_ratio(false), + default_transition_length(30), + timecode_view(TIMECODE_DROP), + show_title_safe_area(false), + use_custom_title_safe_ratio(false), custom_title_safe_ratio(1), enable_drag_files_to_timeline(true), autoscale_by_default(false), recording_mode(2), - enable_seek_to_import(false), - enable_audio_scrubbing(true), - drop_on_media_to_replace(true), - autoscroll(AUTOSCROLL_PAGE_SCROLL), - audio_rate(48000), - fast_seeking(false), - hover_focus(false), - project_view_type(PROJECT_VIEW_TREE) + enable_seek_to_import(false), + enable_audio_scrubbing(true), + drop_on_media_to_replace(true), + autoscroll(AUTOSCROLL_PAGE_SCROLL), + audio_rate(48000), + fast_seeking(false), + hover_focus(false), + project_view_type(PROJECT_VIEW_TREE), + set_name_with_marker(true), + show_project_toolbar(false) {} void Config::load(QString path) { - QFile f(path); - if (f.exists() && f.open(QIODevice::ReadOnly)) { - QXmlStreamReader stream(&f); + QFile f(path); + if (f.exists() && f.open(QIODevice::ReadOnly)) { + QXmlStreamReader stream(&f); - while (!stream.atEnd()) { - stream.readNext(); - if (stream.isStartElement()) { - if (stream.name() == "SavedLayout") { - stream.readNext(); - saved_layout = (stream.text() == "1"); - } else if (stream.name() == "ShowTrackLines") { - stream.readNext(); - show_track_lines = (stream.text() == "1"); - } else if (stream.name() == "ScrollZooms") { - stream.readNext(); - scroll_zooms = (stream.text() == "1"); - } else if (stream.name() == "EditToolSelectsLinks") { - stream.readNext(); - edit_tool_selects_links = (stream.text() == "1"); - } else if (stream.name() == "EditToolAlsoSeeks") { - stream.readNext(); - edit_tool_also_seeks = (stream.text() == "1"); - } else if (stream.name() == "SelectAlsoSeeks") { - stream.readNext(); - select_also_seeks = (stream.text() == "1"); - } else if (stream.name() == "PasteSeeks") { - stream.readNext(); - paste_seeks = (stream.text() == "1"); - } else if (stream.name() == "ImageSequenceFormats") { - stream.readNext(); - img_seq_formats = stream.text().toString(); + while (!stream.atEnd()) { + stream.readNext(); + if (stream.isStartElement()) { + if (stream.name() == "SavedLayout") { + stream.readNext(); + saved_layout = (stream.text() == "1"); + } else if (stream.name() == "ShowTrackLines") { + stream.readNext(); + show_track_lines = (stream.text() == "1"); + } else if (stream.name() == "ScrollZooms") { + stream.readNext(); + scroll_zooms = (stream.text() == "1"); + } else if (stream.name() == "EditToolSelectsLinks") { + stream.readNext(); + edit_tool_selects_links = (stream.text() == "1"); + } else if (stream.name() == "EditToolAlsoSeeks") { + stream.readNext(); + edit_tool_also_seeks = (stream.text() == "1"); + } else if (stream.name() == "SelectAlsoSeeks") { + stream.readNext(); + select_also_seeks = (stream.text() == "1"); + } else if (stream.name() == "PasteSeeks") { + stream.readNext(); + paste_seeks = (stream.text() == "1"); + } else if (stream.name() == "ImageSequenceFormats") { + stream.readNext(); + img_seq_formats = stream.text().toString(); } else if (stream.name() == "RectifiedWaveforms") { - stream.readNext(); + stream.readNext(); rectified_waveforms = (stream.text() == "1"); } else if (stream.name() == "DefaultTransitionLength") { - stream.readNext(); - default_transition_length = stream.text().toInt(); - } else if (stream.name() == "TimecodeView") { - stream.readNext(); - timecode_view = stream.text().toInt(); - }else if (stream.name() == "ShowTitleSafeArea") { - stream.readNext(); - show_title_safe_area = (stream.text() == "1"); - } else if (stream.name() == "UseCustomTitleSafeRatio") { - stream.readNext(); - use_custom_title_safe_ratio = (stream.text() == "1"); - } else if (stream.name() == "CustomTitleSafeRatio") { - stream.readNext(); - custom_title_safe_ratio = stream.text().toDouble(); + stream.readNext(); + default_transition_length = stream.text().toInt(); + } else if (stream.name() == "TimecodeView") { + stream.readNext(); + timecode_view = stream.text().toInt(); + } else if (stream.name() == "ShowTitleSafeArea") { + stream.readNext(); + show_title_safe_area = (stream.text() == "1"); + } else if (stream.name() == "UseCustomTitleSafeRatio") { + stream.readNext(); + use_custom_title_safe_ratio = (stream.text() == "1"); + } else if (stream.name() == "CustomTitleSafeRatio") { + stream.readNext(); + custom_title_safe_ratio = stream.text().toDouble(); } else if (stream.name() == "EnableDragFilesToTimeline") { stream.readNext(); enable_drag_files_to_timeline = (stream.text() == "1");; - } else if (stream.name() == "AutoscaleByDefault") { - stream.readNext(); + } else if (stream.name() == "AutoscaleByDefault") { + stream.readNext(); autoscale_by_default = (stream.text() == "1"); } else if (stream.name() == "RecordingMode") { stream.readNext(); @@ -98,78 +103,86 @@ void Config::load(QString path) { } else if (stream.name() == "EnableSeekToImport") { stream.readNext(); enable_seek_to_import = (stream.text() == "1"); - } else if (stream.name() == "AudioScrubbing") { - stream.readNext(); - enable_audio_scrubbing = (stream.text() == "1"); - } else if (stream.name() == "DropFileOnMediaToReplace") { - stream.readNext(); - drop_on_media_to_replace = (stream.text() == "1"); - } else if (stream.name() == "Autoscroll") { - stream.readNext(); - autoscroll = stream.text().toInt(); - } else if (stream.name() == "AudioRate") { - stream.readNext(); - audio_rate = stream.text().toInt(); - } else if (stream.name() == "FastSeeking") { - stream.readNext(); - fast_seeking = (stream.text() == "1"); - } else if (stream.name() == "HoverFocus") { - stream.readNext(); - hover_focus = (stream.text() == "1"); - } else if (stream.name() == "ProjectViewType") { - stream.readNext(); - project_view_type = stream.text().toInt(); - } - } - } - if (stream.hasError()) { + } else if (stream.name() == "AudioScrubbing") { + stream.readNext(); + enable_audio_scrubbing = (stream.text() == "1"); + } else if (stream.name() == "DropFileOnMediaToReplace") { + stream.readNext(); + drop_on_media_to_replace = (stream.text() == "1"); + } else if (stream.name() == "Autoscroll") { + stream.readNext(); + autoscroll = stream.text().toInt(); + } else if (stream.name() == "AudioRate") { + stream.readNext(); + audio_rate = stream.text().toInt(); + } else if (stream.name() == "FastSeeking") { + stream.readNext(); + fast_seeking = (stream.text() == "1"); + } else if (stream.name() == "HoverFocus") { + stream.readNext(); + hover_focus = (stream.text() == "1"); + } else if (stream.name() == "ProjectViewType") { + stream.readNext(); + project_view_type = stream.text().toInt(); + } else if (stream.name() == "SetNameWithMarker") { + stream.readNext(); + set_name_with_marker = (stream.text() == "1"); + } else if (stream.name() == "ShowProjectToolbar") { + stream.readNext(); + show_project_toolbar = (stream.text() == "1"); + } + } + } + if (stream.hasError()) { dout << "[ERROR] Error parsing config XML." << stream.errorString(); - } + } - f.close(); + f.close(); } } void Config::save(QString path) { - QFile f(path); - if (!f.open(QIODevice::WriteOnly)) { + QFile f(path); + if (!f.open(QIODevice::WriteOnly)) { dout << "[ERROR] Could not save configuration"; - return; - } + return; + } - QXmlStreamWriter stream(&f); - stream.setAutoFormatting(true); - stream.writeStartDocument(); // doc - stream.writeStartElement("Configuration"); // configuration + QXmlStreamWriter stream(&f); + stream.setAutoFormatting(true); + stream.writeStartDocument(); // doc + stream.writeStartElement("Configuration"); // configuration stream.writeTextElement("Version", QString::number(SAVE_VERSION)); - stream.writeTextElement("SavedLayout", QString::number(saved_layout)); - stream.writeTextElement("ShowTrackLines", QString::number(show_track_lines)); - stream.writeTextElement("ScrollZooms", QString::number(scroll_zooms)); - stream.writeTextElement("EditToolSelectsLinks", QString::number(edit_tool_selects_links)); - stream.writeTextElement("EditToolAlsoSeeks", QString::number(edit_tool_also_seeks)); - stream.writeTextElement("SelectAlsoSeeks", QString::number(select_also_seeks)); - stream.writeTextElement("PasteSeeks", QString::number(paste_seeks)); - stream.writeTextElement("ImageSequenceFormats", img_seq_formats); + stream.writeTextElement("SavedLayout", QString::number(saved_layout)); + stream.writeTextElement("ShowTrackLines", QString::number(show_track_lines)); + stream.writeTextElement("ScrollZooms", QString::number(scroll_zooms)); + stream.writeTextElement("EditToolSelectsLinks", QString::number(edit_tool_selects_links)); + stream.writeTextElement("EditToolAlsoSeeks", QString::number(edit_tool_also_seeks)); + stream.writeTextElement("SelectAlsoSeeks", QString::number(select_also_seeks)); + stream.writeTextElement("PasteSeeks", QString::number(paste_seeks)); + stream.writeTextElement("ImageSequenceFormats", img_seq_formats); stream.writeTextElement("RectifiedWaveforms", QString::number(rectified_waveforms)); stream.writeTextElement("DefaultTransitionLength", QString::number(default_transition_length)); - stream.writeTextElement("TimecodeView", QString::number(timecode_view)); - stream.writeTextElement("ShowTitleSafeArea", QString::number(show_title_safe_area)); - stream.writeTextElement("UseCustomTitleSafeRatio", QString::number(use_custom_title_safe_ratio)); - stream.writeTextElement("CustomTitleSafeRatio", QString::number(custom_title_safe_ratio)); + stream.writeTextElement("TimecodeView", QString::number(timecode_view)); + stream.writeTextElement("ShowTitleSafeArea", QString::number(show_title_safe_area)); + stream.writeTextElement("UseCustomTitleSafeRatio", QString::number(use_custom_title_safe_ratio)); + stream.writeTextElement("CustomTitleSafeRatio", QString::number(custom_title_safe_ratio)); stream.writeTextElement("EnableDragFilesToTimeline", QString::number(enable_drag_files_to_timeline)); - stream.writeTextElement("AutoscaleByDefault", QString::number(autoscale_by_default)); + stream.writeTextElement("AutoscaleByDefault", QString::number(autoscale_by_default)); stream.writeTextElement("RecordingMode", QString::number(recording_mode)); stream.writeTextElement("EnableSeekToImport", QString::number(enable_seek_to_import)); - stream.writeTextElement("AudioScrubbing", QString::number(enable_audio_scrubbing)); - stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); - stream.writeTextElement("Autoscroll", QString::number(autoscroll)); - stream.writeTextElement("AudioRate", QString::number(audio_rate)); - stream.writeTextElement("FastSeeking", QString::number(fast_seeking)); - stream.writeTextElement("HoverFocus", QString::number(hover_focus)); - stream.writeTextElement("ProjectViewType", QString::number(project_view_type)); + stream.writeTextElement("AudioScrubbing", QString::number(enable_audio_scrubbing)); + stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); + stream.writeTextElement("Autoscroll", QString::number(autoscroll)); + stream.writeTextElement("AudioRate", QString::number(audio_rate)); + stream.writeTextElement("FastSeeking", QString::number(fast_seeking)); + stream.writeTextElement("HoverFocus", QString::number(hover_focus)); + stream.writeTextElement("ProjectViewType", QString::number(project_view_type)); + stream.writeTextElement("SetNameWithMarker", QString::number(set_name_with_marker)); + stream.writeTextElement("ShowProjectToolbar", QString::number(panel_project->toolbar_widget->isVisible())); stream.writeEndElement(); // configuration - stream.writeEndDocument(); // doc + stream.writeEndDocument(); // doc f.close(); } diff --git a/io/config.h b/io/config.h index 4972c9dd3..312ba5c84 100644 --- a/io/config.h +++ b/io/config.h @@ -3,8 +3,8 @@ #include -#define SAVE_VERSION 181124 // YYMMDD -#define MIN_SAVE_VERSION 181114 // lowest compatible project version +#define SAVE_VERSION 190104 // YYMMDD +#define MIN_SAVE_VERSION 190104 // lowest compatible project version #define TIMECODE_DROP 0 #define TIMECODE_NONDROP 1 @@ -22,35 +22,37 @@ #define PROJECT_VIEW_ICON 1 struct Config { - Config(); - bool saved_layout; - bool show_track_lines; - bool scroll_zooms; - bool edit_tool_selects_links; - bool edit_tool_also_seeks; - bool select_also_seeks; - bool paste_seeks; - QString img_seq_formats; + Config(); + bool saved_layout; + bool show_track_lines; + bool scroll_zooms; + bool edit_tool_selects_links; + bool edit_tool_also_seeks; + bool select_also_seeks; + bool paste_seeks; + QString img_seq_formats; bool rectified_waveforms; int default_transition_length; - int timecode_view; - bool show_title_safe_area; - bool use_custom_title_safe_ratio; - double custom_title_safe_ratio; + int timecode_view; + bool show_title_safe_area; + bool use_custom_title_safe_ratio; + double custom_title_safe_ratio; bool enable_drag_files_to_timeline; - bool autoscale_by_default; + bool autoscale_by_default; int recording_mode; bool enable_seek_to_import; - bool enable_audio_scrubbing; - bool drop_on_media_to_replace; - int autoscroll; - int audio_rate; - bool fast_seeking; - bool hover_focus; - int project_view_type; + bool enable_audio_scrubbing; + bool drop_on_media_to_replace; + int autoscroll; + int audio_rate; + bool fast_seeking; + bool hover_focus; + int project_view_type; + bool set_name_with_marker; + bool show_project_toolbar; - void load(QString path); - void save(QString path); + void load(QString path); + void save(QString path); }; extern Config config; diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 5715ce9f0..a2eeb5962 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -12,9 +12,8 @@ #include "debug.h" extern "C" { - #include - #include - #include + #include + #include #include #include } @@ -25,30 +24,11 @@ extern "C" { #include #include -AVFormatContext* fmt_ctx = NULL; -AVStream* video_stream; -AVCodec* vcodec; -AVCodecContext* vcodec_ctx; -AVFrame* video_frame; -AVFrame* sws_frame; -SwsContext* sws_ctx = NULL; -AVStream* audio_stream; -AVCodec* acodec; -AVFrame* audio_frame; -AVFrame* swr_frame; -AVCodecContext* acodec_ctx; -AVPacket video_pkt; -AVPacket audio_pkt; -SwrContext* swr_ctx = NULL; -int aframe_bytes; -int ret; -char* c_filename; - ExportThread::ExportThread() : continueEncode(true) { surface.create(); } -bool ExportThread::encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream) { +bool ExportThread::encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream, bool rescale) { ret = avcodec_send_frame(codec_ctx, frame); if (ret < 0) { dout << "[ERROR] Failed to send frame to encoder." << ret; @@ -69,6 +49,7 @@ bool ExportThread::encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, } packet->stream_index = stream->index; + if (rescale) av_packet_rescale_ts(packet, codec_ctx->time_base, stream->time_base); av_interleaved_write_frame(ofmt_ctx, packet); av_packet_unref(packet); } @@ -98,7 +79,7 @@ bool ExportThread::setupVideo() { // allocate context // vcodec_ctx = video_stream->codec; - vcodec_ctx = avcodec_alloc_context3(vcodec); + vcodec_ctx = avcodec_alloc_context3(vcodec); if (!vcodec_ctx) { dout << "[ERROR] Could not allocate video encoding context"; ed->export_error = "could not allocate video encoding context"; @@ -107,6 +88,7 @@ bool ExportThread::setupVideo() { // setup context vcodec_ctx->codec_id = static_cast(video_codec); + vcodec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; vcodec_ctx->width = video_width; vcodec_ctx->height = video_height; vcodec_ctx->sample_aspect_ratio = {1, 1}; @@ -122,10 +104,10 @@ bool ExportThread::setupVideo() { if (vcodec_ctx->codec_id == AV_CODEC_ID_H264) { /*char buffer[50]; - itoa(vcodec_ctx, buffer, 10);*/ + itoa(vcodec_ctx, buffer, 10);*/ - //av_opt_set(vcodec_ctx->priv_data, "preset", "fast", AV_OPT_SEARCH_CHILDREN); - //av_opt_set(vcodec_ctx->priv_data, "x264opts", "opencl", AV_OPT_SEARCH_CHILDREN); + //av_opt_set(vcodec_ctx->priv_data, "preset", "fast", AV_OPT_SEARCH_CHILDREN); + //av_opt_set(vcodec_ctx->priv_data, "x264opts", "opencl", AV_OPT_SEARCH_CHILDREN); switch (video_compression_type) { case COMPRESSION_TYPE_CFR: @@ -204,7 +186,7 @@ bool ExportThread::setupAudio() { // allocate context // acodec_ctx = audio_stream->codec; - acodec_ctx = avcodec_alloc_context3(acodec); + acodec_ctx = avcodec_alloc_context3(acodec); if (!acodec_ctx) { dout << "[ERROR] Could not find allocate audio encoding context"; ed->export_error = "could not allocate audio encoding context"; @@ -213,6 +195,7 @@ bool ExportThread::setupAudio() { // setup context acodec_ctx->codec_id = static_cast(audio_codec); + acodec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; acodec_ctx->sample_rate = audio_sampling_rate; acodec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is what the user sets the output audio to) acodec_ctx->channels = av_get_channel_layout_nb_channels(acodec_ctx->channel_layout); @@ -261,7 +244,7 @@ bool ExportThread::setupAudio() { audio_frame = av_frame_alloc(); audio_frame->sample_rate = sequence->audio_frequency; audio_frame->nb_samples = acodec_ctx->frame_size; - if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 2048; // should possibly be smaller? + if (audio_frame->nb_samples == 0) audio_frame->nb_samples = 256; // should possibly be smaller? audio_frame->format = AV_SAMPLE_FMT_S16; audio_frame->channel_layout = AV_CH_LAYOUT_STEREO; // change this to support surround/mono sound in the future (this is whatever format they're held in the internal buffer) audio_frame->channels = av_get_channel_layout_nb_channels(audio_frame->channel_layout); @@ -295,7 +278,7 @@ bool ExportThread::setupContainer() { return false; } -// av_dump_format(fmt_ctx, 0, c_filename, 1); + //av_dump_format(fmt_ctx, 0, c_filename, 1); ret = avio_open(&fmt_ctx->pb, c_filename, AVIO_FLAG_WRITE); if (ret < 0) { @@ -323,9 +306,9 @@ void ExportThread::run() { continueEncode = setupContainer(); - if (video_enabled && continueEncode) continueEncode = setupVideo(); + if (video_enabled && continueEncode) continueEncode = setupVideo(); - if (audio_enabled && continueEncode) continueEncode = setupAudio(); + if (audio_enabled && continueEncode) continueEncode = setupAudio(); if (continueEncode) { ret = avformat_write_header(fmt_ctx, NULL); @@ -345,11 +328,11 @@ void ExportThread::run() { panel_sequence_viewer->viewer_widget->default_fbo = &fbo; long file_audio_samples = 0; - qint64 start_time, frame_time, avg_time, eta, total_time = 0; - long remaining_frames, frame_count = 1; + qint64 start_time, frame_time, avg_time, eta, total_time = 0; + long remaining_frames, frame_count = 1; - while (sequence->playhead < end_frame && continueEncode) { - start_time = QDateTime::currentMSecsSinceEpoch(); + while (sequence->playhead <= end_frame && continueEncode) { + start_time = QDateTime::currentMSecsSinceEpoch(); panel_sequence_viewer->viewer_widget->paintGL(); @@ -360,10 +343,10 @@ void ExportThread::run() { // change pixel format sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize); - sws_frame->pts = round(timecode_secs/av_q2d(video_stream->time_base)); + sws_frame->pts = qRound(timecode_secs/av_q2d(video_stream->time_base)); - // send to encoder - if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream)) continueEncode = false; + // send to encoder + if (!encode(fmt_ctx, vcodec_ctx, sws_frame, &video_pkt, video_stream, false)) continueEncode = false; } if (audio_enabled) { // do we need to encode more audio samples? @@ -386,25 +369,25 @@ void ExportThread::run() { swr_convert_frame(swr_ctx, swr_frame, audio_frame); swr_frame->pts = file_audio_samples; - // send to encoder - if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) continueEncode = false; + // send to encoder + if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream, true)) continueEncode = false; file_audio_samples += swr_frame->nb_samples; } } - // encoding stats - frame_time = (QDateTime::currentMSecsSinceEpoch()-start_time); - total_time += frame_time; - remaining_frames = (end_frame-sequence->playhead); - avg_time = (total_time/frame_count); - eta = (remaining_frames*avg_time); + // encoding stats + frame_time = (QDateTime::currentMSecsSinceEpoch()-start_time); + total_time += frame_time; + remaining_frames = (end_frame-sequence->playhead); + avg_time = (total_time/frame_count); + eta = (remaining_frames*avg_time); // dout << "[INFO] Encoded frame" << sequence->playhead << "- took" << frame_time << "ms (avg:" << avg_time << "ms, remaining:" << remaining_frames << ", ETA:" << eta << ")"; - emit progress_changed(qRound(((double) (sequence->playhead-start_frame) / (double) (end_frame-start_frame)) * 100), eta); + emit progress_changed(qRound(((double) (sequence->playhead-start_frame) / (double) (end_frame-start_frame)) * 100), eta); sequence->playhead++; - frame_count++; + frame_count++; } panel_sequence_viewer->viewer_widget->default_fbo = NULL; @@ -418,7 +401,7 @@ void ExportThread::run() { swr_convert_frame(swr_ctx, swr_frame, NULL); if (swr_frame->nb_samples == 0) break; swr_frame->pts = file_audio_samples; - if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream)) continueEncode = false; + if (!encode(fmt_ctx, acodec_ctx, swr_frame, &audio_pkt, audio_stream, true)) continueEncode = false; file_audio_samples += swr_frame->nb_samples; } while (swr_frame->nb_samples > 0); } @@ -428,8 +411,8 @@ void ExportThread::run() { if (continueEncode) { // flush remaining packets while (continueVideo && continueAudio) { - if (continueVideo && video_enabled) continueVideo = encode(fmt_ctx, vcodec_ctx, NULL, &video_pkt, video_stream); - if (continueAudio && audio_enabled) continueAudio = encode(fmt_ctx, acodec_ctx, NULL, &audio_pkt, audio_stream); + if (continueVideo && video_enabled) continueVideo = encode(fmt_ctx, vcodec_ctx, NULL, &video_pkt, video_stream, false); + if (continueAudio && audio_enabled) continueAudio = encode(fmt_ctx, acodec_ctx, NULL, &audio_pkt, audio_stream, true); } ret = av_write_trailer(fmt_ctx); @@ -439,7 +422,7 @@ void ExportThread::run() { continueEncode = false; } - emit progress_changed(100, 0); + emit progress_changed(100, 0); } avio_closep(&fmt_ctx->pb); @@ -448,14 +431,14 @@ void ExportThread::run() { avcodec_close(vcodec_ctx); av_packet_unref(&video_pkt); av_frame_free(&video_frame); - avcodec_free_context(&vcodec_ctx); + avcodec_free_context(&vcodec_ctx); } if (audio_enabled) { avcodec_close(acodec_ctx); av_packet_unref(&audio_pkt); av_frame_free(&audio_frame); - avcodec_free_context(&acodec_ctx); + avcodec_free_context(&acodec_ctx); } avformat_free_context(fmt_ctx); diff --git a/io/exportthread.h b/io/exportthread.h index dfc48c442..0eb74b952 100644 --- a/io/exportthread.h +++ b/io/exportthread.h @@ -10,6 +10,13 @@ struct AVCodecContext; struct AVFrame; struct AVPacket; struct AVStream; +struct AVCodec; +struct SwsContext; +struct SwrContext; + +extern "C" { + #include +} #define COMPRESSION_TYPE_CBR 0 #define COMPRESSION_TYPE_CFR 1 @@ -20,7 +27,7 @@ class ExportThread : public QThread { Q_OBJECT public: ExportThread(); - void run(); + void run(); // export parameters QString filename; @@ -35,8 +42,8 @@ public: int audio_codec; int audio_sampling_rate; int audio_bitrate; - long start_frame; - long end_frame; + long start_frame; + long end_frame; QOffscreenSurface surface; @@ -44,12 +51,31 @@ public: bool continueEncode; signals: - void progress_changed(int value, qint64 remaining_ms); + void progress_changed(int value, qint64 remaining_ms); private: - bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream); + bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream, bool rescale); bool setupVideo(); bool setupAudio(); bool setupContainer(); + + AVFormatContext* fmt_ctx = NULL; + AVStream* video_stream; + AVCodec* vcodec; + AVCodecContext* vcodec_ctx; + AVFrame* video_frame; + AVFrame* sws_frame; + SwsContext* sws_ctx = NULL; + AVStream* audio_stream; + AVCodec* acodec; + AVFrame* audio_frame; + AVFrame* swr_frame; + AVCodecContext* acodec_ctx; + AVPacket video_pkt; + AVPacket audio_pkt; + SwrContext* swr_ctx = NULL; + int aframe_bytes; + int ret; + char* c_filename; }; #endif // EXPORTTHREAD_H diff --git a/io/loadthread.cpp b/io/loadthread.cpp index b7688a1a6..e343369d8 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -20,621 +20,623 @@ #include struct TransitionData { - int id; - QString name; - long length; - Clip* otc; - Clip* ctc; + int id; + QString name; + long length; + Clip* otc; + Clip* ctc; }; LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a), cancelled(false) { - connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); - connect(this, SIGNAL(success()), this, SLOT(success_func())); + connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); + connect(this, SIGNAL(success()), this, SLOT(success_func())); connect(this, SIGNAL(error()), this, SLOT(error_func())); connect(this, SIGNAL(start_create_dual_transition(const TransitionData*,Clip*,Clip*,const EffectMeta*)), this, SLOT(create_dual_transition(const TransitionData*,Clip*,Clip*,const EffectMeta*))); - connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool))); + connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool))); } const EffectMeta* get_meta_from_name(const QString& name) { - for (int j=0;jtrack < 0) ? "Transform" : "Volume"; break; - case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; - case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; - case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; - case 4: effect_name = "Invert"; break; - case 5: effect_name = "Chroma Key"; break; - case 6: effect_name = "Gaussian Blur"; break; - case 7: effect_name = "Crop"; break; - case 8: effect_name = "Flip"; break; - case 9: effect_name = "Box Blur"; break; - case 10: effect_name = "Wave"; break; - case 11: effect_name = "Temperature"; break; - } - } + // backwards compatibility with 180820 + if (stream.name() == "effect" && effect_id != -1) { + switch (effect_id) { + case 0: effect_name = (c->track < 0) ? "Transform" : "Volume"; break; + case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; + case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; + case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; + case 4: effect_name = "Invert"; break; + case 5: effect_name = "Chroma Key"; break; + case 6: effect_name = "Gaussian Blur"; break; + case 7: effect_name = "Crop"; break; + case 8: effect_name = "Flip"; break; + case 9: effect_name = "Box Blur"; break; + case 10: effect_name = "Wave"; break; + case 11: effect_name = "Temperature"; break; + } + } - // wait for effects to be loaded - effects_loaded.lock(); + // wait for effects to be loaded + effects_loaded.lock(); - const EffectMeta* meta = NULL; + const EffectMeta* meta = NULL; - // find effect with this name - if (!effect_name.isEmpty()) { - meta = get_meta_from_name(effect_name); - } + // find effect with this name + if (!effect_name.isEmpty()) { + meta = get_meta_from_name(effect_name); + } - effects_loaded.unlock(); + effects_loaded.unlock(); - if (meta == NULL) { - dout << "[WARNING] An effect used by this project is missing. It was not loaded."; - } else { - QString tag = stream.name().toString(); + if (meta == NULL) { + dout << "[WARNING] An effect used by this project is missing. It was not loaded."; + } else { + QString tag = stream.name().toString(); - int type; - if (tag == "opening") { - type = TA_OPENING_TRANSITION; - } else if (tag == "closing") { - type = TA_CLOSING_TRANSITION; - } else { - type = TA_NO_TRANSITION; - } + int type; + if (tag == "opening") { + type = TA_OPENING_TRANSITION; + } else if (tag == "closing") { + type = TA_CLOSING_TRANSITION; + } else { + type = TA_NO_TRANSITION; + } - emit start_create_effect_ui(&stream, c, type, meta, effect_length, effect_enabled); + emit start_create_effect_ui(&stream, c, type, meta, effect_length, effect_enabled); - waitCond.wait(&mutex); - } + waitCond.wait(&mutex); + } } void LoadThread::read_next(QXmlStreamReader &stream) { - stream.readNext(); - update_current_element_count(stream); + stream.readNext(); + update_current_element_count(stream); } void LoadThread::read_next_start_element(QXmlStreamReader &stream) { - stream.readNextStartElement(); - update_current_element_count(stream); + stream.readNextStartElement(); + update_current_element_count(stream); } void LoadThread::update_current_element_count(QXmlStreamReader &stream) { - if (is_element(stream)) { - current_element_count++; - report_progress((current_element_count * 100) / total_element_count); - } + if (is_element(stream)) { + current_element_count++; + report_progress((current_element_count * 100) / total_element_count); + } } bool LoadThread::is_element(QXmlStreamReader &stream) { - return stream.isStartElement() - && (stream.name() == "folder" - || stream.name() == "footage" - || stream.name() == "sequence" - || stream.name() == "clip" - || stream.name() == "effect"); + return stream.isStartElement() + && (stream.name() == "folder" + || stream.name() == "footage" + || stream.name() == "sequence" + || stream.name() == "clip" + || stream.name() == "effect"); } bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { - f.seek(0); - stream.setDevice(stream.device()); + f.seek(0); + stream.setDevice(stream.device()); - QString root_search; - QString child_search; + QString root_search; + QString child_search; - switch (type) { - case LOAD_TYPE_VERSION: - root_search = "version"; - break; - case LOAD_TYPE_URL: - root_search = "url"; - break; - case MEDIA_TYPE_FOLDER: - root_search = "folders"; - child_search = "folder"; - break; - case MEDIA_TYPE_FOOTAGE: - root_search = "media"; - child_search = "footage"; - break; - case MEDIA_TYPE_SEQUENCE: - root_search = "sequences"; - child_search = "sequence"; - break; - } + switch (type) { + case LOAD_TYPE_VERSION: + root_search = "version"; + break; + case LOAD_TYPE_URL: + root_search = "url"; + break; + case MEDIA_TYPE_FOLDER: + root_search = "folders"; + child_search = "folder"; + break; + case MEDIA_TYPE_FOOTAGE: + root_search = "media"; + child_search = "footage"; + break; + case MEDIA_TYPE_SEQUENCE: + root_search = "sequences"; + child_search = "sequence"; + break; + } - show_err = true; + show_err = true; - while (!stream.atEnd() && !cancelled) { - read_next_start_element(stream); - if (stream.name() == root_search) { - if (type == LOAD_TYPE_VERSION) { - int proj_version = stream.readElementText().toInt(); - if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { - if (QMessageBox::warning(mainWindow, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - show_err = false; - return false; - } - } - } else if (type == LOAD_TYPE_URL) { - internal_proj_url = stream.readElementText(); - internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); - } else { - while (!cancelled && !(stream.name() == root_search && stream.isEndElement())) { - read_next(stream); - if (stream.name() == child_search && stream.isStartElement()) { - switch (type) { - case MEDIA_TYPE_FOLDER: - { - Media* folder = panel_project->new_folder(0); - folder->temp_id2 = 0; - for (int j=0;jtemp_id = attr.value().toInt(); - } else if (attr.name() == "name") { - folder->set_name(attr.value().toString()); - } else if (attr.name() == "parent") { - folder->temp_id2 = attr.value().toInt(); - } - } - loaded_folders.append(folder); - } - break; - case MEDIA_TYPE_FOOTAGE: - { - int folder = 0; + while (!stream.atEnd() && !cancelled) { + read_next_start_element(stream); + if (stream.name() == root_search) { + if (type == LOAD_TYPE_VERSION) { + int proj_version = stream.readElementText().toInt(); + if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { + if (QMessageBox::warning(mainWindow, "Version Mismatch", "This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + show_err = false; + return false; + } + } + } else if (type == LOAD_TYPE_URL) { + internal_proj_url = stream.readElementText(); + internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); + } else { + while (!cancelled && !(stream.name() == root_search && stream.isEndElement())) { + read_next(stream); + if (stream.name() == child_search && stream.isStartElement()) { + switch (type) { + case MEDIA_TYPE_FOLDER: + { + Media* folder = panel_project->new_folder(0); + folder->temp_id2 = 0; + for (int j=0;jtemp_id = attr.value().toInt(); + } else if (attr.name() == "name") { + folder->set_name(attr.value().toString()); + } else if (attr.name() == "parent") { + folder->temp_id2 = attr.value().toInt(); + } + } + loaded_folders.append(folder); + } + break; + case MEDIA_TYPE_FOOTAGE: + { + int folder = 0; - Media* item = new Media(0); - Footage* m = new Footage(); + Media* item = new Media(0); + Footage* m = new Footage(); - m->using_inout = false; + m->using_inout = false; - for (int j=0;jsave_id = attr.value().toInt(); - } else if (attr.name() == "folder") { - folder = attr.value().toInt(); - } else if (attr.name() == "name") { - m->name = attr.value().toString(); - } else if (attr.name() == "url") { - m->url = attr.value().toString(); + for (int j=0;jsave_id = attr.value().toInt(); + } else if (attr.name() == "folder") { + folder = attr.value().toInt(); + } else if (attr.name() == "name") { + m->name = attr.value().toString(); + } else if (attr.name() == "url") { + m->url = attr.value().toString(); - if (!QFileInfo::exists(m->url)) { // if path is not absolute - QString proj_dir_test = proj_dir.absoluteFilePath(m->url); - QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(m->url); + if (!QFileInfo::exists(m->url)) { // if path is not absolute + QString proj_dir_test = proj_dir.absoluteFilePath(m->url); + QString internal_proj_dir_test = internal_proj_dir.absoluteFilePath(m->url); - if (QFileInfo::exists(proj_dir_test)) { // if path is relative to the project's current dir - m->url = proj_dir_test; - dout << "[INFO] Matched" << attr.value().toString() << "relative to project's current directory"; - } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in - m->url = internal_proj_dir_test; - dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; - } else if (m->url.contains('%')) { - // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) - m->url = internal_proj_dir_test; - dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's current directory"; - } else { - dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; - } - } else { - dout << "[INFO] Matched" << attr.value().toString() << "with absolute path"; - } - } else if (attr.name() == "duration") { - m->length = attr.value().toLongLong(); - } else if (attr.name() == "using_inout") { - m->using_inout = (attr.value() == "1"); - } else if (attr.name() == "in") { - m->in = attr.value().toLong(); - } else if (attr.name() == "out") { - m->out = attr.value().toLong(); - } - } + if (QFileInfo::exists(proj_dir_test)) { // if path is relative to the project's current dir + m->url = proj_dir_test; + dout << "[INFO] Matched" << attr.value().toString() << "relative to project's current directory"; + } else if (QFileInfo::exists(internal_proj_dir_test)) { // if path is relative to the last directory the project was saved in + m->url = internal_proj_dir_test; + dout << "[INFO] Matched" << attr.value().toString() << "relative to project's internal directory"; + } else if (m->url.contains('%')) { + // hack for image sequences (qt won't be able to find the URL with %, but ffmpeg may) + m->url = internal_proj_dir_test; + dout << "[INFO] Guess image sequence" << attr.value().toString() << "path to project's internal directory"; + } else { + dout << "[INFO] Failed to match" << attr.value().toString() << "to file"; + } + } else { + dout << "[INFO] Matched" << attr.value().toString() << "with absolute path"; + } + } else if (attr.name() == "duration") { + m->length = attr.value().toLongLong(); + } else if (attr.name() == "using_inout") { + m->using_inout = (attr.value() == "1"); + } else if (attr.name() == "in") { + m->in = attr.value().toLong(); + } else if (attr.name() == "out") { + m->out = attr.value().toLong(); + } else if (attr.name() == "speed") { + m->speed = attr.value().toDouble(); + } + } - item->set_footage(m); + item->set_footage(m); - project_model.appendChild(find_loaded_folder_by_id(folder), item); + project_model.appendChild(find_loaded_folder_by_id(folder), item); - // analyze media to see if it's the same - loaded_media_items.append(item); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Media* parent = NULL; - Sequence* s = new Sequence(); + // analyze media to see if it's the same + loaded_media_items.append(item); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Media* parent = NULL; + Sequence* s = new Sequence(); - // load attributes about sequence - for (int j=0;jname = attr.value().toString(); - } else if (attr.name() == "folder") { - int folder = attr.value().toInt(); - if (folder > 0) parent = find_loaded_folder_by_id(folder); - } else if (attr.name() == "id") { - s->save_id = attr.value().toInt(); - } else if (attr.name() == "width") { - s->width = attr.value().toInt(); - } else if (attr.name() == "height") { - s->height = attr.value().toInt(); - } else if (attr.name() == "framerate") { - s->frame_rate = attr.value().toDouble(); - } else if (attr.name() == "afreq") { - s->audio_frequency = attr.value().toInt(); - } else if (attr.name() == "alayout") { - s->audio_layout = attr.value().toInt(); - } else if (attr.name() == "open") { - open_seq = s; - } else if (attr.name() == "workarea") { - s->using_workarea = (attr.value() == "1"); - } else if (attr.name() == "workareaIn") { - s->workarea_in = attr.value().toLong(); - } else if (attr.name() == "workareaOut") { - s->workarea_out = attr.value().toLong(); - } - } + // load attributes about sequence + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "folder") { + int folder = attr.value().toInt(); + if (folder > 0) parent = find_loaded_folder_by_id(folder); + } else if (attr.name() == "id") { + s->save_id = attr.value().toInt(); + } else if (attr.name() == "width") { + s->width = attr.value().toInt(); + } else if (attr.name() == "height") { + s->height = attr.value().toInt(); + } else if (attr.name() == "framerate") { + s->frame_rate = attr.value().toDouble(); + } else if (attr.name() == "afreq") { + s->audio_frequency = attr.value().toInt(); + } else if (attr.name() == "alayout") { + s->audio_layout = attr.value().toInt(); + } else if (attr.name() == "open") { + open_seq = s; + } else if (attr.name() == "workarea") { + s->using_workarea = (attr.value() == "1"); + } else if (attr.name() == "workareaIn") { + s->workarea_in = attr.value().toLong(); + } else if (attr.name() == "workareaOut") { + s->workarea_out = attr.value().toLong(); + } + } - QVector transition_data; + QVector transition_data; - // load all clips and clip information - while (!cancelled && !(stream.name() == child_search && stream.isEndElement()) && !stream.atEnd()) { - read_next_start_element(stream); - if (stream.name() == "marker" && stream.isStartElement()) { - Marker m; - for (int j=0;jmarkers.append(m); - } else if (stream.name() == "transition" && stream.isStartElement()) { - TransitionData td; - td.otc = NULL; - td.ctc = NULL; - for (int j=0;jmarkers.append(m); + } else if (stream.name() == "transition" && stream.isStartElement()) { + TransitionData td; + td.otc = NULL; + td.ctc = NULL; + for (int j=0;jautoscale = false; + // backwards compatibility code + c->autoscale = false; - c->media = NULL; + c->media = NULL; - for (int j=0;jname = attr.value().toString(); - } else if (attr.name() == "enabled") { - c->enabled = (attr.value() == "1"); - } else if (attr.name() == "id") { - c->load_id = attr.value().toInt(); - } else if (attr.name() == "clipin") { - c->clip_in = attr.value().toLong(); - } else if (attr.name() == "in") { - c->timeline_in = attr.value().toLong(); - } else if (attr.name() == "out") { - c->timeline_out = attr.value().toLong(); - } else if (attr.name() == "track") { - c->track = attr.value().toInt(); - } else if (attr.name() == "r") { - c->color_r = attr.value().toInt(); - } else if (attr.name() == "g") { - c->color_g = attr.value().toInt(); - } else if (attr.name() == "b") { - c->color_b = attr.value().toInt(); - } else if (attr.name() == "autoscale") { - c->autoscale = (attr.value() == "1"); - } else if (attr.name() == "media") { - media_type = MEDIA_TYPE_FOOTAGE; - media_id = attr.value().toInt(); - } else if (attr.name() == "stream") { - stream_id = attr.value().toInt(); - } else if (attr.name() == "speed") { - c->speed = attr.value().toDouble(); - } else if (attr.name() == "maintainpitch") { - c->maintain_audio_pitch = (attr.value() == "1"); - } else if (attr.name() == "reverse") { - c->reverse = (attr.value() == "1"); - } else if (attr.name() == "opening") { - c->opening_transition = attr.value().toInt(); - } else if (attr.name() == "closing") { - c->closing_transition = attr.value().toInt(); - } else if (attr.name() == "sequence") { - media_type = MEDIA_TYPE_SEQUENCE; + for (int j=0;jname = attr.value().toString(); + } else if (attr.name() == "enabled") { + c->enabled = (attr.value() == "1"); + } else if (attr.name() == "id") { + c->load_id = attr.value().toInt(); + } else if (attr.name() == "clipin") { + c->clip_in = attr.value().toLong(); + } else if (attr.name() == "in") { + c->timeline_in = attr.value().toLong(); + } else if (attr.name() == "out") { + c->timeline_out = attr.value().toLong(); + } else if (attr.name() == "track") { + c->track = attr.value().toInt(); + } else if (attr.name() == "r") { + c->color_r = attr.value().toInt(); + } else if (attr.name() == "g") { + c->color_g = attr.value().toInt(); + } else if (attr.name() == "b") { + c->color_b = attr.value().toInt(); + } else if (attr.name() == "autoscale") { + c->autoscale = (attr.value() == "1"); + } else if (attr.name() == "media") { + media_type = MEDIA_TYPE_FOOTAGE; + media_id = attr.value().toInt(); + } else if (attr.name() == "stream") { + stream_id = attr.value().toInt(); + } else if (attr.name() == "speed") { + c->speed = attr.value().toDouble(); + } else if (attr.name() == "maintainpitch") { + c->maintain_audio_pitch = (attr.value() == "1"); + } else if (attr.name() == "reverse") { + c->reverse = (attr.value() == "1"); + } else if (attr.name() == "opening") { + c->opening_transition = attr.value().toInt(); + } else if (attr.name() == "closing") { + c->closing_transition = attr.value().toInt(); + } else if (attr.name() == "sequence") { + media_type = MEDIA_TYPE_SEQUENCE; - // since we haven't finished loading sequences, we defer linking this until later - c->media = NULL; - c->media_stream = attr.value().toInt(); - loaded_clips.append(c); - } - } + // since we haven't finished loading sequences, we defer linking this until later + c->media = NULL; + c->media_stream = attr.value().toInt(); + loaded_clips.append(c); + } + } - // set media and media stream - switch (media_type) { - case MEDIA_TYPE_FOOTAGE: - if (media_id >= 0) { - for (int j=0;jto_footage(); - if (m->save_id == media_id) { - c->media = loaded_media_items.at(j); - c->media_stream = stream_id; - break; - } - } - } - break; - } + // set media and media stream + switch (media_type) { + case MEDIA_TYPE_FOOTAGE: + if (media_id >= 0) { + for (int j=0;jto_footage(); + if (m->save_id == media_id) { + c->media = loaded_media_items.at(j); + c->media_stream = stream_id; + break; + } + } + } + break; + } - // load links and effects - while (!cancelled && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { - read_next(stream); - if (stream.isStartElement()) { - if (stream.name() == "linked") { - while (!cancelled && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { - read_next(stream); - if (stream.name() == "link" && stream.isStartElement()) { - for (int k=0;klinked.append(link_attr.value().toInt()); - break; - } - } - } - } - if (cancelled) return false; - } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { - // "opening" and "closing" are backwards compatibility code - load_effect(stream, c); - } - } - } - if (cancelled) return false; + // load links and effects + while (!cancelled && !(stream.name() == "clip" && stream.isEndElement()) && !stream.atEnd()) { + read_next(stream); + if (stream.isStartElement()) { + if (stream.name() == "linked") { + while (!cancelled && !(stream.name() == "linked" && stream.isEndElement()) && !stream.atEnd()) { + read_next(stream); + if (stream.name() == "link" && stream.isStartElement()) { + for (int k=0;klinked.append(link_attr.value().toInt()); + break; + } + } + } + } + if (cancelled) return false; + } else if (stream.isStartElement() && (stream.name() == "effect" || stream.name() == "opening" || stream.name() == "closing")) { + // "opening" and "closing" are backwards compatibility code + load_effect(stream, c); + } + } + } + if (cancelled) return false; - s->clips.append(c); - } - } - if (cancelled) return false; + s->clips.append(c); + } + } + if (cancelled) return false; - // correct links, clip IDs, transitions - for (int i=0;iclips.size();i++) { - // correct links - Clip* correct_clip = s->clips.at(i); - for (int j=0;jlinked.size();j++) { - bool found = false; - for (int k=0;kclips.size();k++) { - if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) { - correct_clip->linked[j] = k; - found = true; - break; - } - } - if (!found) { - correct_clip->linked.removeAt(j); - j--; - if (QMessageBox::warning(mainWindow, "Invalid Clip Link", "This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - delete s; - return false; - } - } - } + // correct links, clip IDs, transitions + for (int i=0;iclips.size();i++) { + // correct links + Clip* correct_clip = s->clips.at(i); + for (int j=0;jlinked.size();j++) { + bool found = false; + for (int k=0;kclips.size();k++) { + if (s->clips.at(k)->load_id == correct_clip->linked.at(j)) { + correct_clip->linked[j] = k; + found = true; + break; + } + } + if (!found) { + correct_clip->linked.removeAt(j); + j--; + if (QMessageBox::warning(mainWindow, "Invalid Clip Link", "This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { + delete s; + return false; + } + } + } - // re-link clips to transitions - if (correct_clip->opening_transition > -1) { - for (int j=0;jopening_transition) { - transition_data[j].otc = correct_clip; - } - } - } - if (correct_clip->closing_transition > -1) { - for (int j=0;jclosing_transition) { - transition_data[j].ctc = correct_clip; - } - } - } - } + // re-link clips to transitions + if (correct_clip->opening_transition > -1) { + for (int j=0;jopening_transition) { + transition_data[j].otc = correct_clip; + } + } + } + if (correct_clip->closing_transition > -1) { + for (int j=0;jclosing_transition) { + transition_data[j].ctc = correct_clip; + } + } + } + } - // create transitions - for (int i=0;iopening_transition = -1; - if (td.ctc != NULL) td.ctc->closing_transition = -1; + // create transitions + for (int i=0;iopening_transition = -1; + if (td.ctc != NULL) td.ctc->closing_transition = -1; } else { emit start_create_dual_transition(&td, primary, secondary, meta); waitCond.wait(&mutex); - } - } - } + } + } + } - Media* m = panel_project->new_sequence(NULL, s, false, parent); + Media* m = panel_project->new_sequence(NULL, s, false, parent); - loaded_sequences.append(m); - } - break; - } - } - } - if (cancelled) return false; - } - break; - } - } - return !cancelled; + loaded_sequences.append(m); + } + break; + } + } + } + if (cancelled) return false; + } + break; + } + } + return !cancelled; } Media* LoadThread::find_loaded_folder_by_id(int id) { - if (id == 0) return NULL; - for (int j=0;jtemp_id == id) { - return parent_item; - } - } - return NULL; + if (id == 0) return NULL; + for (int j=0;jtemp_id == id) { + return parent_item; + } + } + return NULL; } void LoadThread::run() { - mutex.lock(); + mutex.lock(); - QFile file(project_url); - if (!file.open(QIODevice::ReadOnly)) { - dout << "[ERROR] Could not open file"; - return; - } + QFile file(project_url); + if (!file.open(QIODevice::ReadOnly)) { + dout << "[ERROR] Could not open file"; + return; + } - /* set up directories to search for media - * most of the time, these will be the same but in - * case the project file has moved without the footage, - * we check both - */ - proj_dir = QFileInfo(project_url).absoluteDir(); - internal_proj_dir = QFileInfo(project_url).absoluteDir(); - internal_proj_url = project_url; + /* set up directories to search for media + * most of the time, these will be the same but in + * case the project file has moved without the footage, + * we check both + */ + proj_dir = QFileInfo(project_url).absoluteDir(); + internal_proj_dir = QFileInfo(project_url).absoluteDir(); + internal_proj_url = project_url; - QXmlStreamReader stream(&file); + QXmlStreamReader stream(&file); - bool cont = false; - error_str.clear(); - show_err = true; + bool cont = false; + error_str.clear(); + show_err = true; - // temp variables for loading (unnecessary?) - open_seq = NULL; - loaded_folders.clear(); - loaded_media_items.clear(); - loaded_clips.clear(); - loaded_sequences.clear(); + // temp variables for loading (unnecessary?) + open_seq = NULL; + loaded_folders.clear(); + loaded_media_items.clear(); + loaded_clips.clear(); + loaded_sequences.clear(); - // get "element" count - current_element_count = 0; - total_element_count = 0; - while (!cancelled && !stream.atEnd()) { - stream.readNextStartElement(); - if (is_element(stream)) { - total_element_count++; - } - } - cont = !cancelled; + // get "element" count + current_element_count = 0; + total_element_count = 0; + while (!cancelled && !stream.atEnd()) { + stream.readNextStartElement(); + if (is_element(stream)) { + total_element_count++; + } + } + cont = !cancelled; - // find project file version - cont = load_worker(file, stream, LOAD_TYPE_VERSION); + // find project file version + cont = load_worker(file, stream, LOAD_TYPE_VERSION); - // find project's internal URL + // find project's internal URL cont = load_worker(file, stream, LOAD_TYPE_URL); - // load folders first - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); - } + // load folders first + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_FOLDER); + } - // load media - if (cont) { - // since folders loaded correctly, organize them appropriately - for (int i=0;itemp_id2; - project_model.appendChild(find_loaded_folder_by_id(parent), folder); - } + // load media + if (cont) { + // since folders loaded correctly, organize them appropriately + for (int i=0;itemp_id2; + project_model.appendChild(find_loaded_folder_by_id(parent), folder); + } - cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); - } + cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE); + } - // load sequences - if (cont) { - cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); - } + // load sequences + if (cont) { + cont = load_worker(file, stream, MEDIA_TYPE_SEQUENCE); + } - if (!cancelled) { - if (!cont) { + if (!cancelled) { + if (!cont) { xml_error = false; if (show_err) emit error(); - } else if (stream.hasError()) { + } else if (stream.hasError()) { error_str = stream.errorString(); xml_error = true; emit error(); - cont = false; + cont = false; - } else { - // attach nested sequence clips to their sequences - for (int i=0;imedia == NULL && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { - loaded_clips.at(i)->media = loaded_sequences.at(j); - loaded_clips.at(i)->refresh(); - break; - } - } - } - } - } - - if (cont) { - emit success(); // run in main thread - - for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); - } + } else { + // attach nested sequence clips to their sequences + for (int i=0;imedia == NULL && loaded_clips.at(i)->media_stream == loaded_sequences.at(j)->to_sequence()->save_id) { + loaded_clips.at(i)->media = loaded_sequences.at(j); + loaded_clips.at(i)->refresh(); + break; + } + } + } + } } - file.close(); + if (cont) { + emit success(); // run in main thread - mutex.unlock(); + for (int i=0;istart_preview_generator(loaded_media_items.at(i), true); + } + } + + file.close(); + + mutex.unlock(); } void LoadThread::cancel() { - waitCond.wakeAll(); + waitCond.wakeAll(); cancelled = true; } @@ -663,64 +665,64 @@ void LoadThread::success_func() { counter++; } mainWindow->updateTitle(orig_filename); - } else { - panel_project->add_recent_project(project_url); - } + } else { + panel_project->add_recent_project(project_url); + } - mainWindow->setWindowModified(autorecovery); - if (open_seq != NULL) set_sequence(open_seq); - update_ui(false); + mainWindow->setWindowModified(autorecovery); + if (open_seq != NULL) set_sequence(open_seq); + update_ui(false); } void LoadThread::create_effect_ui( - QXmlStreamReader* stream, - Clip* c, - int type, - const EffectMeta* meta, - long effect_length, - bool effect_enabled) + QXmlStreamReader* stream, + Clip* c, + int type, + const EffectMeta* meta, + long effect_length, + bool effect_enabled) { - /* This is extremely hacky - prepare yourself. - * - * When moving project loading to a separate thread, it was soon discovered - * that effects wouldn't load correctly anymore. They were actually still - * "functional", but there were no controls appearing in EffectControls. - * - * Turns out since Effect creates its UI in its constructor, the UI was - * created in this thread rather than the main GUI thread, which is a big - * no-no. Unfortunately the design of Effect does not separate UI and data, - * so having the UI set up was integral to creating annd loading the effect. - * - * Therefore, rather than rewrite the class (I just rewrote QTreeWidget to - * QTreeView with a custom model/item so I'm exhausted), for - * quick-n-dirty-ness, I made LoadThread offload the effect creation to the - * main thread (and since the effect loads data from the same XML stream, - * the LoadThread has to wait for the effect to finish before it can - * continue. - * - * Sorry. I'll fix it one day. - */ + /* This is extremely hacky - prepare yourself. + * + * When moving project loading to a separate thread, it was soon discovered + * that effects wouldn't load correctly anymore. They were actually still + * "functional", but there were no controls appearing in EffectControls. + * + * Turns out since Effect creates its UI in its constructor, the UI was + * created in this thread rather than the main GUI thread, which is a big + * no-no. Unfortunately the design of Effect does not separate UI and data, + * so having the UI set up was integral to creating annd loading the effect. + * + * Therefore, rather than rewrite the class (I just rewrote QTreeWidget to + * QTreeView with a custom model/item so I'm exhausted), for + * quick-n-dirty-ness, I made LoadThread offload the effect creation to the + * main thread (and since the effect loads data from the same XML stream, + * the LoadThread has to wait for the effect to finish before it can + * continue. + * + * Sorry. I'll fix it one day. + */ - if (cancelled) return; - if (type == TA_NO_TRANSITION) { - Effect* e = create_effect(c, meta); - e->set_enabled(effect_enabled); - e->load(*stream); + if (cancelled) return; + if (type == TA_NO_TRANSITION) { + Effect* e = create_effect(c, meta); + e->set_enabled(effect_enabled); + e->load(*stream); - c->effects.append(e); - } else { - int transition_index = create_transition(c, NULL, meta); - Transition* t = c->sequence->transitions.at(transition_index); - if (effect_length > -1) t->set_length(effect_length); - t->set_enabled(effect_enabled); - t->load(*stream); + c->effects.append(e); + } else { + int transition_index = create_transition(c, NULL, meta); + Transition* t = c->sequence->transitions.at(transition_index); + if (effect_length > -1) t->set_length(effect_length); + t->set_enabled(effect_enabled); + t->load(*stream); - if (type == TA_OPENING_TRANSITION) { - c->opening_transition = transition_index; - } else { - c->closing_transition = transition_index; - } - } + if (type == TA_OPENING_TRANSITION) { + c->opening_transition = transition_index; + } else { + c->closing_transition = transition_index; + } + } waitCond.wakeAll(); } diff --git a/io/math.cpp b/io/math.cpp index c26bb5cad..4d71229a4 100644 --- a/io/math.cpp +++ b/io/math.cpp @@ -1,15 +1,54 @@ #include "math.h" #include +#include + +#include "debug.h" int lerp(int a, int b, double t) { - return qRound(((1.0 - t) * a) + (t * b)); + return qRound(((1.0 - t) * a) + (t * b)); } float float_lerp(float a, float b, float t) { - return ((1.0F - t) * a) + (t * b); + return ((1.0F - t) * a) + (t * b); } double double_lerp(double a, double b, double t) { - return ((1.0 - t) * a) + (t * b); + return ((1.0 - t) * a) + (t * b); +} + +double quad_from_t(double a, double b, double c, double t) { + return qPow(1.0 - t, 2)*a + 2*(1.0 - t)*t*b + qPow(t, 2)*c; +} + +double quad_t_from_x(double x, double a, double b, double c) { + return (a - b + qSqrt(a*x + c*x - 2*b*x + qPow(b, 2) - a*c))/(a - 2*b + c); + // alt: return (a - b - qSqrt(a*x + c*x - 2*b*x + qPow(b, 2) - a*c))/(a - 2*b + c); +} + +double cubic_from_t(double a, double b, double c, double d, double t) { + return qPow(1.0 - t, 3)*a + 3*qPow(1.0 - t, 2)*t*b + 3*(1.0 - t)*qPow(t, 2)*c + qPow(t, 3)*d; +} + +double cubic_t_from_x(double x_target, double a, double b, double c, double d) { + double tolerance = 0.0001; + + double lower = 0.0; + double upper = 1.0; + + double percent = 0.5; + double x = cubic_from_t(a, b, c, d, percent); + + while (qAbs(x_target - x) > tolerance) { + if (x_target > x) { + lower = percent; + } else { + upper = percent; + } + + percent = (upper + lower) / 2.0; + x = cubic_from_t(a, b, c, d, percent); + } + + return percent; } diff --git a/io/math.h b/io/math.h index 927e988ac..ba1e7ed9e 100644 --- a/io/math.h +++ b/io/math.h @@ -4,5 +4,10 @@ int lerp(int a, int b, double t); float float_lerp(float a, float b, float t); double double_lerp(double a, double b, double t); +double quad_from_t(double a, double b, double c, double t); +double quad_t_from_x(double x, double a, double b, double c); +double cubic_from_t(double a, double b, double c, double d, double t); +double cubic_t_from_x(double x_target, double a, double b, double c, double d); +double solveCubicBezier(double p0, double p1, double p2, double p3, double x); #endif // MATH_H diff --git a/io/path.cpp b/io/path.cpp index b90e13345..2d24812f0 100644 --- a/io/path.cpp +++ b/io/path.cpp @@ -9,17 +9,26 @@ QString real_app_dir; QString get_app_dir() { if (real_app_dir.isEmpty()) { - QString app_path = QCoreApplication::applicationFilePath(); - real_app_dir = app_path.left(app_path.lastIndexOf('/')); - } - return real_app_dir; + QString app_path = QCoreApplication::applicationFilePath(); + real_app_dir = app_path.left(app_path.lastIndexOf('/')); + } + return real_app_dir; } QString get_data_path() { - QString app_dir = get_app_dir(); - if (QFileInfo::exists(app_dir + "/portable")) { - return app_dir; - } else { - return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - } + QString app_dir = get_app_dir(); + if (QFileInfo::exists(app_dir + "/portable")) { + return app_dir; + } else { + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + } +} + +QString get_config_path() { + QString app_dir = get_app_dir(); + if (QFileInfo::exists(app_dir + "/portable")) { + return app_dir; + } else { + return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + } } diff --git a/io/path.h b/io/path.h index 31ff1e651..9eff9033f 100644 --- a/io/path.h +++ b/io/path.h @@ -5,5 +5,6 @@ QString get_app_dir(); QString get_data_path(); +QString get_config_path(); #endif // PATH_H diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index bb24f3cbd..afb753ef4 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -55,14 +55,13 @@ void PreviewGenerator::parse_media() { if (avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id) == NULL) { dout << "[ERROR] Unsupported codec in stream" << i << "of file" << footage->name; } else { - FootageStream* ms = footage->get_stream_from_file_index(fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, i); + FootageStream ms; + ms.preview_done = false; + ms.file_index = i; + ms.enabled = true; + bool append = false; - if (ms == NULL) { - ms = new FootageStream(); - ms->preview_done = false; - ms->file_index = i; - append = true; - } + if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && fmt_ctx->streams[i]->codecpar->width > 0 && fmt_ctx->streams[i]->codecpar->height > 0) { @@ -76,33 +75,43 @@ void PreviewGenerator::parse_media() { // heuristic to determine if video is a still image if (fmt_ctx->streams[i]->avg_frame_rate.den == 0 && fmt_ctx->streams[i]->codecpar->codec_id != AV_CODEC_ID_DNXHD) { // silly hack but this is the only scenario i've seen this - ms->infinite_length = true; + ms.infinite_length = true; contains_still_image = true; - ms->video_frame_rate = 0; + ms.video_frame_rate = 0; } else { - ms->infinite_length = false; + ms.infinite_length = false; if (fmt_ctx->streams[i]->r_frame_rate.den == 0) { - ms->video_frame_rate = av_q2d(fmt_ctx->streams[i]->avg_frame_rate); + ms.video_frame_rate = av_q2d(fmt_ctx->streams[i]->avg_frame_rate); } else { - ms->video_frame_rate = av_q2d(fmt_ctx->streams[i]->r_frame_rate); + ms.video_frame_rate = av_q2d(fmt_ctx->streams[i]->r_frame_rate); } } - ms->video_width = fmt_ctx->streams[i]->codecpar->width; - ms->video_height = fmt_ctx->streams[i]->codecpar->height; + ms.video_width = fmt_ctx->streams[i]->codecpar->width; + ms.video_height = fmt_ctx->streams[i]->codecpar->height; // default value, we get the true value later in generate_waveform() - ms->video_auto_interlacing = VIDEO_PROGRESSIVE; - ms->video_interlacing = VIDEO_PROGRESSIVE; + ms.video_auto_interlacing = VIDEO_PROGRESSIVE; + ms.video_interlacing = VIDEO_PROGRESSIVE; - if (append) footage->video_tracks.append(ms); + append = true; } else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - ms->audio_channels = fmt_ctx->streams[i]->codecpar->channels; - ms->audio_layout = fmt_ctx->streams[i]->codecpar->channel_layout; - ms->audio_frequency = fmt_ctx->streams[i]->codecpar->sample_rate; - if (append) footage->audio_tracks.append(ms); - } else if (append) { - delete ms; - } + ms.audio_channels = fmt_ctx->streams[i]->codecpar->channels; + ms.audio_layout = fmt_ctx->streams[i]->codecpar->channel_layout; + ms.audio_frequency = fmt_ctx->streams[i]->codecpar->sample_rate; + + append = true; + } + + if (append) { + QVector& stream_list = (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) ? footage->audio_tracks : footage->video_tracks; + for (int j=0;jlength = fmt_ctx->duration; @@ -123,32 +132,32 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { bool found = true; for (int i=0;ivideo_tracks.size();i++) { - FootageStream* ms = footage->video_tracks.at(i); + FootageStream& ms = footage->video_tracks[i]; QString thumb_path = get_thumbnail_path(hash, ms); QFile f(thumb_path); - if (f.exists() && ms->video_preview.load(thumb_path)) { + if (f.exists() && ms.video_preview.load(thumb_path)) { //dout << "loaded thumb" << ms->file_index << "from" << thumb_path; - ms->make_square_thumb(); - ms->preview_done = true; + ms.make_square_thumb(); + ms.preview_done = true; } else { found = false; break; } } for (int i=0;iaudio_tracks.size();i++) { - FootageStream* ms = footage->audio_tracks.at(i); + FootageStream& ms = footage->audio_tracks[i]; QString waveform_path = get_waveform_path(hash, ms); QFile f(waveform_path); if (f.exists()) { //dout << "loaded wave" << ms->file_index << "from" << waveform_path; f.open(QFile::ReadOnly); QByteArray data = f.readAll(); - ms->audio_preview.resize(data.size()); + ms.audio_preview.resize(data.size()); for (int j=0;jaudio_preview[j] = data.at(j); + ms.audio_preview[j] = data.at(j); } - ms->preview_done = true; + ms.preview_done = true; f.close(); } else { found = false; @@ -157,13 +166,13 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { } if (!found) { for (int i=0;ivideo_tracks.size();i++) { - FootageStream* ms = footage->video_tracks.at(i); - ms->preview_done = false; + FootageStream& ms = footage->video_tracks[i]; + ms.preview_done = false; } for (int i=0;iaudio_tracks.size();i++) { - FootageStream* ms = footage->audio_tracks.at(i); - ms->audio_preview.clear(); - ms->preview_done = false; + FootageStream& ms = footage->audio_tracks[i]; + ms.audio_preview.clear(); + ms.preview_done = false; } } return !found; @@ -336,7 +345,7 @@ void PreviewGenerator::generate_waveform() { break; } } - s->audio_preview.append(min >> 8); + s->audio_preview.append(min >> 8); s->audio_preview.append(max >> 8); if (cancelled) break; } @@ -359,7 +368,7 @@ void PreviewGenerator::generate_waveform() { } else if (footage->audio_tracks.size() == 0) { done = true; for (int i=0;ivideo_tracks.size();i++) { - if (!footage->video_tracks.at(i)->preview_done) { + if (!footage->video_tracks.at(i).preview_done) { done = false; break; } @@ -373,7 +382,7 @@ void PreviewGenerator::generate_waveform() { } } for (int i=0;iaudio_tracks.size();i++) { - footage->audio_tracks.at(i)->preview_done = true; + footage->audio_tracks[i].preview_done = true; } av_frame_free(&temp_frame); av_packet_free(&packet); @@ -397,12 +406,12 @@ void PreviewGenerator::generate_waveform() { delete [] codec_ctx; } -QString PreviewGenerator::get_thumbnail_path(const QString& hash, FootageStream* ms) { - return data_path + "/" + hash + "t" + QString::number(ms->file_index); +QString PreviewGenerator::get_thumbnail_path(const QString& hash, const FootageStream& ms) { + return data_path + "/" + hash + "t" + QString::number(ms.file_index); } -QString PreviewGenerator::get_waveform_path(const QString& hash, FootageStream* ms) { - return data_path + "/" + hash + "w" + QString::number(ms->file_index); +QString PreviewGenerator::get_waveform_path(const QString& hash, const FootageStream& ms) { + return data_path + "/" + hash + "w" + QString::number(ms.file_index); } void PreviewGenerator::run() { @@ -445,15 +454,15 @@ void PreviewGenerator::run() { // save preview to file for (int i=0;ivideo_tracks.size();i++) { - FootageStream* ms = footage->video_tracks.at(i); - ms->video_preview.save(get_thumbnail_path(hash, ms), "PNG"); + FootageStream& ms = footage->video_tracks[i]; + ms.video_preview.save(get_thumbnail_path(hash, ms), "PNG"); //dout << "saved" << ms->file_index << "thumbnail to" << get_thumbnail_path(hash, ms); } for (int i=0;iaudio_tracks.size();i++) { - FootageStream* ms = footage->audio_tracks.at(i); + FootageStream& ms = footage->audio_tracks[i]; QFile f(get_waveform_path(hash, ms)); f.open(QFile::WriteOnly); - f.write(ms->audio_preview.constData(), ms->audio_preview.size()); + f.write(ms.audio_preview.constData(), ms.audio_preview.size()); f.close(); //dout << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms); } diff --git a/io/previewgenerator.h b/io/previewgenerator.h index 2f1f86c7d..3065bb072 100644 --- a/io/previewgenerator.h +++ b/io/previewgenerator.h @@ -36,8 +36,8 @@ private: bool replace; bool cancelled; QString data_path; - QString get_thumbnail_path(const QString &hash, FootageStream* ms); - QString get_waveform_path(const QString& hash, FootageStream* ms); + QString get_thumbnail_path(const QString &hash, const FootageStream &ms); + QString get_waveform_path(const QString& hash, const FootageStream &ms); }; #endif // PREVIEWGENERATOR_H diff --git a/main.cpp b/main.cpp index 4286a3bd0..8dd018067 100644 --- a/main.cpp +++ b/main.cpp @@ -9,10 +9,11 @@ extern "C" { int main(int argc, char *argv[]) { // init ffmpeg subsystem av_register_all(); - avfilter_register_all(); + avfilter_register_all(); - QApplication a(argc, argv); + QApplication a(argc, argv); MainWindow w; + if (argc > 1) w.launch_with_project(argv[1]); w.showMaximized(); return a.exec(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 0759112e0..277d6a2b1 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -19,6 +19,7 @@ #include "panels/effectcontrols.h" #include "panels/viewer.h" #include "panels/timeline.h" +#include "panels/grapheditor.h" #include "dialogs/aboutdialog.h" #include "dialogs/newsequencedialog.h" @@ -53,7 +54,7 @@ MainWindow* mainWindow; #define OLIVE_FILE_FILTER "Olive Project (*.ove)" QTimer autorecovery_timer; -QString config_dir; +QString config_fn; QString appName; bool demoNoticeShown = false; @@ -63,31 +64,28 @@ void MainWindow::setup_layout(bool reset) { panel_footage_viewer->show(); panel_sequence_viewer->show(); panel_timeline->show(); + panel_graph_editor->hide(); - bool load_default = true; - - /*if (!reset) { - QFile panel_config(get_data_path() + "/layout"); - if (panel_config.exists() && panel_config.open(QFile::ReadOnly)) { - if (restoreState(panel_config.readAll(), 0)) { - load_default = false; - } - panel_config.close(); - } - }*/ - - if (load_default) { - addDockWidget(Qt::TopDockWidgetArea, panel_project); - addDockWidget(Qt::TopDockWidgetArea, panel_footage_viewer); - tabifyDockWidget(panel_footage_viewer, panel_effect_controls); - panel_footage_viewer->raise(); - addDockWidget(Qt::TopDockWidgetArea, panel_sequence_viewer); - addDockWidget(Qt::BottomDockWidgetArea, panel_timeline); + addDockWidget(Qt::TopDockWidgetArea, panel_project); + addDockWidget(Qt::TopDockWidgetArea, panel_footage_viewer); + tabifyDockWidget(panel_footage_viewer, panel_effect_controls); + panel_footage_viewer->raise(); + addDockWidget(Qt::TopDockWidgetArea, panel_sequence_viewer); + addDockWidget(Qt::BottomDockWidgetArea, panel_timeline); + panel_graph_editor->setFloating(true); // workaround for strange Qt dock bug (see https://bugreports.qt.io/browse/QTBUG-65592) #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) - resizeDocks({panel_project}, {40}, Qt::Horizontal); + resizeDocks({panel_project}, {40}, Qt::Horizontal); #endif + + // load panels from file + if (!reset) { + QFile panel_config(get_config_path() + "/layout"); + if (panel_config.exists() && panel_config.open(QFile::ReadOnly)) { + restoreState(panel_config.readAll(), 0); + panel_config.close(); + } } layout()->update(); @@ -96,13 +94,15 @@ void MainWindow::setup_layout(bool reset) { MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { - appName = "Olive (December 2018 | Alpha"; + appName = "Olive (January 2019 | Alpha"; #ifdef GITHASH appName += " | "; appName += GITHASH; #endif appName += ")"; + enable_launch_with_project = false; + setup_debug(); mainWindow = this; @@ -194,18 +194,19 @@ MainWindow::MainWindow(QWidget *parent) : } f.close(); } - - config_dir = data_dir + "/config.xml"; - config.load(config_dir); + } + } + QString config_path = get_config_path(); + if (!config_path.isEmpty()) { + QDir config_dir(config_path); + config_dir.mkpath("."); + config_fn = config_path + "/config.xml"; + if (QFileInfo::exists(config_fn)) { + config.load(config_fn); } } - // TODO maybe replace these with non-pointers later on? - panel_sequence_viewer = new Viewer(this); - panel_footage_viewer = new Viewer(this); - panel_project = new Project(this); - panel_effect_controls = new EffectControls(this); - panel_timeline = new Timeline(this); + alloc_panels(this); QStatusBar* statusBar = new QStatusBar(this); statusBar->showMessage("Welcome to " + appName); @@ -218,8 +219,8 @@ MainWindow::MainWindow(QWidget *parent) : autorecovery_filename = data_dir + "/autorecovery.ove"; if (QFile::exists(autorecovery_filename)) { if (QMessageBox::question(NULL, "Auto-recovery", "Olive didn't close properly and an autorecovery file was detected. Would you like to open it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - updateTitle(autorecovery_filename); - panel_project->load_project(true); + enable_launch_with_project = false; + open_project_worker(autorecovery_filename, true); } } autorecovery_timer.setInterval(60000); @@ -233,48 +234,15 @@ MainWindow::MainWindow(QWidget *parent) : } MainWindow::~MainWindow() { - panel_effect_controls->clear_effects(true); - panel_sequence_viewer->viewer_widget->delete_function(); - panel_footage_viewer->viewer_widget->delete_function(); - - set_sequence(NULL); - - QString data_dir = get_data_path(); - if (!data_dir.isEmpty() && !autorecovery_filename.isEmpty()) { - if (QFile::exists(autorecovery_filename)) { - QFile::rename(autorecovery_filename, autorecovery_filename + "." + QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss")); - } - } - if (!config_dir.isEmpty()) { - // save settings - config.save(config_dir); - - // save panel layout - QFile panel_config(data_dir + "/layout"); - if (panel_config.open(QFile::WriteOnly)) { - panel_config.write(saveState(0)); - panel_config.close(); - } else { - dout << "[ERROR] Failed to save layout"; - } - } - - stop_audio(); - - delete panel_sequence_viewer; - panel_sequence_viewer = NULL; - delete panel_footage_viewer; - panel_footage_viewer = NULL; - delete panel_project; - panel_project = NULL; - delete panel_effect_controls; - panel_effect_controls = NULL; - delete panel_timeline; - panel_timeline = NULL; - + free_panels(); close_debug(); } +void MainWindow::launch_with_project(const char* s) { + project_url = s; + enable_launch_with_project = true; +} + void MainWindow::make_new_menu(QMenu *parent) { parent->addAction("&Project", this, SLOT(new_project()), QKeySequence("Ctrl+N")); parent->addSeparator(); @@ -304,12 +272,16 @@ void MainWindow::delete_slot() { panel_project->delete_selected_media(); } else if (panel_effect_controls->keyframe_focus()) { panel_effect_controls->delete_selected_keyframes(); + } else if (panel_graph_editor->view_is_focused()) { + panel_graph_editor->delete_selected_keys(); } } void MainWindow::select_all() { if (panel_timeline->focused()) { panel_timeline->select_all(); + } else if (panel_graph_editor->view_is_focused()) { + panel_graph_editor->select_all(); } } @@ -478,6 +450,47 @@ bool MainWindow::can_close_project() { return true; } +void kbd_shortcut_processor(QByteArray& file, QMenu* menu, bool save) { + QList actions = menu->actions(); + for (int i=0;imenu() != NULL) { + kbd_shortcut_processor(file, a->menu(), save); + } else if (!a->isSeparator()) { + if (save) { + // saving custom shortcuts + if (!a->property("default").isNull()) { + QKeySequence defks(a->property("default").toString()); + if (a->shortcut() != defks) { + // custom shortcut + if (!file.isEmpty()) file.append('\n'); + file.append(a->text().replace("&", "")); + file.append('\t'); + file.append(a->shortcut().toString()); + } + } + } else { + // loading custom shortcuts + a->setProperty("default", a->shortcut().toString()); + QString comp_str = a->text().replace("&", ""); + int shortcut_index = file.indexOf(comp_str); + if (shortcut_index == 0 || (shortcut_index > 0 && file.at(shortcut_index-1) == '\n')) { + shortcut_index += comp_str.size() + 1; + QString shortcut; + while (shortcut_index < file.size() && file.at(shortcut_index) != '\n') { + shortcut.append(file.at(shortcut_index)); + shortcut_index++; + } + QKeySequence ks(shortcut); + if (!ks.isEmpty()) { + a->setShortcut(ks); + } + } + } + } + } +} + void MainWindow::setup_menus() { QMenuBar* menuBar = new QMenuBar(this); setMenuBar(menuBar); @@ -655,6 +668,10 @@ void MainWindow::setup_menus() { window_timeline_action->setCheckable(true); window_timeline_action->setData(reinterpret_cast(panel_timeline)); + QAction* window_graph_editor_action = window_menu->addAction("Graph Editor", this, SLOT(toggle_panel_visibility())); + window_graph_editor_action->setCheckable(true); + window_graph_editor_action->setData(reinterpret_cast(panel_graph_editor)); + QAction* window_footageviewer_action = window_menu->addAction("Footage Viewer", this, SLOT(toggle_panel_visibility())); window_footageviewer_action->setCheckable(true); window_footageviewer_action->setData(reinterpret_cast(panel_footage_viewer)); @@ -756,6 +773,10 @@ void MainWindow::setup_menus() { enable_hover_focus->setCheckable(true); enable_hover_focus->setData(reinterpret_cast(&config.hover_focus)); + set_name_and_marker = tools_menu->addAction("Ask For Name When Setting Marker", this, SLOT(toggle_bool_action())); + set_name_and_marker->setCheckable(true); + set_name_and_marker->setData(reinterpret_cast(&config.set_name_with_marker)); + tools_menu->addSeparator(); no_autoscroll = tools_menu->addAction("No Auto-Scroll", this, SLOT(set_autoscroll())); @@ -775,7 +796,7 @@ void MainWindow::setup_menus() { tools_menu->addAction("Preferences", this, SLOT(preferences()), QKeySequence("Ctrl+.")); #ifdef QT_DEBUG - tools_menu->addAction("Clear Undo", this, SLOT(clear_undo_stack()), QKeySequence("Ctrl+.")); + tools_menu->addAction("Clear Undo", this, SLOT(clear_undo_stack())); #endif // INITIALIZE HELP MENU @@ -783,6 +804,16 @@ void MainWindow::setup_menus() { QMenu* help_menu = menuBar->addMenu("&Help"); help_menu->addAction("&About...", this, SLOT(show_about())); + + QFile shortcut_path(get_config_path() + "/shortcuts"); + if (shortcut_path.exists() && shortcut_path.open(QFile::ReadOnly)) { + QList menus = menuBar->actions(); + QByteArray shortcut_bytes = shortcut_path.readAll(); + for (int i=0;imenu(); + kbd_shortcut_processor(shortcut_bytes, menu, false); + } + } } void MainWindow::set_bool_action_checked(QAction *a) { @@ -809,6 +840,50 @@ void MainWindow::updateTitle(const QString& url) { void MainWindow::closeEvent(QCloseEvent *e) { if (can_close_project()) { + panel_effect_controls->clear_effects(true); + + set_sequence(NULL); + + panel_footage_viewer->set_main_sequence(); + + QString data_dir = get_data_path(); + QString config_dir = get_config_path(); + if (!data_dir.isEmpty() && !autorecovery_filename.isEmpty()) { + if (QFile::exists(autorecovery_filename)) { + QFile::rename(autorecovery_filename, autorecovery_filename + "." + QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss")); + } + } + if (!config_dir.isEmpty() && !config_fn.isEmpty()) { + // save settings + config.save(config_fn); + + // save panel layout + QFile panel_config(config_dir + "/layout"); + if (panel_config.open(QFile::WriteOnly)) { + panel_config.write(saveState(0)); + panel_config.close(); + } else { + dout << "[ERROR] Failed to save layout"; + } + + // save main menu actions + QList menus = menuBar()->actions(); + QByteArray shortcut_file; + for (int i=0;imenu(); + kbd_shortcut_processor(shortcut_file, menu, true); + } + QFile shortcut_file_io(config_dir + "/shortcuts"); + if (shortcut_file_io.open(QFile::WriteOnly)) { + shortcut_file_io.write(shortcut_file); + shortcut_file_io.close(); + } else { + dout << "[ERROR] Failed to save shortcut file"; + } + } + + stop_audio(); + e->accept(); } else { e->ignore(); @@ -817,6 +892,10 @@ void MainWindow::closeEvent(QCloseEvent *e) { void MainWindow::paintEvent(QPaintEvent *event) { QMainWindow::paintEvent(event); + if (enable_launch_with_project) { + QTimer::singleShot(10, this, SLOT(load_with_launch())); + enable_launch_with_project = false; + } #ifndef QT_DEBUG if (!demoNoticeShown) { DemoNotice* d = new DemoNotice(this); @@ -833,18 +912,29 @@ void MainWindow::clear_undo_stack() { void MainWindow::open_project() { QString fn = QFileDialog::getOpenFileName(this, "Open Project...", "", OLIVE_FILE_FILTER); if (!fn.isEmpty() && can_close_project()) { - updateTitle(fn); - panel_project->load_project(false); - undo_stack.clear(); + open_project_worker(fn, false); } } +void MainWindow::open_project_worker(const QString& fn, bool autorecovery) { + updateTitle(fn); + panel_project->load_project(autorecovery); + undo_stack.clear(); +} + +void MainWindow::load_with_launch() { + open_project_worker(project_url, false); +} + void MainWindow::reset_layout() { setup_layout(true); } void MainWindow::go_to_start() { - if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus()) { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->go_to_start(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->go_to_start(); @@ -852,7 +942,10 @@ void MainWindow::go_to_start() { } void MainWindow::prev_frame() { - if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus()) { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->previous_frame(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->previous_frame(); @@ -860,7 +953,10 @@ void MainWindow::prev_frame() { } void MainWindow::next_frame() { - if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus()) { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->next_frame(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->next_frame(); @@ -868,7 +964,10 @@ void MainWindow::next_frame() { } void MainWindow::go_to_end() { - if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus()) { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->go_to_end(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->go_to_end(); @@ -876,7 +975,10 @@ void MainWindow::go_to_end() { } void MainWindow::playpause() { - if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus()) { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->toggle_play(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->toggle_play(); @@ -962,6 +1064,7 @@ void MainWindow::toolMenu_About_To_Be_Shown() { set_bool_action_checked(enable_audio_scrubbing); set_bool_action_checked(enable_drop_on_media_to_replace); set_bool_action_checked(enable_hover_focus); + set_bool_action_checked(set_name_and_marker); set_int_action_checked(no_autoscroll, config.autoscroll); set_int_action_checked(page_autoscroll, config.autoscroll); @@ -1019,8 +1122,7 @@ void MainWindow::load_recent_project() { panel_project->save_recent_projects(); } } else if (can_close_project()) { - updateTitle(recent_url); - panel_project->load_project(false); + open_project_worker(recent_url, false); } } @@ -1203,7 +1305,8 @@ void MainWindow::nest() { Media* m = panel_project->new_sequence(ca, s, false, NULL); // add nested sequence to active sequence - QVector media_list = {m}; + QVector media_list; + media_list.append(m); panel_timeline->create_ghosts_from_media(sequence, earliest_point, media_list); panel_timeline->add_clips_from_ghosts(ca, sequence); diff --git a/mainwindow.h b/mainwindow.h index 043b083b3..76c03eb31 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -15,6 +15,8 @@ public: void updateTitle(const QString &url); ~MainWindow(); + void launch_with_project(const char *s); + void make_new_menu(QMenu* parent); void make_inout_menu(QMenu* parent); @@ -110,6 +112,9 @@ private slots: void menu_click_button(); void toggle_panel_visibility(); void set_timecode_view(); + void open_project_worker(const QString &fn, bool autorecovery); + + void load_with_launch(); private: void setup_layout(bool reset); @@ -161,6 +166,7 @@ private: QAction* enable_audio_scrubbing; QAction* enable_drop_on_media_to_replace; QAction* enable_hover_focus; + QAction* set_name_and_marker; // edit menu actions QAction* undo_action; @@ -169,6 +175,8 @@ private: void set_bool_action_checked(QAction* a); void set_int_action_checked(QAction* a, const int& i); void set_button_action_checked(QAction* a); + + bool enable_launch_with_project; }; extern MainWindow* mainWindow; diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 000000000..1daae80cf --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,973 @@ + + + MainWindow + + + + 0 + 0 + 653 + 394 + + + + MainWindow + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + 0 + 0 + 653 + 16 + + + + + &File + + + + &New + + + + + + + + + Open Recent + + + + + + + + + + + + + + + + + &Edit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &Window + + + + + + + + + + + + + &Help + + + + + + &View + + + + Title/Action Safe Area + + + + + + + + + + + + + + + + + + + + + + + + + + + &Playback + + + + + + + + + + + + + &Tools + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &Open Project + + + Ctrl+O + + + + + &Save Project + + + Ctrl+S + + + + + Save Project &As + + + Ctrl+Shift+S + + + + + &Import... + + + Ctrl+I + + + + + &Export... + + + Ctrl+M + + + + + E&xit + + + + + About... + + + + + &Undo + + + Ctrl+Z + + + + + &Redo + + + Ctrl+Shift+Z + + + + + Cu&t + + + Ctrl+X + + + + + Cop&y + + + Ctrl+C + + + + + &Paste + + + Ctrl+V + + + + + Paste Insert + + + Ctrl+Shift+V + + + + + Delete + + + Del + + + + + Select &All + + + Ctrl+A + + + + + true + + + Track Lines + + + + + Project... + + + Ctrl+N + + + + + Sequence... + + + Ctrl+Shift+N + + + + + Zoom In + + + = + + + + + Zoom Out + + + - + + + + + true + + + true + + + Project + + + + + true + + + true + + + Effect Controls + + + + + true + + + true + + + Sequence Viewer + + + + + true + + + true + + + Timeline + + + + + Ripple Delete + + + Shift+Del + + + + + Split at Playhead + + + Ctrl+K + + + + + Deselect All + + + + + Go to Start + + + Home + + + + + Reset to default layout + + + + + Previous Frame + + + Left + + + + + Play/Pause + + + Space + + + + + Next Frame + + + Right + + + + + Go to End + + + End + + + + + Crash + + + false + + + + + Pointer Tool + + + V + + + + + Edit Tool + + + X + + + + + Razor Tool + + + C + + + + + Ripple Tool + + + B + + + + + Rolling Tool + + + N + + + false + + + + + Preferences + + + Ctrl+. + + + + + true + + + Snapping + + + Snapping + + + S + + + + + Slip Tool + + + Y + + + + + Go to Previous Cut + + + Up + + + + + Go to Next Cut + + + Down + + + + + true + + + Edit Tool Also Seeks + + + + + Increase Track Height + + + Ctrl+= + + + + + Decrease Track Height + + + Ctrl+- + + + + + true + + + Frames + + + + + true + + + Drop-Frame + + + + + true + + + Non-Drop Frame + + + + + true + + + Edit Tool Selects Links + + + + + Duplicate + + + Ctrl+D + + + + + true + + + Selecting Also Seeks + + + + + true + + + Seek to the End of Pastes + + + + + Add Default Transition + + + Ctrl+Shift+D + + + + + Slide Tool + + + U + + + + + Folder + + + + + Clear Recent List + + + + + true + + + Scroll Wheel Zooms + + + + + Link/Unlink + + + Ctrl+L + + + + + Ripple to In Point + + + Q + + + + + Ripple to Out Point + + + W + + + + + Set In Point + + + I + + + + + Set Out Point + + + O + + + + + Clear In/Out + + + G + + + + + Delete In/Out + + + ; + + + + + Ripple Delete In/Out + + + ' + + + + + true + + + Rectified Waveforms + + + + + true + + + Default + + + + + true + + + Off + + + + + true + + + 4:3 + + + + + true + + + 16:9 + + + + + true + + + Custom + + + + + true + + + Enable Drag Files to Timeline + + + + + true + + + Auto-scale by Default + + + + + Set/Edit Marker + + + M + + + + + Enable/Disable Clip + + + Shift+E + + + + + true + + + Enable Seek to Import + + + + + true + + + Audio Scrubbing + + + + + Transition Tool + + + T + + + + + Edit to In Point + + + Ctrl+Alt+Q + + + + + Edit to Out Point + + + Ctrl+Alt+W + + + + + Nest + + + + + Toggle Show All + + + \ + + + + + true + + + Enable Drop File on Media to Replace + + + + + true + + + Footage Viewer + + + + + true + + + No Auto-scroll + + + + + true + + + Page Auto-scroll + + + + + true + + + Smooth Auto-scroll + + + + + true + + + Milliseconds + + + + + true + + + Enable Hover Focus for Zooming + + + + + true + + + Full Screen + + + F11 + + + + + Hand Tool + + + H + + + + + true + + + Graph Editor + + + + + + + diff --git a/olive-editor.pro b/olive-editor.pro deleted file mode 120000 index eb1b3bf56..000000000 --- a/olive-editor.pro +++ /dev/null @@ -1 +0,0 @@ -olive.pro \ No newline at end of file diff --git a/olive.pro b/olive.pro index d6815c471..48ed5688a 100644 --- a/olive.pro +++ b/olive.pro @@ -110,7 +110,13 @@ SOURCES += \ ui/resizablescrollbar.cpp \ ui/sourceiconview.cpp \ project/sourcescommon.cpp \ - ui/keyframenavigator.cpp + ui/keyframenavigator.cpp \ + panels/grapheditor.cpp \ + ui/graphview.cpp \ + ui/keyframedrawing.cpp \ + ui/clickablelabel.cpp \ + project/keyframe.cpp \ + ui/rectangleselect.cpp HEADERS += \ mainwindow.h \ @@ -190,18 +196,24 @@ HEADERS += \ ui/resizablescrollbar.h \ ui/sourceiconview.h \ project/sourcescommon.h \ - ui/keyframenavigator.h + ui/keyframenavigator.h \ + panels/grapheditor.h \ + ui/graphview.h \ + ui/keyframedrawing.h \ + ui/clickablelabel.h \ + project/keyframe.h \ + ui/rectangleselect.h FORMS += win32 { - RC_FILE = icons/resources.rc + RC_FILE = packaging/windows/resources.rc LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 } mac { LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample - ICON = icons/olive.icns + ICON = packaging/macos/olive.icns INCLUDEPATH = /usr/local/include } diff --git a/packaging/linux/org.olivevideoeditor.Olive.appdata.xml b/packaging/linux/org.olivevideoeditor.Olive.appdata.xml index fb3a2c6f5..703ce1de1 100644 --- a/packaging/linux/org.olivevideoeditor.Olive.appdata.xml +++ b/packaging/linux/org.olivevideoeditor.Olive.appdata.xml @@ -3,7 +3,7 @@ org.olivevideoeditor.Olive Olive CC0-1.0 - GPLv3 + GPL-3.0 Olive Team Non-linear video editor Editor de vídeo não-linear @@ -15,9 +15,9 @@

Olive — свободный нелинейный видеоредактор, задуманный как полноценная замена закрытым коммерческим продуктам.

https://www.olivevideoeditor.org https://www.patreon.com/olivevideoeditor - https://github.com/olive-editor/olive/issues + https://github.com/olive-editor/olive/issues - http://images.libregraphicsworld.org/video/2018/12/introducing-olive/olive-alpha-main-window.jpg + https://olivevideoeditor.org/img/screenshot.jpg diff --git a/packaging/linux/org.olivevideoeditor.Olive.xml b/packaging/linux/org.olivevideoeditor.Olive.xml index df8e8e517..418f4a795 100644 --- a/packaging/linux/org.olivevideoeditor.Olive.xml +++ b/packaging/linux/org.olivevideoeditor.Olive.xml @@ -3,5 +3,6 @@ Olive project + diff --git a/icons/olive.icns b/packaging/macos/olive.icns similarity index 100% rename from icons/olive.icns rename to packaging/macos/olive.icns diff --git a/icons/olive.ico b/packaging/windows/olive.ico similarity index 100% rename from icons/olive.ico rename to packaging/windows/olive.ico diff --git a/icons/resources.rc b/packaging/windows/resources.rc similarity index 100% rename from icons/resources.rc rename to packaging/windows/resources.rc diff --git a/icons/version.h b/packaging/windows/version.h similarity index 100% rename from icons/version.h rename to packaging/windows/version.h diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 123633448..de1726900 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -22,6 +22,7 @@ #include "panels/project.h" #include "panels/timeline.h" #include "panels/viewer.h" +#include "panels/grapheditor.h" #include "ui/viewerwidget.h" #include "io/clipboard.h" #include "ui/timelineheader.h" @@ -36,6 +37,8 @@ EffectControls::EffectControls(QWidget *parent) : panel_name("Effects: "), mode(TA_NO_TRANSITION) { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setup_ui(); init_effects(); @@ -210,6 +213,9 @@ void EffectControls::clear_effects(bool clear_cache) { // clear existing clips deselect_all_effects(NULL); + // clear graph editor + if (panel_graph_editor != NULL) panel_graph_editor->set_row(NULL); + QVBoxLayout* video_layout = static_cast(video_effect_area->layout()); QVBoxLayout* audio_layout = static_cast(audio_effect_area->layout()); QLayoutItem* item; @@ -249,8 +255,6 @@ void EffectControls::open_effect(QVBoxLayout* layout, Effect* e) { } void EffectControls::setup_ui() { - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - QWidget* contents = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(contents); diff --git a/panels/grapheditor.cpp b/panels/grapheditor.cpp new file mode 100644 index 000000000..2cb44f673 --- /dev/null +++ b/panels/grapheditor.cpp @@ -0,0 +1,239 @@ +#include "grapheditor.h" + +#include +#include +#include +#include + +#include "ui/keyframenavigator.h" +#include "ui/timelineheader.h" +#include "ui/timelinetools.h" +#include "ui/labelslider.h" +#include "ui/graphview.h" +#include "project/effect.h" +#include "project/effectfield.h" +#include "project/effectrow.h" +#include "project/clip.h" +#include "panels.h" +#include "debug.h" + +GraphEditor::GraphEditor(QWidget* parent) : QDockWidget(parent), row(NULL) { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + setWindowTitle("Graph Editor"); + resize(720, 480); + + QWidget* main_widget = new QWidget(); + setWidget(main_widget); + QVBoxLayout* layout = new QVBoxLayout(); + main_widget->setLayout(layout); + + QWidget* tool_widget = new QWidget(); + tool_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + QHBoxLayout* tools = new QHBoxLayout(); + tool_widget->setLayout(tools); + + QWidget* left_tool_widget = new QWidget(); + QHBoxLayout* left_tool_layout = new QHBoxLayout(); + left_tool_layout->setSpacing(0); + left_tool_layout->setMargin(0); + left_tool_widget->setLayout(left_tool_layout); + tools->addWidget(left_tool_widget); + QWidget* center_tool_widget = new QWidget(); + QHBoxLayout* center_tool_layout = new QHBoxLayout(); + center_tool_layout->setSpacing(0); + center_tool_layout->setMargin(0); + center_tool_widget->setLayout(center_tool_layout); + tools->addWidget(center_tool_widget); + QWidget* right_tool_widget = new QWidget(); + QHBoxLayout* right_tool_layout = new QHBoxLayout(); + right_tool_layout->setSpacing(0); + right_tool_layout->setMargin(0); + right_tool_widget->setLayout(right_tool_layout); + tools->addWidget(right_tool_widget); + + keyframe_nav = new KeyframeNavigator(); + keyframe_nav->enable_keyframes(true); + keyframe_nav->enable_keyframe_toggle(false); + left_tool_layout->addWidget(keyframe_nav); + left_tool_layout->addStretch(); + + linear_button = new QPushButton("Linear"); + linear_button->setProperty("type", KEYFRAME_TYPE_LINEAR); + linear_button->setCheckable(true); + bezier_button = new QPushButton("Bezier"); + bezier_button->setProperty("type", KEYFRAME_TYPE_BEZIER); + bezier_button->setCheckable(true); + hold_button = new QPushButton("Hold"); + hold_button->setProperty("type", KEYFRAME_TYPE_HOLD); + hold_button->setCheckable(true); + + center_tool_layout->addStretch(); + center_tool_layout->addWidget(linear_button); + center_tool_layout->addWidget(bezier_button); + center_tool_layout->addWidget(hold_button); + + layout->addWidget(tool_widget); + + QWidget* central_widget = new QWidget(); + QVBoxLayout* central_layout = new QVBoxLayout(); + central_widget->setLayout(central_layout); + central_layout->setSpacing(0); + central_layout->setMargin(0); + header = new TimelineHeader(); + header->viewer = panel_sequence_viewer; + central_layout->addWidget(header); + view = new GraphView(); + central_layout->addWidget(view); + + layout->addWidget(central_widget); + + QWidget* value_widget = new QWidget(); + value_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + QHBoxLayout* values = new QHBoxLayout(); + value_widget->setLayout(values); + values->addStretch(); + + QWidget* central_value_widget = new QWidget(); + value_layout = new QHBoxLayout(); + value_layout->setMargin(0); + value_layout->addWidget(new QLabel("")); // a spacer so the layout doesn't jump + central_value_widget->setLayout(value_layout); + values->addWidget(central_value_widget); + + values->addStretch(); + layout->addWidget(value_widget); + + current_row_desc = new QLabel(); + current_row_desc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + current_row_desc->setAlignment(Qt::AlignCenter); + layout->addWidget(current_row_desc); + + connect(view, SIGNAL(zoom_changed(double)), header, SLOT(update_zoom(double))); + connect(view, SIGNAL(x_scroll_changed(int)), header, SLOT(set_scroll(int))); + connect(view, SIGNAL(selection_changed(bool, int)), this, SLOT(set_key_button_enabled(bool, int))); + + connect(linear_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); + connect(bezier_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); + connect(hold_button, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_type())); +} + +void GraphEditor::update_panel() { + if (isVisible()) { + if (row != NULL) { + int slider_index = 0; + for (int i=0;ifieldCount();i++) { + EffectField* field = row->field(i); + if (field->type == EFFECT_FIELD_DOUBLE) { + slider_proxies.at(slider_index)->set_value(row->field(i)->get_current_data().toDouble(), false); + slider_index++; + } + } + } + + header->update(); + view->update(); + } +} + +void GraphEditor::set_row(EffectRow *r) { + for (int i=0;iisKeyframing()) { + for (int i=0;ifieldCount();i++) { + EffectField* field = r->field(i); + if (field->type == EFFECT_FIELD_DOUBLE) { + QPushButton* slider_button = new QPushButton(); + slider_button->setCheckable(true); + slider_button->setChecked(true); + slider_button->setIcon(QIcon(":/icons/record.png")); + slider_button->setProperty("field", i); + slider_button->setIconSize(QSize(8, 8)); + slider_button->setMaximumSize(QSize(12, 12)); + connect(slider_button, SIGNAL(toggled(bool)), this, SLOT(set_field_visibility(bool))); + slider_proxy_buttons.append(slider_button); + value_layout->addWidget(slider_button); + + LabelSlider* slider = new LabelSlider(); + slider->set_color(get_curve_color(i, r->fieldCount()).name()); + connect(slider, SIGNAL(valueChanged()), this, SLOT(passthrough_slider_value())); + slider_proxies.append(slider); + value_layout->addWidget(slider); + + slider_proxy_sources.append(static_cast(field->ui_element)); + + found_vals = true; + } + } + } + + if (found_vals) { + row = r; + current_row_desc->setText(row->parent_effect->parent_clip->name + " :: " + row->parent_effect->meta->name + " :: " + row->get_name()); + header->set_visible_in(r->parent_effect->parent_clip->timeline_in); + + connect(keyframe_nav, SIGNAL(goto_previous_key()), row, SLOT(goto_previous_key())); + connect(keyframe_nav, SIGNAL(toggle_key()), row, SLOT(toggle_key())); + connect(keyframe_nav, SIGNAL(goto_next_key()), row, SLOT(goto_next_key())); + } else { + row = NULL; + current_row_desc->setText(0); + } + view->set_row(row); + update_panel(); +} + +bool GraphEditor::view_is_focused() { + return view->hasFocus() || header->hasFocus(); +} + +void GraphEditor::delete_selected_keys() { + view->delete_selected_keys(); +} + +void GraphEditor::select_all() { + view->select_all(); +} + +void GraphEditor::set_key_button_enabled(bool e, int type) { + linear_button->setEnabled(e); + linear_button->setChecked(type == KEYFRAME_TYPE_LINEAR); + bezier_button->setEnabled(e); + bezier_button->setChecked(type == KEYFRAME_TYPE_BEZIER); + hold_button->setEnabled(e); + hold_button->setChecked(type == KEYFRAME_TYPE_HOLD); +} + +void GraphEditor::passthrough_slider_value() { + for (int i=0;iset_value(slider_proxies.at(i)->value(), true); + } + } +} + +void GraphEditor::set_keyframe_type() { + linear_button->setChecked(linear_button == sender()); + bezier_button->setChecked(bezier_button == sender()); + hold_button->setChecked(hold_button == sender()); + view->set_selected_keyframe_type(sender()->property("type").toInt()); +} + +void GraphEditor::set_field_visibility(bool b) { + view->set_field_visibility(sender()->property("field").toInt(), b); +} diff --git a/panels/grapheditor.h b/panels/grapheditor.h new file mode 100644 index 000000000..1a5277b58 --- /dev/null +++ b/panels/grapheditor.h @@ -0,0 +1,44 @@ +#ifndef GRAPHEDITOR_H +#define GRAPHEDITOR_H + +#include + +class GraphView; +class TimelineHeader; +class QPushButton; +class EffectRow; +class QHBoxLayout; +class LabelSlider; +class QLabel; +class KeyframeNavigator; + +class GraphEditor : public QDockWidget { + Q_OBJECT +public: + GraphEditor(QWidget* parent = 0); + void update_panel(); + void set_row(EffectRow* r); + bool view_is_focused(); + void delete_selected_keys(); + void select_all(); +private: + GraphView* view; + TimelineHeader* header; + QHBoxLayout* value_layout; + QVector slider_proxies; + QVector slider_proxy_buttons; + QVector slider_proxy_sources; + QLabel* current_row_desc; + EffectRow* row; + KeyframeNavigator* keyframe_nav; + QPushButton* linear_button; + QPushButton* bezier_button; + QPushButton* hold_button; +private slots: + void set_key_button_enabled(bool e, int type); + void passthrough_slider_value(); + void set_keyframe_type(); + void set_field_visibility(bool b); +}; + +#endif // GRAPHEDITOR_H diff --git a/panels/panels.cpp b/panels/panels.cpp index dd8206ccc..35bed658a 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -8,6 +8,7 @@ #include "project/clip.h" #include "project/transition.h" #include "io/config.h" +#include "grapheditor.h" #include "debug.h" Project* panel_project = 0; @@ -15,6 +16,7 @@ EffectControls* panel_effect_controls = 0; Viewer* panel_sequence_viewer = 0; Viewer* panel_footage_viewer = 0; Timeline* panel_timeline = 0; +GraphEditor* panel_graph_editor = 0; void update_effect_controls() { // SEND CLIPS TO EFFECT CONTROLS @@ -105,20 +107,21 @@ void update_ui(bool modified) { panel_effect_controls->update_keyframes(); panel_timeline->repaint_timeline(); panel_sequence_viewer->update_viewer(); + panel_graph_editor->update_panel(); } QDockWidget *get_focused_panel() { QDockWidget* w = NULL; if (config.hover_focus) { - if (panel_project->rect().contains(panel_project->mapFromGlobal(QCursor::pos()))) { + if (panel_project->underMouse()) { w = panel_project; - } else if (panel_effect_controls->rect().contains(panel_effect_controls->mapFromGlobal(QCursor::pos()))) { + } else if (panel_effect_controls->underMouse()) { w = panel_effect_controls; - } else if (panel_sequence_viewer->rect().contains(panel_sequence_viewer->mapFromGlobal(QCursor::pos()))) { + } else if (panel_sequence_viewer->underMouse()) { w = panel_sequence_viewer; - } else if (panel_footage_viewer->rect().contains(panel_footage_viewer->mapFromGlobal(QCursor::pos()))) { + } else if (panel_footage_viewer->underMouse()) { w = panel_footage_viewer; - } else if (panel_timeline->rect().contains(panel_timeline->mapFromGlobal(QCursor::pos()))) { + } else if (panel_timeline->underMouse()) { w = panel_timeline; } } @@ -137,3 +140,32 @@ QDockWidget *get_focused_panel() { } return w; } + +void alloc_panels(QWidget* parent) { + // TODO maybe replace these with non-pointers later on? + panel_sequence_viewer = new Viewer(parent); + panel_sequence_viewer->setObjectName("seq_viewer"); + panel_footage_viewer = new Viewer(parent); + panel_footage_viewer->setObjectName("footage_viewer"); + panel_project = new Project(parent); + panel_project->setObjectName("proj_root"); + panel_effect_controls = new EffectControls(parent); + panel_effect_controls->setObjectName("fx_controls"); + panel_timeline = new Timeline(parent); + panel_timeline->setObjectName("timeline"); + panel_graph_editor = new GraphEditor(parent); + panel_graph_editor->setObjectName("graph_editor"); +} + +void free_panels() { + delete panel_sequence_viewer; + panel_sequence_viewer = NULL; + delete panel_footage_viewer; + panel_footage_viewer = NULL; + delete panel_project; + panel_project = NULL; + delete panel_effect_controls; + panel_effect_controls = NULL; + delete panel_timeline; + panel_timeline = NULL; +} diff --git a/panels/panels.h b/panels/panels.h index 042a652d8..691736b82 100644 --- a/panels/panels.h +++ b/panels/panels.h @@ -5,6 +5,9 @@ class Project; class EffectControls; class Viewer; class Timeline; +class GraphEditor; + +class QWidget; class QDockWidget; extern Project* panel_project; @@ -12,8 +15,11 @@ extern EffectControls* panel_effect_controls; extern Viewer* panel_sequence_viewer; extern Viewer* panel_footage_viewer; extern Timeline* panel_timeline; +extern GraphEditor* panel_graph_editor; void update_ui(bool modified); QDockWidget* get_focused_panel(); +void alloc_panels(QWidget *parent); +void free_panels(); #endif // PANELS_H diff --git a/panels/project.cpp b/panels/project.cpp index 96640180b..0c245f316 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -42,6 +42,7 @@ #include #include #include +#include extern "C" { #include @@ -60,7 +61,7 @@ QString recent_proj_file; Project::Project(QWidget *parent) : QDockWidget(parent) { - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget* dockWidgetContents = new QWidget(); QVBoxLayout* verticalLayout = new QVBoxLayout(dockWidgetContents); @@ -74,11 +75,62 @@ Project::Project(QWidget *parent) : sorter = new QSortFilterProxyModel(this); sorter->setSourceModel(&project_model); + // optional toolbar + toolbar_widget = new QWidget(); + toolbar_widget->setVisible(config.show_project_toolbar); + QHBoxLayout* toolbar = new QHBoxLayout(); + toolbar->setMargin(0); + toolbar->setSpacing(0); + toolbar_widget->setLayout(toolbar); + + QPushButton* toolbar_new = new QPushButton("New"); + toolbar_new->setIcon(QIcon(":/icons/tri-down.png")); + toolbar_new->setIconSize(QSize(8, 8)); + toolbar_new->setToolTip("New"); + connect(toolbar_new, SIGNAL(clicked(bool)), this, SLOT(make_new_menu())); + toolbar->addWidget(toolbar_new); + + QPushButton* toolbar_open = new QPushButton("Open"); + toolbar_open->setToolTip("Open Project"); + connect(toolbar_open, SIGNAL(clicked(bool)), mainWindow, SLOT(open_project())); + toolbar->addWidget(toolbar_open); + + QPushButton* toolbar_save = new QPushButton("Save"); + toolbar_save->setToolTip("Save Project"); + connect(toolbar_save, SIGNAL(clicked(bool)), mainWindow, SLOT(save_project())); + toolbar->addWidget(toolbar_save); + + QPushButton* toolbar_undo = new QPushButton("Undo"); + toolbar_undo->setToolTip("Undo"); + connect(toolbar_undo, SIGNAL(clicked(bool)), mainWindow, SLOT(undo())); + toolbar->addWidget(toolbar_undo); + + QPushButton* toolbar_redo = new QPushButton("Redo"); + toolbar_redo->setToolTip("Redo"); + connect(toolbar_redo, SIGNAL(clicked(bool)), mainWindow, SLOT(redo())); + toolbar->addWidget(toolbar_redo); + + toolbar->addStretch(); + + QPushButton* toolbar_tree_view = new QPushButton("Tree View"); + toolbar_tree_view->setToolTip("Tree View"); + connect(toolbar_tree_view, SIGNAL(clicked(bool)), this, SLOT(set_tree_view())); + toolbar->addWidget(toolbar_tree_view); + + QPushButton* toolbar_icon_view = new QPushButton("Icon View"); + toolbar_icon_view->setToolTip("Icon View"); + connect(toolbar_icon_view, SIGNAL(clicked(bool)), this, SLOT(set_icon_view())); + toolbar->addWidget(toolbar_icon_view); + + verticalLayout->addWidget(toolbar_widget); + + // tree view tree_view = new SourceTable(dockWidgetContents); tree_view->project_parent = this; tree_view->setModel(sorter); verticalLayout->addWidget(tree_view); + // icon view icon_view_container = new QWidget(); QVBoxLayout* icon_view_container_layout = new QVBoxLayout(); @@ -181,13 +233,13 @@ Sequence* create_sequence_from_media(QVector& media_list) { if (m->ready) { if (!got_video_values) { for (int j=0;jvideo_tracks.size();j++) { - FootageStream* ms = m->video_tracks.at(j); - s->width = ms->video_width; - s->height = ms->video_height; - if (ms->video_frame_rate != 0) { - s->frame_rate = ms->video_frame_rate; + const FootageStream& ms = m->video_tracks.at(j); + s->width = ms.video_width; + s->height = ms.video_height; + if (ms.video_frame_rate != 0) { + s->frame_rate = ms.video_frame_rate * m->speed; - if (ms->video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; + if (ms.video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; // only break with a decent frame rate, otherwise there may be a better candidate got_video_values = true; @@ -197,8 +249,8 @@ Sequence* create_sequence_from_media(QVector& media_list) { } if (!got_audio_values) { for (int j=0;jaudio_tracks.size();j++) { - FootageStream* ms = m->audio_tracks.at(j); - s->audio_frequency = ms->audio_frequency; + const FootageStream& ms = m->audio_tracks.at(j); + s->audio_frequency = ms.audio_frequency; got_audio_values = true; break; } @@ -469,12 +521,14 @@ void Project::delete_selected_media() { if (confirm_delete) { delete_clips_in_clipboard_with_media(ca, item); } - } } // remove if (remove) { + panel_effect_controls->clear_effects(true); + sequence->selections.clear(); + // remove media and parents for (int m=0;musing_inout)); stream.writeAttribute("in", QString::number(f->in)); stream.writeAttribute("out", QString::number(f->out)); + stream.writeAttribute("speed", QString::number(f->speed)); for (int j=0;jvideo_tracks.size();j++) { - FootageStream* ms = f->video_tracks.at(j); + const FootageStream& ms = f->video_tracks.at(j); stream.writeStartElement("video"); - stream.writeAttribute("id", QString::number(ms->file_index)); - stream.writeAttribute("width", QString::number(ms->video_width)); - stream.writeAttribute("height", QString::number(ms->video_height)); - stream.writeAttribute("framerate", QString::number(ms->video_frame_rate, 'f', 10)); - stream.writeAttribute("infinite", QString::number(ms->infinite_length)); + stream.writeAttribute("id", QString::number(ms.file_index)); + stream.writeAttribute("width", QString::number(ms.video_width)); + stream.writeAttribute("height", QString::number(ms.video_height)); + stream.writeAttribute("framerate", QString::number(ms.video_frame_rate, 'f', 10)); + stream.writeAttribute("infinite", QString::number(ms.infinite_length)); stream.writeEndElement(); } for (int j=0;jaudio_tracks.size();j++) { - FootageStream* ms = f->audio_tracks.at(j); + const FootageStream& ms = f->audio_tracks.at(j); stream.writeStartElement("audio"); - stream.writeAttribute("id", QString::number(ms->file_index)); - stream.writeAttribute("channels", QString::number(ms->audio_channels)); - stream.writeAttribute("layout", QString::number(ms->audio_layout)); - stream.writeAttribute("frequency", QString::number(ms->audio_frequency)); + stream.writeAttribute("id", QString::number(ms.file_index)); + stream.writeAttribute("channels", QString::number(ms.audio_channels)); + stream.writeAttribute("layout", QString::number(ms.audio_layout)); + stream.writeAttribute("frequency", QString::number(ms.audio_frequency)); stream.writeEndElement(); } stream.writeEndElement(); @@ -1084,6 +1139,12 @@ void Project::go_up_dir() { set_up_dir_enabled(); } +void Project::make_new_menu() { + QMenu new_menu(this); + mainWindow->make_new_menu(&new_menu); + new_menu.exec(QCursor::pos()); +} + void Project::add_recent_project(QString url) { bool found = false; for (int i=0;i &items, QList &list, int type = -1); + + QWidget* toolbar_widget; public slots: void import_dialog(); void delete_selected_media(); @@ -103,6 +105,7 @@ private slots: void set_icon_view_size(int); void set_up_dir_enabled(); void go_up_dir(); + void make_new_menu(); }; class MediaThrobber : public QObject { diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 432fb6996..3ba55d0ef 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -43,12 +43,6 @@ long refactor_frame_number(long framenumber, double source_frame_rate, double ta return qRound(((double)framenumber/source_frame_rate)*target_frame_rate); } -void draw_selection_rectangle(QPainter& painter, const QRect& rect) { - painter.setPen(QColor(204, 204, 204)); - painter.setBrush(QColor(0, 0, 0, 32)); - painter.drawRect(rect); -} - Timeline::Timeline(QWidget *parent) : QDockWidget(parent), cursor_frame(0), @@ -80,6 +74,8 @@ Timeline::Timeline(QWidget *parent) : last_frame(0), scroll(0) { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setup_ui(); default_track_height = (QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96) * TRACK_DEFAULT_HEIGHT; @@ -182,7 +178,7 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector can_import = m->ready; if (m->using_inout) { double source_fr = 30; - if (m->video_tracks.size() > 0 && !qIsNull(m->video_tracks.at(0)->video_frame_rate)) source_fr = m->video_tracks.at(0)->video_frame_rate; + if (m->video_tracks.size() > 0 && !qIsNull(m->video_tracks.at(0).video_frame_rate)) source_fr = m->video_tracks.at(0).video_frame_rate * m->speed; default_clip_in = refactor_frame_number(m->in, source_fr, seq->frame_rate); default_clip_out = refactor_frame_number(m->out, source_fr, seq->frame_rate); } @@ -214,7 +210,7 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector switch (medium->get_type()) { case MEDIA_TYPE_FOOTAGE: // is video source a still image? - if (m->video_tracks.size() > 0 && m->video_tracks[0]->infinite_length && m->audio_tracks.size() == 0) { + if (m->video_tracks.size() > 0 && m->video_tracks.at(0).infinite_length && m->audio_tracks.size() == 0) { g.out = g.in + 100; } else { long length = m->get_length_in_frames(seq->frame_rate); @@ -225,16 +221,20 @@ void Timeline::create_ghosts_from_media(Sequence* seq, long entry_point, QVector } for (int j=0;jaudio_tracks.size();j++) { - g.track = j; - g.media_stream = m->audio_tracks.at(j)->file_index; - ghosts.append(g); - audio_ghosts = true; + if (m->audio_tracks.at(j).enabled) { + g.track = j; + g.media_stream = m->audio_tracks.at(j).file_index; + ghosts.append(g); + audio_ghosts = true; + } } for (int j=0;jvideo_tracks.size();j++) { - g.track = -1-j; - g.media_stream = m->video_tracks.at(j)->file_index; - ghosts.append(g); - video_ghosts = true; + if (m->video_tracks.at(j).enabled) { + g.track = -1-j; + g.media_stream = m->video_tracks.at(j).file_index; + ghosts.append(g); + video_ghosts = true; + } } break; case MEDIA_TYPE_SEQUENCE: @@ -1043,15 +1043,17 @@ void Timeline::paste(bool insert) { void Timeline::ripple_to_in_point(bool in, bool ripple) { if (sequence != NULL) { if (sequence->clips.size() > 0) { - if (!in && sequence->playhead == 0) return; - // get track count int track_min = INT_MAX; int track_max = INT_MIN; long sequence_end = 0; + bool playhead_falls_on_in = false; + bool playhead_falls_on_out = false; + long next_cut = LONG_MAX; + long prev_cut = 0; + // find closest in point to playhead - long in_point = in ? LONG_MIN : LONG_MAX; for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL) { @@ -1060,32 +1062,36 @@ void Timeline::ripple_to_in_point(bool in, bool ripple) { sequence_end = qMax(c->timeline_out, sequence_end); - if (sequence->playhead != in_point) { - if ((in && c->timeline_in > in_point && c->timeline_in <= sequence->playhead) - || (!in && c->timeline_in < in_point && c->timeline_in >= sequence->playhead)) { - in_point = c->timeline_in; - } - if ((in && c->timeline_out > in_point && c->timeline_out <= sequence->playhead) - || (!in && c->timeline_out < in_point && c->timeline_out >= sequence->playhead)) { - in_point = c->timeline_out; - } - } + if (c->timeline_in == sequence->playhead) + playhead_falls_on_in = true; + if (c->timeline_out == sequence->playhead) + playhead_falls_on_out = true; + if (c->timeline_in > sequence->playhead) + next_cut = qMin(c->timeline_in, next_cut); + if (c->timeline_out > sequence->playhead) + next_cut = qMin(c->timeline_out, next_cut); + if (c->timeline_in < sequence->playhead) + prev_cut = qMax(c->timeline_in, prev_cut); + if (c->timeline_out < sequence->playhead) + prev_cut = qMax(c->timeline_out, prev_cut); } } - if (in && sequence->playhead == sequence_end) return; + next_cut = qMin(sequence_end, next_cut); QVector areas; ComboAction* ca = new ComboAction(); bool push_undo = true; + long seek = sequence->playhead; - if (sequence->playhead == in_point) { // one frame mode + if ((in && (playhead_falls_on_out || (playhead_falls_on_in && sequence->playhead == 0))) + || (!in && (playhead_falls_on_in || (playhead_falls_on_out && sequence->playhead == sequence_end)))) { // one frame mode if (ripple) { // set up deletion areas based on track count - if (in) { - in_point = sequence->playhead; - } else { - in_point = sequence->playhead - 1; + long in_point = sequence->playhead; + if (!in) { + in_point--; + seek--; } if (in_point >= 0) { @@ -1110,16 +1116,22 @@ void Timeline::ripple_to_in_point(bool in, bool ripple) { } else { // set up deletion areas based on track count Selection s; - s.in = qMin(in_point, sequence->playhead); - s.out = qMax(in_point, sequence->playhead); - for (int i=track_min;i<=track_max;i++) { - s.track = i; - areas.append(s); - } + if (in) seek = prev_cut; + s.in = in ? prev_cut : sequence->playhead; + s.out = in ? sequence->playhead : next_cut; - // trim and move clips around the in point - delete_areas_and_relink(ca, areas); - if (ripple) ripple_clips(ca, sequence, in_point, (in) ? (in_point - sequence->playhead) : (sequence->playhead - in_point)); + if (s.in == s.out) { + push_undo = false; + } else { + for (int i=track_min;i<=track_max;i++) { + s.track = i; + areas.append(s); + } + + // trim and move clips around the in point + delete_areas_and_relink(ca, areas); + if (ripple) ripple_clips(ca, sequence, s.in, s.in - s.out); + } } if (push_undo) { @@ -1127,7 +1139,7 @@ void Timeline::ripple_to_in_point(bool in, bool ripple) { update_ui(true); - if (in_point < sequence->playhead && ripple) panel_sequence_viewer->seek(in_point); + if (seek != sequence->playhead && ripple) panel_sequence_viewer->seek(seek); } else { delete ca; } @@ -1355,12 +1367,21 @@ bool Timeline::snap_to_timeline(long* l, bool use_playhead, bool use_markers, bo } void Timeline::set_marker() { - QInputDialog d(this); - d.setWindowTitle("Set Marker"); - d.setLabelText("Set marker name:"); - d.setInputMode(QInputDialog::TextInput); - if (d.exec() == QDialog::Accepted) { - undo_stack.push(new AddMarkerAction(sequence, sequence->playhead, d.textValue())); + bool add_marker = !config.set_name_with_marker; + QString marker_name; + + if (!add_marker) { + QInputDialog d(this); + d.setWindowTitle("Set Marker"); + d.setLabelText("Set marker name:"); + d.setInputMode(QInputDialog::TextInput); + add_marker = (d.exec() == QDialog::Accepted); + marker_name = d.textValue(); + } + + + if (add_marker) { + undo_stack.push(new AddMarkerAction(sequence, sequence->playhead, marker_name)); } } diff --git a/panels/timeline.h b/panels/timeline.h index a76adc3a9..278aacbb3 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -38,7 +38,6 @@ struct FootageStream; long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); int getScreenPointFromFrame(double zoom, long frame); long getFrameFromScreenPoint(double zoom, int x); -void draw_selection_rectangle(QPainter& painter, const QRect& rect); bool selection_contains_transition(const Selection& s, Clip* c, int type); void move_clip(ComboAction *ca, Clip *c, long iin, long iout, long iclip_in, int itrack, bool verify_transitions = true, bool relative = false); void ripple_clips(ComboAction *ca, Sequence* s, long point, long length, const QVector& ignore = QVector()); diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 01e488079..397364e13 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -46,13 +46,15 @@ Viewer::Viewer(QWidget *parent) : panel_name("Viewer: "), minimum_zoom(1.0) { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setup_ui(); headers->viewer = this; headers->snapping = false; headers->show_text(false); - glViewerPane->viewer = this; - viewer_widget = glViewerPane->child; + viewer_container->viewer = this; + viewer_widget = viewer_container->child; viewer_widget->viewer = this; set_media(NULL); @@ -67,9 +69,9 @@ Viewer::Viewer(QWidget *parent) : connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); - connect(horizontalScrollBar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int))); - connect(horizontalScrollBar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); - connect(horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); + connect(horizontal_bar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int))); + connect(horizontal_bar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); + connect(horizontal_bar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); update_playhead_timecode(0); update_end_timecode(); @@ -439,35 +441,33 @@ void Viewer::set_zoom_value(double d) { } if (seq != NULL) { set_sb_max(); - if (!horizontalScrollBar->is_resizing()) - center_scroll_to_playhead(horizontalScrollBar, headers->get_zoom(), seq->playhead); + if (!horizontal_bar->is_resizing()) + center_scroll_to_playhead(horizontal_bar, headers->get_zoom(), seq->playhead); } } void Viewer::set_sb_max() { - headers->set_scrollbar_max(horizontalScrollBar, seq->getEndFrame(), headers->width()); + headers->set_scrollbar_max(horizontal_bar, seq->getEndFrame(), headers->width()); } void Viewer::setup_ui() { - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - QWidget* contents = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(contents); layout->setSpacing(0); layout->setMargin(0); - glViewerPane = new ViewerContainer(contents); - glViewerPane->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - layout->addWidget(glViewerPane); + viewer_container = new ViewerContainer(contents); + viewer_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(viewer_container); headers = new TimelineHeader(contents); layout->addWidget(headers); - horizontalScrollBar = new ResizableScrollBar(contents); - horizontalScrollBar->setSingleStep(20); - horizontalScrollBar->setOrientation(Qt::Horizontal); - layout->addWidget(horizontalScrollBar); + horizontal_bar = new ResizableScrollBar(contents); + horizontal_bar->setSingleStep(20); + horizontal_bar->setOrientation(Qt::Horizontal); + layout->addWidget(horizontal_bar); QWidget* lower_controls = new QWidget(contents); @@ -506,9 +506,8 @@ void Viewer::setup_ui() { playback_control_layout->addWidget(btnRewind); btnPlay = new QPushButton(playback_controls); - QIcon playIcon; - playIcon.addFile(QStringLiteral(":/icons/play.png"), QSize(), QIcon::Normal, QIcon::Off); - playIcon.addFile(QStringLiteral(":/icons/play-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); + playIcon.addFile(QStringLiteral(":/icons/play.png"), QSize(), QIcon::Normal, QIcon::On); + playIcon.addFile(QStringLiteral(":/icons/play-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); btnPlay->setIcon(playIcon); connect(btnPlay, SIGNAL(clicked(bool)), this, SLOT(toggle_play())); playback_control_layout->addWidget(btnPlay); @@ -573,14 +572,14 @@ void Viewer::set_media(Media* m) { seq->frame_rate = 30; if (footage->video_tracks.size() > 0) { - FootageStream* video_stream = footage->video_tracks.at(0); - seq->width = video_stream->video_width; - seq->height = video_stream->video_height; - if (video_stream->video_frame_rate > 0 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; + const FootageStream& video_stream = footage->video_tracks.at(0); + seq->width = video_stream.video_width; + seq->height = video_stream.video_height; + if (video_stream.video_frame_rate > 0 && !video_stream.infinite_length) seq->frame_rate = video_stream.video_frame_rate * footage->speed; Clip* c = new Clip(seq); c->media = media; - c->media_stream = video_stream->file_index; + c->media_stream = video_stream.file_index; c->timeline_in = 0; c->timeline_out = footage->get_length_in_frames(seq->frame_rate); if (c->timeline_out <= 0) c->timeline_out = 150; @@ -594,12 +593,12 @@ void Viewer::set_media(Media* m) { } if (footage->audio_tracks.size() > 0) { - FootageStream* audio_stream = footage->audio_tracks.at(0); - seq->audio_frequency = audio_stream->audio_frequency; + const FootageStream& audio_stream = footage->audio_tracks.at(0); + seq->audio_frequency = audio_stream.audio_frequency; Clip* c = new Clip(seq); c->media = media; - c->media_stream = audio_stream->file_index; + c->media_stream = audio_stream.file_index; c->timeline_in = 0; c->timeline_out = footage->get_length_in_frames(seq->frame_rate); c->track = 0; @@ -610,7 +609,7 @@ void Viewer::set_media(Media* m) { if (footage->video_tracks.size() == 0) { viewer_widget->waveform = true; viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = audio_stream; + viewer_widget->waveform_ms = &audio_stream; viewer_widget->update(); } } else { @@ -628,26 +627,6 @@ void Viewer::set_media(Media* m) { set_sequence(false, seq); } -void Viewer::on_btnSkipToStart_clicked() { - go_to_start(); -} - -void Viewer::on_btnSkipToEnd_clicked() { - go_to_end(); -} - -void Viewer::on_btnRewind_clicked() { - previous_frame(); -} - -void Viewer::on_btnFastForward_clicked() { - next_frame(); -} - -void Viewer::on_btnPlay_clicked() { - toggle_play(); -} - void Viewer::update_playhead() { seek(currentTimecode->value()); } @@ -696,8 +675,14 @@ void Viewer::clean_created_seq() { } void Viewer::set_sequence(bool main, Sequence *s) { + pause(); + reset_all_audio(); + if (seq != NULL) { + closeActiveClips(seq); + } + main_sequence = main; seq = (main) ? sequence : s; @@ -721,7 +706,7 @@ void Viewer::set_sequence(bool main, Sequence *s) { update_playhead_timecode(seq->playhead); update_end_timecode(); - glViewerPane->adjust(); + viewer_container->adjust(); setWindowTitle(panel_name + seq->name); } else { @@ -739,5 +724,5 @@ void Viewer::set_sequence(bool main, Sequence *s) { } void Viewer::set_playpause_icon(bool play) { - btnPlay->setIcon(QIcon((play) ? ":/icons/play.png" : ":/icons/pause.png")); + btnPlay->setIcon(play ? playIcon : QIcon(":/icons/pause.png")); } diff --git a/panels/viewer.h b/panels/viewer.h index d46ba94c4..624d8df8e 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -3,6 +3,7 @@ #include #include +#include class Timeline; class ViewerWidget; @@ -77,12 +78,6 @@ public slots: void go_to_end(); private slots: - void on_btnSkipToStart_clicked(); - void on_btnRewind_clicked(); - void on_btnPlay_clicked(); - void on_btnFastForward_clicked(); - void on_btnSkipToEnd_clicked(); - void update_playhead(); void timer_update(); void recording_flasher_update(); @@ -99,11 +94,13 @@ private: void set_zoom_value(double d); void set_sb_max(); + QIcon playIcon; + void setup_ui(); TimelineHeader* headers; - ResizableScrollBar* horizontalScrollBar; - ViewerContainer* glViewerPane; + ResizableScrollBar* horizontal_bar; + ViewerContainer* viewer_container; LabelSlider* currentTimecode; QLabel* endTimecode; diff --git a/playback/audio.cpp b/playback/audio.cpp index e5e90601e..302ad6af6 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -8,6 +8,7 @@ #include "panels/timeline.h" #include "panels/viewer.h" #include "ui/audiomonitor.h" +#include "playback/playback.h" #include "debug.h" #include @@ -100,13 +101,19 @@ void stop_audio() { } void clear_audio_ibuffer() { + if (audio_thread != NULL) audio_thread->lock.lock(); memset(audio_ibuffer, 0, audio_ibuffer_size); audio_ibuffer_read = 0; + if (audio_thread != NULL) audio_thread->lock.unlock(); +} + +int current_audio_freq() { + return rendering ? sequence->audio_frequency : audio_output->format().sampleRate(); } int get_buffer_offset_from_frame(double framerate, long frame) { if (frame >= audio_ibuffer_frame) { - return qFloor(((double) (frame - audio_ibuffer_frame)/framerate)*audio_output->format().sampleRate())*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); + return qFloor(((double) (frame - audio_ibuffer_frame)/framerate)*current_audio_freq())*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO); } else { dout << "[WARNING] Invalid values passed to get_buffer_offset_from_frame"; return 0; diff --git a/playback/audio.h b/playback/audio.h index 795c6ba01..081a92e78 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -43,6 +43,8 @@ extern double audio_ibuffer_timecode; extern bool audio_scrub; void clear_audio_ibuffer(); +int current_audio_freq(); + bool is_audio_device_set(); void init_audio(); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index ae3ecc683..7df2ad948 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -42,36 +42,36 @@ double bytes_to_seconds(int nb_bytes, int nb_channels, int sample_rate) { } void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_bytes, QVector nests) { - // perform all audio effects + // perform all audio effects double timecode_end; timecode_end = timecode_start + bytes_to_seconds(nb_bytes, frame->channels, frame->sample_rate); - for (int j=0;jeffects.size();j++) { + for (int j=0;jeffects.size();j++) { Effect* e = c->effects.at(j); if (e->is_enabled()) e->process_audio(timecode_start, timecode_end, frame->data[0], nb_bytes, 2); - } - if (c->get_opening_transition() != NULL) { - if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { - double transition_start = (c->get_clip_in_with_transition() / c->sequence->frame_rate); - double transition_end = (c->get_clip_in_with_transition() + c->get_opening_transition()->get_length()) / c->sequence->frame_rate; + } + if (c->get_opening_transition() != NULL) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + double transition_start = (c->get_clip_in_with_transition() / c->sequence->frame_rate); + double transition_end = (c->get_clip_in_with_transition() + c->get_opening_transition()->get_length()) / c->sequence->frame_rate; if (timecode_end < transition_end) { double adjustment = transition_end - transition_start; double adjusted_range_start = (timecode_start - transition_start) / adjustment; double adjusted_range_end = (timecode_end - transition_start) / adjustment; - c->get_opening_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, TA_OPENING_TRANSITION); + c->get_opening_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, TA_OPENING_TRANSITION); } } } - if (c->get_closing_transition() != NULL) { - if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { - long length_with_transitions = c->get_timeline_out_with_transition() - c->get_timeline_in_with_transition(); - double transition_start = (c->get_clip_in_with_transition() + length_with_transitions - c->get_closing_transition()->get_length()) / c->sequence->frame_rate; - double transition_end = (c->get_clip_in_with_transition() + length_with_transitions) / c->sequence->frame_rate; - if (timecode_start > transition_start) { + if (c->get_closing_transition() != NULL) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + long length_with_transitions = c->get_timeline_out_with_transition() - c->get_timeline_in_with_transition(); + double transition_start = (c->get_clip_in_with_transition() + length_with_transitions - c->get_closing_transition()->get_length()) / c->sequence->frame_rate; + double transition_end = (c->get_clip_in_with_transition() + length_with_transitions) / c->sequence->frame_rate; + if (timecode_start > transition_start) { double adjustment = transition_end - transition_start; double adjusted_range_start = (timecode_start - transition_start) / adjustment; double adjusted_range_end = (timecode_end - transition_start) / adjustment; - c->get_closing_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, TA_CLOSING_TRANSITION); + c->get_closing_transition()->process_audio(adjusted_range_start, adjusted_range_end, frame->data[0], nb_bytes, TA_CLOSING_TRANSITION); } } } @@ -79,290 +79,291 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_ if (!nests.isEmpty()) { Clip* next_nest = nests.last(); nests.removeLast(); - apply_audio_effects(next_nest, timecode_start + (((double)c->get_timeline_in_with_transition()-c->get_clip_in_with_transition())/c->sequence->frame_rate), frame, nb_bytes, nests); + apply_audio_effects(next_nest, timecode_start + (((double)c->get_timeline_in_with_transition()-c->get_clip_in_with_transition())/c->sequence->frame_rate), frame, nb_bytes, nests); } } #define AUDIO_BUFFER_PADDING 2048 void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { - long timeline_in = c->get_timeline_in_with_transition(); - long timeline_out = c->get_timeline_out_with_transition(); + long timeline_in = c->get_timeline_in_with_transition(); + long timeline_out = c->get_timeline_out_with_transition(); long target_frame = c->audio_target_frame; long frame_skip = 0; - double last_fr = c->sequence->frame_rate; + double last_fr = c->sequence->frame_rate; if (!nests.isEmpty()) { for (int i=nests.size()-1;i>=0;i--) { - timeline_in = refactor_frame_number(timeline_in, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition(); - timeline_out = refactor_frame_number(timeline_out, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition(); - target_frame = refactor_frame_number(target_frame, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition(); + timeline_in = refactor_frame_number(timeline_in, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition(); + timeline_out = refactor_frame_number(timeline_out, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition(); + target_frame = refactor_frame_number(target_frame, last_fr, nests.at(i)->sequence->frame_rate) + nests.at(i)->get_timeline_in_with_transition() - nests.at(i)->get_clip_in_with_transition(); - timeline_out = qMin(timeline_out, nests.at(i)->get_timeline_out_with_transition()); + timeline_out = qMin(timeline_out, nests.at(i)->get_timeline_out_with_transition()); frame_skip = refactor_frame_number(frame_skip, last_fr, nests.at(i)->sequence->frame_rate); - long validator = nests.at(i)->get_timeline_in_with_transition() - timeline_in; + long validator = nests.at(i)->get_timeline_in_with_transition() - timeline_in; if (validator > 0) { frame_skip += validator; - //timeline_in = nests.at(i)->get_timeline_in_with_transition(); + //timeline_in = nests.at(i)->get_timeline_in_with_transition(); } last_fr = nests.at(i)->sequence->frame_rate; } } - while (true) { + while (true) { AVFrame* frame; int nb_bytes = INT_MAX; - if (c->media == NULL) { - frame = c->frame; - nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; - while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { - // create "new frame" - memset(c->frame->data[0], 0, nb_bytes); - apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests); - c->frame->pts += nb_bytes; - c->frame_sample_index = 0; - if (c->audio_buffer_write == 0) { - c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); - } - int offset = audio_ibuffer_read - c->audio_buffer_write; - if (offset > 0) { - c->audio_buffer_write += offset; - c->frame_sample_index += offset; - } - } - } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) { - double timebase = av_q2d(c->stream->time_base); + if (c->media == NULL) { + frame = c->frame; + nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { + // create "new frame" + memset(c->frame->data[0], 0, nb_bytes); + apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes, nests); + c->frame->pts += nb_bytes; + c->frame_sample_index = 0; + if (c->audio_buffer_write == 0) { + c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); + } + int offset = audio_ibuffer_read - c->audio_buffer_write; + if (offset > 0) { + c->audio_buffer_write += offset; + c->frame_sample_index += offset; + } + } + } else if (c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + double timebase = av_q2d(c->stream->time_base); - frame = c->queue.at(0); + frame = c->queue.at(0); - // retrieve frame - bool new_frame = false; - while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { - // no more audio left in frame, get a new one - if (!c->reached_end) { - int loop = 0; + // retrieve frame + bool new_frame = false; + while ((c->frame_sample_index == -1 || c->frame_sample_index >= nb_bytes) && nb_bytes > 0) { + // no more audio left in frame, get a new one + if (!c->reached_end) { + int loop = 0; - if (c->reverse && !c->audio_just_reset) { - avcodec_flush_buffers(c->codecCtx); - c->reached_end = false; - int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0)); - av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); + if (c->reverse && !c->audio_just_reset) { + avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; + int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0)); + av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); #ifdef AUDIOWARNINGS - if (backtrack_seek == 0) { - dout << "backtracked to 0"; - } + if (backtrack_seek == 0) { + dout << "backtracked to 0"; + } #endif - } + } - do { - av_frame_unref(frame); + do { + av_frame_unref(frame); - int ret; + int ret; - while ((ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) { - ret = retrieve_next_frame(c, c->frame); - if (ret >= 0) { - if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - dout << "[ERROR] Could not feed filtergraph -" << ret; - break; - } - } else { - if (ret == AVERROR_EOF) { + while ((ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) { + ret = retrieve_next_frame(c, c->frame); + if (ret >= 0) { + if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + dout << "[ERROR] Could not feed filtergraph -" << ret; + break; + } + } else { + if (ret == AVERROR_EOF) { #ifdef AUDIOWARNINGS - dout << "reached EOF while reading"; + dout << "reached EOF while reading"; #endif - // TODO revise usage of reached_end in audio - if (!c->reverse) { - c->reached_end = true; - } else { - } - } else { - dout << "[WARNING] Raw audio frame data could not be retrieved." << ret; - c->reached_end = true; - } - break; - } - } + // TODO revise usage of reached_end in audio + if (!c->reverse) { + c->reached_end = true; + } else { + } + } else { + dout << "[WARNING] Raw audio frame data could not be retrieved." << ret; + c->reached_end = true; + } + break; + } + } - if (ret < 0) { - if (ret != AVERROR_EOF) { - dout << "[ERROR] Could not pull from filtergraph"; - c->reached_end = true; - break; - } else { + if (ret < 0) { + if (ret != AVERROR_EOF) { + dout << "[ERROR] Could not pull from filtergraph"; + c->reached_end = true; + break; + } else { #ifdef AUDIOWARNINGS - dout << "reached EOF while pulling from filtergraph"; + dout << "reached EOF while pulling from filtergraph"; #endif - if (!c->reverse) break; - } - } + if (!c->reverse) break; + } + } - if (c->reverse) { - if (loop > 1) { - AVFrame* rev_frame = c->queue.at(1); - if (ret != AVERROR_EOF) { - if (loop == 2) { + if (c->reverse) { + if (loop > 1) { + AVFrame* rev_frame = c->queue.at(1); + if (ret != AVERROR_EOF) { + if (loop == 2) { #ifdef AUDIOWARNINGS - dout << "starting rev_frame"; + dout << "starting rev_frame"; #endif - rev_frame->nb_samples = 0; - rev_frame->pts = c->frame->pkt_pts; - } - int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels; + rev_frame->nb_samples = 0; + rev_frame->pts = c->frame->pkt_pts; + } + int offset = rev_frame->nb_samples * av_get_bytes_per_sample(static_cast(rev_frame->format)) * rev_frame->channels; #ifdef AUDIOWARNINGS - dout << "offset 1:" << offset; - dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels); + dout << "offset 1:" << offset; + dout << "retrieved samples:" << frame->nb_samples << "size:" << (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels); #endif - memcpy( - rev_frame->data[0]+offset, - frame->data[0], - (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) - ); + memcpy( + rev_frame->data[0]+offset, + frame->data[0], + (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) + ); #ifdef AUDIOWARNINGS - dout << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; + dout << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; #endif - } + } - rev_frame->nb_samples += frame->nb_samples; + rev_frame->nb_samples += frame->nb_samples; - if ((c->frame->pts >= c->reverse_target) || (ret == AVERROR_EOF)) { + if ((c->frame->pts >= c->reverse_target) || (ret == AVERROR_EOF)) { /* #ifdef AUDIOWARNINGS - dout << "time for the end of rev cache" << rev_frame->nb_samples << c->rev_target << c->frame->pts << c->frame->pkt_duration << c->frame->nb_samples; - dout << "diff:" << (c->frame->pkt_pts + c->frame->pkt_duration) - c->rev_target; + dout << "time for the end of rev cache" << rev_frame->nb_samples << c->rev_target << c->frame->pts << c->frame->pkt_duration << c->frame->nb_samples; + dout << "diff:" << (c->frame->pkt_pts + c->frame->pkt_duration) - c->rev_target; #endif - int cutoff = qRound64((((c->frame->pkt_pts + c->frame->pkt_duration) - c->reverse_target) * timebase) * audio_output->format().sampleRate()); - if (cutoff > 0) { + int cutoff = qRound64((((c->frame->pkt_pts + c->frame->pkt_duration) - c->reverse_target) * timebase) * audio_output->format().sampleRate()); + if (cutoff > 0) { #ifdef AUDIOWARNINGS - dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")"; + dout << "cut off" << cutoff << "samples (rate:" << audio_output->format().sampleRate() << ")"; #endif - rev_frame->nb_samples -= cutoff; - } + rev_frame->nb_samples -= cutoff; + } */ #ifdef AUDIOWARNINGS - dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target; + dout << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target; #endif - rev_frame->nb_samples = qRound64(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * (audio_output->format().sampleRate() / c->speed)); + double playback_speed = c->speed * c->media->to_footage()->speed; + rev_frame->nb_samples = qRound64(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * (current_audio_freq() / playback_speed)); #ifdef AUDIOWARNINGS - dout << "post cutoff deets::" << rev_frame->nb_samples; + dout << "post cutoff deets::" << rev_frame->nb_samples; #endif - int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format)); - int half_frame_size = frame_size >> 1; + int frame_size = rev_frame->nb_samples * rev_frame->channels * av_get_bytes_per_sample(static_cast(rev_frame->format)); + int half_frame_size = frame_size >> 1; - int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format)); - char* temp_chars = new char[sample_size]; - for (int i=0;idata[0][i+j]; - } - for (int j=0;jdata[0][i+j] = rev_frame->data[0][frame_size-i-sample_size+j]; - } - for (int j=0;jdata[0][frame_size-i-sample_size+j] = temp_chars[j]; - } - } - delete [] temp_chars; + int sample_size = rev_frame->channels*av_get_bytes_per_sample(static_cast(rev_frame->format)); + char* temp_chars = new char[sample_size]; + for (int i=0;idata[0][i+j]; + } + for (int j=0;jdata[0][i+j] = rev_frame->data[0][frame_size-i-sample_size+j]; + } + for (int j=0;jdata[0][frame_size-i-sample_size+j] = temp_chars[j]; + } + } + delete [] temp_chars; - c->reverse_target = rev_frame->pts; - frame = rev_frame; - break; - } - } + c->reverse_target = rev_frame->pts; + frame = rev_frame; + break; + } + } - loop++; + loop++; #ifdef AUDIOWARNINGS - dout << "loop" << loop; + dout << "loop" << loop; #endif - } else { - frame->pts = c->frame->pts; - break; - } - } while (true); - } else { - // if there is no more data in the file, we flush the remainder out of swresample - break; - } + } else { + frame->pts = c->frame->pts; + break; + } + } while (true); + } else { + // if there is no more data in the file, we flush the remainder out of swresample + break; + } - new_frame = true; + new_frame = true; - if (c->frame_sample_index < 0) { - c->frame_sample_index = 0; - } else { - c->frame_sample_index -= nb_bytes; - } + if (c->frame_sample_index < 0) { + c->frame_sample_index = 0; + } else { + c->frame_sample_index -= nb_bytes; + } - nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; - if (c->audio_just_reset) { - // get precise sample offset for the elected clip_in from this audio frame - double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame); - double frame_sts = ((frame->pts - c->stream->start_time) * timebase); - int nb_samples = qRound64((target_sts - frame_sts)*audio_output->format().sampleRate()); - c->frame_sample_index = nb_samples * 4; + if (c->audio_just_reset) { + // get precise sample offset for the elected clip_in from this audio frame + double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame); + double frame_sts = ((frame->pts - c->stream->start_time) * timebase); + int nb_samples = qRound64((target_sts - frame_sts)*current_audio_freq()); + c->frame_sample_index = nb_samples * 4; #ifdef AUDIOWARNINGS - dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase); - dout << "fsi-calc:" << c->frame_sample_index; + dout << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase); + dout << "fsi-calc:" << c->frame_sample_index; #endif - if (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index; - c->audio_just_reset = false; - } + if (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index; + c->audio_just_reset = false; + } #ifdef AUDIOWARNINGS - dout << "fsi-post-post:" << c->frame_sample_index; + dout << "fsi-post-post:" << c->frame_sample_index; #endif - if (c->audio_buffer_write == 0) { - c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); + if (c->audio_buffer_write == 0) { + c->audio_buffer_write = get_buffer_offset_from_frame(last_fr, qMax(timeline_in, target_frame)); - if (frame_skip > 0) { - int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame)); - c->frame_sample_index += (target - c->audio_buffer_write); - c->audio_buffer_write = target; - } - } + if (frame_skip > 0) { + int target = get_buffer_offset_from_frame(last_fr, qMax(timeline_in + frame_skip, target_frame)); + c->frame_sample_index += (target - c->audio_buffer_write); + c->audio_buffer_write = target; + } + } - int offset = audio_ibuffer_read - c->audio_buffer_write; - if (offset > 0) { - c->audio_buffer_write += offset; - c->frame_sample_index += offset; - } + int offset = audio_ibuffer_read - c->audio_buffer_write; + if (offset > 0) { + c->audio_buffer_write += offset; + c->frame_sample_index += offset; + } - // try to correct negative fsi - if (c->frame_sample_index < 0) { - c->audio_buffer_write -= c->frame_sample_index; - c->frame_sample_index = 0; - } - } + // try to correct negative fsi + if (c->frame_sample_index < 0) { + c->audio_buffer_write -= c->frame_sample_index; + c->frame_sample_index = 0; + } + } - if (c->reverse) frame = c->queue.at(1); + if (c->reverse) frame = c->queue.at(1); #ifdef AUDIOWARNINGS - dout << "j" << c->frame_sample_index << nb_bytes; + dout << "j" << c->frame_sample_index << nb_bytes; #endif - // apply any audio effects to the data - if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; - if (new_frame) { - apply_audio_effects(c, bytes_to_seconds(c->audio_buffer_write, 2, audio_output->format().sampleRate()) + audio_ibuffer_timecode + ((double)c->get_clip_in_with_transition()/c->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests); - } - } else { - // shouldn't ever get here - dout << "[ERROR] Tried to cache a non-footage/tone clip"; - return; - } + // apply any audio effects to the data + if (nb_bytes == INT_MAX) nb_bytes = frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels; + if (new_frame) { + apply_audio_effects(c, bytes_to_seconds(c->audio_buffer_write, 2, current_audio_freq()) + audio_ibuffer_timecode + ((double)c->get_clip_in_with_transition()/c->sequence->frame_rate) - ((double)timeline_in/last_fr), frame, nb_bytes, nests); + } + } else { + // shouldn't ever get here + dout << "[ERROR] Tried to cache a non-footage/tone clip"; + return; + } // mix audio into internal buffer if (frame->nb_samples == 0) { break; } else { - long buffer_timeline_out = get_buffer_offset_from_frame(c->sequence->frame_rate, timeline_out); + long buffer_timeline_out = get_buffer_offset_from_frame(c->sequence->frame_rate, timeline_out); audio_write_lock.lock(); while (c->frame_sample_index < nb_bytes @@ -387,9 +388,9 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { audio_write_lock.unlock(); - if (scrubbing) { - if (audio_thread != NULL) audio_thread->notifyReceiver(); - } + if (scrubbing) { + if (audio_thread != NULL) audio_thread->notifyReceiver(); + } if (c->frame_sample_index == nb_bytes) { c->frame_sample_index = -1; @@ -403,9 +404,9 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { if (c->reached_end) { frame->nb_samples = 0; } - if (scrubbing) { - break; - } + if (scrubbing) { + break; + } } QMetaObject::invokeMethod(panel_footage_viewer, "play_wake", Qt::QueuedConnection); @@ -425,7 +426,7 @@ void cache_video_worker(Clip* c, long playhead) { limit *= 2; } - if (c->queue.size() < limit) { + if (c->queue.size() < limit) { bool reverse = (c->reverse && !c->ignore_reverse); c->ignore_reverse = false; @@ -443,40 +444,40 @@ void cache_video_worker(Clip* c, long playhead) { smallest_pts = target_pts; } - if (c->multithreaded && c->cacher->interrupt) { // ignore interrupts for now - c->cacher->interrupt = false; - } + if (c->multithreaded && c->cacher->interrupt) { // ignore interrupts for now + c->cacher->interrupt = false; + } while (true) { AVFrame* frame = av_frame_alloc(); - Footage* media = c->media->to_footage(); - FootageStream* ms = media->get_stream_from_file_index(true, c->media_stream); + Footage* media = c->media->to_footage(); + const FootageStream* ms = media->get_stream_from_file_index(true, c->media_stream); while ((retr_ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) { - if (c->multithreaded && c->cacher->interrupt) return; // abort + if (c->multithreaded && c->cacher->interrupt) return; // abort AVFrame* send_frame = c->frame; read_ret = (c->use_existing_frame) ? 0 : retrieve_next_frame(c, send_frame); c->use_existing_frame = false; if (read_ret >= 0) { - bool send_it = true; + bool send_it = true; - /*if (reverse) { + /*if (reverse) { send_it = true; } else if (send_frame->pts > target_pts - eighth_second) { send_it = true; } else if (media->get_stream_from_file_index(true, c->media_stream)->infinite_length) { send_it = true; - } else { + } else { dout << "skipped adding a frame to the queue - fpts:" << send_frame->pts << "target:" << target_pts; - }*/ + }*/ - if (send_it) { + if (send_it) { if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { dout << "[ERROR] Failed to add frame to buffer source." << send_ret; break; - } + } } av_frame_unref(c->frame); @@ -510,7 +511,7 @@ void cache_video_worker(Clip* c, long playhead) { if (!ms->infinite_length && !reverse && c->queue.size() == limit) { // see if we got the frame we needed (used for speed ups primarily) bool found = false; - for (int i=0;iqueue.size();i++) { + for (int i=0;iqueue.size();i++) { if (c->queue.at(i)->pts >= target_pts) { found = true; break; @@ -528,92 +529,92 @@ void cache_video_worker(Clip* c, long playhead) { } } - if (c->multithreaded && c->cacher->interrupt) { // abort - return; - } + if (c->multithreaded && c->cacher->interrupt) { // abort + return; + } } } } void reset_cache(Clip* c, long target_frame) { - // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values - if (c->media == NULL) { - if (c->track >= 0) { - // tone clip - c->reached_end = false; - c->audio_target_frame = target_frame; - c->frame_sample_index = -1; - c->frame->pts = 0; - } - } else { - FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); - if (ms->infinite_length) { - /*avcodec_flush_buffers(c->codecCtx); - av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);*/ - c->use_existing_frame = false; - } else { - if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - // clear current queue - c->queue_clear(); + // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values + if (c->media == NULL) { + if (c->track >= 0) { + // tone clip + c->reached_end = false; + c->audio_target_frame = target_frame; + c->frame_sample_index = -1; + c->frame->pts = 0; + } + } else { + const FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); + if (ms->infinite_length) { + /*avcodec_flush_buffers(c->codecCtx); + av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);*/ + c->use_existing_frame = false; + } else { + if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + // clear current queue + c->queue_clear(); - // seeks to nearest keyframe (target_frame represents internal clip frame) - int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); - int64_t seek_ts = target_ts; - int64_t timebase_half_second = qRound64(av_q2d(av_inv_q(c->stream->time_base))); - if (c->reverse) seek_ts -= timebase_half_second; + // seeks to nearest keyframe (target_frame represents internal clip frame) + int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); + int64_t seek_ts = target_ts; + int64_t timebase_half_second = qRound64(av_q2d(av_inv_q(c->stream->time_base))); + if (c->reverse) seek_ts -= timebase_half_second; - while (true) { - // flush ffmpeg codecs - avcodec_flush_buffers(c->codecCtx); - c->reached_end = false; + while (true) { + // flush ffmpeg codecs + avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; - if (seek_ts > 0) { - av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD); + if (seek_ts > 0) { + av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD); - av_frame_unref(c->frame); - int ret = retrieve_next_frame(c, c->frame); - if (ret < 0) { - dout << "[WARNING] Seeking terminated prematurely"; - break; - } - if (c->frame->pts <= target_ts) { - c->use_existing_frame = true; - break; - } else { - seek_ts -= timebase_half_second; - } - } else { - av_frame_unref(c->frame); - av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD); - c->use_existing_frame = false; - break; - } - } - } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - // flush ffmpeg codecs - avcodec_flush_buffers(c->codecCtx); - c->reached_end = false; + av_frame_unref(c->frame); + int ret = retrieve_next_frame(c, c->frame); + if (ret < 0) { + dout << "[WARNING] Seeking terminated prematurely"; + break; + } + if (c->frame->pts <= target_ts) { + c->use_existing_frame = true; + break; + } else { + seek_ts -= timebase_half_second; + } + } else { + av_frame_unref(c->frame); + av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD); + c->use_existing_frame = false; + break; + } + } + } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + // flush ffmpeg codecs + avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; - // seek (target_frame represents timeline timecode in frames, not clip timecode) + // seek (target_frame represents timeline timecode in frames, not clip timecode) - int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); + int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); - if (c->reverse) { - c->reverse_target = timestamp; - timestamp -= av_q2d(av_inv_q(c->stream->time_base)); + if (c->reverse) { + c->reverse_target = timestamp; + timestamp -= av_q2d(av_inv_q(c->stream->time_base)); #ifdef AUDIOWARNINGS - dout << "seeking to" << timestamp << "(originally" << c->reverse_target << ")"; - } else { - dout << "reset called; seeking to" << timestamp; + dout << "seeking to" << timestamp << "(originally" << c->reverse_target << ")"; + } else { + dout << "reset called; seeking to" << timestamp; #endif - } - av_seek_frame(c->formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD); - c->audio_target_frame = target_frame; - c->frame_sample_index = -1; - c->audio_just_reset = true; - } - } - } + } + av_seek_frame(c->formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD); + c->audio_target_frame = target_frame; + c->frame_sample_index = -1; + c->audio_just_reset = true; + } + } + } } Cacher::Cacher(Clip* c) : clip(c) {} @@ -621,122 +622,122 @@ Cacher::Cacher(Clip* c) : clip(c) {} AVSampleFormat sample_format = AV_SAMPLE_FMT_S16; void open_clip_worker(Clip* clip) { - if (clip->media == NULL) { - if (clip->track >= 0) { - clip->frame = av_frame_alloc(); - clip->frame->format = sample_format; - clip->frame->channel_layout = clip->sequence->audio_layout; - clip->frame->channels = av_get_channel_layout_nb_channels(clip->frame->channel_layout); - clip->frame->sample_rate = audio_output->format().sampleRate(); - clip->frame->nb_samples = 2048; - av_frame_make_writable(clip->frame); - if (av_frame_get_buffer(clip->frame, 0)) { - dout << "[ERROR] Could not allocate buffer for tone clip"; - } - clip->audio_reset = true; - } - } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { - // opens file resource for FFmpeg and prepares Clip struct for playback - Footage* m = clip->media->to_footage(); - QByteArray ba = m->url.toUtf8(); - const char* filename = ba.constData(); - FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); + if (clip->media == NULL) { + if (clip->track >= 0) { + clip->frame = av_frame_alloc(); + clip->frame->format = sample_format; + clip->frame->channel_layout = clip->sequence->audio_layout; + clip->frame->channels = av_get_channel_layout_nb_channels(clip->frame->channel_layout); + clip->frame->sample_rate = current_audio_freq(); + clip->frame->nb_samples = 2048; + av_frame_make_writable(clip->frame); + if (av_frame_get_buffer(clip->frame, 0)) { + dout << "[ERROR] Could not allocate buffer for tone clip"; + } + clip->audio_reset = true; + } + } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { + // opens file resource for FFmpeg and prepares Clip struct for playback + Footage* m = clip->media->to_footage(); + QByteArray ba = m->url.toUtf8(); + const char* filename = ba.constData(); + const FootageStream* ms = m->get_stream_from_file_index(clip->track < 0, clip->media_stream); - int errCode = avformat_open_input( - &clip->formatCtx, - filename, - NULL, - NULL - ); - if (errCode != 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - dout << "[ERROR] Could not open" << filename << "-" << err; - return; - } + int errCode = avformat_open_input( + &clip->formatCtx, + filename, + NULL, + NULL + ); + if (errCode != 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + dout << "[ERROR] Could not open" << filename << "-" << err; + return; + } - errCode = avformat_find_stream_info(clip->formatCtx, NULL); - if (errCode < 0) { - char err[1024]; - av_strerror(errCode, err, 1024); - dout << "[ERROR] Could not open" << filename << "-" << err; - return; - } + errCode = avformat_find_stream_info(clip->formatCtx, NULL); + if (errCode < 0) { + char err[1024]; + av_strerror(errCode, err, 1024); + dout << "[ERROR] Could not open" << filename << "-" << err; + return; + } - av_dump_format(clip->formatCtx, 0, filename, 0); + av_dump_format(clip->formatCtx, 0, filename, 0); - clip->stream = clip->formatCtx->streams[ms->file_index]; - clip->codec = avcodec_find_decoder(clip->stream->codecpar->codec_id); - clip->codecCtx = avcodec_alloc_context3(clip->codec); - avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar); + clip->stream = clip->formatCtx->streams[ms->file_index]; + clip->codec = avcodec_find_decoder(clip->stream->codecpar->codec_id); + clip->codecCtx = avcodec_alloc_context3(clip->codec); + avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar); - clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.5); - if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; + clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate * m->speed * 0.5); + if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; - clip->opts = NULL; + clip->opts = NULL; - // optimized decoding settings - if (clip->stream->codecpar->codec_id != AV_CODEC_ID_PNG && - clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && - clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && - clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { - av_dict_set(&clip->opts, "threads", "auto", 0); - } - if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) { - av_dict_set(&clip->opts, "tune", "fastdecode", 0); - av_dict_set(&clip->opts, "tune", "zerolatency", 0); - } + // optimized decoding settings + if (clip->stream->codecpar->codec_id != AV_CODEC_ID_PNG && + clip->stream->codecpar->codec_id != AV_CODEC_ID_APNG && + clip->stream->codecpar->codec_id != AV_CODEC_ID_TIFF && + clip->stream->codecpar->codec_id != AV_CODEC_ID_PSD) { + av_dict_set(&clip->opts, "threads", "auto", 0); + } + if (clip->stream->codecpar->codec_id == AV_CODEC_ID_H264) { + av_dict_set(&clip->opts, "tune", "fastdecode", 0); + av_dict_set(&clip->opts, "tune", "zerolatency", 0); + } - // Open codec - if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) { - dout << "[ERROR] Could not open codec"; - } + // Open codec + if (avcodec_open2(clip->codecCtx, clip->codec, &clip->opts) < 0) { + dout << "[ERROR] Could not open codec"; + } - // allocate filtergraph - clip->filter_graph = avfilter_graph_alloc(); - if (clip->filter_graph == NULL) { - dout << "[ERROR] Could not create filtergraph"; - } - char filter_args[512]; + // allocate filtergraph + clip->filter_graph = avfilter_graph_alloc(); + if (clip->filter_graph == NULL) { + dout << "[ERROR] Could not create filtergraph"; + } + char filter_args[512]; - if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", - clip->stream->codecpar->width, - clip->stream->codecpar->height, - clip->stream->codecpar->format, - clip->stream->time_base.num, - clip->stream->time_base.den, - clip->stream->codecpar->sample_aspect_ratio.num, - clip->stream->codecpar->sample_aspect_ratio.den - ); + if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", + clip->stream->codecpar->width, + clip->stream->codecpar->height, + clip->stream->codecpar->format, + clip->stream->time_base.num, + clip->stream->time_base.den, + clip->stream->codecpar->sample_aspect_ratio.num, + clip->stream->codecpar->sample_aspect_ratio.den + ); - avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", filter_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); - AVFilterContext* last_filter = clip->buffersrc_ctx; + AVFilterContext* last_filter = clip->buffersrc_ctx; - if (ms->video_interlacing != VIDEO_PROGRESSIVE) { - AVFilterContext* yadif_filter; - char yadif_args[100]; - snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); + if (ms->video_interlacing != VIDEO_PROGRESSIVE) { + AVFilterContext* yadif_filter; + char yadif_args[100]; + snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); - avfilter_link(last_filter, 0, yadif_filter, 0); - last_filter = yadif_filter; - } + avfilter_link(last_filter, 0, yadif_filter, 0); + last_filter = yadif_filter; + } /* stabilization code */ - bool stabilize = false; - if (stabilize) { - AVFilterContext* stab_filter; - int stab_ret = avfilter_graph_create_filter(&stab_filter, avfilter_get_by_name("vidstabtransform"), "vidstab", "input=/media/matt/Home/samples/transforms.trf", NULL, clip->filter_graph); - if (stab_ret < 0) { - char err[100]; - av_strerror(stab_ret, err, sizeof(err)); - } else { - avfilter_link(last_filter, 0, stab_filter, 0); - last_filter = stab_filter; - } + bool stabilize = false; + if (stabilize) { + AVFilterContext* stab_filter; + int stab_ret = avfilter_graph_create_filter(&stab_filter, avfilter_get_by_name("vidstabtransform"), "vidstab", "input=/media/matt/Home/samples/transforms.trf", NULL, clip->filter_graph); + if (stab_ret < 0) { + char err[100]; + av_strerror(stab_ret, err, sizeof(err)); + } else { + avfilter_link(last_filter, 0, stab_filter, 0); + last_filter = stab_filter; + } } enum AVPixelFormat valid_pix_fmts[] = { @@ -750,102 +751,104 @@ void open_clip_worker(Clip* clip) { char format_args[100]; snprintf(format_args, sizeof(format_args), "pix_fmts=%s", chosen_format); - AVFilterContext* format_conv; + AVFilterContext* format_conv; avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", format_args, NULL, clip->filter_graph); - avfilter_link(last_filter, 0, format_conv, 0); + avfilter_link(last_filter, 0, format_conv, 0); - avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); + avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); - avfilter_graph_config(clip->filter_graph, NULL); - } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - if (clip->codecCtx->channel_layout == 0) clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels); + avfilter_graph_config(clip->filter_graph, NULL); + } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + if (clip->codecCtx->channel_layout == 0) clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels); - // set up cache - clip->queue.append(av_frame_alloc()); - if (clip->reverse) { - AVFrame* reverse_frame = av_frame_alloc(); + // set up cache + clip->queue.append(av_frame_alloc()); + if (clip->reverse) { + AVFrame* reverse_frame = av_frame_alloc(); - reverse_frame->format = sample_format; - reverse_frame->nb_samples = audio_output->format().sampleRate()*2; - reverse_frame->channel_layout = clip->sequence->audio_layout; - reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout); - av_frame_get_buffer(reverse_frame, 0); + reverse_frame->format = sample_format; + reverse_frame->nb_samples = current_audio_freq()*2; + reverse_frame->channel_layout = clip->sequence->audio_layout; + reverse_frame->channels = av_get_channel_layout_nb_channels(clip->sequence->audio_layout); + av_frame_get_buffer(reverse_frame, 0); - clip->queue.append(reverse_frame); - } + clip->queue.append(reverse_frame); + } - snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64, - clip->stream->time_base.num, - clip->stream->time_base.den, - clip->stream->codecpar->sample_rate, - av_get_sample_fmt_name(clip->codecCtx->sample_fmt), - clip->codecCtx->channel_layout - ); + snprintf(filter_args, sizeof(filter_args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64, + clip->stream->time_base.num, + clip->stream->time_base.den, + clip->stream->codecpar->sample_rate, + av_get_sample_fmt_name(clip->codecCtx->sample_fmt), + clip->codecCtx->channel_layout + ); - avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("abuffer"), "in", filter_args, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("abuffersink"), "out", NULL, NULL, clip->filter_graph); - enum AVSampleFormat sample_fmts[] = { sample_format, static_cast(-1) }; - if (av_opt_set_int_list(clip->buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output sample format"; - } + enum AVSampleFormat sample_fmts[] = { sample_format, static_cast(-1) }; + if (av_opt_set_int_list(clip->buffersink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output sample format"; + } - int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) }; - if (av_opt_set_int_list(clip->buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output sample format"; - } + int64_t channel_layouts[] = { AV_CH_LAYOUT_STEREO, static_cast(-1) }; + if (av_opt_set_int_list(clip->buffersink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output sample format"; + } - int target_sample_rate = audio_output->format().sampleRate(); + int target_sample_rate = current_audio_freq(); - if (qFuzzyCompare(clip->speed, 1.0)) { - avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); - } else if (clip->maintain_audio_pitch) { - AVFilterContext* previous_filter = clip->buffersrc_ctx; - AVFilterContext* last_filter = clip->buffersrc_ctx; + double playback_speed = clip->speed * m->speed; - char speed_param[10]; + if (qFuzzyCompare(playback_speed, 1.0)) { + avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + } else if (clip->maintain_audio_pitch) { + AVFilterContext* previous_filter = clip->buffersrc_ctx; + AVFilterContext* last_filter = clip->buffersrc_ctx; - if (clip->speed != 1.0) { - double base = (clip->speed > 1.0) ? 2.0 : 0.5; + char speed_param[10]; - double speedlog = log(clip->speed) / log(base); - int whole2 = qFloor(speedlog); - speedlog -= whole2; +// if (playback_speed != 1.0) { + double base = (playback_speed > 1.0) ? 2.0 : 0.5; - if (whole2 > 0) { - snprintf(speed_param, sizeof(speed_param), "%f", base); - for (int i=0;ifilter_graph); - avfilter_link(previous_filter, 0, tempo_filter, 0); - previous_filter = tempo_filter; - } - } + double speedlog = log(playback_speed) / log(base); + int whole2 = qFloor(speedlog); + speedlog -= whole2; - snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog)); - last_filter = NULL; - avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, NULL, clip->filter_graph); - avfilter_link(previous_filter, 0, last_filter, 0); - } + if (whole2 > 0) { + snprintf(speed_param, sizeof(speed_param), "%f", base); + for (int i=0;ifilter_graph); + avfilter_link(previous_filter, 0, tempo_filter, 0); + previous_filter = tempo_filter; + } + } - avfilter_link(last_filter, 0, clip->buffersink_ctx, 0); - } else { - target_sample_rate = qRound64(target_sample_rate / clip->speed); - avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); - } + snprintf(speed_param, sizeof(speed_param), "%f", qPow(base, speedlog)); + last_filter = NULL; + avfilter_graph_create_filter(&last_filter, avfilter_get_by_name("atempo"), "atempo", speed_param, NULL, clip->filter_graph); + avfilter_link(previous_filter, 0, last_filter, 0); +// } - int sample_rates[] = { target_sample_rate, 0 }; - if (av_opt_set_int_list(clip->buffersink_ctx, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN) < 0) { - dout << "[ERROR] Could not set output sample rates"; - } + avfilter_link(last_filter, 0, clip->buffersink_ctx, 0); + } else { + target_sample_rate = qRound64(target_sample_rate / playback_speed); + avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + } - avfilter_graph_config(clip->filter_graph, NULL); + int sample_rates[] = { target_sample_rate, 0 }; + if (av_opt_set_int_list(clip->buffersink_ctx, "sample_rates", sample_rates, 0, AV_OPT_SEARCH_CHILDREN) < 0) { + dout << "[ERROR] Could not set output sample rates"; + } - clip->audio_reset = true; - } + avfilter_graph_config(clip->filter_graph, NULL); - clip->frame = av_frame_alloc(); - } + clip->audio_reset = true; + } + + clip->frame = av_frame_alloc(); + } for (int i=0;ieffects.size();i++) { clip->effects.at(i)->open(); @@ -856,30 +859,30 @@ void open_clip_worker(Clip* clip) { dout << "[INFO] Clip opened on track" << clip->track; } -void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector nests) { - if (reset) { - // note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead - reset_cache(clip, playhead); - clip->audio_reset = false; - } +void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, QVector nests) { + if (reset) { + // note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead + reset_cache(clip, playhead); + clip->audio_reset = false; + } - if (clip->media == NULL) { - if (clip->track >= 0) { - cache_audio_worker(clip, scrubbing, nests); - } - } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { - if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - cache_video_worker(clip, playhead); - } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - cache_audio_worker(clip, scrubbing, nests); - } - } + if (clip->media == NULL) { + if (clip->track >= 0) { + cache_audio_worker(clip, scrubbing, nests); + } + } else if (clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { + if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + cache_video_worker(clip, playhead); + } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + cache_audio_worker(clip, scrubbing, nests); + } + } } void close_clip_worker(Clip* clip) { clip->finished_opening = false; - if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE) { clip->queue_clear(); avfilter_graph_free(&clip->filter_graph); @@ -887,46 +890,46 @@ void close_clip_worker(Clip* clip) { avcodec_close(clip->codecCtx); avcodec_free_context(&clip->codecCtx); - av_dict_free(&clip->opts); + av_dict_free(&clip->opts); avformat_close_input(&clip->formatCtx); } av_frame_free(&clip->frame); - clip->reset(); + clip->reset(); dout << "[INFO] Clip closed on track" << clip->track; } void Cacher::run() { // open_lock is used to prevent the clip from being destroyed before the cacher has closed it properly - clip->lock.lock(); - clip->finished_opening = false; - clip->open = true; - caching = true; - interrupt = false; + clip->lock.lock(); + clip->finished_opening = false; + clip->open = true; + caching = true; + interrupt = false; open_clip_worker(clip); while (caching) { clip->can_cache.wait(&clip->lock); - if (!caching) { + if (!caching) { break; } else { - while (true) { - cache_clip_worker(clip, playhead, reset, scrubbing, nests); - if (clip->multithreaded && clip->cacher->interrupt && clip->track < 0) { - clip->cacher->interrupt = false; - } else { - break; - } - } + while (true) { + cache_clip_worker(clip, playhead, reset, scrubbing, nests); + if (clip->multithreaded && clip->cacher->interrupt && clip->track < 0) { + clip->cacher->interrupt = false; + } else { + break; + } + } } } - close_clip_worker(clip); + close_clip_worker(clip); - clip->lock.unlock(); + clip->lock.unlock(); clip->open_lock.unlock(); } diff --git a/playback/playback.cpp b/playback/playback.cpp index 1448e312a..cd6c906d7 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -37,39 +37,39 @@ bool texture_failed = false; bool rendering = false; bool clip_uses_cacher(Clip* clip) { - return (clip->media == NULL && clip->track >= 0) || (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE); + return (clip->media == NULL && clip->track >= 0) || (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_FOOTAGE); } void open_clip(Clip* clip, bool multithreaded) { - if (clip_uses_cacher(clip)) { - clip->multithreaded = multithreaded; - if (multithreaded) { - if (clip->open_lock.tryLock()) { - // maybe keep cacher instance in memory while clip exists for performance? - clip->cacher = new Cacher(clip); - QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater())); - clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority); - } - } else { - clip->finished_opening = false; - clip->open = true; + if (clip_uses_cacher(clip)) { + clip->multithreaded = multithreaded; + if (multithreaded) { + if (clip->open_lock.tryLock()) { + // maybe keep cacher instance in memory while clip exists for performance? + clip->cacher = new Cacher(clip); + QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater())); + clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority); + } + } else { + clip->finished_opening = false; + clip->open = true; - open_clip_worker(clip); - } - } else { - clip->open = true; - } + open_clip_worker(clip); + } + } else { + clip->open = true; + } } -void close_clip(Clip* clip) { +void close_clip(Clip* clip, bool wait) { // destroy opengl texture in main thread - if (clip->texture != NULL) { + if (clip->texture != NULL) { delete clip->texture; clip->texture = NULL; } for (int i=0;ieffects.size();i++) { - clip->effects.at(i)->close(); + if (clip->effects.at(i)->is_open()) clip->effects.at(i)->close(); } if (clip->fbo != NULL) { @@ -79,29 +79,30 @@ void close_clip(Clip* clip) { clip->fbo = NULL; } - if (clip_uses_cacher(clip)) { - if (clip->multithreaded) { - clip->cacher->caching = false; - clip->can_cache.wakeAll(); - } else { - close_clip_worker(clip); - } - } else { - if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_SEQUENCE) - closeActiveClips(clip->media->to_sequence(), false); + if (clip_uses_cacher(clip)) { + if (clip->multithreaded) { + clip->cacher->caching = false; + clip->can_cache.wakeAll(); + if (wait) clip->cacher->wait(); + } else { + close_clip_worker(clip); + } + } else { + if (clip->media != NULL && clip->media->get_type() == MEDIA_TYPE_SEQUENCE) + closeActiveClips(clip->media->to_sequence()); clip->open = false; - } + } } void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector& nests) { - if (clip_uses_cacher(clip)) { + if (clip_uses_cacher(clip)) { if (clip->multithreaded) { clip->cacher->playhead = playhead; clip->cacher->reset = reset; clip->cacher->nests = nests; - clip->cacher->scrubbing = scrubbing; - if (reset && clip->queue.size() > 0) clip->cacher->interrupt = true; + clip->cacher->scrubbing = scrubbing; + if (reset && clip->queue.size() > 0) clip->cacher->interrupt = true; clip->can_cache.wakeAll(); } else { @@ -111,15 +112,15 @@ void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVectorget_timeline_in_with_transition()+c->get_clip_in_with_transition())/(double)c->sequence->frame_rate); + return ((double)(playhead-c->get_timeline_in_with_transition()+c->get_clip_in_with_transition())/(double)c->sequence->frame_rate); } void get_clip_frame(Clip* c, long playhead) { if (c->finished_opening) { - FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); + const FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); - int64_t target_pts = qMax(static_cast(0), playhead_to_timestamp(c, playhead)); - int64_t second_pts = qRound64(av_q2d(av_inv_q(c->stream->time_base))); + int64_t target_pts = qMax(static_cast(0), playhead_to_timestamp(c, playhead)); + int64_t second_pts = qRound64(av_q2d(av_inv_q(c->stream->time_base))); if (ms->video_interlacing != VIDEO_PROGRESSIVE) { target_pts *= 2; second_pts *= 2; @@ -192,14 +193,14 @@ void get_clip_frame(Clip* c, long playhead) { #endif c->reached_end = false; cache = false; - } else if (target_pts != c->last_invalid_ts && (target_pts < target_frame->pts || pts_diff > second_pts)) { + } else if (target_pts != c->last_invalid_ts && (target_pts < target_frame->pts || pts_diff > second_pts)) { #ifdef GCF_DEBUG dout << "GCF ==> RESET" << target_pts << "(" << target_frame->pts << "-" << target_frame->pts+target_frame->pkt_duration << ")"; #endif - if (!config.fast_seeking) target_frame = NULL; + if (!config.fast_seeking) target_frame = NULL; reset = true; - c->last_invalid_ts = target_pts; + c->last_invalid_ts = target_pts; } else { #ifdef GCF_DEBUG dout << "GCF ==> WAIT - target pts:" << target_pts << "closest frame:" << target_frame->pts; @@ -225,26 +226,26 @@ void get_clip_frame(Clip* c, long playhead) { int nb_components = av_pix_fmt_desc_get(static_cast(c->pix_fmt))->nb_components; glPixelStorei(GL_UNPACK_ROW_LENGTH, target_frame->linesize[0]/nb_components); - bool copied = false; - uint8_t* data = target_frame->data[0]; - int frame_size; + bool copied = false; + uint8_t* data = target_frame->data[0]; + int frame_size; - for (int i=0;ieffects.size();i++) { - Effect* e = c->effects.at(i); - if (e->enable_image) { - if (!copied) { - frame_size = target_frame->linesize[0]*target_frame->height; - data = new uint8_t[frame_size]; - memcpy(data, target_frame->data[0], frame_size); - copied = true; - } - e->process_image(get_timecode(c, playhead), data, frame_size); - } - } + for (int i=0;ieffects.size();i++) { + Effect* e = c->effects.at(i); + if (e->enable_image) { + if (!copied) { + frame_size = target_frame->linesize[0]*target_frame->height; + data = new uint8_t[frame_size]; + memcpy(data, target_frame->data[0], frame_size); + copied = true; + } + e->process_image(get_timecode(c, playhead), data, frame_size); + } + } c->texture->setData(0, get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8, data); - if (copied) delete [] data; + if (copied) delete [] data; glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } @@ -252,20 +253,22 @@ void get_clip_frame(Clip* c, long playhead) { c->queue_lock.unlock(); // get more frames - QVector empty; - if (cache) cache_clip(c, playhead, reset, false, empty); + QVector empty; + if (cache) cache_clip(c, playhead, reset, false, empty); } } long playhead_to_clip_frame(Clip* c, long playhead) { - return (qMax(0L, playhead - c->get_timeline_in_with_transition()) + c->get_clip_in_with_transition()); + return (qMax(0L, playhead - c->get_timeline_in_with_transition()) + c->get_clip_in_with_transition()); } double playhead_to_clip_seconds(Clip* c, long playhead) { // returns time in seconds long clip_frame = playhead_to_clip_frame(c, playhead); if (c->reverse) clip_frame = c->getMaximumLength() - clip_frame - 1; - return ((double) clip_frame/c->sequence->frame_rate)*c->speed; + double secs = ((double) clip_frame/c->sequence->frame_rate)*c->speed; + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) secs *= c->media->to_footage()->speed; + return secs; } int64_t seconds_to_timestamp(Clip* c, double seconds) { @@ -278,11 +281,11 @@ int64_t playhead_to_timestamp(Clip* c, long playhead) { int retrieve_next_frame(Clip* c, AVFrame* f) { int result = 0; - int receive_ret; + int receive_ret; // do we need to retrieve a new packet for a new frame? av_frame_unref(f); - while ((receive_ret = avcodec_receive_frame(c->codecCtx, f)) == AVERROR(EAGAIN)) { + while ((receive_ret = avcodec_receive_frame(c->codecCtx, f)) == AVERROR(EAGAIN)) { int read_ret = 0; do { if (c->pkt_written) { @@ -293,15 +296,15 @@ int retrieve_next_frame(Clip* c, AVFrame* f) { if (read_ret >= 0) { c->pkt_written = true; } - } while (read_ret >= 0 && c->pkt->stream_index != c->media_stream); + } while (read_ret >= 0 && c->pkt->stream_index != c->media_stream); if (read_ret >= 0) { int send_ret = avcodec_send_packet(c->codecCtx, c->pkt); if (send_ret < 0) { dout << "[ERROR] Failed to send packet to decoder." << send_ret; - return send_ret; + return send_ret; } - } else { + } else { if (read_ret == AVERROR_EOF) { int send_ret = avcodec_send_packet(c->codecCtx, NULL); if (send_ret < 0) { @@ -323,32 +326,30 @@ int retrieve_next_frame(Clip* c, AVFrame* f) { } bool is_clip_active(Clip* c, long playhead) { - return c->enabled - && c->get_timeline_in_with_transition() < playhead + ceil(c->sequence->frame_rate*2) - && c->get_timeline_out_with_transition() > playhead - && playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition() < c->getMaximumLength(); + return c->enabled + && c->get_timeline_in_with_transition() < playhead + ceil(c->sequence->frame_rate*2) + && c->get_timeline_out_with_transition() > playhead + && playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition() < c->getMaximumLength(); } void set_sequence(Sequence* s) { - closeActiveClips(sequence, true); panel_effect_controls->clear_effects(true); - sequence = s; + sequence = s; panel_sequence_viewer->set_main_sequence(); - panel_timeline->update_sequence(); - panel_timeline->setFocus(); + panel_timeline->update_sequence(); + panel_timeline->setFocus(); } -void closeActiveClips(Sequence *s, bool wait) { +void closeActiveClips(Sequence *s) { if (s != NULL) { - for (int i=0;iclips.size();i++) { - Clip* c = s->clips.at(i); - if (c != NULL) { - if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { - closeActiveClips(c->media->to_sequence(), wait); - if (c->open) close_clip(c); - } else if (clip_uses_cacher(c) && c->open) { - close_clip(c); - if (c->multithreaded && wait) c->cacher->wait(); + for (int i=0;iclips.size();i++) { + Clip* c = s->clips.at(i); + if (c != NULL) { + if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + closeActiveClips(c->media->to_sequence()); + if (c->open) close_clip(c, true); + } else if (c->open) { + close_clip(c, true); } } } diff --git a/playback/playback.h b/playback/playback.h index 67a18c6d4..db4a80600 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -15,7 +15,7 @@ extern bool rendering; bool clip_uses_cacher(Clip* clip); void open_clip(Clip* clip, bool multithreaded); void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector &nests); -void close_clip(Clip* clip); +void close_clip(Clip* clip, bool wait); void cache_audio_worker(Clip* c, bool write_A); void cache_video_worker(Clip* c, long playhead); void handle_media(Sequence* sequence, long playhead, bool multithreaded); @@ -32,6 +32,6 @@ int retrieve_next_frame(Clip* c, AVFrame* f); bool is_clip_active(Clip* c, long playhead); void get_next_audio(Clip* c, bool mix); void set_sequence(Sequence* s); -void closeActiveClips(Sequence* s, bool wait); +void closeActiveClips(Sequence* s); #endif // PLAYBACK_H diff --git a/project/clip.cpp b/project/clip.cpp index 213a53858..05758928c 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -12,6 +12,7 @@ #include "project/media.h" #include "io/clipboard.h" #include "undo.h" +#include "debug.h" extern "C" { #include @@ -113,9 +114,9 @@ void Clip::refresh() { Footage* m = media->to_footage(); if (track < 0 && m->video_tracks.size() > 0) { - media_stream = m->video_tracks.at(0)->file_index; + media_stream = m->video_tracks.at(0).file_index; } else if (track >= 0 && m->audio_tracks.size() > 0) { - media_stream = m->audio_tracks.at(0)->file_index; + media_stream = m->audio_tracks.at(0).file_index; } } replaced = false; @@ -171,12 +172,7 @@ Transition* Clip::get_closing_transition() { Clip::~Clip() { if (open) { - close_clip(this); - - // make sure clip has closed before clip is destroyed - if (multithreaded && media != NULL && media->get_type() == MEDIA_TYPE_FOOTAGE) { - cacher->wait(); - } + close_clip(this, true); } if (opening_transition != -1) this->sequence->hard_delete_transition(this, TA_OPENING_TRANSITION); @@ -241,7 +237,7 @@ void Clip::recalculateMaxLength() { case MEDIA_TYPE_FOOTAGE: { Footage* m = media->to_footage(); - FootageStream* ms = m->get_stream_from_file_index(track < 0, media_stream); + const FootageStream* ms = m->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL && ms->infinite_length) { calculated_length = LONG_MAX; } else { @@ -269,7 +265,7 @@ int Clip::getWidth() { switch (media->get_type()) { case MEDIA_TYPE_FOOTAGE: { - FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); + const FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL) return ms->video_width; if (sequence != NULL) return sequence->width; } @@ -287,7 +283,7 @@ int Clip::getHeight() { switch (media->get_type()) { case MEDIA_TYPE_FOOTAGE: { - FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); + const FootageStream* ms = media->to_footage()->get_stream_from_file_index(track < 0, media_stream); if (ms != NULL) return ms->video_height; if (sequence != NULL) return sequence->height; } @@ -309,17 +305,16 @@ void Clip::refactor_frame_rate(ComboAction* ca, double multiplier, bool change_t track); } + // move keyframes for (int i=0;irow_count();j++) { EffectRow* r = e->row(j); - for (int k=0;kkeyframe_times.size();k++) { - long new_pos = r->keyframe_times.at(k) * multiplier; - KeyframeMove* km = new KeyframeMove(); - km->movement = new_pos - r->keyframe_times.at(k); - km->rows.append(r); - km->keyframes.append(k); - ca->append(km); + for (int l=0;lfieldCount();l++) { + EffectField* f = r->field(l); + for (int k=0;kkeyframes.size();k++) { + ca->append(new SetLong(&f->keyframes[k].time, f->keyframes[k].time, f->keyframes[k].time * multiplier)); + } } } } diff --git a/project/effect.cpp b/project/effect.cpp index 99268ba7c..3b5baef6a 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -10,6 +10,7 @@ #include "project/clip.h" #include "panels/timeline.h" #include "panels/effectcontrols.h" +#include "panels/grapheditor.h" #include "ui/checkboxex.h" #include "debug.h" #include "io/path.h" @@ -51,14 +52,14 @@ Effect* create_effect(Clip* c, const EffectMeta* em) { switch (em->internal) { case EFFECT_INTERNAL_TRANSFORM: return new TransformEffect(c, em); case EFFECT_INTERNAL_TEXT: return new TextEffect(c, em); - case EFFECT_INTERNAL_TIMECODE: return new TimecodeEffect(c, em); + case EFFECT_INTERNAL_TIMECODE: return new TimecodeEffect(c, em); case EFFECT_INTERNAL_SOLID: return new SolidEffect(c, em); case EFFECT_INTERNAL_NOISE: return new AudioNoiseEffect(c, em); case EFFECT_INTERNAL_VOLUME: return new VolumeEffect(c, em); case EFFECT_INTERNAL_PAN: return new PanEffect(c, em); case EFFECT_INTERNAL_TONE: return new ToneEffect(c, em); - case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); - case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); + case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); + case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); } } else { dout << "[ERROR] Invalid effect data"; @@ -68,12 +69,12 @@ Effect* create_effect(Clip* c, const EffectMeta* em) { } const EffectMeta* get_internal_meta(int internal_id, int type) { - for (int i=0;i get_effects_paths() { QList effects_paths; effects_paths.append(get_app_dir() + "/effects"); effects_paths.append(get_app_dir() + "/../share/olive-editor/effects"); QString env_path(qgetenv("OLIVE_EFFECTS_PATH")); if (!env_path.isEmpty()) effects_paths.append(env_path); + return effects_paths; +} + +void load_shader_effects() { + QList effects_paths = get_effects_paths(); for (int h=0;henabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); - ui = new QWidget(); - ui_layout = new QGridLayout(); + // set up base UI + container = new CollapsibleWidget(); + connect(container->enabled_check, SIGNAL(clicked(bool)), this, SLOT(field_changed())); + ui = new QWidget(); + ui_layout = new QGridLayout(); ui_layout->setSpacing(4); - ui->setLayout(ui_layout); + ui->setLayout(ui_layout); container->setContents(ui); connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); - // set up UI from effect file + // set up UI from effect file container->setText(em->name); if (!em->filename.isEmpty()) { @@ -276,7 +282,7 @@ Effect::Effect(Clip* c, const EffectMeta *em) : } } if (!row_name.isEmpty()) { - EffectRow* row = add_row(row_name + ":"); + EffectRow* row = add_row(row_name); while (!reader.atEnd() && !(reader.name() == "row" && reader.isEndElement())) { reader.readNext(); if (reader.name() == "field" && reader.isStartElement()) { @@ -310,7 +316,7 @@ Effect::Effect(Clip* c, const EffectMeta *em) : if (id.isEmpty()) { dout << "[ERROR] Couldn't load field from" << em->filename << "- ID cannot be empty."; } else if (type > -1) { - EffectField* field = row->add_field(type, id); + EffectField* field = row->add_field(type, id); connect(field, SIGNAL(changed()), this, SLOT(field_changed())); switch (type) { case EFFECT_FIELD_DOUBLE: @@ -446,9 +452,9 @@ Effect::~Effect() { for (int i=0;irows.at(i); copy_row->setKeyframing(row->isKeyframing()); - copy_row->keyframe_times = row->keyframe_times; - copy_row->keyframe_types = row->keyframe_types; for (int j=0;jfieldCount();j++) { EffectField* field = row->field(j); - copy_row->field(j)->set_current_data(field->get_current_data()); - copy_row->field(j)->keyframe_data = field->keyframe_data; + EffectField* copy_field = copy_row->field(j); + copy_field->keyframes = field->keyframes; } } } EffectRow* Effect::add_row(const QString& name, bool savable) { - EffectRow* row = new EffectRow(this, savable, ui_layout, name, rows.size()); - rows.append(row); + EffectRow* row = new EffectRow(this, savable, ui_layout, name, rows.size()); + rows.append(row); return row; } @@ -477,52 +481,53 @@ EffectRow* Effect::row(int i) { } int Effect::row_count() { - return rows.size(); + return rows.size(); } EffectGizmo *Effect::add_gizmo(int type) { - EffectGizmo* gizmo = new EffectGizmo(type); - gizmos.append(gizmo); - return gizmo; + EffectGizmo* gizmo = new EffectGizmo(type); + gizmos.append(gizmo); + return gizmo; } EffectGizmo *Effect::gizmo(int i) { - return gizmos.at(i); + return gizmos.at(i); } int Effect::gizmo_count(){ - return gizmos.size(); + return gizmos.size(); } void Effect::refresh() {} void Effect::field_changed() { - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->update(); + panel_graph_editor->update_panel(); } void Effect::show_context_menu(const QPoint& pos) { - if (meta->type == EFFECT_TYPE_EFFECT) { - QMenu menu(mainWindow); + if (meta->type == EFFECT_TYPE_EFFECT) { + QMenu menu(mainWindow); - int index = get_index_in_clip(); + int index = get_index_in_clip(); - if (index > 0) { - QAction* move_up = menu.addAction("Move &Up"); - connect(move_up, SIGNAL(triggered(bool)), this, SLOT(move_up())); - } + if (index > 0) { + QAction* move_up = menu.addAction("Move &Up"); + connect(move_up, SIGNAL(triggered(bool)), this, SLOT(move_up())); + } - if (index < parent_clip->effects.size() - 1) { - QAction* move_down = menu.addAction("Move &Down"); - connect(move_down, SIGNAL(triggered(bool)), this, SLOT(move_down())); - } + if (index < parent_clip->effects.size() - 1) { + QAction* move_down = menu.addAction("Move &Down"); + connect(move_down, SIGNAL(triggered(bool)), this, SLOT(move_down())); + } - menu.addSeparator(); + menu.addSeparator(); - QAction* del_action = menu.addAction("D&elete"); - connect(del_action, SIGNAL(triggered(bool)), this, SLOT(delete_self())); + QAction* del_action = menu.addAction("D&elete"); + connect(del_action, SIGNAL(triggered(bool)), this, SLOT(delete_self())); - menu.exec(container->title_bar->mapToGlobal(pos)); - } + menu.exec(container->title_bar->mapToGlobal(pos)); + } } void Effect::delete_self() { @@ -572,24 +577,24 @@ void Effect::set_enabled(bool b) { QVariant load_data_from_string(int type, const QString& string) { switch (type) { - case EFFECT_FIELD_DOUBLE: return string.toDouble(); - case EFFECT_FIELD_COLOR: return QColor(string); - case EFFECT_FIELD_STRING: return string; - case EFFECT_FIELD_BOOL: return (string == "1"); - case EFFECT_FIELD_COMBO: return string.toInt(); - case EFFECT_FIELD_FONT: return string; + case EFFECT_FIELD_DOUBLE: return string.toDouble(); + case EFFECT_FIELD_COLOR: return QColor(string); + case EFFECT_FIELD_STRING: return string; + case EFFECT_FIELD_BOOL: return (string == "1"); + case EFFECT_FIELD_COMBO: return string.toInt(); + case EFFECT_FIELD_FONT: return string; } return QVariant(); } QString save_data_to_string(int type, const QVariant& data) { switch (type) { - case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble()); - case EFFECT_FIELD_COLOR: return data.value().name(); - case EFFECT_FIELD_STRING: return data.toString(); - case EFFECT_FIELD_BOOL: return QString::number(data.toBool()); - case EFFECT_FIELD_COMBO: return QString::number(data.toInt()); - case EFFECT_FIELD_FONT: return data.toString(); + case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble()); + case EFFECT_FIELD_COLOR: return data.value().name(); + case EFFECT_FIELD_STRING: return data.toString(); + case EFFECT_FIELD_BOOL: return QString::number(data.toBool()); + case EFFECT_FIELD_COMBO: return QString::number(data.toInt()); + case EFFECT_FIELD_FONT: return data.toString(); } return QString(); } @@ -610,7 +615,7 @@ void Effect::load(QXmlStreamReader& stream) { stream.readNext(); // read keyframes - if (stream.name() == "keyframes" && stream.isStartElement()) { + /*if (stream.name() == "keyframes" && stream.isStartElement()) { for (int k=0;kkeyframe_times.append(keyframe_frame); - row->keyframe_types.append(keyframe_type); + for (int k=0;kfieldCount();k++) { + EffectField* field = row->field(k); + EffectKeyframe key; + key.time = keyframe_frame; + key.type = keyframe_type; + field->keyframes.append(key); + } } stream.readNext(); } } stream.readNext(); - } + }*/ // read field if (stream.name() == "field" && stream.isStartElement()) { @@ -678,10 +688,30 @@ void Effect::load(QXmlStreamReader& stream) { while (!stream.atEnd() && !(stream.name() == "field" && stream.isEndElement())) { stream.readNext(); - // read all keyframes + // read keyframes if (stream.name() == "key" && stream.isStartElement()) { - stream.readNext(); - field->keyframe_data.append(load_data_from_string(field->type, stream.text().toString())); + row->setKeyframing(true); + + EffectKeyframe key; + for (int k=0;ktype, attr.value().toString()); + } else if (attr.name() == "frame") { + key.time = attr.value().toLong(); + } else if (attr.name() == "type") { + key.type = attr.value().toInt(); + } else if (attr.name() == "prehx") { + key.pre_handle_x = attr.value().toDouble(); + } else if (attr.name() == "prehy") { + key.pre_handle_y = attr.value().toDouble(); + } else if (attr.name() == "posthx") { + key.post_handle_x = attr.value().toDouble(); + } else if (attr.name() == "posthy") { + key.post_handle_y = attr.value().toDouble(); + } + } + field->keyframes.append(key); } } } else { @@ -701,33 +731,54 @@ void Effect::load(QXmlStreamReader& stream) { void Effect::save(QXmlStreamWriter& stream) { stream.writeAttribute("name", meta->name); - stream.writeAttribute("enabled", QString::number(is_enabled())); + stream.writeAttribute("enabled", QString::number(is_enabled())); for (int i=0;isavable) { - stream.writeStartElement("row"); // row - stream.writeStartElement("keyframes"); // keyframes - stream.writeAttribute("enabled", QString::number(row->isKeyframing())); - for (int j=0;jkeyframe_times.size();j++) { - stream.writeStartElement("key"); // key - stream.writeAttribute("frame", QString::number(row->keyframe_times.at(j))); - stream.writeAttribute("type", QString::number(row->keyframe_types.at(j))); - stream.writeEndElement(); // key - } - stream.writeEndElement(); // keyframes - for (int j=0;jfieldCount();j++) { - EffectField* field = row->field(j); - stream.writeStartElement("field"); // field - stream.writeAttribute("id", field->id); - stream.writeAttribute("value", save_data_to_string(field->type, field->get_current_data())); - for (int k=0;kkeyframe_data.size();k++) { - stream.writeTextElement("key", save_data_to_string(field->type, field->keyframe_data.at(k))); - } - stream.writeEndElement(); // field - } - stream.writeEndElement(); // row - } + if (row->savable) { + stream.writeStartElement("row"); // row + for (int j=0;jfieldCount();j++) { + EffectField* field = row->field(j); + stream.writeStartElement("field"); // field + stream.writeAttribute("id", field->id); + stream.writeAttribute("value", save_data_to_string(field->type, field->get_current_data())); + for (int k=0;kkeyframes.size();k++) { + const EffectKeyframe& key = field->keyframes.at(k); + stream.writeStartElement("key"); + stream.writeAttribute("value", save_data_to_string(field->type, key.data)); + stream.writeAttribute("frame", QString::number(key.time)); + stream.writeAttribute("type", QString::number(key.type)); + stream.writeAttribute("prehx", QString::number(key.pre_handle_x)); + stream.writeAttribute("prehy", QString::number(key.pre_handle_y)); + stream.writeAttribute("posthx", QString::number(key.post_handle_x)); + stream.writeAttribute("posthy", QString::number(key.post_handle_y)); + stream.writeEndElement(); // key + } + stream.writeEndElement(); // field + } + stream.writeEndElement(); // row + } + } +} + +bool Effect::is_open() { + return isOpen; +} + +void Effect::validate_meta_path() { + if (!meta->path.isEmpty() || (vertPath.isEmpty() && fragPath.isEmpty())) return; + QList effects_paths = get_effects_paths(); + const QString& test_fn = vertPath.isEmpty() ? fragPath : vertPath; + for (int i=0;iaddShaderFromSourceFile(QOpenGLShader::Vertex, meta->path + "/" + vertPath); if (!fragPath.isEmpty()) glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, meta->path + "/" + fragPath); glslProgram->link(); @@ -777,16 +829,16 @@ void Effect::startEffect() { void Effect::endEffect() { if (bound) glslProgram->release(); - bound = false; + bound = false; } void Effect::process_image(double, uint8_t *, int) {} Effect* Effect::copy(Clip* c) { Effect* copy = create_effect(c, meta); - copy->set_enabled(is_enabled()); - copy_field_keyframes(copy); - return copy; + copy->set_enabled(is_enabled()); + copy_field_keyframes(copy); + return copy; } void Effect::process_shader(double timecode, GLTextureCoords&) { @@ -831,7 +883,7 @@ GLuint Effect::process_superimpose(double timecode) { recreate_texture = true; } - if (valueHasChanged(timecode) || recreate_texture || enable_always_update) { + if (valueHasChanged(timecode) || recreate_texture || enable_always_update) { redraw(timecode); } @@ -852,78 +904,78 @@ GLuint Effect::process_superimpose(double timecode) { void Effect::process_audio(double, double, quint8*, int, int) { // only volume/pan, hand off to AU and VST for all other cases - /*double interval = (timecode_end-timecode_start)/nb_bytes; + /*double interval = (timecode_end-timecode_start)/nb_bytes; for (int i=0;ifield(0)->get_double_value(timecode_start+(interval*i), true)); QJSValue result = eval.call(); - samp = result.toInt(); - QJSValueList args; - args << samples << nb_bytes; + samp = result.toInt(); + QJSValueList args; + args << samples << nb_bytes; samples[i+1] = (quint8) (samp >> 8); samples[i] = (quint8) samp; - }*/ + }*/ } void Effect::gizmo_draw(double, GLTextureCoords &) {} void Effect::gizmo_move(EffectGizmo* gizmo, int x_movement, int y_movement, double timecode, bool done) { - for (int i=0;ix_field1 != NULL) { - gizmo->x_field1->set_double_value(gizmo->x_field1->get_double_value(timecode) + x_movement*gizmo->x_field_multi1); - gizmo->x_field1->make_key_from_change(ca); - } - if (gizmo->y_field1 != NULL) { - gizmo->y_field1->set_double_value(gizmo->y_field1->get_double_value(timecode) + y_movement*gizmo->y_field_multi1); - gizmo->y_field1->make_key_from_change(ca); - } - if (gizmo->x_field2 != NULL) { - gizmo->x_field2->set_double_value(gizmo->x_field2->get_double_value(timecode) + x_movement*gizmo->x_field_multi2); - gizmo->x_field2->make_key_from_change(ca); - } - if (gizmo->y_field2 != NULL) { - gizmo->y_field2->set_double_value(gizmo->y_field2->get_double_value(timecode) + y_movement*gizmo->y_field_multi2); - gizmo->y_field2->make_key_from_change(ca); - } - if (done) undo_stack.push(ca); - break; - } - } + for (int i=0;ix_field1 != NULL) { + gizmo->x_field1->set_double_value(gizmo->x_field1->get_double_value(timecode) + x_movement*gizmo->x_field_multi1); + gizmo->x_field1->make_key_from_change(ca); + } + if (gizmo->y_field1 != NULL) { + gizmo->y_field1->set_double_value(gizmo->y_field1->get_double_value(timecode) + y_movement*gizmo->y_field_multi1); + gizmo->y_field1->make_key_from_change(ca); + } + if (gizmo->x_field2 != NULL) { + gizmo->x_field2->set_double_value(gizmo->x_field2->get_double_value(timecode) + x_movement*gizmo->x_field_multi2); + gizmo->x_field2->make_key_from_change(ca); + } + if (gizmo->y_field2 != NULL) { + gizmo->y_field2->set_double_value(gizmo->y_field2->get_double_value(timecode) + y_movement*gizmo->y_field_multi2); + gizmo->y_field2->make_key_from_change(ca); + } + if (done) undo_stack.push(ca); + break; + } + } } void Effect::gizmo_world_to_screen() { - GLfloat view_val[16]; - GLfloat projection_val[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, view_val); - glGetFloatv(GL_PROJECTION_MATRIX, projection_val); + GLfloat view_val[16]; + GLfloat projection_val[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, view_val); + glGetFloatv(GL_PROJECTION_MATRIX, projection_val); - QMatrix4x4 view_matrix(view_val); - QMatrix4x4 projection_matrix(projection_val); + QMatrix4x4 view_matrix(view_val); + QMatrix4x4 projection_matrix(projection_val); - for (int i=0;iget_point_count();j++) { - QVector4D screen_pos = QVector4D(g->world_pos[j].x(), g->world_pos[j].y(), 0, 1.0) * (view_matrix * projection_matrix); + for (int j=0;jget_point_count();j++) { + QVector4D screen_pos = QVector4D(g->world_pos[j].x(), g->world_pos[j].y(), 0, 1.0) * (view_matrix * projection_matrix); - int adjusted_sx1 = qRound(((screen_pos.x()*0.5f)+0.5f)*parent_clip->sequence->width); - int adjusted_sy1 = qRound((1.0f-((screen_pos.y()*0.5f)+0.5f))*parent_clip->sequence->height); + int adjusted_sx1 = qRound(((screen_pos.x()*0.5f)+0.5f)*parent_clip->sequence->width); + int adjusted_sy1 = qRound((1.0f-((screen_pos.y()*0.5f)+0.5f))*parent_clip->sequence->height); - g->screen_pos[j] = QPoint(adjusted_sx1, adjusted_sy1); - } - } + g->screen_pos[j] = QPoint(adjusted_sx1, adjusted_sy1); + } + } } bool Effect::are_gizmos_enabled() { - return (gizmos.size() > 0); + return (gizmos.size() > 0); } void Effect::redraw(double) { diff --git a/project/effect.h b/project/effect.h index 3123af3cc..290fa6cc7 100644 --- a/project/effect.h +++ b/project/effect.h @@ -23,16 +23,15 @@ class QXmlStreamWriter; class Effect; class EffectRow; class CheckboxEx; -class KeyframeDelete; struct EffectMeta { - QString name; - QString category; - QString filename; + QString name; + QString category; + QString filename; QString path; int internal; int type; - int subtype; + int subtype; }; extern QVector effects; @@ -70,37 +69,37 @@ extern QMutex effects_loaded; #define EFFECT_INTERNAL_COUNT 13 #define KEYFRAME_TYPE_LINEAR 0 -#define KEYFRAME_TYPE_SMOOTH 1 -#define KEYFRAME_TYPE_BEZIER 2 +#define KEYFRAME_TYPE_BEZIER 1 +#define KEYFRAME_TYPE_HOLD 2 struct GLTextureCoords { - int grid_size; + int grid_size; int vertexTopLeftX; int vertexTopLeftY; - int vertexTopLeftZ; + int vertexTopLeftZ; int vertexTopRightX; int vertexTopRightY; - int vertexTopRightZ; + int vertexTopRightZ; int vertexBottomLeftX; int vertexBottomLeftY; - int vertexBottomLeftZ; + int vertexBottomLeftZ; int vertexBottomRightX; int vertexBottomRightY; - int vertexBottomRightZ; + int vertexBottomRightZ; - float textureTopLeftX; - float textureTopLeftY; - float textureTopLeftQ; - float textureTopRightX; - float textureTopRightY; - float textureTopRightQ; - float textureBottomRightX; - float textureBottomRightY; - float textureBottomRightQ; - float textureBottomLeftX; - float textureBottomLeftY; - float textureBottomLeftQ; + float textureTopLeftX; + float textureTopLeftY; + float textureTopLeftQ; + float textureTopRightX; + float textureTopRightY; + float textureTopRightQ; + float textureBottomRightX; + float textureBottomRightY; + float textureBottomRightQ; + float textureBottomLeftX; + float textureBottomLeftY; + float textureBottomLeftQ; }; qint16 mix_audio_sample(qint16 a, qint16 b); @@ -114,21 +113,21 @@ class Effect : public QObject { public: Effect(Clip* c, const EffectMeta* em); ~Effect(); - Clip* parent_clip; + Clip* parent_clip; const EffectMeta* meta; - int id; + int id; QString name; CollapsibleWidget* container; - EffectRow* add_row(const QString &name, bool savable = true); + EffectRow* add_row(const QString &name, bool savable = true); EffectRow* row(int i); int row_count(); - EffectGizmo* add_gizmo(int type); - EffectGizmo* gizmo(int i); - int gizmo_count(); + EffectGizmo* add_gizmo(int type); + EffectGizmo* gizmo(int i); + int gizmo_count(); - bool is_enabled(); + bool is_enabled(); void set_enabled(bool b); virtual void refresh(); @@ -137,9 +136,10 @@ public: void copy_field_keyframes(Effect *e); void load(QXmlStreamReader& stream); - void save(QXmlStreamWriter& stream); + void save(QXmlStreamWriter& stream); // glsl handling + bool is_open(); void open(); void close(); virtual void startEffect(); @@ -147,24 +147,24 @@ public: bool enable_shader; bool enable_coords; - bool enable_superimpose; - bool enable_image; + bool enable_superimpose; + bool enable_image; int getIterations(); void setIterations(int i); const char* ffmpeg_filter; - virtual void process_image(double timecode, uint8_t* data, int size); + virtual void process_image(double timecode, uint8_t* data, int size); virtual void process_shader(double timecode, GLTextureCoords&); - virtual void process_coords(double timecode, GLTextureCoords& coords, int data); + virtual void process_coords(double timecode, GLTextureCoords& coords, int data); virtual GLuint process_superimpose(double timecode); virtual void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); - virtual void gizmo_draw(double timecode, GLTextureCoords& coords); - void gizmo_move(EffectGizmo* sender, int x_movement, int y_movement, double timecode, bool done); - void gizmo_world_to_screen(); - bool are_gizmos_enabled(); + virtual void gizmo_draw(double timecode, GLTextureCoords& coords); + void gizmo_move(EffectGizmo* sender, int x_movement, int y_movement, double timecode, bool done); + void gizmo_world_to_screen(); + bool are_gizmos_enabled(); public slots: void field_changed(); private slots: @@ -182,17 +182,17 @@ protected: QImage img; QOpenGLTexture* texture; - // enable effect to update constantly - bool enable_always_update; + // enable effect to update constantly + bool enable_always_update; private: // superimpose effect QString script; bool isOpen; QVector rows; - QVector gizmos; + QVector gizmos; QGridLayout* ui_layout; - QWidget* ui; + QWidget* ui; bool bound; // superimpose functions @@ -201,6 +201,7 @@ private: QVector cachedValues; void delete_texture(); int get_index_in_clip(); + void validate_meta_path(); }; class EffectInit : public QThread { diff --git a/project/effectfield.cpp b/project/effectfield.cpp index d2166a835..6eda75505 100644 --- a/project/effectfield.cpp +++ b/project/effectfield.cpp @@ -15,347 +15,366 @@ #include "project/sequence.h" #include "io/math.h" -#include +#include + +#include "debug.h" EffectField::EffectField(EffectRow *parent, int t, const QString &i) : - parent_row(parent), - type(t), - id(i) + parent_row(parent), + type(t), + id(i) { - switch (t) { - case EFFECT_FIELD_DOUBLE: - { - LabelSlider* ls = new LabelSlider(); - ui_element = ls; - connect(ls, SIGNAL(valueChanged()), this, SLOT(ui_element_change())); - } - break; - case EFFECT_FIELD_COLOR: - { - ColorButton* cb = new ColorButton(); - ui_element = cb; - connect(cb, SIGNAL(color_changed()), this, SLOT(ui_element_change())); - } - break; - case EFFECT_FIELD_STRING: - { - TextEditEx* edit = new TextEditEx(); - edit->setUndoRedoEnabled(true); - ui_element = edit; - connect(edit, SIGNAL(textChanged()), this, SLOT(ui_element_change())); - } - break; - case EFFECT_FIELD_BOOL: - { - CheckboxEx* cb = new CheckboxEx(); - ui_element = cb; - connect(cb, SIGNAL(clicked(bool)), this, SLOT(ui_element_change())); - connect(cb, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool))); - } - break; - case EFFECT_FIELD_COMBO: - { - ComboBoxEx* cb = new ComboBoxEx(); - ui_element = cb; - connect(cb, SIGNAL(activated(int)), this, SLOT(ui_element_change())); - } - break; - case EFFECT_FIELD_FONT: - { - FontCombobox* fcb = new FontCombobox(); - ui_element = fcb; - connect(fcb, SIGNAL(activated(int)), this, SLOT(ui_element_change())); - } - break; - } + switch (t) { + case EFFECT_FIELD_DOUBLE: + { + LabelSlider* ls = new LabelSlider(); + ui_element = ls; + connect(ls, SIGNAL(valueChanged()), this, SLOT(ui_element_change())); + connect(ls, SIGNAL(clicked()), this, SIGNAL(clicked())); + } + break; + case EFFECT_FIELD_COLOR: + { + ColorButton* cb = new ColorButton(); + ui_element = cb; + connect(cb, SIGNAL(color_changed()), this, SLOT(ui_element_change())); + } + break; + case EFFECT_FIELD_STRING: + { + TextEditEx* edit = new TextEditEx(); + edit->setUndoRedoEnabled(true); + ui_element = edit; + connect(edit, SIGNAL(textChanged()), this, SLOT(ui_element_change())); + } + break; + case EFFECT_FIELD_BOOL: + { + CheckboxEx* cb = new CheckboxEx(); + ui_element = cb; + connect(cb, SIGNAL(clicked(bool)), this, SLOT(ui_element_change())); + connect(cb, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool))); + } + break; + case EFFECT_FIELD_COMBO: + { + ComboBoxEx* cb = new ComboBoxEx(); + ui_element = cb; + connect(cb, SIGNAL(activated(int)), this, SLOT(ui_element_change())); + } + break; + case EFFECT_FIELD_FONT: + { + FontCombobox* fcb = new FontCombobox(); + ui_element = fcb; + connect(fcb, SIGNAL(activated(int)), this, SLOT(ui_element_change())); + } + break; + } } QVariant EffectField::get_previous_data() { - switch (type) { - case EFFECT_FIELD_DOUBLE: return static_cast(ui_element)->getPreviousValue(); - case EFFECT_FIELD_COLOR: return static_cast(ui_element)->getPreviousValue(); - case EFFECT_FIELD_STRING: return static_cast(ui_element)->getPreviousValue(); - case EFFECT_FIELD_BOOL: return !static_cast(ui_element)->isChecked(); - case EFFECT_FIELD_COMBO: return static_cast(ui_element)->getPreviousIndex(); - case EFFECT_FIELD_FONT: return static_cast(ui_element)->getPreviousValue(); - } - return QVariant(); + switch (type) { + case EFFECT_FIELD_DOUBLE: return static_cast(ui_element)->getPreviousValue(); + case EFFECT_FIELD_COLOR: return static_cast(ui_element)->getPreviousValue(); + case EFFECT_FIELD_STRING: return static_cast(ui_element)->getPreviousValue(); + case EFFECT_FIELD_BOOL: return !static_cast(ui_element)->isChecked(); + case EFFECT_FIELD_COMBO: return static_cast(ui_element)->getPreviousIndex(); + case EFFECT_FIELD_FONT: return static_cast(ui_element)->getPreviousValue(); + } + return QVariant(); } QVariant EffectField::get_current_data() { - switch (type) { - case EFFECT_FIELD_DOUBLE: return static_cast(ui_element)->value(); - case EFFECT_FIELD_COLOR: return static_cast(ui_element)->get_color(); - case EFFECT_FIELD_STRING: return static_cast(ui_element)->getPlainTextEx(); - case EFFECT_FIELD_BOOL: return static_cast(ui_element)->isChecked(); - case EFFECT_FIELD_COMBO: return static_cast(ui_element)->currentIndex(); - case EFFECT_FIELD_FONT: return static_cast(ui_element)->currentText(); - } - return QVariant(); + switch (type) { + case EFFECT_FIELD_DOUBLE: return static_cast(ui_element)->value(); + case EFFECT_FIELD_COLOR: return static_cast(ui_element)->get_color(); + case EFFECT_FIELD_STRING: return static_cast(ui_element)->getPlainTextEx(); + case EFFECT_FIELD_BOOL: return static_cast(ui_element)->isChecked(); + case EFFECT_FIELD_COMBO: return static_cast(ui_element)->currentIndex(); + case EFFECT_FIELD_FONT: return static_cast(ui_element)->currentText(); + } + return QVariant(); } double EffectField::frameToTimecode(long frame) { - return ((double) frame / parent_row->parent_effect->parent_clip->sequence->frame_rate); + return ((double) frame / parent_row->parent_effect->parent_clip->sequence->frame_rate); } long EffectField::timecodeToFrame(double timecode) { - return qRound(timecode * parent_row->parent_effect->parent_clip->sequence->frame_rate); + return qRound(timecode * parent_row->parent_effect->parent_clip->sequence->frame_rate); } void EffectField::set_current_data(const QVariant& data) { - switch (type) { - case EFFECT_FIELD_DOUBLE: return static_cast(ui_element)->set_value(data.toDouble(), false); - case EFFECT_FIELD_COLOR: return static_cast(ui_element)->set_color(data.value()); - case EFFECT_FIELD_STRING: return static_cast(ui_element)->setPlainTextEx(data.toString()); - case EFFECT_FIELD_BOOL: return static_cast(ui_element)->setChecked(data.toBool()); - case EFFECT_FIELD_COMBO: return static_cast(ui_element)->setCurrentIndexEx(data.toInt()); - case EFFECT_FIELD_FONT: return static_cast(ui_element)->setCurrentTextEx(data.toString()); - } + switch (type) { + case EFFECT_FIELD_DOUBLE: return static_cast(ui_element)->set_value(data.toDouble(), false); + case EFFECT_FIELD_COLOR: return static_cast(ui_element)->set_color(data.value()); + case EFFECT_FIELD_STRING: return static_cast(ui_element)->setPlainTextEx(data.toString()); + case EFFECT_FIELD_BOOL: return static_cast(ui_element)->setChecked(data.toBool()); + case EFFECT_FIELD_COMBO: return static_cast(ui_element)->setCurrentIndexEx(data.toInt()); + case EFFECT_FIELD_FONT: return static_cast(ui_element)->setCurrentTextEx(data.toString()); + } } void EffectField::get_keyframe_data(double timecode, int &before, int &after, double &progress) { - int before_keyframe_index = -1; - int after_keyframe_index = -1; - long before_keyframe_time = LONG_MIN; - long after_keyframe_time = LONG_MAX; - long frame = timecodeToFrame(timecode); + int before_keyframe_index = -1; + int after_keyframe_index = -1; + long before_keyframe_time = LONG_MIN; + long after_keyframe_time = LONG_MAX; + long frame = timecodeToFrame(timecode); - for (int i=0;ikeyframe_times.size();i++) { - long eval_keyframe_time = parent_row->keyframe_times.at(i); - if (eval_keyframe_time == frame) { - before = i; - after = i; - return; - } else if (eval_keyframe_time < frame && eval_keyframe_time > before_keyframe_time) { - before_keyframe_index = i; - before_keyframe_time = eval_keyframe_time; - } else if (eval_keyframe_time > frame && eval_keyframe_time < after_keyframe_time) { - after_keyframe_index = i; - after_keyframe_time = eval_keyframe_time; - } - } + for (int i=0;i before_keyframe_time) { + before_keyframe_index = i; + before_keyframe_time = eval_keyframe_time; + } else if (eval_keyframe_time > frame && eval_keyframe_time < after_keyframe_time) { + after_keyframe_index = i; + after_keyframe_time = eval_keyframe_time; + } + } - if ((type == EFFECT_FIELD_DOUBLE || type == EFFECT_FIELD_COLOR) && (before_keyframe_index > -1 && after_keyframe_index > -1)) { - // interpolate - before = before_keyframe_index; - after = after_keyframe_index; - - progress = (timecode-frameToTimecode(before_keyframe_time))/(frameToTimecode(after_keyframe_time)-frameToTimecode(before_keyframe_time)); - } else if (before_keyframe_index > -1) { - before = before_keyframe_index; - after = before_keyframe_index; - } else { - before = after_keyframe_index; - after = after_keyframe_index; - } + if ((type == EFFECT_FIELD_DOUBLE || type == EFFECT_FIELD_COLOR) && (before_keyframe_index > -1 && after_keyframe_index > -1)) { + // interpolate + before = before_keyframe_index; + after = after_keyframe_index; + progress = (timecode-frameToTimecode(before_keyframe_time))/(frameToTimecode(after_keyframe_time)-frameToTimecode(before_keyframe_time)); + } else if (before_keyframe_index > -1) { + before = before_keyframe_index; + after = before_keyframe_index; + } else { + before = after_keyframe_index; + after = after_keyframe_index; + } } bool EffectField::hasKeyframes() { - return (parent_row->isKeyframing() && keyframe_data.size() > 0); + return (parent_row->isKeyframing() && keyframes.size() > 0); } QVariant EffectField::validate_keyframe_data(double timecode, bool async) { - if (hasKeyframes()) { - int before_keyframe; - int after_keyframe; - double progress; - get_keyframe_data(timecode, before_keyframe, after_keyframe, progress); + if (hasKeyframes()) { + int before_keyframe; + int after_keyframe; + double progress; + get_keyframe_data(timecode, before_keyframe, after_keyframe, progress); - int kf_type = (progress < 0.5) ? parent_row->keyframe_types.at(before_keyframe) : parent_row->keyframe_types.at(after_keyframe); - if (kf_type == KEYFRAME_TYPE_SMOOTH) { - double x = (8.0 * progress) - 4.0; - progress = 1.0 / (1.0 + qPow(M_E, -x)); - progress *= 1.0373; - progress -= 0.01865; - } + const QVariant& before_data = keyframes.at(before_keyframe).data; + switch (type) { + case EFFECT_FIELD_DOUBLE: + { + double value; + if (before_keyframe == after_keyframe) { + value = keyframes.at(before_keyframe).data.toDouble(); + } else { + const EffectKeyframe& before_key = keyframes.at(before_keyframe); + const EffectKeyframe& after_key = keyframes.at(after_keyframe); - const QVariant& before_data = keyframe_data.at(before_keyframe); - switch (type) { - case EFFECT_FIELD_DOUBLE: - { - double value; - if (before_keyframe == after_keyframe) { - value = keyframe_data.at(before_keyframe).toDouble(); - } else { - double before_dbl = keyframe_data.at(before_keyframe).toDouble(); - double after_dbl = keyframe_data.at(after_keyframe).toDouble(); - value = double_lerp(before_dbl, after_dbl, progress); - } - if (async) { - return value; - } - static_cast(ui_element)->set_value(value, false); - } - break; - case EFFECT_FIELD_COLOR: - { - QColor value; - if (before_keyframe == after_keyframe) { - value = keyframe_data.at(before_keyframe).value(); - } else { - QColor before_data = keyframe_data.at(before_keyframe).value(); - QColor after_data = keyframe_data.at(after_keyframe).value(); - value = QColor(lerp(before_data.red(), after_data.red(), progress), lerp(before_data.green(), after_data.green(), progress), lerp(before_data.blue(), after_data.blue(), progress)); - } - if (async) { - return value; - } - static_cast(ui_element)->set_color(value); - } - break; - case EFFECT_FIELD_STRING: - if (async) { - return before_data; - } - static_cast(ui_element)->setPlainTextEx(before_data.toString()); - break; - case EFFECT_FIELD_BOOL: - if (async) { - return before_data; - } - static_cast(ui_element)->setChecked(before_data.toBool()); - break; - case EFFECT_FIELD_COMBO: - if (async) { - return before_data; - } - static_cast(ui_element)->setCurrentIndexEx(before_data.toInt()); - break; - case EFFECT_FIELD_FONT: - if (async) { - return before_data; - } - static_cast(ui_element)->setCurrentTextEx(before_data.toString()); - break; - } - } - return QVariant(); + double before_dbl = before_key.data.toDouble(); + double after_dbl = after_key.data.toDouble(); + + if (before_key.type == KEYFRAME_TYPE_HOLD) { + // hold + value = before_dbl; + } else if (before_key.type == KEYFRAME_TYPE_BEZIER || after_key.type == KEYFRAME_TYPE_BEZIER) { + // bezier interpolation + if (before_key.type == KEYFRAME_TYPE_BEZIER && after_key.type == KEYFRAME_TYPE_BEZIER) { + // cubic bezier + double t = cubic_t_from_x(timecode*parent_row->parent_effect->parent_clip->sequence->frame_rate, before_key.time, before_key.time+before_key.post_handle_x, after_key.time+after_key.pre_handle_x, after_key.time); + value = cubic_from_t(before_dbl, before_dbl+before_key.post_handle_y, after_dbl+after_key.pre_handle_y, after_dbl, t); + } else if (after_key.type == KEYFRAME_TYPE_LINEAR) { // quadratic bezier + // last keyframe is the bezier one + double t = quad_t_from_x(timecode*parent_row->parent_effect->parent_clip->sequence->frame_rate, before_key.time, before_key.time+before_key.post_handle_x, after_key.time); + value = quad_from_t(before_dbl, before_dbl+before_key.post_handle_y, after_dbl, t); + } else { + // this keyframe is the bezier one + double t = quad_t_from_x(timecode*parent_row->parent_effect->parent_clip->sequence->frame_rate, before_key.time, after_key.time+after_key.pre_handle_x, after_key.time); + value = quad_from_t(before_dbl, after_dbl+after_key.pre_handle_y, after_dbl, t); + } + } else { + // linear + value = double_lerp(before_dbl, after_dbl, progress); + } + } + if (async) { + return value; + } + static_cast(ui_element)->set_value(value, false); + } + break; + case EFFECT_FIELD_COLOR: + { + QColor value; + if (before_keyframe == after_keyframe) { + value = keyframes.at(before_keyframe).data.value(); + } else { + QColor before_data = keyframes.at(before_keyframe).data.value(); + QColor after_data = keyframes.at(after_keyframe).data.value(); + value = QColor(lerp(before_data.red(), after_data.red(), progress), lerp(before_data.green(), after_data.green(), progress), lerp(before_data.blue(), after_data.blue(), progress)); + } + if (async) { + return value; + } + static_cast(ui_element)->set_color(value); + } + break; + case EFFECT_FIELD_STRING: + if (async) { + return before_data; + } + static_cast(ui_element)->setPlainTextEx(before_data.toString()); + break; + case EFFECT_FIELD_BOOL: + if (async) { + return before_data; + } + static_cast(ui_element)->setChecked(before_data.toBool()); + break; + case EFFECT_FIELD_COMBO: + if (async) { + return before_data; + } + static_cast(ui_element)->setCurrentIndexEx(before_data.toInt()); + break; + case EFFECT_FIELD_FONT: + if (async) { + return before_data; + } + static_cast(ui_element)->setCurrentTextEx(before_data.toString()); + break; + } + } + return QVariant(); } void EffectField::ui_element_change() { - bool dragging_double = (type == EFFECT_FIELD_DOUBLE && static_cast(ui_element)->is_dragging()); - ComboAction* ca = NULL; - if (!dragging_double) ca = new ComboAction(); - make_key_from_change(ca); - if (!dragging_double) undo_stack.push(ca); - emit changed(); + bool dragging_double = (type == EFFECT_FIELD_DOUBLE && static_cast(ui_element)->is_dragging()); + ComboAction* ca = NULL; + if (!dragging_double) ca = new ComboAction(); + make_key_from_change(ca); + if (!dragging_double) undo_stack.push(ca); + emit changed(); } void EffectField::make_key_from_change(ComboAction* ca) { - if (parent_row->isKeyframing()) { - parent_row->set_keyframe_now(ca); - } else if (ca != NULL) { - // set undo - ca->append(new EffectFieldUndo(this)); - } + if (parent_row->isKeyframing()) { + parent_row->set_keyframe_now(ca); + } else if (ca != NULL) { + // set undo + ca->append(new EffectFieldUndo(this)); + } } QWidget* EffectField::get_ui_element() { - return ui_element; + return ui_element; } void EffectField::set_enabled(bool e) { - ui_element->setEnabled(e); + ui_element->setEnabled(e); } double EffectField::get_double_value(double timecode, bool async) { - if (async && hasKeyframes()) { - return validate_keyframe_data(timecode, true).toDouble(); - } - validate_keyframe_data(timecode); - return static_cast(ui_element)->value(); + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).toDouble(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->value(); } void EffectField::set_double_value(double v) { - static_cast(ui_element)->set_value(v, false); + static_cast(ui_element)->set_value(v, false); } void EffectField::set_double_default_value(double v) { - static_cast(ui_element)->set_default_value(v); + static_cast(ui_element)->set_default_value(v); } void EffectField::set_double_minimum_value(double v) { - static_cast(ui_element)->set_minimum_value(v); + static_cast(ui_element)->set_minimum_value(v); } void EffectField::set_double_maximum_value(double v) { - static_cast(ui_element)->set_maximum_value(v); + static_cast(ui_element)->set_maximum_value(v); } void EffectField::add_combo_item(const QString& name, const QVariant& data) { - static_cast(ui_element)->addItem(name, data); + static_cast(ui_element)->addItem(name, data); } int EffectField::get_combo_index(double timecode, bool async) { - if (async && hasKeyframes()) { - return validate_keyframe_data(timecode, true).toInt(); - } - validate_keyframe_data(timecode); - return static_cast(ui_element)->currentIndex(); + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).toInt(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->currentIndex(); } const QVariant EffectField::get_combo_data(double timecode) { - validate_keyframe_data(timecode); - return static_cast(ui_element)->currentData(); + validate_keyframe_data(timecode); + return static_cast(ui_element)->currentData(); } const QString EffectField::get_combo_string(double timecode) { - validate_keyframe_data(timecode); - return static_cast(ui_element)->currentText(); + validate_keyframe_data(timecode); + return static_cast(ui_element)->currentText(); } void EffectField::set_combo_index(int index) { - static_cast(ui_element)->setCurrentIndexEx(index); + static_cast(ui_element)->setCurrentIndexEx(index); } void EffectField::set_combo_string(const QString& s) { - static_cast(ui_element)->setCurrentTextEx(s); + static_cast(ui_element)->setCurrentTextEx(s); } bool EffectField::get_bool_value(double timecode, bool async) { - if (async && hasKeyframes()) { - return validate_keyframe_data(timecode, true).toBool(); - } - validate_keyframe_data(timecode); - return static_cast(ui_element)->isChecked(); + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).toBool(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->isChecked(); } void EffectField::set_bool_value(bool b) { - return static_cast(ui_element)->setChecked(b); + return static_cast(ui_element)->setChecked(b); } const QString EffectField::get_string_value(double timecode, bool async) { - if (async && hasKeyframes()) { - return validate_keyframe_data(timecode, true).toString(); - } - validate_keyframe_data(timecode); - return static_cast(ui_element)->getPlainTextEx(); + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).toString(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->getPlainTextEx(); } void EffectField::set_string_value(const QString& s) { - static_cast(ui_element)->setPlainTextEx(s); + static_cast(ui_element)->setPlainTextEx(s); } const QString EffectField::get_font_name(double timecode, bool async) { - if (async && hasKeyframes()) { - return validate_keyframe_data(timecode, true).toString(); - } - validate_keyframe_data(timecode); - return static_cast(ui_element)->currentText(); + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).toString(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->currentText(); } void EffectField::set_font_name(const QString& s) { - static_cast(ui_element)->setCurrentText(s); + static_cast(ui_element)->setCurrentText(s); } QColor EffectField::get_color_value(double timecode, bool async) { - if (async && hasKeyframes()) { - return validate_keyframe_data(timecode, true).value(); - } - validate_keyframe_data(timecode); - return static_cast(ui_element)->get_color(); + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).value(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->get_color(); } void EffectField::set_color_value(QColor color) { - static_cast(ui_element)->set_color(color); + static_cast(ui_element)->set_color(color); } diff --git a/project/effectfield.h b/project/effectfield.h index f2b99002e..c946c475b 100644 --- a/project/effectfield.h +++ b/project/effectfield.h @@ -12,63 +12,66 @@ #include #include +#include "keyframe.h" + class EffectRow; class ComboAction; class EffectField : public QObject { - Q_OBJECT + Q_OBJECT public: - EffectField(EffectRow* parent, int t, const QString& i); - EffectRow* parent_row; - int type; - QString id; + EffectField(EffectRow* parent, int t, const QString& i); + EffectRow* parent_row; + int type; + QString id; - QVariant get_previous_data(); - QVariant get_current_data(); - double frameToTimecode(long frame); - long timecodeToFrame(double timecode); - void set_current_data(const QVariant&); - void get_keyframe_data(double timecode, int& before, int& after, double& d); - QVariant validate_keyframe_data(double timecode, bool async = false); + QVariant get_previous_data(); + QVariant get_current_data(); + double frameToTimecode(long frame); + long timecodeToFrame(double timecode); + void set_current_data(const QVariant&); + void get_keyframe_data(double timecode, int& before, int& after, double& d); + QVariant validate_keyframe_data(double timecode, bool async = false); - double get_double_value(double timecode, bool async = false); - void set_double_value(double v); - void set_double_default_value(double v); - void set_double_minimum_value(double v); - void set_double_maximum_value(double v); + double get_double_value(double timecode, bool async = false); + void set_double_value(double v); + void set_double_default_value(double v); + void set_double_minimum_value(double v); + void set_double_maximum_value(double v); - const QString get_string_value(double timecode, bool async = false); - void set_string_value(const QString &s); + const QString get_string_value(double timecode, bool async = false); + void set_string_value(const QString &s); - void add_combo_item(const QString& name, const QVariant &data); - int get_combo_index(double timecode, bool async = false); - const QVariant get_combo_data(double timecode); - const QString get_combo_string(double timecode); - void set_combo_index(int index); - void set_combo_string(const QString& s); + void add_combo_item(const QString& name, const QVariant &data); + int get_combo_index(double timecode, bool async = false); + const QVariant get_combo_data(double timecode); + const QString get_combo_string(double timecode); + void set_combo_index(int index); + void set_combo_string(const QString& s); - bool get_bool_value(double timecode, bool async = false); - void set_bool_value(bool b); + bool get_bool_value(double timecode, bool async = false); + void set_bool_value(bool b); - const QString get_font_name(double timecode, bool async = false); - void set_font_name(const QString& s); + const QString get_font_name(double timecode, bool async = false); + void set_font_name(const QString& s); - QColor get_color_value(double timecode, bool async = false); - void set_color_value(QColor color); + QColor get_color_value(double timecode, bool async = false); + void set_color_value(QColor color); - QWidget* get_ui_element(); - void set_enabled(bool e); - QVector keyframe_data; - QWidget* ui_element; + QWidget* get_ui_element(); + void set_enabled(bool e); + QVector keyframes; + QWidget* ui_element; - void make_key_from_change(ComboAction* ca); + void make_key_from_change(ComboAction* ca); private: - bool hasKeyframes(); + bool hasKeyframes(); private slots: - void ui_element_change(); + void ui_element_change(); signals: - void changed(); - void toggled(bool); + void changed(); + void toggled(bool); + void clicked(); }; #endif // EFFECTFIELD_H diff --git a/project/effectrow.cpp b/project/effectrow.cpp index 1cfd81cb9..08a5608f5 100644 --- a/project/effectrow.cpp +++ b/project/effectrow.cpp @@ -1,6 +1,5 @@ #include "effectrow.h" -#include #include #include #include @@ -11,169 +10,266 @@ #include "panels/panels.h" #include "panels/effectcontrols.h" #include "panels/viewer.h" +#include "panels/grapheditor.h" #include "effect.h" #include "ui/viewerwidget.h" #include "ui/keyframenavigator.h" +#include "ui/clickablelabel.h" EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QString &n, int row) : - parent_effect(parent), - savable(save), - keyframing(false), - ui(uilayout), - name(n), - ui_row(row), - just_made_unsafe_keyframe(false) + parent_effect(parent), + savable(save), + keyframing(false), + ui(uilayout), + name(n), + ui_row(row), + just_made_unsafe_keyframe(false) { - label = new QLabel(name); - ui->addWidget(label, row, 0); + label = new ClickableLabel(name + ":"); - if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) { - keyframe_nav = new KeyframeNavigator(); - connect(keyframe_nav, SIGNAL(goto_previous_key()), this, SLOT(goto_previous_key())); - connect(keyframe_nav, SIGNAL(toggle_key()), this, SLOT(toggle_key())); - connect(keyframe_nav, SIGNAL(goto_next_key()), this, SLOT(goto_next_key())); - connect(keyframe_nav, SIGNAL(set_keyframe_enabled(bool)), this, SLOT(set_keyframe_enabled(bool))); - ui->addWidget(keyframe_nav, row, 6); - } + ui->addWidget(label, row, 0); + + if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) { + connect(label, SIGNAL(clicked()), this, SLOT(focus_row())); + + keyframe_nav = new KeyframeNavigator(); + connect(keyframe_nav, SIGNAL(goto_previous_key()), this, SLOT(goto_previous_key())); + connect(keyframe_nav, SIGNAL(toggle_key()), this, SLOT(toggle_key())); + connect(keyframe_nav, SIGNAL(goto_next_key()), this, SLOT(goto_next_key())); + connect(keyframe_nav, SIGNAL(keyframe_enabled_changed(bool)), this, SLOT(set_keyframe_enabled(bool))); + connect(keyframe_nav, SIGNAL(clicked()), this, SLOT(focus_row())); + ui->addWidget(keyframe_nav, row, 6); + } } bool EffectRow::isKeyframing() { - return keyframing; + return keyframing; } void EffectRow::setKeyframing(bool b) { - if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) { - keyframing = b; - keyframe_nav->enable_keyframes(b); - } + if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) { + keyframing = b; + keyframe_nav->enable_keyframes(b); + } } void EffectRow::set_keyframe_enabled(bool enabled) { - if (enabled) { - ComboAction* ca = new ComboAction(); - set_keyframe_now(ca); - undo_stack.push(ca); - } else { - if (QMessageBox::question(panel_effect_controls, "Disable Keyframes", "Disabling keyframes will delete all current keyframes. Are you sure you want to do this?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - // clear - KeyframeDelete* kd = new KeyframeDelete(); - for (int i=keyframe_times.size()-1;i>=0;i--) { - delete_keyframe(kd, i); - } - kd->disable_keyframes_on_row = this; - undo_stack.push(kd); - panel_effect_controls->update_keyframes(); - } else { - setKeyframing(true); - } - } + if (enabled) { + ComboAction* ca = new ComboAction(); + ca->append(new SetKeyframing(this, true)); + set_keyframe_now(ca); + undo_stack.push(ca); + } else { + if (QMessageBox::question(panel_effect_controls, "Disable Keyframes", "Disabling keyframes will delete all current keyframes. Are you sure you want to do this?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + // clear + ComboAction* ca = new ComboAction(); + for (int i=0;ikeyframes.size();j++) { + ca->append(new KeyframeDelete(f, 0)); + } + } + ca->append(new SetKeyframing(this, false)); + undo_stack.push(ca); + panel_effect_controls->update_keyframes(); + } else { + setKeyframing(true); + } + } } void EffectRow::goto_previous_key() { - long key = LONG_MIN; - Clip* c = parent_effect->parent_clip; - for (int i=0;iclip_in + c->timeline_in; - if (comp < sequence->playhead) { - key = qMax(comp, key); - } - } - if (key != LONG_MIN) panel_sequence_viewer->seek(key); + long key = LONG_MIN; + Clip* c = parent_effect->parent_clip; + for (int i=0;ikeyframes.size();j++) { + long comp = f->keyframes.at(j).time - c->clip_in + c->timeline_in; + if (comp < sequence->playhead) { + key = qMax(comp, key); + } + } + } + if (key != LONG_MIN) panel_sequence_viewer->seek(key); } void EffectRow::toggle_key() { - int index = -1; - Clip* c = parent_effect->parent_clip; - for (int i=0;itimeline_in - c->clip_in + keyframe_times.at(i); - if (comp == sequence->playhead) { - index = i; - break; - } - } - if (index < 0) { - // keyframe doesn't exist, set one - ComboAction* ca = new ComboAction(); - set_keyframe_now(ca); - undo_stack.push(ca); - } else { - KeyframeDelete* kd = new KeyframeDelete(); - delete_keyframe(kd, index); - undo_stack.push(kd); - panel_effect_controls->update_keyframes(); - panel_sequence_viewer->viewer_widget->update(); - } + QVector key_fields; + QVector key_field_index; + Clip* c = parent_effect->parent_clip; + for (int j=0;jkeyframes.size();i++) { + long comp = c->timeline_in - c->clip_in + f->keyframes.at(i).time; + if (comp == sequence->playhead) { + key_fields.append(f); + key_field_index.append(i); + } + } + } + + ComboAction* ca = new ComboAction(); + if (key_fields.size() == 0) { + // keyframe doesn't exist, set one + set_keyframe_now(ca); + } else { + for (int i=0;iappend(new KeyframeDelete(key_fields.at(i), key_field_index.at(i))); + } + } + undo_stack.push(ca); + update_ui(false); } void EffectRow::goto_next_key() { - long key = LONG_MAX; - Clip* c = parent_effect->parent_clip; - for (int i=0;itimeline_in - c->clip_in + keyframe_times.at(i); - if (comp > sequence->playhead) { - key = qMin(comp, key); - } - } - if (key != LONG_MAX) panel_sequence_viewer->seek(key); + long key = LONG_MAX; + Clip* c = parent_effect->parent_clip; + for (int i=0;ikeyframes.size();j++) { + long comp = f->keyframes.at(j).time - c->clip_in + c->timeline_in; + if (comp > sequence->playhead) { + key = qMin(comp, key); + } + } + } + if (key != LONG_MAX) panel_sequence_viewer->seek(key); +} + +void EffectRow::focus_row() { + panel_graph_editor->set_row(this); } EffectField* EffectRow::add_field(int type, const QString& id, int colspan) { - EffectField* field = new EffectField(this, type, id); - fields.append(field); - QWidget* element = field->get_ui_element(); - ui->addWidget(element, ui_row, fields.size(), 1, colspan); - connect(field, SIGNAL(changed()), parent_effect, SLOT(field_changed())); - return field; + EffectField* field = new EffectField(this, type, id); + if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) connect(field, SIGNAL(clicked()), this, SLOT(focus_row())); + fields.append(field); + QWidget* element = field->get_ui_element(); + ui->addWidget(element, ui_row, fields.size(), 1, colspan); + connect(field, SIGNAL(changed()), parent_effect, SLOT(field_changed())); + return field; } EffectRow::~EffectRow() { - for (int i=0;iplayhead-parent_effect->parent_clip->timeline_in+parent_effect->parent_clip->clip_in; - for (int i=0;iplayhead-parent_effect->parent_clip->timeline_in+parent_effect->parent_clip->clip_in; - KeyframeSet* ks = new KeyframeSet(this, index, time, just_made_unsafe_keyframe); + if (!just_made_unsafe_keyframe) { + EffectKeyframe key; + key.time = time; - if (ca != NULL) { - just_made_unsafe_keyframe = false; - ca->append(ks); - } else { - if (index == -1) just_made_unsafe_keyframe = true; - ks->redo(); - delete ks; - } + unsafe_keys.resize(fieldCount()); + unsafe_old_data.resize(fieldCount()); + key_is_new.resize(fieldCount()); - panel_effect_controls->update_keyframes(); + for (int i=0;ikeyframes.size();j++) { + if (f->keyframes.at(j).time == time) { + exist_key = j; + } else if (f->keyframes.at(j).time < time + && f->keyframes.at(closest_key).time < f->keyframes.at(j).time) { + closest_key = j; + } + } + if (exist_key == -1) { + key.type = (f->keyframes.size() == 0) ? KEYFRAME_TYPE_LINEAR : f->keyframes.at(closest_key).type; + key.data = f->get_current_data();//f->keyframes.at(closest_key).data; + unsafe_keys[i] = f->keyframes.size(); + f->keyframes.append(key); + key_is_new[i] = true; + } else { + unsafe_keys[i] = exist_key; + key_is_new[i] = false; + } + unsafe_old_data[i] = f->get_current_data(); + } + just_made_unsafe_keyframe = true; + } + + for (int i=0;ikeyframes[unsafe_keys.at(i)].data = field(i)->get_current_data(); + } + + if (ca != NULL) { + for (int i=0;iappend(new KeyframeFieldSet(field(i), unsafe_keys.at(i))); + ca->append(new SetQVariant(&field(i)->keyframes[unsafe_keys.at(i)].data, unsafe_old_data.at(i), field(i)->get_current_data())); + } + unsafe_keys.clear(); + unsafe_old_data.clear(); + just_made_unsafe_keyframe = false; + } + + panel_effect_controls->update_keyframes(); + + + + + + /*if (ca != NULL) { + just_made_unsafe_keyframe = false; + } else { + if (!just_made_unsafe_keyframe) { + just_made_unsafe_keyframe = true; + } + }*/ + + + /*int index = -1; + long time = sequence->playhead-parent_effect->parent_clip->timeline_in+parent_effect->parent_clip->clip_in; + for (int j=0;jkeyframes.size();i++) { + if (f->keyframes.at(i).time == time) { + index = i; + break; + } + } + } + + KeyframeSet* ks = new KeyframeSet(this, index, time, just_made_unsafe_keyframe); + + if (ca != NULL) { + just_made_unsafe_keyframe = false; + ca->append(ks); + } else { + if (index == -1) just_made_unsafe_keyframe = true; + ks->redo(); + delete ks; + } + + panel_effect_controls->update_keyframes();*/ } -void EffectRow::delete_keyframe_at_time(KeyframeDelete* kd, long time) { - for (int i=0;ikeyframes.size();i++) { + if (f->keyframes.at(i).time == time) { + ca->append(new KeyframeDelete(f, i)); + break; + } + } + } } -void EffectRow::delete_keyframe(KeyframeDelete* kd, int index) { - kd->rows.append(this); - kd->keyframes.append(index); +const QString &EffectRow::get_name() { + return name; } EffectField* EffectRow::field(int i) { - return fields.at(i); + return fields.at(i); } int EffectRow::fieldCount() { - return fields.size(); + return fields.size(); } diff --git a/project/effectrow.h b/project/effectrow.h index 943ba709f..ad91a5ab9 100644 --- a/project/effectrow.h +++ b/project/effectrow.h @@ -8,47 +8,50 @@ class Effect; class QGridLayout; class EffectField; class QLabel; -class KeyframeDelete; class QPushButton; class ComboAction; class QHBoxLayout; class KeyframeNavigator; +class ClickableLabel; class EffectRow : public QObject { - Q_OBJECT + Q_OBJECT public: - EffectRow(Effect* parent, bool save, QGridLayout* uilayout, const QString& n, int row); - ~EffectRow(); - EffectField* add_field(int type, const QString &id, int colspan = 1); - EffectField* field(int i); - int fieldCount(); - void set_keyframe_now(ComboAction *ca); - void delete_keyframe(KeyframeDelete *kd, int index); - void delete_keyframe_at_time(KeyframeDelete* kd, long time); - QLabel* label; - Effect* parent_effect; - bool savable; + EffectRow(Effect* parent, bool save, QGridLayout* uilayout, const QString& n, int row); + ~EffectRow(); + EffectField* add_field(int type, const QString &id, int colspan = 1); + EffectField* field(int i); + int fieldCount(); + void set_keyframe_now(ComboAction *ca); + void delete_keyframe_at_time(ComboAction *ca, long time); + ClickableLabel* label; + Effect* parent_effect; + bool savable; + const QString& get_name(); - bool isKeyframing(); - void setKeyframing(bool); - - QVector keyframe_times; - QVector keyframe_types; + bool isKeyframing(); + void setKeyframing(bool); +public slots: + void goto_previous_key(); + void toggle_key(); + void goto_next_key(); + void focus_row(); private slots: - void set_keyframe_enabled(bool); - void goto_previous_key(); - void toggle_key(); - void goto_next_key(); + void set_keyframe_enabled(bool); private: - bool keyframing; - QGridLayout* ui; - QString name; - int ui_row; - QVector fields; + bool keyframing; + QGridLayout* ui; + QString name; + int ui_row; + QVector fields; - KeyframeNavigator* keyframe_nav; + KeyframeNavigator* keyframe_nav; + + bool just_made_unsafe_keyframe; + QVector unsafe_keys; + QVector unsafe_old_data; + QVector key_is_new; - bool just_made_unsafe_keyframe; }; #endif // EFFECTROW_H diff --git a/project/footage.cpp b/project/footage.cpp index b54f5a4a3..e63d343f2 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -11,61 +11,55 @@ extern "C" { #include "project/clip.h" -Footage::Footage() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0) { - ready_lock.lock(); +Footage::Footage() : ready(false), preview_gen(NULL), invalid(false), in(0), out(0), speed(1.0) { + ready_lock.lock(); } Footage::~Footage() { - reset(); + reset(); } void Footage::reset() { if (preview_gen != NULL) { preview_gen->cancel(); preview_gen->wait(); - } - for (int i=0;i= 0) return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate); - return 0; + if (length >= 0) return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate / speed); + return 0; } FootageStream* Footage::get_stream_from_file_index(bool video, int index) { - if (video) { - for (int i=0;ifile_index == index) { - return video_tracks.at(i); - } - } - } else { - for (int i=0;ifile_index == index) { - return audio_tracks.at(i); - } - } - } - return NULL; + if (video) { + for (int i=0;i>1; - int sqx = (diff < 0) ? -diff : 0; - int sqy = (diff > 0) ? diff : 0; - p.drawImage(sqx, sqy, video_preview); - video_preview_square = QIcon(pixmap); + // generate square version for QListView? + int square_size = qMax(video_preview.width(), video_preview.height()); + QPixmap pixmap(square_size, square_size); + pixmap.fill(Qt::transparent); + QPainter p(&pixmap); + int diff = (video_preview.width() - video_preview.height())>>1; + int sqx = (diff < 0) ? -diff : 0; + int sqy = (diff > 0) ? diff : 0; + p.drawImage(sqx, sqy, video_preview); + video_preview_square = QIcon(pixmap); } diff --git a/project/footage.h b/project/footage.h index 015253173..d6408857b 100644 --- a/project/footage.h +++ b/project/footage.h @@ -23,44 +23,46 @@ struct FootageStream { int video_width; int video_height; bool infinite_length; - double video_frame_rate; - int video_interlacing; + double video_frame_rate; + int video_interlacing; int video_auto_interlacing; - int audio_channels; - int audio_layout; - int audio_frequency; + int audio_channels; + int audio_layout; + int audio_frequency; + bool enabled; - // preview thumbnail/waveform - bool preview_done; - QImage video_preview; - QIcon video_preview_square; + // preview thumbnail/waveform + bool preview_done; + QImage video_preview; + QIcon video_preview_square; QVector audio_preview; - void make_square_thumb(); + void make_square_thumb(); }; struct Footage { - Footage(); - ~Footage(); + Footage(); + ~Footage(); - QString url; - QString name; + QString url; + QString name; int64_t length; - QVector video_tracks; - QVector audio_tracks; - int save_id; - bool ready; - bool invalid; + QVector video_tracks; + QVector audio_tracks; + int save_id; + bool ready; + bool invalid; + double speed; - PreviewGenerator* preview_gen; - QMutex ready_lock; + PreviewGenerator* preview_gen; + QMutex ready_lock; - bool using_inout; - long in; - long out; + bool using_inout; + long in; + long out; - long get_length_in_frames(double frame_rate); - FootageStream* get_stream_from_file_index(bool video, int index); - void reset(); + long get_length_in_frames(double frame_rate); + FootageStream *get_stream_from_file_index(bool video, int index); + void reset(); }; #endif // FOOTAGE_H diff --git a/project/keyframe.cpp b/project/keyframe.cpp new file mode 100644 index 000000000..09b73be75 --- /dev/null +++ b/project/keyframe.cpp @@ -0,0 +1,46 @@ +#include "keyframe.h" + +#include + +#include "effectfield.h" +#include "undo.h" +#include "panels/panels.h" + +EffectKeyframe::EffectKeyframe() { + pre_handle_x = -40; + pre_handle_y = 0; + post_handle_x = 40; + post_handle_y = 0; +} + +void delete_keyframes(QVector& selected_key_fields, QVector &selected_keys) { + QVector fields; + QVector key_indices; + + for (int i=0;i 0) { + ComboAction* ca = new ComboAction(); + for (int i=0;iappend(new KeyframeDelete(fields.at(i), key_indices.at(i))); + } + undo_stack.push(ca); + selected_keys.clear(); + selected_key_fields.clear(); + update_ui(false); + } +} diff --git a/project/keyframe.h b/project/keyframe.h new file mode 100644 index 000000000..fd7e4ac0f --- /dev/null +++ b/project/keyframe.h @@ -0,0 +1,25 @@ +#ifndef KEYFRAME_H +#define KEYFRAME_H + +#include + +class EffectField; + +class EffectKeyframe { +public: + EffectKeyframe(); + + long time; + int type; + QVariant data; + + // only for bezier type + double pre_handle_x; + double pre_handle_y; + double post_handle_x; + double post_handle_y; +}; + +void delete_keyframes(QVector &selected_key_fields, QVector &selected_keys); + +#endif // KEYFRAME_H diff --git a/project/media.cpp b/project/media.cpp index cec9c7f8f..1ecb0cc8b 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -13,310 +13,311 @@ #include "debug.h" extern "C" { - #include - #include + #include + #include } QString get_interlacing_name(int interlacing) { - switch (interlacing) { - case VIDEO_PROGRESSIVE: return "None (Progressive)"; - case VIDEO_TOP_FIELD_FIRST: return "Top Field First"; - case VIDEO_BOTTOM_FIELD_FIRST: return "Bottom Field First"; - default: return "Invalid"; - } + switch (interlacing) { + case VIDEO_PROGRESSIVE: return "None (Progressive)"; + case VIDEO_TOP_FIELD_FIRST: return "Top Field First"; + case VIDEO_BOTTOM_FIELD_FIRST: return "Bottom Field First"; + default: return "Invalid"; + } } QString get_channel_layout_name(int channels, uint64_t layout) { - switch (channels) { - case 0: return "Invalid"; break; - case 1: return "Mono"; break; - case 2: return "Stereo"; break; - default: { - char buf[50]; - av_get_channel_layout_string(buf, sizeof(buf), channels, layout); - return QString(buf); - } - } + switch (channels) { + case 0: return "Invalid"; break; + case 1: return "Mono"; break; + case 2: return "Stereo"; break; + default: { + char buf[50]; + av_get_channel_layout_string(buf, sizeof(buf), channels, layout); + return QString(buf); + } + } } Media::Media(Media* iparent) : - parent(iparent), - throbber(NULL), - root(false), - type(-1) + parent(iparent), + throbber(NULL), + root(false), + type(-1) {} Media::~Media() { - switch (get_type()) { - case MEDIA_TYPE_FOOTAGE: delete to_footage(); break; - case MEDIA_TYPE_SEQUENCE: if (object != NULL) delete to_sequence(); break; - } - if (throbber != NULL) delete throbber; - qDeleteAll(children); + switch (get_type()) { + case MEDIA_TYPE_FOOTAGE: delete to_footage(); break; + case MEDIA_TYPE_SEQUENCE: if (object != NULL) delete to_sequence(); break; + } + if (throbber != NULL) delete throbber; + qDeleteAll(children); } Footage *Media::to_footage() { - return static_cast(object); + return static_cast(object); } Sequence *Media::to_sequence() { - return static_cast(object); + return static_cast(object); } void Media::set_footage(Footage *f) { - type = MEDIA_TYPE_FOOTAGE; - object = f; + type = MEDIA_TYPE_FOOTAGE; + object = f; } void Media::set_sequence(Sequence *s) { - set_icon(QIcon(":/icons/sequence.png")); - type = MEDIA_TYPE_SEQUENCE; - object = s; - if (s != NULL) update_tooltip(); + set_icon(QIcon(":/icons/sequence.png")); + type = MEDIA_TYPE_SEQUENCE; + object = s; + if (s != NULL) update_tooltip(); } void Media::set_folder() { - if (folder_name.isEmpty()) folder_name = "New Folder"; - set_icon(QIcon(":/icons/folder.png")); - type = MEDIA_TYPE_FOLDER; - object = NULL; + if (folder_name.isEmpty()) folder_name = "New Folder"; + set_icon(QIcon(":/icons/folder.png")); + type = MEDIA_TYPE_FOLDER; + object = NULL; } void Media::set_icon(const QIcon &ico) { - icon = ico; + icon = ico; } void Media::set_parent(Media *p) { - parent = p; + parent = p; } void Media::update_tooltip(const QString& error) { - switch (type) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* f = to_footage(); - tooltip = "Name: " + f->name + "\nFilename: " + f->url + "\n"; + switch (type) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* f = to_footage(); + tooltip = "Name: " + f->name + "\nFilename: " + f->url + "\n"; - if (error.isEmpty()) { - if (f->video_tracks.size() > 0) { - tooltip += "Video Dimensions: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += QString::number(f->video_tracks.at(i)->video_width) + "x" + QString::number(f->video_tracks.at(i)->video_height); - } - tooltip += "\n"; + if (error.isEmpty()) { + if (f->video_tracks.size() > 0) { + tooltip += "Video Dimensions: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += QString::number(f->video_tracks.at(i).video_width) + "x" + QString::number(f->video_tracks.at(i).video_height); + } + tooltip += "\n"; - if (!f->video_tracks.at(0)->infinite_length) { - tooltip += "Frame Rate: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - if (f->video_tracks.at(i)->video_interlacing == VIDEO_PROGRESSIVE) { - tooltip += QString::number(f->video_tracks.at(i)->video_frame_rate); - } else { - tooltip += QString::number(f->video_tracks.at(i)->video_frame_rate * 2); - tooltip += " fields (" + QString::number(f->video_tracks.at(i)->video_frame_rate) + " frames)"; - } - } - tooltip += "\n"; - } + if (!f->video_tracks.at(0).infinite_length) { + tooltip += "Frame Rate: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + if (f->video_tracks.at(i).video_interlacing == VIDEO_PROGRESSIVE) { + tooltip += QString::number(f->video_tracks.at(i).video_frame_rate * f->speed); + } else { + tooltip += QString::number(f->video_tracks.at(i).video_frame_rate * f->speed * 2); + tooltip += " fields (" + QString::number(f->video_tracks.at(i).video_frame_rate * f->speed) + " frames)"; + } + } + tooltip += "\n"; + } - tooltip += "Interlacing: "; - for (int i=0;ivideo_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += get_interlacing_name(f->video_tracks.at(i)->video_interlacing); - } - } + tooltip += "Interlacing: "; + for (int i=0;ivideo_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += get_interlacing_name(f->video_tracks.at(i).video_interlacing); + } + } - if (f->audio_tracks.size() > 0) { - tooltip += "\n"; + if (f->audio_tracks.size() > 0) { + tooltip += "\n"; - tooltip += "Audio Frequency: "; - for (int i=0;iaudio_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += QString::number(f->audio_tracks.at(i)->audio_frequency); - } - tooltip += "\n"; + tooltip += "Audio Frequency: "; + for (int i=0;iaudio_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += QString::number(f->audio_tracks.at(i).audio_frequency * f->speed); + } + tooltip += "\n"; - tooltip += "Audio Channels: "; - for (int i=0;iaudio_tracks.size();i++) { - if (i > 0) { - tooltip += ", "; - } - tooltip += get_channel_layout_name(f->audio_tracks.at(i)->audio_channels, f->audio_tracks.at(i)->audio_layout); - } - // tooltip += "\n"; - } - } else { - tooltip += error; - } - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* s = to_sequence(); - tooltip = "Name: " + s->name - + "\nVideo Dimensions: " + QString::number(s->width) + "x" + QString::number(s->height) - + "\nFrame Rate: " + QString::number(s->frame_rate) - + "\nAudio Frequency: " + QString::number(s->audio_frequency) - + "\nAudio Layout: " + get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout); - } - break; - } + tooltip += "Audio Channels: "; + for (int i=0;iaudio_tracks.size();i++) { + if (i > 0) { + tooltip += ", "; + } + tooltip += get_channel_layout_name(f->audio_tracks.at(i).audio_channels, f->audio_tracks.at(i).audio_layout); + } + // tooltip += "\n"; + } + } else { + tooltip += error; + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* s = to_sequence(); + tooltip = "Name: " + s->name + + "\nVideo Dimensions: " + QString::number(s->width) + "x" + QString::number(s->height) + + "\nFrame Rate: " + QString::number(s->frame_rate) + + "\nAudio Frequency: " + QString::number(s->audio_frequency) + + "\nAudio Layout: " + get_channel_layout_name(av_get_channel_layout_nb_channels(s->audio_layout), s->audio_layout); + } + break; + } } void *Media::to_object() { - return object; + return object; } int Media::get_type() { - return type; + return type; } const QString &Media::get_name() { - switch (type) { - case MEDIA_TYPE_FOOTAGE: return to_footage()->name; - case MEDIA_TYPE_SEQUENCE: return to_sequence()->name; + switch (type) { + case MEDIA_TYPE_FOOTAGE: return to_footage()->name; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->name; default: return folder_name; } } void Media::set_name(const QString &n) { - switch (type) { - case MEDIA_TYPE_FOOTAGE: to_footage()->name = n; break; - case MEDIA_TYPE_SEQUENCE: to_sequence()->name = n; break; - case MEDIA_TYPE_FOLDER: folder_name = n; break; - } + switch (type) { + case MEDIA_TYPE_FOOTAGE: to_footage()->name = n; break; + case MEDIA_TYPE_SEQUENCE: to_sequence()->name = n; break; + case MEDIA_TYPE_FOLDER: folder_name = n; break; + } } double Media::get_frame_rate(int stream) { - switch (get_type()) { + switch (get_type()) { case MEDIA_TYPE_FOOTAGE: { Footage* f = to_footage(); - if (stream < 0) return f->video_tracks.at(0)->video_frame_rate; - return f->get_stream_from_file_index(true, stream)->video_frame_rate; + if (stream < 0) return f->video_tracks.at(0).video_frame_rate * f->speed; + return f->get_stream_from_file_index(true, stream)->video_frame_rate * f->speed; + } + case MEDIA_TYPE_SEQUENCE: return to_sequence()->frame_rate; } - case MEDIA_TYPE_SEQUENCE: return to_sequence()->frame_rate; - } return NULL; } int Media::get_sampling_rate(int stream) { - switch (get_type()) { + switch (get_type()) { case MEDIA_TYPE_FOOTAGE: { Footage* f = to_footage(); - if (stream < 0) return f->audio_tracks.at(0)->audio_frequency; - return to_footage()->get_stream_from_file_index(false, stream)->audio_frequency; + if (stream < 0) return f->audio_tracks.at(0).audio_frequency * f->speed; + return to_footage()->get_stream_from_file_index(false, stream)->audio_frequency * f->speed; } - case MEDIA_TYPE_SEQUENCE: return to_sequence()->audio_frequency; - } - return 0; + case MEDIA_TYPE_SEQUENCE: return to_sequence()->audio_frequency; + } + return 0; } void Media::appendChild(Media *child) { - child->set_parent(this); - children.append(child); + child->set_parent(this); + children.append(child); } bool Media::setData(int col, const QVariant &value) { - if (col == 0) { - QString n = value.toString(); - if (!n.isEmpty() && get_name() != n) { - undo_stack.push(new MediaRename(this, value.toString())); - return true; - } - } - return false; + if (col == 0) { + QString n = value.toString(); + if (!n.isEmpty() && get_name() != n) { + undo_stack.push(new MediaRename(this, value.toString())); + return true; + } + } + return false; } Media *Media::child(int row) { - return children.value(row); + return children.value(row); } int Media::childCount() const { - return children.count(); + return children.count(); } int Media::columnCount() const { - return 3; + return 3; } QVariant Media::data(int column, int role) { - switch (role) { - case Qt::DecorationRole: - if (column == 0) { - if (get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* f = to_footage(); - if (f->video_tracks.size() > 0 - && f->video_tracks.at(0)->preview_done) { - return f->video_tracks.at(0)->video_preview_square; - } - } + switch (role) { + case Qt::DecorationRole: + if (column == 0) { + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + if (f->video_tracks.size() > 0 + && f->video_tracks.at(0).preview_done) { + return f->video_tracks.at(0).video_preview_square; + } + } - return icon; - } - break; - case Qt::DisplayRole: - switch (column) { - case 0: return (root) ? "Name" : get_name(); - case 1: - if (root) return "Duration"; - if (get_type() == MEDIA_TYPE_SEQUENCE) { - Sequence* s = to_sequence(); - return frame_to_timecode(s->getEndFrame(), config.timecode_view, s->frame_rate); - } - if (get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* f = to_footage(); - double r = 30; + return icon; + } + break; + case Qt::DisplayRole: + switch (column) { + case 0: return (root) ? "Name" : get_name(); + case 1: + if (root) return "Duration"; + if (get_type() == MEDIA_TYPE_SEQUENCE) { + Sequence* s = to_sequence(); + return frame_to_timecode(s->getEndFrame(), config.timecode_view, s->frame_rate); + } + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + double r = 30; - if (f->video_tracks.size() > 0 && !qIsNull(f->video_tracks.at(0)->video_frame_rate)) r = f->video_tracks.at(0)->video_frame_rate; + if (f->video_tracks.size() > 0 && !qIsNull(f->video_tracks.at(0).video_frame_rate)) + r = f->video_tracks.at(0).video_frame_rate * f->speed; long len = f->get_length_in_frames(r); if (len > 0) return frame_to_timecode(len, config.timecode_view, r); - } - break; - case 2: - if (root) return "Rate"; - if (get_type() == MEDIA_TYPE_SEQUENCE) return QString::number(get_frame_rate()) + " FPS"; - if (get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* f = to_footage(); + } + break; + case 2: + if (root) return "Rate"; + if (get_type() == MEDIA_TYPE_SEQUENCE) return QString::number(get_frame_rate()) + " FPS"; + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); double r; if (f->video_tracks.size() > 0 && !qIsNull(r = get_frame_rate())) { return QString::number(get_frame_rate()) + " FPS"; - } else if (f->audio_tracks.size() > 0) { - return QString::number(get_sampling_rate()) + " Hz"; - } - } - break; - } - break; - case Qt::ToolTipRole: - return tooltip; - } - return QVariant(); + } else if (f->audio_tracks.size() > 0) { + return QString::number(get_sampling_rate()) + " Hz"; + } + } + break; + } + break; + case Qt::ToolTipRole: + return tooltip; + } + return QVariant(); } int Media::row() const { - if (parent) { - return parent->children.indexOf(const_cast(this)); - } - return 0; + if (parent) { + return parent->children.indexOf(const_cast(this)); + } + return 0; } Media *Media::parentItem() { - return parent; + return parent; } void Media::removeChild(int i) { - children.removeAt(i); + children.removeAt(i); } diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index b5272c8cd..0dd870944 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -127,6 +127,11 @@ void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& it QAction* icon_view_action = menu.addAction("Icon View"); connect(icon_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_icon_view())); + QAction* toolbar_action = menu.addAction("Show Toolbar"); + toolbar_action->setCheckable(true); + toolbar_action->setChecked(project_parent->toolbar_widget->isVisible()); + connect(toolbar_action, SIGNAL(triggered(bool)), project_parent->toolbar_widget, SLOT(setVisible(bool))); + menu.exec(QCursor::pos()); } diff --git a/project/undo.cpp b/project/undo.cpp index e67c38941..a169d9d7f 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -150,7 +150,7 @@ void DeleteClipAction::redo() { // remove ref to clip ref = seq->clips.at(index); if (ref->open) { - close_clip(ref); + close_clip(ref, true); } seq->clips[index] = NULL; @@ -493,7 +493,7 @@ void AddClipCommand::undo() { Clip* c = seq->clips.last(); panel_timeline->deselect_area(c->timeline_in, c->timeline_out, c->track); undone_clips.prepend(c); - if (c->open) close_clip(c); + if (c->open) close_clip(c, true); seq->clips.removeLast(); } mainWindow->setWindowModified(old_project_changed); @@ -587,8 +587,7 @@ void ReplaceMediaCommand::replace(QString& filename) { for (int j=0;jclips.size();j++) { Clip* c = s->clips.at(j); if (c != NULL && c->media == item && c->open) { - close_clip(c); - if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); + close_clip(c, true); c->replaced = true; } } @@ -628,8 +627,7 @@ void ReplaceClipMediaCommand::replace(bool undo) { for (int i=0;iopen) { - close_clip(c); - if (c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) c->cacher->wait(); + close_clip(c, true); } if (undo) { @@ -737,148 +735,21 @@ void MediaRename::redo() { mainWindow->setWindowModified(true); } -KeyframeMove::KeyframeMove() : old_project_changed(mainWindow->isWindowModified()) {} - -void KeyframeMove::undo() { - for (int i=0;ikeyframe_times[keyframes.at(i)] -= movement; - } - mainWindow->setWindowModified(old_project_changed); -} - -void KeyframeMove::redo() { - for (int i=0;ikeyframe_times[keyframes.at(i)] += movement; - } - mainWindow->setWindowModified(true); -} - -KeyframeDelete::KeyframeDelete() : - disable_keyframes_on_row(NULL), - old_project_changed(mainWindow->isWindowModified()), - sorted(false) +KeyframeDelete::KeyframeDelete(EffectField *ifield, int iindex) : + field(ifield), + index(iindex), + old_project_changed(mainWindow->isWindowModified()) {} void KeyframeDelete::undo() { - if (disable_keyframes_on_row != NULL) disable_keyframes_on_row->setKeyframing(true); - - int data_index = deleted_keyframe_data.size()-1; - for (int i=rows.size()-1;i>=0;i--) { - EffectRow* row = rows.at(i); - int keyframe_index = keyframes.at(i); - - row->keyframe_times.insert(keyframe_index, deleted_keyframe_times.at(i)); - row->keyframe_types.insert(keyframe_index, deleted_keyframe_types.at(i)); - - for (int j=row->fieldCount()-1;j>=0;j--) { - row->field(j)->keyframe_data.insert(keyframe_index, deleted_keyframe_data.at(data_index)); - data_index--; - } - } - + field->keyframes.insert(index, deleted_key); mainWindow->setWindowModified(old_project_changed); } void KeyframeDelete::redo() { - if (!sorted) { - deleted_keyframe_times.resize(rows.size()); - deleted_keyframe_types.resize(rows.size()); - deleted_keyframe_data.clear(); - } - - for (int i=0;ikeyframe_times.at(keyframe_index)); - deleted_keyframe_types[i] = (row->keyframe_types.at(keyframe_index)); - } - - row->keyframe_times.removeAt(keyframe_index); - row->keyframe_types.removeAt(keyframe_index); - - for (int j=0;jfieldCount();j++) { - if (!sorted) deleted_keyframe_data.append(row->field(j)->keyframe_data.at(keyframe_index)); - row->field(j)->keyframe_data.removeAt(keyframe_index); - } - - // correct following indices - if (!sorted) { - for (int j=i+1;j keyframe_index) { - keyframes[j]--; - } - } - } - } - - if (disable_keyframes_on_row != NULL) disable_keyframes_on_row->setKeyframing(false); + deleted_key = field->keyframes.at(index); + field->keyframes.removeAt(index); mainWindow->setWindowModified(true); - sorted = true; -} - -KeyframeSet::KeyframeSet(EffectRow* r, int i, long t, bool justMadeKeyframe) : - old_project_changed(mainWindow->isWindowModified()), - row(r), - index(i), - time(t), - just_made_keyframe(justMadeKeyframe), - done(true) -{ - enable_keyframes = !row->isKeyframing(); - if (index != -1) old_values.resize(row->fieldCount()); - new_values.resize(row->fieldCount()); - for (int i=0;ifieldCount();i++) { - EffectField* field = row->field(i); - if (index != -1) { - if (field->type == EFFECT_FIELD_DOUBLE) { - old_values[i] = static_cast(field->ui_element)->getPreviousValue(); - } else { - old_values[i] = field->keyframe_data.at(index); - } - } - new_values[i] = field->get_current_data(); - } -} - -void KeyframeSet::undo() { - if (enable_keyframes) row->setKeyframing(false); - - bool append = (index == -1 || just_made_keyframe); - if (append) { - row->keyframe_times.removeLast(); - row->keyframe_types.removeLast(); - } - for (int i=0;ifieldCount();i++) { - if (append) { - row->field(i)->keyframe_data.removeLast(); - } else { - row->field(i)->keyframe_data[index] = old_values.at(i); - } - } - - mainWindow->setWindowModified(old_project_changed); - done = false; -} - -void KeyframeSet::redo() { - bool append = (index == -1 || (just_made_keyframe && !done)); - if (append) { - row->keyframe_times.append(time); - row->keyframe_types.append((row->keyframe_types.size() > 0) ? row->keyframe_types.last() : EFFECT_KEYFRAME_LINEAR); - } - for (int i=0;ifieldCount();i++) { - if (append) { - row->field(i)->keyframe_data.append(new_values.at(i)); - } else { - row->field(i)->keyframe_data[index] = new_values.at(i); - } - } - row->setKeyframing(true); - - mainWindow->setWindowModified(true); - done = true; } EffectFieldUndo::EffectFieldUndo(EffectField* f) : @@ -1122,12 +993,7 @@ void EditSequenceCommand::update() { item->set_sequence(seq); for (int i=0;iclips.size();i++) { - // TODO shift in/out/clipin points to match new frame rate - // BUT ALSO copy/paste must need a similar routine, no? - // See if one exists or if you have to make one, make it - // re-usable - - seq->clips.at(i)->refresh(); + if (seq->clips.at(i) != NULL) seq->clips.at(i)->refresh(); } if (sequence == seq) { @@ -1174,7 +1040,7 @@ void CloseAllClipsCommand::undo() { } void CloseAllClipsCommand::redo() { - closeActiveClips(sequence, true); + closeActiveClips(sequence); } UpdateFootageTooltip::UpdateFootageTooltip(Media *i) : @@ -1295,3 +1161,109 @@ void RippleAction::redo() { } ca->redo(); } + +SetDouble::SetDouble(double* pointer, double old_value, double new_value) : + p(pointer), + oldval(old_value), + newval(new_value), + old_project_changed(mainWindow->isWindowModified()) +{} + +void SetDouble::undo() { + *p = oldval; + mainWindow->setWindowModified(old_project_changed); +} + +void SetDouble::redo() { + *p = newval; + mainWindow->setWindowModified(true); +} + +SetQVariant::SetQVariant(QVariant *itarget, const QVariant &iold, const QVariant &inew) : + target(itarget), + old_val(iold), + new_val(inew) +{} + +void SetQVariant::undo() { + *target = old_val; +} + +void SetQVariant::redo() { + *target = new_val; +} + +SetLong::SetLong(long *pointer, long old_value, long new_value) : + p(pointer), + oldval(old_value), + newval(new_value), + old_project_changed(mainWindow->isWindowModified()) +{} + +void SetLong::undo() { + *p = oldval; + mainWindow->setWindowModified(old_project_changed); +} + +void SetLong::redo() { + *p = newval; + mainWindow->setWindowModified(true); +} + +KeyframeFieldSet::KeyframeFieldSet(EffectField *ifield, int ii) : + field(ifield), + index(ii), + key(ifield->keyframes.at(ii)), + done(true), + old_project_changed(mainWindow->isWindowModified()) +{} + +void KeyframeFieldSet::undo() { + field->keyframes.removeAt(index); + mainWindow->setWindowModified(old_project_changed); + done = false; +} + +void KeyframeFieldSet::redo() { + if (!done) { + field->keyframes.insert(index, key); + mainWindow->setWindowModified(true); + } + done = true; +} + +SetKeyframing::SetKeyframing(EffectRow *irow, bool ib) : + row(irow), + b(ib) +{} + +void SetKeyframing::undo() { + row->setKeyframing(!b); +} + +void SetKeyframing::redo() { + row->setKeyframing(b); +} + +RefreshClips::RefreshClips(Media *m) : + media(m) +{} + +void RefreshClips::undo() { + redo(); +} + +void RefreshClips::redo() { + // close any clips currently using this media + QVector all_sequences = panel_project->list_all_project_sequences(); + for (int i=0;ito_sequence(); + for (int j=0;jclips.size();j++) { + Clip* c = s->clips.at(j); + if (c != NULL && c->media == media) { + c->replaced = true; + c->refresh(); + } + } + } +} diff --git a/project/undo.h b/project/undo.h index a1f79212f..ebc7348de 100644 --- a/project/undo.h +++ b/project/undo.h @@ -17,6 +17,7 @@ struct EffectMeta; #include "project/marker.h" #include "project/selection.h" +#include "project/effectfield.h" #include #include @@ -328,50 +329,32 @@ private: QString to; }; -class KeyframeMove : public QUndoCommand { -public: - KeyframeMove(); - QVector rows; - QVector keyframes; - long movement; - void undo(); - void redo(); -private: - bool old_project_changed; -}; - class KeyframeDelete : public QUndoCommand { public: - KeyframeDelete(); - QVector rows; - EffectRow* disable_keyframes_on_row; - QVector keyframes; + KeyframeDelete(EffectField* ifield, int iindex); void undo(); void redo(); private: + EffectField* field; + int index; + bool done; + EffectKeyframe deleted_key; bool old_project_changed; - QVector deleted_keyframe_times; - QVector deleted_keyframe_types; - QVector deleted_keyframe_data; - bool sorted; }; - -class KeyframeSet : public QUndoCommand { +// a more modern version of the above, could probably replace it +// assumes the keyframe already exists +class KeyframeFieldSet : public QUndoCommand { public: - KeyframeSet(EffectRow* r, int i, long t, bool justMadeKeyframe); + KeyframeFieldSet(EffectField* ifield, int ii); void undo(); void redo(); - QVector old_values; - QVector new_values; private: - bool old_project_changed; - EffectRow* row; + EffectField* field; int index; - long time; - bool enable_keyframes; - bool just_made_keyframe; + EffectKeyframe key; bool done; + bool old_project_changed; }; class EffectFieldUndo : public QUndoCommand { @@ -523,6 +506,30 @@ private: bool old_project_changed; }; +class SetLong : public QUndoCommand { +public: + SetLong(long* pointer, long old_value, long new_value); + void undo(); + void redo(); +private: + long* p; + long oldval; + long newval; + bool old_project_changed; +}; + +class SetDouble : public QUndoCommand { +public: + SetDouble(double* pointer, double old_value, double new_value); + void undo(); + void redo(); +private: + double* p; + double oldval; + double newval; + bool old_project_changed; +}; + class SetString : public QUndoCommand { public: SetString(QString* pointer, QString new_value); @@ -605,4 +612,34 @@ public: void redo(); }; +class SetQVariant : public QUndoCommand { +public: + SetQVariant(QVariant* itarget, const QVariant& iold, const QVariant& inew); + void undo(); + void redo(); +private: + QVariant* target; + QVariant old_val; + QVariant new_val; +}; + +class SetKeyframing : public QUndoCommand { +public: + SetKeyframing(EffectRow* irow, bool ib); + void undo(); + void redo(); +private: + EffectRow* row; + bool b; +}; + +class RefreshClips : public QUndoCommand { +public: + RefreshClips(Media* m); + void undo(); + void redo(); +private: + Media* media; +}; + #endif // UNDO_H diff --git a/ui/clickablelabel.cpp b/ui/clickablelabel.cpp new file mode 100644 index 000000000..b5afb2327 --- /dev/null +++ b/ui/clickablelabel.cpp @@ -0,0 +1,13 @@ +#include "clickablelabel.h" + +ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f) : + QLabel(parent, f) +{} + +ClickableLabel::ClickableLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : + QLabel(text, parent, f) +{} + +void ClickableLabel::mousePressEvent(QMouseEvent *) { + emit clicked(); +} diff --git a/ui/clickablelabel.h b/ui/clickablelabel.h new file mode 100644 index 000000000..785c4c7c6 --- /dev/null +++ b/ui/clickablelabel.h @@ -0,0 +1,16 @@ +#ifndef CLICKABLELABEL_H +#define CLICKABLELABEL_H + +#include + +class ClickableLabel : public QLabel { + Q_OBJECT +public: + ClickableLabel(QWidget * parent = 0, Qt::WindowFlags f = 0); + ClickableLabel(const QString & text, QWidget * parent = 0, Qt::WindowFlags f = 0); + void mousePressEvent(QMouseEvent *ev); +signals: + void clicked(); +}; + +#endif // CLICKABLELABEL_H diff --git a/ui/graphview.cpp b/ui/graphview.cpp new file mode 100644 index 000000000..4be23a422 --- /dev/null +++ b/ui/graphview.cpp @@ -0,0 +1,840 @@ +#include "graphview.h" + +#include +#include +#include +#include +#include + +#include "panels/panels.h" +#include "panels/timeline.h" +#include "panels/viewer.h" +#include "project/sequence.h" +#include "project/effectrow.h" +#include "project/effectfield.h" +#include "ui/keyframedrawing.h" +#include "project/undo.h" +#include "project/effect.h" +#include "project/clip.h" +#include "ui/rectangleselect.h" + +#include "debug.h" + +#define GRAPH_ZOOM_SPEED 0.05 +#define GRAPH_SIZE 100 +#define BEZIER_HANDLE_SIZE 3 +#define BEZIER_LINE_SIZE 2 + +#define BEZIER_HANDLE_NONE 1 +#define BEZIER_HANDLE_PRE 2 +#define BEZIER_HANDLE_POST 3 + +QColor get_curve_color(int index, int length) { + QColor c; + int hue = qRound((double(index)/double(length))*255); + c.setHsv(hue, 255, 255); + return c; +} + +GraphView::GraphView(QWidget* parent) : + QWidget(parent), + x_scroll(0), + y_scroll(0), + mousedown(false), + zoom(1.0), + row(NULL), + moved_keys(false), + current_handle(BEZIER_HANDLE_NONE), + rect_select(false), + visible_in(0), + click_add_proc(false) +{ + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); +} + +void GraphView::show_context_menu(const QPoint& pos) { + QMenu menu(this); + + QAction* zoom_to_selection = menu.addAction("Zoom to Selection"); + if (selected_keys.size() == 0) { + zoom_to_selection->setEnabled(false); + } else { + connect(zoom_to_selection, SIGNAL(triggered(bool)), this, SLOT(set_view_to_selection())); + } + + QAction* zoom_to_all = menu.addAction("Zoom to Show All"); + connect(zoom_to_all, SIGNAL(triggered(bool)), this, SLOT(set_view_to_all())); + + menu.addSeparator(); + + QAction* reset_action = menu.addAction("Reset View"); + connect(reset_action, SIGNAL(triggered(bool)), this, SLOT(reset_view())); + + menu.exec(mapToGlobal(pos)); +} + +void GraphView::reset_view() { + zoom = 1.0; + set_scroll_x(0); + set_scroll_y(0); + emit zoom_changed(zoom); + update(); +} + +void GraphView::set_view_to_selection() { + if (selected_keys.size() > 0) { + long min_time = LONG_MAX; + long max_time = LONG_MIN; + double min_dbl = DBL_MAX; + double max_dbl = DBL_MIN; + for (int i=0;ifield(selected_keys_fields.at(i))->keyframes.at(selected_keys.at(i)); + min_time = qMin(key.time, min_time); + max_time = qMax(key.time, max_time); + min_dbl = qMin(key.data.toDouble(), min_dbl); + max_dbl = qMax(key.data.toDouble(), max_dbl); + } + set_view_to_rect(min_time, min_dbl, max_time, max_dbl); + } +} + +void GraphView::set_view_to_all() { + bool can_set = false; + + long min_time = LONG_MAX; + long max_time = LONG_MIN; + double min_dbl = DBL_MAX; + double max_dbl = DBL_MIN; + for (int i=0;ifieldCount();i++) { + for (int j=0;jfield(i)->keyframes.size();j++) { + const EffectKeyframe& key = row->field(i)->keyframes.at(j); + min_time = qMin(key.time, min_time); + max_time = qMax(key.time, max_time); + min_dbl = qMin(key.data.toDouble(), min_dbl); + max_dbl = qMax(key.data.toDouble(), max_dbl); + can_set = true; + } + } + if (can_set) { + set_view_to_rect(min_time, min_dbl, max_time, max_dbl); + } +} + +void GraphView::set_view_to_rect(int x1, double y1, int x2, double y2) { + double padding = 1.5; + double x_diff = double(x2 - x1); + double y_diff = (y2 - y1); + double x_diff_padded = (x_diff+10)*padding; + double y_diff_padded = (y_diff+10)*padding; + set_zoom(qMin(double(width()) / x_diff_padded, double(height()) / y_diff_padded)); + + set_scroll_x(qRound((double(x1) - ((x_diff_padded-x_diff)/2))*zoom)); + set_scroll_y(qRound((double(y1) - ((y_diff_padded-y_diff)/2))*zoom)); +} + +void GraphView::draw_line_text(QPainter &p, bool vert, int line_no, int line_pos, int next_line_pos) { + // draws last line's text + QString str = QString::number(line_no*GRAPH_SIZE); + int text_sz = vert ? fontMetrics().height() : fontMetrics().width(str); + if (text_sz < (next_line_pos - line_pos)) { + QRect text_rect = vert ? QRect(0, line_pos-50, 50, 50) : QRect(line_pos, height()-50, 50, 50); + p.drawText(text_rect, Qt::AlignBottom | Qt::AlignLeft, str); + } +} + +void GraphView::draw_lines(QPainter& p, bool vert) { + int last_line = INT_MIN; + int last_line_x = INT_MIN; + int lim = vert ? height() : width(); + int scroll = vert ? y_scroll : x_scroll; + for (int i=0;i sort_keys_from_field(EffectField* field) { + QVector sorted_keys; + for (int k=0;kkeyframes.size();k++) { + bool inserted = false; + for (int j=0;jkeyframes.at(sorted_keys.at(j)).time > field->keyframes.at(k).time) { + sorted_keys.insert(j, k); + inserted = true; + break; + } + } + if (!inserted) { + sorted_keys.append(k); + } + } + return sorted_keys; +} + +void GraphView::paintEvent(QPaintEvent *) { + QPainter p(this); + + if (panel_sequence_viewer->seq != NULL) { + // draw grid lines + + p.setPen(Qt::gray); + + draw_lines(p, true); + draw_lines(p, false); + + // draw keyframes + if (row != NULL) { + QPen line_pen; + line_pen.setWidth(BEZIER_LINE_SIZE); + + for (int i=row->fieldCount()-1;i>=0;i--) { + EffectField* field = row->field(i); + + if (field->type == EFFECT_FIELD_DOUBLE && field_visibility.at(i)) { + // sort keyframes by time + QVector sorted_keys = sort_keys_from_field(field); + + int last_key_x, last_key_y; + + // draw lines + for (int j=0;jkeyframes.at(sorted_keys.at(j)); + + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + + line_pen.setColor(get_curve_color(i, row->fieldCount())); + p.setPen(line_pen); + if (j == 0) { + p.drawLine(0, key_y, key_x, key_y); + } else { + const EffectKeyframe& last_key = field->keyframes.at(sorted_keys.at(j-1)); + if (last_key.type == KEYFRAME_TYPE_HOLD) { + // hold + p.drawLine(last_key_x, last_key_y, key_x, last_key_y); + p.drawLine(key_x, last_key_y, key_x, key_y); + } else if (last_key.type == KEYFRAME_TYPE_BEZIER || key.type == KEYFRAME_TYPE_BEZIER) { + QPainterPath bezier_path; + bezier_path.moveTo(last_key_x, last_key_y); + if (last_key.type == KEYFRAME_TYPE_BEZIER && key.type == KEYFRAME_TYPE_BEZIER) { + // cubic bezier + bezier_path.cubicTo( + QPointF(last_key_x+last_key.post_handle_x*zoom, last_key_y-last_key.post_handle_y*zoom), + QPointF(key_x+key.pre_handle_x*zoom, key_y-key.pre_handle_y*zoom), + QPointF(key_x, key_y) + ); + } else if (key.type == KEYFRAME_TYPE_LINEAR) { // quadratic bezier + // last keyframe is the bezier one + bezier_path.quadTo( + QPointF(last_key_x+last_key.post_handle_x*zoom, last_key_y-last_key.post_handle_y*zoom), + QPointF(key_x, key_y) + ); + } else { + // this keyframe is the bezier one + bezier_path.quadTo( + QPointF(key_x+key.pre_handle_x*zoom, key_y-key.pre_handle_y*zoom), + QPointF(key_x, key_y) + ); + } + p.drawPath(bezier_path); + } else { + // linear + p.drawLine(last_key_x, last_key_y, key_x, key_y); + } + } + last_key_x = key_x; + last_key_y = key_y; + } + if (last_key_x < width()) p.drawLine(last_key_x, last_key_y, width(), last_key_y); + + // draw keys + for (int j=0;jkeyframes.at(sorted_keys.at(j)); + + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + + if (key.type == KEYFRAME_TYPE_BEZIER) { + p.setPen(Qt::gray); + + // pre handle line + QPointF pre_point(key_x + key.pre_handle_x*zoom, key_y - key.pre_handle_y*zoom); + p.drawLine(pre_point, QPointF(key_x, key_y)); + p.drawEllipse(pre_point, BEZIER_HANDLE_SIZE, BEZIER_HANDLE_SIZE); + + // post handle line + QPointF post_point(key_x + key.post_handle_x*zoom, key_y - key.post_handle_y*zoom); + p.drawLine(post_point, QPointF(key_x, key_y)); + p.drawEllipse(post_point, BEZIER_HANDLE_SIZE, BEZIER_HANDLE_SIZE); + } + + bool selected = false; + for (int k=0;kseq->playhead - visible_in); + p.drawLine(playhead_x, 0, playhead_x, height()); + + if (rect_select) { + draw_selection_rectangle(p, QRect(rect_select_x, rect_select_y, rect_select_w, rect_select_h)); + p.setBrush(Qt::NoBrush); + } + } + + p.setPen(Qt::white); + + QRect border = rect(); + border.setWidth(border.width()-1); + border.setHeight(border.height()-1); + p.drawRect(border); +} + +void GraphView::mousePressEvent(QMouseEvent *event) { + if (row != NULL) { + mousedown = true; + start_x = event->pos().x(); + start_y = event->pos().y(); + + // selecting + int sel_key = -1; + int sel_key_field = -1; + current_handle = BEZIER_HANDLE_NONE; + + if (click_add && (event->buttons() & Qt::LeftButton)) { + selected_keys.clear(); + selected_keys_fields.clear(); + + EffectKeyframe key; + key.time = get_value_x(event->pos().x()); + key.data = get_value_y(event->pos().y()); + key.type = click_add_type; + click_add_key = click_add_field->keyframes.size(); + click_add_field->keyframes.append(key); + update_ui(false); + click_add_proc = true; + } else { + for (int i=0;ifieldCount();i++) { + EffectField* field = row->field(i); + if (field->type == EFFECT_FIELD_DOUBLE && field_visibility.at(i)) { + for (int j=0;jkeyframes.size();j++) { + const EffectKeyframe& key = field->keyframes.at(j); + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + if (event->pos().x() > key_x-KEYFRAME_SIZE + && event->pos().x() < key_x+KEYFRAME_SIZE + && event->pos().y() > key_y-KEYFRAME_SIZE + && event->pos().y() < key_y+KEYFRAME_SIZE) { + sel_key = j; + sel_key_field = i; + break; + } else { + // selecting a handle + QPointF pre_point(key_x + key.pre_handle_x*zoom, key_y - key.pre_handle_y*zoom); + QPointF post_point(key_x + key.post_handle_x*zoom, key_y - key.post_handle_y*zoom); + if (event->pos().x() > pre_point.x()-BEZIER_HANDLE_SIZE + && event->pos().x() < pre_point.x()+BEZIER_HANDLE_SIZE + && event->pos().y() > pre_point.y()-BEZIER_HANDLE_SIZE + && event->pos().y() < pre_point.y()+BEZIER_HANDLE_SIZE) { + current_handle = BEZIER_HANDLE_PRE; + } else if (event->pos().x() > post_point.x()-BEZIER_HANDLE_SIZE + && event->pos().x() < post_point.x()+BEZIER_HANDLE_SIZE + && event->pos().y() > post_point.y()-BEZIER_HANDLE_SIZE + && event->pos().y() < post_point.y()+BEZIER_HANDLE_SIZE) { + current_handle = BEZIER_HANDLE_POST; + } + + if (current_handle != BEZIER_HANDLE_NONE) { + sel_key = j; + sel_key_field = i; + handle_index = j; + handle_field = i; + old_pre_handle_x = key.pre_handle_x; + old_pre_handle_y = key.pre_handle_y; + old_post_handle_x = key.post_handle_x; + old_post_handle_y = key.post_handle_y; + break; + } + } + } + } + if (sel_key > -1) break; + } + + bool already_selected = false; + if (sel_key > -1) { + for (int i=0;imodifiers() & Qt::ShiftModifier) && current_handle == BEZIER_HANDLE_NONE) { + selected_keys.removeAt(i); + selected_keys_fields.removeAt(i); + } + already_selected = true; + break; + } + } + } + if (!already_selected) { + if (!(event->modifiers() & Qt::ShiftModifier)) { + selected_keys.clear(); + selected_keys_fields.clear(); + } + if (sel_key > -1) { + selected_keys.append(sel_key); + selected_keys_fields.append(sel_key_field); + } else { + rect_select = true; + rect_select_x = event->pos().x(); + rect_select_y = event->pos().y(); + rect_select_w = 0; + rect_select_h = 0; + rect_select_offset = selected_keys.size(); + } + } + + selection_update(); + } + } +} + +void GraphView::mouseMoveEvent(QMouseEvent *event) { + if (!mousedown || !click_add) unsetCursor(); + if (mousedown) { + if (event->buttons() & Qt::MiddleButton || panel_timeline->tool == TIMELINE_TOOL_HAND) { + set_scroll_x(x_scroll + start_x - event->pos().x()); + set_scroll_y(y_scroll + event->pos().y() - start_y); + start_x = event->pos().x(); + start_y = event->pos().y(); + update(); + } else if (click_add_proc) { + click_add_field->keyframes[click_add_key].time = get_value_x(event->pos().x()); + click_add_field->keyframes[click_add_key].data = get_value_y(event->pos().y()); + update_ui(false); + } else if (rect_select) { + rect_select_w = event->pos().x() - rect_select_x; + rect_select_h = event->pos().y() - rect_select_y; + + selected_keys.resize(rect_select_offset); + selected_keys_fields.resize(rect_select_offset); + + for (int i=0;ifieldCount();i++) { + EffectField* f = row->field(i); + for (int j=0;jkeyframes.size();j++) { + bool already_selected = false; + for (int k=0;kkeyframes.at(j).time), get_screen_y(f->keyframes.at(j).data.toDouble())); + QRect select_rect(rect_select_x, rect_select_y, rect_select_w, rect_select_h); + if (select_rect.contains(key_screen_point)) { + selected_keys.append(j); + selected_keys_fields.append(i); + } + } + } + } + update(); + } else { + switch (current_handle) { + case BEZIER_HANDLE_NONE: + for (int i=0;ifield(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].time = qRound(selected_keys_old_vals.at(i) + (double(event->pos().x() - start_x)/zoom)); + if (event->modifiers() & Qt::ShiftModifier) { + row->field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].data = selected_keys_old_doubles.at(i); + } else { + row->field(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)].data = qRound(selected_keys_old_doubles.at(i) + (double(start_y - event->pos().y())/zoom)); + } + } + moved_keys = true; + update_ui(false); + break; + case BEZIER_HANDLE_PRE: + case BEZIER_HANDLE_POST: + { + double new_pre_handle_x = old_pre_handle_x; + double new_pre_handle_y = old_pre_handle_y; + double new_post_handle_x = old_post_handle_x; + double new_post_handle_y = old_post_handle_y; + + double x_diff = double(event->pos().x() - start_x)/zoom; + double y_diff = double(start_y - event->pos().y())/zoom; + + if (current_handle == BEZIER_HANDLE_PRE) { + new_pre_handle_x += x_diff; + if (!(event->modifiers() & Qt::ShiftModifier)) new_pre_handle_y += y_diff; + if (!(event->modifiers() & Qt::ControlModifier)) { + new_post_handle_x = -new_pre_handle_x; + new_post_handle_y = -new_pre_handle_y; + } + } else { + new_post_handle_x += x_diff; + if (!(event->modifiers() & Qt::ShiftModifier)) new_post_handle_y += y_diff; + if (!(event->modifiers() & Qt::ControlModifier)) { + new_pre_handle_x = -new_post_handle_x; + new_pre_handle_y = -new_post_handle_y; + } + } + + EffectKeyframe& key = row->field(handle_field)->keyframes[handle_index]; + key.pre_handle_x = new_pre_handle_x; + key.pre_handle_y = new_pre_handle_y; + key.post_handle_x = new_post_handle_x; + key.post_handle_y = new_post_handle_y; + + moved_keys = true; + update_ui(false); + } + break; + } + } + } else if (row != NULL) { + // clicking on the curve + click_add = false; + + bool hovering_key = false; + + for (int i=0;ifieldCount();i++) { + for (int j=0;jfield(i)->keyframes.size();j++) { + const EffectKeyframe& key = row->field(i)->keyframes.at(j); + int key_x = get_screen_x(key.time); + int key_y = get_screen_y(key.data.toDouble()); + QRect test_rect( + key_x - KEYFRAME_SIZE, + key_y - KEYFRAME_SIZE, + KEYFRAME_SIZE+KEYFRAME_SIZE, + KEYFRAME_SIZE+KEYFRAME_SIZE + ); + QRect pre_rect( + qRound(key_x + key.pre_handle_x*zoom - BEZIER_HANDLE_SIZE), + qRound(key_y + key.pre_handle_y*zoom - BEZIER_HANDLE_SIZE), + BEZIER_HANDLE_SIZE+BEZIER_HANDLE_SIZE, + BEZIER_HANDLE_SIZE+BEZIER_HANDLE_SIZE + ); + QRect post_rect( + qRound(key_x + key.post_handle_x*zoom - BEZIER_HANDLE_SIZE), + qRound(key_y + key.post_handle_y*zoom - BEZIER_HANDLE_SIZE), + BEZIER_HANDLE_SIZE+BEZIER_HANDLE_SIZE, + BEZIER_HANDLE_SIZE+BEZIER_HANDLE_SIZE + ); + + if (test_rect.contains(event->pos()) + || pre_rect.contains(event->pos()) + || post_rect.contains(event->pos())) { + hovering_key = true; + break; + } + } + } + + if (!hovering_key) { + for (int i=0;ifieldCount();i++) { + EffectField* f = row->field(i); + if (field_visibility.at(i)) { + QVector sorted_keys = sort_keys_from_field(f); + + if (event->pos().x() <= get_screen_x(f->keyframes.at(sorted_keys.first()).time)) { + int y_comp = get_screen_y(f->keyframes.at(sorted_keys.first()).data.toDouble()); + if (event->pos().y() >= y_comp-BEZIER_LINE_SIZE + && event->pos().y() <= y_comp+BEZIER_LINE_SIZE) { + // dout << "make an EARLY key on field" << i; + click_add = true; + click_add_type = f->keyframes.at(sorted_keys.first()).type; + } + } else if (event->pos().x() >= get_screen_x(f->keyframes.at(sorted_keys.last()).time)) { + int y_comp = get_screen_y(f->keyframes.at(sorted_keys.last()).data.toDouble()); + if (event->pos().y() >= y_comp-BEZIER_LINE_SIZE + && event->pos().y() <= y_comp+BEZIER_LINE_SIZE) { + // dout << "make an LATE key on field" << i; + click_add = true; + click_add_type = f->keyframes.at(sorted_keys.last()).type; + } + } else { + for (int j=1;jkeyframes.at(sorted_keys.at(j-1)); + const EffectKeyframe& key = f->keyframes.at(sorted_keys.at(j)); + + int last_key_x = get_screen_x(last_key.time); + int key_x = get_screen_x(key.time); + int last_key_y = get_screen_y(last_key.data.toDouble()); + int key_y = get_screen_y(key.data.toDouble()); + + click_add_type = last_key.type; + + if (event->pos().x() >= last_key_x + && event->pos().x() <= key_x) { + QRect mouse_rect(event->pos().x()-BEZIER_LINE_SIZE, event->pos().y()-BEZIER_LINE_SIZE, BEZIER_LINE_SIZE+BEZIER_LINE_SIZE, BEZIER_LINE_SIZE+BEZIER_LINE_SIZE); + // NOTE: FILTHY copy/paste from paintEvent + if (last_key.type == KEYFRAME_TYPE_HOLD) { + // hold + if (event->pos().y() >= last_key_y-BEZIER_LINE_SIZE + && event->pos().y() <= last_key_y+BEZIER_LINE_SIZE) { + // dout << "make an HOLD key on field" << i << "after key" << j; + click_add = true; + } + } else if (last_key.type == KEYFRAME_TYPE_BEZIER || key.type == KEYFRAME_TYPE_BEZIER) { + QPainterPath bezier_path; + bezier_path.moveTo(last_key_x, last_key_y); + if (last_key.type == KEYFRAME_TYPE_BEZIER && key.type == KEYFRAME_TYPE_BEZIER) { + // cubic bezier + bezier_path.cubicTo( + QPointF(last_key_x+last_key.post_handle_x*zoom, last_key_y-last_key.post_handle_y*zoom), + QPointF(key_x+key.pre_handle_x*zoom, key_y-key.pre_handle_y*zoom), + QPointF(key_x, key_y) + ); + } else if (key.type == KEYFRAME_TYPE_LINEAR) { // quadratic bezier + // last keyframe is the bezier one + bezier_path.quadTo( + QPointF(last_key_x+last_key.post_handle_x*zoom, last_key_y-last_key.post_handle_y*zoom), + QPointF(key_x, key_y) + ); + } else { + // this keyframe is the bezier one + bezier_path.quadTo( + QPointF(key_x+key.pre_handle_x*zoom, key_y-key.pre_handle_y*zoom), + QPointF(key_x, key_y) + ); + } + if (bezier_path.intersects(mouse_rect)) { + // dout << "make an BEZIER key on field" << i << "after key" << j; + click_add = true; + } + } else { + // linear + QPainterPath linear_path; + linear_path.moveTo(last_key_x, last_key_y); + linear_path.lineTo(key_x, key_y); + if (linear_path.intersects(mouse_rect)) { + // dout << "make an LINEAR key on field" << i << "after key" << j; + click_add = true; + } + } + } + } + } + } + if (click_add) { + click_add_field = f; + setCursor(Qt::CrossCursor); + break; + } + } + } + } +} + +void GraphView::mouseReleaseEvent(QMouseEvent *e) { + if (click_add_proc) { + undo_stack.push(new KeyframeFieldSet(click_add_field, click_add_key)); + } else if (moved_keys && selected_keys.size() > 0) { + ComboAction* ca = new ComboAction(); + switch (current_handle) { + case BEZIER_HANDLE_NONE: + for (int i=0;ifield(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; + ca->append(new SetLong(&key.time, selected_keys_old_vals.at(i), key.time)); + ca->append(new SetQVariant(&key.data, selected_keys_old_doubles.at(i), key.data)); + } + break; + case BEZIER_HANDLE_PRE: + case BEZIER_HANDLE_POST: + { + EffectKeyframe& key = row->field(handle_field)->keyframes[handle_index]; + ca->append(new SetDouble(&key.pre_handle_x, old_pre_handle_x, key.pre_handle_x)); + ca->append(new SetDouble(&key.pre_handle_y, old_pre_handle_y, key.pre_handle_y)); + ca->append(new SetDouble(&key.post_handle_x, old_post_handle_x, key.post_handle_x)); + ca->append(new SetDouble(&key.post_handle_y, old_post_handle_y, key.post_handle_y)); + } + break; + } + undo_stack.push(ca); + } + moved_keys = false; + mousedown = false; + click_add = false; + click_add_proc = false; + if (rect_select) { + rect_select = false; + selection_update(); + update(); + } +} + +void GraphView::wheelEvent(QWheelEvent *event) { + dout << x_scroll << y_scroll; + + bool redraw = false; + bool shift = (event->modifiers() & Qt::ShiftModifier); // scroll instead of zoom + + if (shift) { + // scroll + set_scroll_x(x_scroll + event->angleDelta().x()/10); + set_scroll_y(y_scroll + event->angleDelta().y()/10); + redraw = true; + } else { + // set zoom + if (event->angleDelta().y() != 0) { + double zoom_diff = (GRAPH_ZOOM_SPEED*zoom); + double new_zoom = (event->angleDelta().y() < 0) ? zoom - zoom_diff : zoom + zoom_diff; + + // center zoom on screen + set_scroll_x(qRound((double(x_scroll)/zoom*new_zoom) + double(event->pos().x())*new_zoom - double(event->pos().x())*zoom)); + set_scroll_y(qRound((double(y_scroll)/zoom*new_zoom) + double(height()-event->pos().y())*new_zoom - double(height()-event->pos().y())*zoom)); + + set_zoom(new_zoom); + + redraw = true; + } + } + + if (redraw) { + update(); + } +} + +void GraphView::set_row(EffectRow *r) { + if (row != r) { + selected_keys.clear(); + selected_keys_fields.clear(); + selected_keys_old_vals.clear(); + selected_keys_old_doubles.clear(); + emit selection_changed(false, -1); + row = r; + if (row != NULL) { + field_visibility.resize(row->fieldCount()); + field_visibility.fill(true); + visible_in = row->parent_effect->parent_clip->timeline_in; + set_view_to_all(); + } else { + update(); + } + } +} + +void GraphView::set_selected_keyframe_type(int type) { + if (selected_keys.size() > 0) { + ComboAction* ca = new ComboAction(); + for (int i=0;ifield(selected_keys_fields.at(i))->keyframes[selected_keys.at(i)]; + ca->append(new SetInt(&key.type, type)); + } + undo_stack.push(ca); + update_ui(false); + } +} + +void GraphView::set_field_visibility(int field, bool b) { + field_visibility[field] = b; + update(); +} + +void GraphView::delete_selected_keys() { + if (row != NULL) { + QVector fields; + for (int i=0;ifield(selected_keys_fields.at(i))); + } + delete_keyframes(fields, selected_keys); + } +} + +void GraphView::select_all() { + if (row != NULL) { + selected_keys.clear(); + selected_keys_fields.clear(); + for (int i=0;ifieldCount();i++) { + EffectField* field = row->field(i); + for (int j=0;jkeyframes.size();j++) { + selected_keys.append(j); + selected_keys_fields.append(i); + } + } + selection_update(); + } +} + +void GraphView::set_scroll_x(int s) { + x_scroll = s; + emit x_scroll_changed(x_scroll); +} + +void GraphView::set_scroll_y(int s) { + y_scroll = s; + emit y_scroll_changed(y_scroll); +} + +void GraphView::set_zoom(double z) { + zoom = z; + emit zoom_changed(zoom); +} + +int GraphView::get_screen_x(double d) { + return qRound((d*zoom) - x_scroll); +} + +int GraphView::get_screen_y(double d) { + return qRound(height() + y_scroll - d*zoom); +} + +long GraphView::get_value_x(int i) { + return qRound((i + x_scroll)/zoom); +} + +double GraphView::get_value_y(int i) { + return double(height() + y_scroll - i)/zoom; +} + +void GraphView::selection_update() { + selected_keys_old_vals.clear(); + selected_keys_old_doubles.clear(); + + int selected_key_type = -1; + + for (int i=0;ifield(selected_keys_fields.at(i))->keyframes.at(selected_keys.at(i)); + selected_keys_old_vals.append(key.time); + selected_keys_old_doubles.append(key.data.toDouble()); + + if (selected_key_type == -1) { + selected_key_type = key.type; + } else if (selected_key_type != key.type) { + selected_key_type = -2; + } + } + + update(); + + emit selection_changed(selected_keys.size() > 0, selected_key_type); +} diff --git a/ui/graphview.h b/ui/graphview.h new file mode 100644 index 000000000..29d4f6f91 --- /dev/null +++ b/ui/graphview.h @@ -0,0 +1,100 @@ +#ifndef GRAPHVIEW_H +#define GRAPHVIEW_H + +#include +#include + +class EffectRow; +class EffectField; + +QColor get_curve_color(int index, int length); + +class GraphView : public QWidget { + Q_OBJECT +public: + GraphView(QWidget* parent = 0); + + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void wheelEvent(QWheelEvent *event); + + void set_row(EffectRow* r); + + void set_selected_keyframe_type(int type); + void set_field_visibility(int field, bool b); + + void delete_selected_keys(); + void select_all(); +signals: + void x_scroll_changed(int); + void y_scroll_changed(int); + void zoom_changed(double); + void selection_changed(bool, int); +private: + int x_scroll; + int y_scroll; + bool mousedown; + int start_x; + int start_y; + double zoom; + + void set_scroll_x(int s); + void set_scroll_y(int s); + void set_zoom(double z); + + int get_screen_x(double); + int get_screen_y(double); + long get_value_x(int); + double get_value_y(int); + + void selection_update(); + + QVector field_visibility; + + QVector selected_keys; + QVector selected_keys_fields; + QVector selected_keys_old_vals; + QVector selected_keys_old_doubles; + + double old_pre_handle_x; + double old_pre_handle_y; + double old_post_handle_x; + double old_post_handle_y; + + int handle_field; + int handle_index; + + bool moved_keys; + + int current_handle; + + void draw_lines(QPainter &p, bool vert); + void draw_line_text(QPainter &p, bool vert, int line_no, int line_pos, int next_line_pos); + + EffectRow* row; + + bool rect_select; + int rect_select_x; + int rect_select_y; + int rect_select_w; + int rect_select_h; + int rect_select_offset; + + long visible_in; + + bool click_add; + bool click_add_proc; + EffectField* click_add_field; + int click_add_key; + int click_add_type; +private slots: + void show_context_menu(const QPoint& pos); + void reset_view(); + void set_view_to_selection(); + void set_view_to_all(); + void set_view_to_rect(int x1, double y1, int x2, double y2); +}; + +#endif // GRAPHVIEW_H diff --git a/ui/keyframedrawing.cpp b/ui/keyframedrawing.cpp new file mode 100644 index 000000000..53229e46c --- /dev/null +++ b/ui/keyframedrawing.cpp @@ -0,0 +1,32 @@ +#include "keyframedrawing.h" + +#include "project/effect.h" + +#define KEYFRAME_POINT_COUNT 4 + +void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r, int g, int b) { + if (darker) { + r *= 0.625; + g *= 0.625; + b *= 0.625; + } + p.setPen(QColor(0, 0, 0)); + p.setBrush(QColor(r, g, b)); + + switch (type) { + case KEYFRAME_TYPE_LINEAR: + { + QPoint points[KEYFRAME_POINT_COUNT] = {QPoint(x-KEYFRAME_SIZE, y), QPoint(x, y-KEYFRAME_SIZE), QPoint(x+KEYFRAME_SIZE, y), QPoint(x, y+KEYFRAME_SIZE)}; + p.drawPolygon(points, KEYFRAME_POINT_COUNT); + } + break; + case KEYFRAME_TYPE_BEZIER: + p.drawEllipse(QPoint(x, y), KEYFRAME_SIZE, KEYFRAME_SIZE); + break; + case KEYFRAME_TYPE_HOLD: + p.drawRect(QRect(x - KEYFRAME_SIZE, y - KEYFRAME_SIZE, KEYFRAME_SIZE*2, KEYFRAME_SIZE*2)); + break; + } + + p.setBrush(Qt::NoBrush); +} diff --git a/ui/keyframedrawing.h b/ui/keyframedrawing.h new file mode 100644 index 000000000..c8c60e9e3 --- /dev/null +++ b/ui/keyframedrawing.h @@ -0,0 +1,11 @@ +#ifndef KEYFRAMEDRAWING_H +#define KEYFRAMEDRAWING_H + +#include + +#define KEYFRAME_SIZE 6 +#define KEYFRAME_COLOR 160 + +void draw_keyframe(QPainter &p, int type, int x, int y, bool darker, int r = KEYFRAME_COLOR, int g = KEYFRAME_COLOR, int b = KEYFRAME_COLOR); + +#endif // KEYFRAMEDRAWING_H diff --git a/ui/keyframenavigator.cpp b/ui/keyframenavigator.cpp index a7022fe45..f5495a135 100644 --- a/ui/keyframenavigator.cpp +++ b/ui/keyframenavigator.cpp @@ -2,15 +2,22 @@ #include #include +#include +#include KeyframeNavigator::KeyframeNavigator(QWidget *parent) : QWidget(parent) { - QSize icon_size(12, 12); - QSize button_size(20, 20); + int icon_size_val = 8*QApplication::desktop()->devicePixelRatio(); + int clock_size_val = 12*QApplication::desktop()->devicePixelRatio(); + int button_size_val = 20*QApplication::desktop()->devicePixelRatio(); + + QSize icon_size(icon_size_val, icon_size_val); + QSize clock_size(clock_size_val, clock_size_val); + QSize button_size(button_size_val, button_size_val); key_controls = new QHBoxLayout(); key_controls->setSpacing(0); key_controls->setMargin(0); - key_controls->addStretch(); + //key_controls->addStretch(); setLayout(key_controls); @@ -21,6 +28,7 @@ KeyframeNavigator::KeyframeNavigator(QWidget *parent) : QWidget(parent) { left_key_nav->setVisible(false); key_controls->addWidget(left_key_nav); connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_previous_key())); + connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); key_addremove = new QPushButton(); key_addremove->setIcon(QIcon(":/icons/diamond.png")); @@ -29,6 +37,7 @@ KeyframeNavigator::KeyframeNavigator(QWidget *parent) : QWidget(parent) { key_addremove->setVisible(false); key_controls->addWidget(key_addremove); connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(toggle_key())); + connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); right_key_nav = new QPushButton(); right_key_nav->setIcon(QIcon(":/icons/tri-right.png")); @@ -37,20 +46,26 @@ KeyframeNavigator::KeyframeNavigator(QWidget *parent) : QWidget(parent) { right_key_nav->setVisible(false); key_controls->addWidget(right_key_nav); connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_next_key())); + connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); keyframe_enable = new QPushButton(QIcon(":/icons/clock.png"), ""); keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); keyframe_enable->setMaximumSize(button_size); - keyframe_enable->setIconSize(icon_size); + keyframe_enable->setIconSize(clock_size); keyframe_enable->setCheckable(true); keyframe_enable->setToolTip("Enable Keyframes"); - connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(set_keyframe_enabled(bool))); + connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(keyframe_enabled_changed(bool))); connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool))); + connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); key_controls->addWidget(keyframe_enable); } void KeyframeNavigator::enable_keyframes(bool b) { - keyframe_enable->setChecked(b); + keyframe_enable->setChecked(b); +} + +void KeyframeNavigator::enable_keyframe_toggle(bool b) { + keyframe_enable->setVisible(b); } void KeyframeNavigator::keyframe_ui_enabled(bool enabled) { diff --git a/ui/keyframenavigator.h b/ui/keyframenavigator.h index d41a7ac0d..6b6eaee85 100644 --- a/ui/keyframenavigator.h +++ b/ui/keyframenavigator.h @@ -12,11 +12,13 @@ class KeyframeNavigator : public QWidget public: KeyframeNavigator(QWidget* parent = 0); void enable_keyframes(bool); + void enable_keyframe_toggle(bool); signals: void goto_previous_key(); void toggle_key(); void goto_next_key(); - void set_keyframe_enabled(bool); + void keyframe_enabled_changed(bool); + void clicked(); private slots: void keyframe_ui_enabled(bool); private: diff --git a/ui/keyframeview.cpp b/ui/keyframeview.cpp index 914b979b3..4a53bd118 100644 --- a/ui/keyframeview.cpp +++ b/ui/keyframeview.cpp @@ -11,16 +11,18 @@ #include "panels/viewer.h" #include "ui/viewerwidget.h" #include "project/sequence.h" +#include "panels/grapheditor.h" +#include "ui/keyframedrawing.h" +#include "ui/clickablelabel.h" #include "ui/resizablescrollbar.h" +#include "ui/rectangleselect.h" +#include "project/keyframe.h" +#include "ui/graphview.h" -#include #include #include #include -#define KEYFRAME_SIZE 6 -#define KEYFRAME_POINT_COUNT 4 - long KeyframeView::adjust_row_keyframe(EffectRow* row, long time) { return time-row->parent_effect->parent_clip->clip_in+(row->parent_effect->parent_clip->timeline_in-visible_in); } @@ -45,26 +47,36 @@ KeyframeView::KeyframeView(QWidget *parent) : } void KeyframeView::show_context_menu(const QPoint& pos) { - if (selected_rows.size() > 0) { + if (selected_fields.size() > 0) { QMenu menu(this); + QAction* linear = menu.addAction("Linear"); linear->setData(KEYFRAME_TYPE_LINEAR); - QAction* smooth = menu.addAction("Smooth"); - smooth->setData(KEYFRAME_TYPE_SMOOTH); - /*QAction* bezier = menu.addAction("Bezier"); - bezier->setData(KEYFRAME_TYPE_BEZIER);*/ + QAction* bezier = menu.addAction("Bezier"); + bezier->setData(KEYFRAME_TYPE_BEZIER); + QAction* hold = menu.addAction("Hold"); + hold->setData(KEYFRAME_TYPE_HOLD); + menu.addSeparator(); + menu.addAction("Graph Editor"); + connect(&menu, SIGNAL(triggered(QAction*)), this, SLOT(menu_set_key_type(QAction*))); menu.exec(mapToGlobal(pos)); } } void KeyframeView::menu_set_key_type(QAction* a) { - ComboAction* ca = new ComboAction(); - for (int i=0;iappend(new SetInt(&selected_rows.at(i)->keyframe_types[selected_keyframes.at(i)], a->data().toInt())); + if (a->data().isNull()) { + // load graph editor + panel_graph_editor->show(); + } else { + ComboAction* ca = new ComboAction(); + for (int i=0;iappend(new SetInt(&f->keyframes[selected_keyframes.at(i)].type, a->data().toInt())); + } + undo_stack.push(ca); + update_ui(false); } - undo_stack.push(ca); - update(); } void KeyframeView::paintEvent(QPaintEvent*) { @@ -91,15 +103,39 @@ void KeyframeView::paintEvent(QPaintEvent*) { for (int j=0;jrow_count();j++) { EffectRow* row = e->row(j); - QLabel* label = row->label; + ClickableLabel* label = row->label; QWidget* contents = e->container->contents; + QVector key_times; int keyframe_y = label->y() + (label->height()>>1) + mapFrom(panel_effect_controls, contents->mapTo(panel_effect_controls, contents->pos())).y() - e->container->title_bar->height()/* - y_scroll*/; - for (int k=0;kkeyframe_times.size();k++) { - bool keyframe_selected = keyframeIsSelected(row, k); - long keyframe_frame = adjust_row_keyframe(row, row->keyframe_times.at(k)); - if (dragging && keyframe_selected) keyframe_frame += frame_diff; - draw_keyframe(p, row->keyframe_types.at(k), getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected); + for (int l=0;lfieldCount();l++) { + EffectField* f = row->field(l); + for (int k=0;kkeyframes.size();k++) { + if (!key_times.contains(f->keyframes.at(k).time)) { + bool keyframe_selected = keyframeIsSelected(f, k); + long keyframe_frame = adjust_row_keyframe(row, f->keyframes.at(k).time); + + // see if any other keyframes have this time + int appearances = 0; + for (int m=0;mfieldCount();m++) { + EffectField* compf = row->field(m); + for (int n=0;nkeyframes.size();n++) { + if (f->keyframes.at(k).time == compf->keyframes.at(n).time) { + appearances++; + } + } + } + + if (appearances != row->fieldCount()) { + QColor cc = get_curve_color(l, row->fieldCount()); + draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected, cc.red(), cc.green(), cc.blue()); + } else { + draw_keyframe(p, f->keyframes.at(k).type, getScreenPointFromFrame(panel_effect_controls->zoom, keyframe_frame) - x_scroll, keyframe_y, keyframe_selected); + } + + key_times.append(f->keyframes.at(k).time); + } + } } rows.append(row); @@ -134,60 +170,32 @@ void KeyframeView::paintEvent(QPaintEvent*) { }*/ } -bool KeyframeView::keyframeIsSelected(EffectRow *row, int keyframe) { - for (int i=0;idelete_keyframe(kd, selected_keyframes.at(i)); - del = true; - } - if (del) { - undo_stack.push(kd); +void KeyframeView::update_keys() { +// panel_graph_editor->update_panel(); + update(); +} - selected_keyframes.clear(); - selected_rows.clear(); - update(); - panel_sequence_viewer->viewer_widget->update(); - } else { - delete kd; - } +void KeyframeView::delete_selected_keyframes() { + delete_keyframes(selected_fields, selected_keyframes); } void KeyframeView::set_x_scroll(int s) { x_scroll = s; - update(); + update_keys(); } void KeyframeView::set_y_scroll(int s) { y_scroll = s; - update(); -} - -void KeyframeView::draw_keyframe(QPainter &p, int type, int x, int y, bool darker) { - int color = (darker) ? 100 : 160; - p.setPen(QColor(0, 0, 0)); - p.setBrush(QColor(color, color, color)); - - switch (type) { - case KEYFRAME_TYPE_LINEAR: - { - QPoint points[KEYFRAME_POINT_COUNT] = {QPoint(x-KEYFRAME_SIZE, y), QPoint(x, y-KEYFRAME_SIZE), QPoint(x+KEYFRAME_SIZE, y), QPoint(x, y+KEYFRAME_SIZE)}; - p.drawPolygon(points, KEYFRAME_POINT_COUNT); - } - break; - case KEYFRAME_TYPE_SMOOTH: - p.drawEllipse(QPoint(x, y), KEYFRAME_SIZE, KEYFRAME_SIZE); - break; - } + update_keys(); } void KeyframeView::resize_move(double d) { @@ -197,15 +205,20 @@ void KeyframeView::resize_move(double d) { void KeyframeView::mousePressEvent(QMouseEvent *event) { rect_select_x = event->x(); rect_select_y = event->y(); + rect_select_w = 0; + rect_select_h = 0; if (panel_timeline->tool == TIMELINE_TOOL_HAND || event->buttons() & Qt::MiddleButton) { scroll_drag = true; return; } + old_key_vals.clear(); + int mouse_x = event->x() + x_scroll; int mouse_y = event->y(); int row_index = -1; + int field_index = -1; int keyframe_index = -1; long frame_diff = 0; long frame_min = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x-KEYFRAME_SIZE); @@ -214,14 +227,21 @@ void KeyframeView::mousePressEvent(QMouseEvent *event) { for (int i=0;i rowY.at(i)-KEYFRAME_SIZE-KEYFRAME_SIZE && mouse_y < rowY.at(i)+KEYFRAME_SIZE+KEYFRAME_SIZE) { EffectRow* row = rows.at(i); - for (int j=0;jkeyframe_times.size();j++) { - long eval_keyframe_time = row->keyframe_times.at(j)-row->parent_effect->parent_clip->clip_in+(row->parent_effect->parent_clip->timeline_in-visible_in); - if (eval_keyframe_time >= frame_min && eval_keyframe_time <= frame_max) { - long eval_frame_diff = qAbs(eval_keyframe_time - drag_frame_start); - if (keyframe_index == -1 || eval_frame_diff < frame_diff) { - row_index = i; - keyframe_index = j; - frame_diff = eval_frame_diff; + + row->focus_row(); + + for (int k=0;kfieldCount();k++) { + EffectField* f = row->field(k); + for (int j=0;jkeyframes.size();j++) { + long eval_keyframe_time = f->keyframes.at(j).time-row->parent_effect->parent_clip->clip_in+(row->parent_effect->parent_clip->timeline_in-visible_in); + if (eval_keyframe_time >= frame_min && eval_keyframe_time <= frame_max) { + long eval_frame_diff = qAbs(eval_keyframe_time - drag_frame_start); + if (keyframe_index == -1 || eval_frame_diff < frame_diff) { + row_index = i; + field_index = k; + keyframe_index = j; + frame_diff = eval_frame_diff; + } } } } @@ -230,23 +250,46 @@ void KeyframeView::mousePressEvent(QMouseEvent *event) { } bool already_selected = false; keys_selected = false; - if (keyframe_index > -1) already_selected = keyframeIsSelected(rows.at(row_index), keyframe_index); + if (keyframe_index > -1) { + already_selected = keyframeIsSelected(rows.at(row_index)->field(field_index), keyframe_index); + } else { + select_rect = true; + } if (!already_selected) { if (!(event->modifiers() & Qt::ShiftModifier)) { - selected_rows.clear(); + selected_fields.clear(); selected_keyframes.clear(); } if (keyframe_index > -1) { - selected_rows.append(rows.at(row_index)); + selected_fields.append(rows.at(row_index)->field(field_index)); selected_keyframes.append(keyframe_index); + + // find other field with keyframes at the same time + long comp_time = rows.at(row_index)->field(field_index)->keyframes.at(keyframe_index).time; + for (int i=0;ifieldCount();i++) { + if (i != field_index) { + EffectField* f = rows.at(row_index)->field(i); + for (int j=0;jkeyframes.size();j++) { + if (f->keyframes.at(j).time == comp_time) { + selected_fields.append(f); + selected_keyframes.append(j); + } + } + } + } } } - if (selected_rows.size() > 0) { + if (selected_fields.size() > 0) { + for (int i=0;ikeyframes.at(selected_keyframes.at(i)).time); + } keys_selected = true; } - update(); + rect_select_offset = selected_fields.size(); + + update_keys(); if (event->button() == Qt::LeftButton) { mousedown = true; @@ -266,46 +309,11 @@ void KeyframeView::mouseMoveEvent(QMouseEvent* event) { rect_select_y = event->pos().y(); } else if (mousedown) { int mouse_x = event->x() + x_scroll; - if (keys_selected) { - // move keyframes - frame_diff = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x) - drag_frame_start; - - // snapping to playhead - if (panel_timeline->snapping) { - for (int i=0;iparent_effect->parent_clip; - long key_time = row->keyframe_times.at(selected_keyframes.at(i)) + frame_diff - c->clip_in + c->timeline_in; - long key_eval = key_time; - if (panel_timeline->snap_to_point(sequence->playhead, &key_eval)) { - frame_diff += (key_eval - key_time); - break; - } - } - } - - // validate frame_diff (make sure no keyframes overlap each other) - for (int i=0;ikeyframe_times.at(selected_keyframes.at(i)); - for (int j=0;jkeyframe_times.size();j++) { - while (!keyframeIsSelected(row, j) && row->keyframe_times.at(j) == eval_key + frame_diff) { - if (last_frame_diff > frame_diff) { - frame_diff++; - panel_timeline->snapped = false; - } else { - frame_diff--; - panel_timeline->snapped = false; - } - } - } - } - - last_frame_diff = frame_diff; - - dragging = true; - } else { + if (select_rect) { // do a rect select + selected_fields.resize(rect_select_offset); + selected_keyframes.resize(rect_select_offset); + rect_select_w = event->x() - rect_select_x; rect_select_h = event->y() - rect_select_y; @@ -320,29 +328,82 @@ void KeyframeView::mouseMoveEvent(QMouseEvent* event) { for (int i=0;i= min_row && rowY.at(i) <= max_row) { EffectRow* row = rows.at(i); - for (int j=0;jkeyframe_times.size();j++) { - long keyframe_frame = adjust_row_keyframe(row, row->keyframe_times.at(j)); - if (!keyframeIsSelected(row, j) && keyframe_frame >= min_frame && keyframe_frame <= max_frame) { - selected_rows.append(rows.at(i)); - selected_keyframes.append(j); + for (int k=0;kfieldCount();k++) { + EffectField* field = row->field(k); + for (int j=0;jkeyframes.size();j++) { + long keyframe_frame = adjust_row_keyframe(row, field->keyframes.at(j).time); + if (!keyframeIsSelected(field, j) && keyframe_frame >= min_frame && keyframe_frame <= max_frame) { + selected_fields.append(field); + selected_keyframes.append(j); + } } } } } - select_rect = true; + update_keys(); + } else if (keys_selected) { + // move keyframes + long frame_diff = getFrameFromScreenPoint(panel_effect_controls->zoom, mouse_x) - drag_frame_start; + + // snapping to playhead + panel_timeline->snapped = false; + if (panel_timeline->snapping) { + for (int i=0;iparent_row->parent_effect->parent_clip; + long key_time = old_key_vals.at(i) + frame_diff - c->clip_in + c->timeline_in; + long key_eval = key_time; + if (panel_timeline->snap_to_point(sequence->playhead, &key_eval)) { + frame_diff += (key_eval - key_time); + break; + } + } + } + + // validate frame_diff (make sure no keyframes overlap each other) + for (int i=0;ikeyframes.size();j++) { + while (!keyframeIsSelected(field, j) && field->keyframes.at(j).time == eval_key + frame_diff) { + if (last_frame_diff > frame_diff) { + frame_diff++; + panel_timeline->snapped = false; + } else { + frame_diff--; + panel_timeline->snapped = false; + } + } + } + } + + // apply frame_diffs + for (int i=0;ikeyframes[selected_keyframes.at(i)].time = old_key_vals.at(i) + frame_diff; + } + + last_frame_diff = frame_diff; + + dragging = true; + + update_ui(false); } - update(); } } void KeyframeView::mouseReleaseEvent(QMouseEvent*) { - if (dragging && frame_diff != 0) { - KeyframeMove* ka = new KeyframeMove(); - ka->movement = frame_diff; - ka->keyframes = selected_keyframes; - ka->rows = selected_rows; - undo_stack.push(ka); + if (dragging) { + ComboAction* ca = new ComboAction(); + for (int i=0;iappend(new SetLong( + &selected_fields.at(i)->keyframes[selected_keyframes.at(i)].time, + old_key_vals.at(i), + selected_fields.at(i)->keyframes.at(selected_keyframes.at(i)).time + )); + } + undo_stack.push(ca); } select_rect = false; @@ -350,5 +411,5 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent*) { mousedown = false; scroll_drag = false; panel_timeline->snapped = false; - update(); + update_ui(false); } diff --git a/ui/keyframeview.h b/ui/keyframeview.h index 6c3a72f31..51bbd361d 100644 --- a/ui/keyframeview.h +++ b/ui/keyframeview.h @@ -7,6 +7,7 @@ struct Clip; class Effect; class EffectRow; +class EffectField; class TimelineHeader; class KeyframeView : public QWidget { @@ -23,26 +24,25 @@ public: public slots: void set_x_scroll(int); void set_y_scroll(int); - void resize_move(double d); + void resize_move(double d); private: long adjust_row_keyframe(EffectRow* row, long time); - QVector selected_rows; - QVector selected_keyframes; + QVector selected_fields; + QVector selected_keyframes; QVector rowY; - long frame_diff; QVector rows; + QVector old_key_vals; void mousePressEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent *event); void paintEvent(QPaintEvent *event); - void draw_keyframe(QPainter& p, int type, int x, int y, bool darker); - bool mousedown; + bool mousedown; bool dragging; bool keys_selected; - bool select_rect; - bool scroll_drag; + bool select_rect; + bool scroll_drag; - bool keyframeIsSelected(EffectRow* row, int keyframe); + bool keyframeIsSelected(EffectField *field, int keyframe); long drag_frame_start; long last_frame_diff; @@ -50,12 +50,15 @@ private: int rect_select_y; int rect_select_w; int rect_select_h; + int rect_select_offset; int x_scroll; int y_scroll; + + void update_keys(); private slots: - void show_context_menu(const QPoint& pos); - void menu_set_key_type(QAction*); + void show_context_menu(const QPoint& pos); + void menu_set_key_type(QAction*); }; #endif // KEYFRAMEVIEW_H diff --git a/ui/labelslider.cpp b/ui/labelslider.cpp index 357a891db..7395ad752 100644 --- a/ui/labelslider.cpp +++ b/ui/labelslider.cpp @@ -16,7 +16,7 @@ LabelSlider::LabelSlider(QWidget* parent) : QLabel(parent) { drag_proc = false; min_enabled = false; max_enabled = false; - setStyleSheet("QLabel{color:#ffc000;text-decoration:underline;}QLabel:disabled{color:#808080;}"); + set_color(); setCursor(Qt::SizeHorCursor); internal_value = -1; set = false; @@ -75,7 +75,12 @@ double LabelSlider::getPreviousValue() { } void LabelSlider::set_previous_value() { - previous_value = internal_value; + previous_value = internal_value; +} + +void LabelSlider::set_color(QString c) { + if (c.isEmpty()) c = "#ffc000"; + setStyleSheet("QLabel{color:" + c + ";text-decoration:underline;}QLabel:disabled{color:#808080;}"); } double LabelSlider::value() { @@ -115,6 +120,7 @@ void LabelSlider::mousePressEvent(QMouseEvent *ev) { drag_start_x = cursor().pos().x(); drag_start_y = cursor().pos().y(); } + emit clicked(); } void LabelSlider::mouseMoveEvent(QMouseEvent* event) { diff --git a/ui/labelslider.h b/ui/labelslider.h index 3476128ff..f671d18df 100644 --- a/ui/labelslider.h +++ b/ui/labelslider.h @@ -25,6 +25,7 @@ public: QString valueToString(double v); double getPreviousValue(); void set_previous_value(); + void set_color(QString c = 0); int decimal_places; protected: void mousePressEvent(QMouseEvent *ev); @@ -53,6 +54,7 @@ private: double frame_rate; signals: void valueChanged(); + void clicked(); }; #endif // LABELSLIDER_H diff --git a/ui/rectangleselect.cpp b/ui/rectangleselect.cpp new file mode 100644 index 000000000..b4a9348ce --- /dev/null +++ b/ui/rectangleselect.cpp @@ -0,0 +1,7 @@ +#include "rectangleselect.h" + +void draw_selection_rectangle(QPainter& painter, const QRect& rect) { + painter.setPen(QColor(204, 204, 204)); + painter.setBrush(QColor(0, 0, 0, 32)); + painter.drawRect(rect); +} diff --git a/ui/rectangleselect.h b/ui/rectangleselect.h new file mode 100644 index 000000000..40eee2aeb --- /dev/null +++ b/ui/rectangleselect.h @@ -0,0 +1,8 @@ +#ifndef RECTANGLESELECT_H +#define RECTANGLESELECT_H + +#include + +void draw_selection_rectangle(QPainter& painter, const QRect& rect); + +#endif // RECTANGLESELECT_H diff --git a/ui/timelineheader.h b/ui/timelineheader.h index 884a44adf..2950cb0fc 100644 --- a/ui/timelineheader.h +++ b/ui/timelineheader.h @@ -21,12 +21,12 @@ public: bool snapping; void show_text(bool enable); - void update_zoom(double z); double get_zoom(); void delete_markers(); void set_scrollbar_max(QScrollBar* bar, long sequence_end_frame, int offset); public slots: + void update_zoom(double z); void set_scroll(int); void set_visible_in(long i); void show_context_menu(const QPoint &pos); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index da2d3b738..d56a36765 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -21,6 +21,7 @@ #include "ui/resizablescrollbar.h" #include "dialogs/newsequencedialog.h" #include "mainwindow.h" +#include "ui/rectangleselect.h" #include "debug.h" #include "project/effect.h" @@ -1234,15 +1235,19 @@ void validate_transitions(Clip* c, int transition_type, long& frame_diff) { } void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { + int effective_tool = panel_timeline->tool; + if (panel_timeline->importing || panel_timeline->creating) effective_tool = TIMELINE_TOOL_POINTER; + int mouse_track = getTrackFromScreenPoint(mouse_pos.y()); long frame_diff = (lock_frame) ? 0 : panel_timeline->getTimelineFrameFromScreenPoint(mouse_pos.x()) - panel_timeline->drag_frame_start; - int track_diff = ((panel_timeline->tool == TIMELINE_TOOL_SLIDE || panel_timeline->transition_select != TA_NO_TRANSITION) && !panel_timeline->importing) ? 0 : mouse_track - panel_timeline->drag_track_start; + int track_diff = ((effective_tool == TIMELINE_TOOL_SLIDE || panel_timeline->transition_select != TA_NO_TRANSITION) && !panel_timeline->importing) ? 0 : mouse_track - panel_timeline->drag_track_start; long validator; long earliest_in_point = LONG_MAX; // first try to snap long fm; - if (panel_timeline->tool != TIMELINE_TOOL_SLIP) { + + if (effective_tool != TIMELINE_TOOL_SLIP) { // slipping doesn't move the clips so we don't bother snapping for it for (int i=0;ighosts.size();i++) { const Ghost& g = panel_timeline->ghosts.at(i); @@ -1263,7 +1268,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { } } - bool clips_are_movable = (panel_timeline->tool == TIMELINE_TOOL_POINTER || panel_timeline->tool == TIMELINE_TOOL_SLIDE || panel_timeline->importing); + bool clips_are_movable = (effective_tool == TIMELINE_TOOL_POINTER || effective_tool == TIMELINE_TOOL_SLIDE); // validate ghosts long temp_frame_diff = frame_diff; // cache to see if we change it (thus cancelling any snap) @@ -1272,7 +1277,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { Clip* c = NULL; if (g.clip != -1) c = sequence->clips.at(g.clip); - FootageStream* ms = NULL; + const FootageStream* ms = NULL; if (g.clip != -1 && c->media != NULL && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); } @@ -1280,8 +1285,8 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { // validate ghosts for trimming if (panel_timeline->creating) { // i feel like we might need something here but we haven't so far? - } else if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { - if (c->media->get_type() == MEDIA_TYPE_SEQUENCE + } else if (effective_tool == TIMELINE_TOOL_SLIP) { + if ((c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) || (ms != NULL && !ms->infinite_length)) { // prevent slip moving a clip below 0 clip_in validator = g.old_clip_in - frame_diff; @@ -1298,13 +1303,13 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { if (validator < 1) frame_diff -= (1 - validator); // prevent timeline in from going below 0 - if (panel_timeline->tool != TIMELINE_TOOL_RIPPLE) { + if (effective_tool != TIMELINE_TOOL_RIPPLE) { validator = g.old_in + frame_diff; if (validator < 0) frame_diff -= validator; } // prevent clip_in from going below 0 - if (c->media->get_type() == MEDIA_TYPE_SEQUENCE + if ((c->media != NULL && c->media->get_type() == MEDIA_TYPE_SEQUENCE) || (ms != NULL && !ms->infinite_length)) { validator = g.old_clip_in + frame_diff; if (validator < 0) frame_diff -= validator; @@ -1349,7 +1354,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { } // ripple ops - if (panel_timeline->tool == TIMELINE_TOOL_RIPPLE) { + if (effective_tool == TIMELINE_TOOL_RIPPLE) { for (int j=0;jtool == TIMELINE_TOOL_TRANSITION) { + } else if (effective_tool == TIMELINE_TOOL_TRANSITION) { if (panel_timeline->transition_tool_post_clip == -1) { validate_transitions(c, panel_timeline->transition_tool_type, frame_diff); } else { @@ -1453,7 +1458,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { for (int i=0;ighosts.size();i++) { Ghost& g = panel_timeline->ghosts[i]; - if (panel_timeline->tool == TIMELINE_TOOL_SLIP) { + if (effective_tool == TIMELINE_TOOL_SLIP) { g.clip_in = g.old_clip_in - frame_diff; } else if (g.trimming) { long ghost_diff = frame_diff; @@ -1506,7 +1511,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { } else if (same_sign(g.old_track, panel_timeline->drag_track_start)) { g.track += track_diff; } - } else if (panel_timeline->tool == TIMELINE_TOOL_TRANSITION) { + } else if (effective_tool == TIMELINE_TOOL_TRANSITION) { if (panel_timeline->transition_tool_post_clip > -1) { g.in = g.old_in - frame_diff; g.out = g.old_out + frame_diff; @@ -1521,7 +1526,7 @@ void TimelineWidget::update_ghosts(const QPoint& mouse_pos, bool lock_frame) { } // apply changes to selections - if (panel_timeline->tool != TIMELINE_TOOL_SLIP && !panel_timeline->importing && !panel_timeline->creating) { + if (effective_tool != TIMELINE_TOOL_SLIP && !panel_timeline->importing && !panel_timeline->creating) { for (int i=0;iselections.size();i++) { Selection& s = sequence->selections[i]; if (panel_timeline->trim_target > -1) { @@ -2145,7 +2150,7 @@ int color_brightness(int r, int g, int b) { return (0.2126*r + 0.7152*g + 0.0722*b); } -void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { +void draw_waveform(Clip* clip, const FootageStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { int divider = ms->audio_channels*2; int channel_height = clip_rect.height()/ms->audio_channels; @@ -2342,6 +2347,10 @@ void TimelineWidget::paintEvent(QPaintEvent*) { p.drawImage(QRect(thumb_x, clip_rect.y()+thumb_y, thumb_clip_width, thumb_height), ms->video_preview, QRect(0, 0, thumb_clip_width*((double)ms->video_preview.width()/(double)thumb_width), ms->video_preview.height())); } } + if (clip->timeline_out - clip->timeline_in + clip->clip_in > clip->getMaximumLength()) { + draw_checkerboard = true; + checkerboard_rect.setLeft(panel_timeline->getTimelineScreenPointFromFrame(clip->getMaximumLength() + clip->timeline_in - clip->clip_in)); + } } else if (clip_rect.height() > TRACK_MIN_HEIGHT) { // draw waveform p.setPen(QColor(80, 80, 80)); diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index bf03b1595..b8339a1b6 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -22,7 +22,7 @@ class QPainter; class Media; bool same_sign(int a, int b); -void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); +void draw_waveform(Clip* clip, const FootageStream *ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); class TimelineWidget : public QWidget { Q_OBJECT diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index a501fbfec..8481f90fa 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -72,7 +72,7 @@ void ViewerWidget::delete_function() { // destroy all textures as well if (viewer->seq != NULL) { makeCurrent(); - closeActiveClips(viewer->seq, true); + closeActiveClips(viewer->seq); doneCurrent(); } } @@ -485,7 +485,7 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) Footage* m = c->media->to_footage(); if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) { if (m->ready) { - FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); + const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); if (ms != NULL && is_clip_active(c, playhead)) { // if thread is already working, we don't want to touch this, // but we also don't want to hang the UI thread @@ -495,7 +495,7 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) clip_is_active = true; if (c->track >= 0) audio_track_count++; } else if (c->open) { - close_clip(c); + close_clip(c, false); } } else { //dout << "[WARNING] Media '" + m->name + "' was not ready, retrying..."; @@ -507,7 +507,7 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) if (!c->open) open_clip(c, !rendering); clip_is_active = true; } else if (c->open) { - close_clip(c); + close_clip(c, false); } } if (clip_is_active) { diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index fa51aa383..0450efd52 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -34,7 +34,7 @@ public: bool waveform; Clip* waveform_clip; - FootageStream* waveform_ms; + const FootageStream* waveform_ms; double waveform_zoom; int waveform_scroll;