replace clip media implemented

This commit is contained in:
itsmattkc
2018-08-05 14:09:50 +10:00
parent e19fb30d0d
commit 43d7e80b9e
16 changed files with 748 additions and 371 deletions
+135
View File
@@ -0,0 +1,135 @@
#include "replaceclipmediadialog.h"
#include "ui/sourcetable.h"
#include "panels/panels.h"
#include "panels/timeline.h"
#include "panels/project.h"
#include "project/sequence.h"
#include "playback/playback.h"
#include "playback/cacher.h"
#include <QVBoxLayout>
#include <QTreeWidget>
#include <QLabel>
#include <QPushButton>
#include <QMessageBox>
#include <QCheckBox>
#include <QDebug>
ReplaceClipMediaDialog::ReplaceClipMediaDialog(QWidget *parent, SourceTable *table, QTreeWidgetItem *old_media) :
QDialog(parent),
source_table(table),
media(old_media)
{
resize(300, 400);
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(new QLabel("Select which media you want to replace this media's clips with:"));
tree = new QTreeWidget();
tree->setHeaderHidden(true);
layout->addWidget(tree);
use_same_media_in_points = new QCheckBox("Keep the same media in points");
use_same_media_in_points->setChecked(true);
layout->addWidget(use_same_media_in_points);
QHBoxLayout* buttons = new QHBoxLayout();
buttons->addStretch();
QPushButton* replace_button = new QPushButton("Replace");
connect(replace_button, SIGNAL(clicked(bool)), this, SLOT(replace()));
buttons->addWidget(replace_button);
QPushButton* cancel_button = new QPushButton("Cancel");
connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(close()));
buttons->addWidget(cancel_button);
buttons->addStretch();
layout->addLayout(buttons);
setLayout(layout);
copy_tree(NULL, NULL);
}
void ReplaceClipMediaDialog::replace() {
if (tree->selectedItems().size() != 1) {
QMessageBox::critical(this, "No media selected", "Please select a media to replace with or click 'Cancel'.", QMessageBox::Ok);
} else {
QTreeWidgetItem* selected_item = tree->selectedItems().at(0);
QTreeWidgetItem* new_item = reinterpret_cast<QTreeWidgetItem*>(selected_item->data(0, Qt::UserRole + 1).value<quintptr>());
if (media == new_item) {
QMessageBox::critical(this, "Same media selected", "You selected the same media that you're replacing. Please select a different one or click 'Cancel'.", QMessageBox::Ok);
} else if (get_type_from_tree(new_item) == MEDIA_TYPE_FOLDER) {
QMessageBox::critical(this, "Folder selected", "You cannot replace footage with a folder.", QMessageBox::Ok);
} else {
if (get_type_from_tree(new_item) == MEDIA_TYPE_SEQUENCE && sequence == get_sequence_from_tree(new_item)) {
QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself.", QMessageBox::Ok);
} else {
void* old_media = get_media_from_tree(media);
void* new_media = get_media_from_tree(new_item);
// TODO not undoable yet
bool changed = false;
for (int i=0;i<sequence->clip_count();i++) {
Clip* c = sequence->get_clip(i);
if (c != NULL && c->media == old_media) {
if (c->open) {
close_clip(c);
c->cacher->wait();
}
if (!use_same_media_in_points->isChecked()) {
c->clip_in = 0;
}
c->media = new_media;
c->refresh();
changed = true;
}
}
if (changed) {
panel_timeline->redraw_all_clips(true);
}
close();
}
}
}
}
void ReplaceClipMediaDialog::copy_tree(QTreeWidgetItem* parent, QTreeWidgetItem* target) {
QVector<QTreeWidgetItem*> items;
if (parent == NULL) {
for (int i=0;i<source_table->topLevelItemCount();i++) {
items.append(source_table->topLevelItem(i));
}
} else {
for (int i=0;i<parent->childCount();i++) {
items.append(parent->child(i));
}
}
for (int i=0;i<items.size();i++) {
QTreeWidgetItem* original = items.at(i);
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setText(0, original->text(0));
item->setData(0, Qt::UserRole + 1, reinterpret_cast<quintptr>(original));
if (target == NULL) {
tree->addTopLevelItem(item);
} else {
target->addChild(item);
}
if (original->childCount() > 0) {
copy_tree(original, item);
}
}
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef REPLACECLIPMEDIADIALOG_H
#define REPLACECLIPMEDIADIALOG_H
#include <QDialog>
class SourceTable;
class QTreeWidget;
class QTreeWidgetItem;
class QCheckBox;
class ReplaceClipMediaDialog : public QDialog {
Q_OBJECT
public:
ReplaceClipMediaDialog(QWidget* parent, SourceTable* table, QTreeWidgetItem *old_media);
private slots:
void replace();
private:
SourceTable* source_table;
QTreeWidget* tree;
QTreeWidgetItem* media;
QCheckBox* use_same_media_in_points;
void copy_tree(QTreeWidgetItem* parent, QTreeWidgetItem *target);
};
#endif // REPLACECLIPMEDIADIALOG_H
+1 -1
View File
@@ -28,7 +28,7 @@ struct MediaStream {
struct Media {
Media();
~Media();
~Media();
QString url;
QString name;
+6 -3
View File
@@ -5,6 +5,8 @@
#include "project/sequence.h"
#include "ui/sourcetable.h"
#include "panels/panels.h"
#include "panels/project.h"
#include "panels/effectcontrols.h"
@@ -590,9 +592,10 @@ void MainWindow::on_actionSlide_Tool_triggered()
if (panel_timeline->focused()) panel_timeline->ui->toolSlideButton->click();
}
void MainWindow::on_actionFolder_triggered()
{
panel_project->new_folder();
void MainWindow::on_actionFolder_triggered() {
TimelineAction* ta = new TimelineAction();
ta->add_media(panel_project->new_folder(0), panel_project->get_selected_folder());
undo_stack.push(ta);
}
void MainWindow::fileMenu_About_To_Be_Shown() {
+4 -2
View File
@@ -24,7 +24,7 @@
<x>0</x>
<y>0</y>
<width>653</width>
<height>17</height>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@@ -151,7 +151,6 @@
<addaction name="actionScroll_Wheel_Zooms"/>
<addaction name="separator"/>
<addaction name="actionPreferences"/>
<addaction name="separator"/>
<addaction name="actionCrash"/>
</widget>
<addaction name="menu_File"/>
@@ -420,6 +419,9 @@
<property name="text">
<string>Crash</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="actionPointer_Tool">
<property name="text">
+4 -2
View File
@@ -65,7 +65,8 @@ SOURCES += \
effects/texteffect.cpp \
ui/comboboxex.cpp \
ui/colorbutton.cpp \
effects/solideffect.cpp
effects/solideffect.cpp \
dialogs/replaceclipmediadialog.cpp
HEADERS += \
mainwindow.h \
@@ -102,7 +103,8 @@ HEADERS += \
project/undo.h \
ui/scrollarea.h \
ui/comboboxex.h \
ui/colorbutton.h
ui/colorbutton.h \
dialogs/replaceclipmediadialog.h
FORMS += \
mainwindow.ui \
+269 -115
View File
@@ -15,6 +15,8 @@
#include "project/undo.h"
#include "mainwindow.h"
#include "io/config.h"
#include "playback/cacher.h"
#include "dialogs/replaceclipmediadialog.h"
#include <QFileDialog>
#include <QString>
@@ -81,7 +83,7 @@ void Project::rename_media(QTreeWidgetItem* item, int column) {
int type = get_type_from_tree(item);
QString n = item->text(column);
switch (type) {
case MEDIA_TYPE_FOOTAGE: get_media_from_tree(item)->name = n; break;
case MEDIA_TYPE_FOOTAGE: get_footage_from_tree(item)->name = n; break;
case MEDIA_TYPE_SEQUENCE: get_sequence_from_tree(item)->name = n; break;
}
}
@@ -104,6 +106,71 @@ void Project::duplicate_selected() {
}
}
void Project::replace_selected_file() {
QList<QTreeWidgetItem*> selected_items = ui->treeWidget->selectedItems();
if (selected_items.size() == 1) {
QTreeWidgetItem* item = selected_items.at(0);
if (get_type_from_tree(item) == MEDIA_TYPE_FOOTAGE) {
replace_media(item, 0);
}
}
}
void Project::replace_media(QTreeWidgetItem* item, QString filename) {
Media* m = get_footage_from_tree(item);
QStringList files;
if (filename.isEmpty()) {
QString browse_file = QFileDialog::getOpenFileName(this, "Replace '" + item->text(0) + "'", "", "All Files (*)");
if (!browse_file.isEmpty()) files.append(browse_file);
} else {
files.append(filename);
}
if (files.size() > 0) {
// close any clips currently using this media
QVector<Sequence*> all_sequences = list_all_project_sequences();
for (int i=0;i<all_sequences.size();i++) {
Sequence* s = all_sequences.at(i);
for (int j=0;j<s->clip_count();j++) {
Clip* c = s->get_clip(j);
if (c != NULL && c->media == m && c->open) {
close_clip(c);
c->cacher->wait();
}
}
}
process_file_list(NULL, files, NULL, item);
for (int i=0;i<all_sequences.size();i++) {
Sequence* s = all_sequences.at(i);
for (int j=0;j<s->clip_count();j++) {
Clip* c = s->get_clip(j);
if (c != NULL && c->media == m) {
c->refresh();
}
}
}
panel_timeline->redraw_all_clips(true);
}
}
void Project::replace_clip_media() {
if (sequence == NULL) {
QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok);
} else if (ui->treeWidget->selectedItems().size() == 1) {
QTreeWidgetItem* item = ui->treeWidget->selectedItems().at(0);
if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE && sequence == get_sequence_from_tree(item)) {
QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok);
} else {
ReplaceClipMediaDialog dialog(this, ui->treeWidget, item);
dialog.exec();
}
}
}
void Project::new_sequence(TimelineAction *ta, Sequence *s, bool open, QTreeWidgetItem* parent) {
QTreeWidgetItem* item = new_item();
item->setText(0, s->name);
@@ -134,17 +201,16 @@ void Project::start_preview_generator(QTreeWidgetItem* item, Media* media) {
pg->start(QThread::LowPriority);
}
QTreeWidgetItem* Project::import_file(QString file, QString imported_filename) {
QTreeWidgetItem* item = new_item();
Media* m = new Media();
QString Project::get_file_name_from_path(const QString& path) {
return path.mid(path.lastIndexOf('/')+1);
}
void Project::import_file(QTreeWidgetItem* item, Media* m, QString file, QString imported_filename) {
m->url = file;
m->name = imported_filename.mid(file.lastIndexOf('/')+1);
set_media_of_tree(item, m);
m->name = get_file_name_from_path(imported_filename);
// generate waveform/thumbnail in another thread
start_preview_generator(item, m);
return item;
start_preview_generator(item, m);
}
QTreeWidgetItem* Project::new_item() {
@@ -157,17 +223,15 @@ bool Project::is_focused() {
return ui->treeWidget->hasFocus();
}
QTreeWidgetItem* Project::new_folder() {
QTreeWidgetItem* Project::new_folder(QString name) {
QTreeWidgetItem* item = new_item();
item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
item->setText(0, "New Folder");
item->setText(0, (name.isEmpty()) ? "New Folder" : name);
set_item_to_folder(item);
ui->treeWidget->addTopLevelItem(item);
project_changed = true;
return item;
}
void Project::get_media_from_table(QList<QTreeWidgetItem*> items, QList<QTreeWidgetItem*>& list, int search_type) {
void Project::get_all_media_from_table(QList<QTreeWidgetItem*> items, QList<QTreeWidgetItem*>& list, int search_type) {
for (int i=0;i<items.size();i++) {
QTreeWidgetItem* item = items.at(i);
int type = get_type_from_tree(item);
@@ -176,7 +240,7 @@ void Project::get_media_from_table(QList<QTreeWidgetItem*> items, QList<QTreeWid
for (int j=0;j<item->childCount();j++) {
children.append(item->child(j));
}
get_media_from_table(children, list, search_type);
get_all_media_from_table(children, list, search_type);
} else if (search_type == type) {
list.append(item);
}
@@ -196,13 +260,13 @@ void Project::delete_selected_media() {
for (int i=0;i<ui->treeWidget->topLevelItemCount();i++) {
all_top_level_items.append(ui->treeWidget->topLevelItem(i));
}
get_media_from_table(all_top_level_items, sequence_items, MEDIA_TYPE_SEQUENCE); // find all sequences in project
get_all_media_from_table(all_top_level_items, sequence_items, MEDIA_TYPE_SEQUENCE); // find all sequences in project
if (sequence_items.size() > 0) {
QList<QTreeWidgetItem*> media_items;
get_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE);
get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE);
for (int i=0;i<media_items.size();i++) {
QTreeWidgetItem* item = media_items.at(i);
Media* media = get_media_from_tree(item);
Media* media = get_footage_from_tree(item);
bool confirm_delete = false;
for (int j=0;j<sequence_items.size();j++) {
Sequence* s = get_sequence_from_tree(sequence_items.at(j));
@@ -299,114 +363,186 @@ void Project::delete_selected_media() {
}
}
void Project::process_file_list(QStringList& files) {
void Project::process_file_list(bool recursive, QStringList& files, QTreeWidgetItem* parent, QTreeWidgetItem* replace) {
bool imported = false;
QVector<QString> image_sequence_urls;
QVector<bool> image_sequence_importassequence;
QStringList image_sequence_formats = config.img_seq_formats.split("|");
TimelineAction* ta = new TimelineAction();
TimelineAction* ta;
if (!recursive) ta = new TimelineAction();
for (int i=0;i<files.count();i++) {
QString file(files.at(i));
bool skip = false;
if (QFileInfo(files.at(i)).isDir()) {
QString folder_name = get_file_name_from_path(files.at(i));
QTreeWidgetItem* folder = new_folder(folder_name);
/* Heuristic to determine whether file is part of an image sequence */
QDir directory(files.at(i));
directory.setFilter(QDir::NoDotAndDotDot | QDir::AllEntries);
// check file extension (assume it's not a
int lastcharindex = file.lastIndexOf(".");
bool found = true;
if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) {
// image_sequence_formats
found = false;
QString ext = file.mid(lastcharindex+1);
for (int j=0;j<image_sequence_formats.size();j++) {
if (ext == image_sequence_formats.at(j)) {
found = true;
break;
}
}
} else {
lastcharindex = file.length();
}
QFileInfoList subdir_files = directory.entryInfoList();
QStringList subdir_filenames;
if (found && file[lastcharindex-1].isDigit()) {
bool is_img_sequence = false;
for (int j=0;j<subdir_files.size();j++) {
subdir_filenames.append(subdir_files.at(j).filePath());
}
// how many digits are in the filename?
int digit_count = 0;
int digit_test = lastcharindex-1;
while (file[digit_test].isDigit()) {
digit_count++;
digit_test--;
}
process_file_list(true, subdir_filenames, folder, NULL);
// retrieve number from filename
digit_test++;
int file_number = file.mid(digit_test, digit_count).toInt();
if (!recursive) {
ta->add_media(folder, parent);
} else {
parent->addChild(folder);
}
// Check if there are files with the same filename but just different numbers
if (QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number-1, digit_count, 10, QChar('0')) + file.mid(lastcharindex)))
|| QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number+1, digit_count, 10, QChar('0')) + file.mid(lastcharindex)))) {
is_img_sequence = true;
}
imported = true;
} else {
QString file(files.at(i));
bool skip = false;
if (is_img_sequence) {
// get the URL that we would pass to FFmpeg to force it to read the image as a sequence
QString new_filename = file.left(digit_test) + "%" + QString("%1").arg(digit_count, 2, 10, QChar('0')) + "d" + file.mid(lastcharindex);
/* Heuristic to determine whether file is part of an image sequence */
// add image sequence url to a vector in case the user imported several files that
// we're interpreting as a possible sequence
found = false;
for (int i=0;i<image_sequence_urls.size();i++) {
if (image_sequence_urls.at(i) == new_filename) {
// either SKIP if we're importing as a sequence, or leave it if we aren't
if (image_sequence_importassequence.at(i)) {
skip = true;
}
found = true;
break;
}
}
if (!found) {
image_sequence_urls.append(new_filename);
if (QMessageBox::question(this, "Image sequence detected", "The file '" + file + "' appears to be part of an image sequence. Would you like to import it as such?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
file = new_filename;
image_sequence_importassequence.append(true);
} else {
image_sequence_importassequence.append(false);
}
}
}
}
// check file extension (assume it's not a
int lastcharindex = file.lastIndexOf(".");
bool found = true;
if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) {
// image_sequence_formats
found = false;
QString ext = file.mid(lastcharindex+1);
for (int j=0;j<image_sequence_formats.size();j++) {
if (ext == image_sequence_formats.at(j)) {
found = true;
break;
}
}
} else {
lastcharindex = file.length();
}
if (!skip) {
ta->add_media(import_file(file, files.at(i)));
imported = true;
project_changed = true;
}
}
if (imported) {
undo_stack.push(ta);
} else {
delete ta;
if (found && file[lastcharindex-1].isDigit()) {
bool is_img_sequence = false;
// how many digits are in the filename?
int digit_count = 0;
int digit_test = lastcharindex-1;
while (file[digit_test].isDigit()) {
digit_count++;
digit_test--;
}
// retrieve number from filename
digit_test++;
int file_number = file.mid(digit_test, digit_count).toInt();
// Check if there are files with the same filename but just different numbers
if (QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number-1, digit_count, 10, QChar('0')) + file.mid(lastcharindex)))
|| QFileInfo::exists(QString(file.left(digit_test) + QString("%1").arg(file_number+1, digit_count, 10, QChar('0')) + file.mid(lastcharindex)))) {
is_img_sequence = true;
}
if (is_img_sequence) {
// get the URL that we would pass to FFmpeg to force it to read the image as a sequence
QString new_filename = file.left(digit_test) + "%" + QString("%1").arg(digit_count, 2, 10, QChar('0')) + "d" + file.mid(lastcharindex);
// add image sequence url to a vector in case the user imported several files that
// we're interpreting as a possible sequence
found = false;
for (int i=0;i<image_sequence_urls.size();i++) {
if (image_sequence_urls.at(i) == new_filename) {
// either SKIP if we're importing as a sequence, or leave it if we aren't
if (image_sequence_importassequence.at(i)) {
skip = true;
}
found = true;
break;
}
}
if (!found) {
image_sequence_urls.append(new_filename);
if (QMessageBox::question(this, "Image sequence detected", "The file '" + file + "' appears to be part of an image sequence. Would you like to import it as such?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
file = new_filename;
image_sequence_importassequence.append(true);
} else {
image_sequence_importassequence.append(false);
}
}
}
}
if (!skip) {
QTreeWidgetItem* item;
Media* m;
if (replace != NULL) {
item = replace;
m = get_footage_from_tree(replace);
m->reset();
} else {
item = new_item();
m = new Media();
}
import_file(item, m, file, files.at(i));
set_footage_of_tree(item, m);
if (recursive) {
parent->addChild(item);
} else {
ta->add_media(item, parent);
}
imported = true;
}
}
}
if (!recursive) {
if (imported) {
undo_stack.push(ta);
} else {
delete ta;
}
}
}
QTreeWidgetItem* Project::get_selected_folder() {
// if one item is selected and it's a folder, return it
QList<QTreeWidgetItem*> selected_items = panel_project->source_table->selectedItems();
if (selected_items.size() == 1 && get_type_from_tree(selected_items.at(0)) == MEDIA_TYPE_FOLDER) {
return selected_items.at(0);
}
return NULL;
}
void Project::import_dialog() {
QStringList files = QFileDialog::getOpenFileNames(this, "Import media...", "", "All Files (*)");
process_file_list(files);
QFileDialog fd(this, "Import media...", "", "All Files (*)");
fd.setFileMode(QFileDialog::ExistingFiles);
if (fd.exec()) {
QStringList files = fd.selectedFiles();
process_file_list(NULL, files, get_selected_folder(), NULL);
}
}
void set_item_to_folder(QTreeWidgetItem* item) {
item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOLDER);
}
Media* get_media_from_tree(QTreeWidgetItem* item) {
void* get_media_from_tree(QTreeWidgetItem* item) {
int type = get_type_from_tree(item);
switch (type) {
case MEDIA_TYPE_FOOTAGE: return get_footage_from_tree(item);
case MEDIA_TYPE_SEQUENCE: return get_sequence_from_tree(item);
default: qDebug() << "[ERROR] Invalid media type when retrieving media";
}
return NULL;
}
Media* get_footage_from_tree(QTreeWidgetItem* item) {
return reinterpret_cast<Media*>(item->data(0, Qt::UserRole + 2).value<quintptr>());
}
void set_media_of_tree(QTreeWidgetItem* item, Media* media) {
void set_footage_of_tree(QTreeWidgetItem* item, Media* media) {
item->setText(0, media->name);
item->setData(0, Qt::UserRole + 1, MEDIA_TYPE_FOOTAGE);
item->setData(0, Qt::UserRole + 2, QVariant::fromValue(reinterpret_cast<quintptr>(media)));
@@ -426,18 +562,35 @@ int get_type_from_tree(QTreeWidgetItem* item) {
}
void Project::delete_media(QTreeWidgetItem* item) {
int type = get_type_from_tree(item);
switch (type) {
case MEDIA_TYPE_FOOTAGE:
delete get_media_from_tree(item);
break;
case MEDIA_TYPE_SEQUENCE:
Sequence* s = get_sequence_from_tree(item);
delete s;
break;
}
delete get_media_from_tree(item);
}
project_changed = true;
void Project::delete_clips_using_selected_media() {
if (sequence == NULL) {
QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok);
} else {
TimelineAction* ta = new TimelineAction();
bool deleted = false;
for (int i=0;i<sequence->clip_count();i++) {
Clip* c = sequence->get_clip(i);
if (c != NULL) {
QList<QTreeWidgetItem*> items = source_table->selectedItems();
for (int j=0;j<items.size();j++) {
Media* m = get_footage_from_tree(items.at(j));
if (c->media == m) {
ta->delete_clip(sequence, i);
deleted = true;
}
}
}
}
if (deleted) {
undo_stack.push(ta);
panel_timeline->redraw_all_clips(true);
} else {
delete ta;
}
}
}
void Project::clear() {
@@ -516,7 +669,7 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
switch (type) {
case MEDIA_TYPE_FOLDER:
{
QTreeWidgetItem* folder = new_folder();
QTreeWidgetItem* folder = new_folder(0);
for (int j=0;j<stream.attributes().size();j++) {
const QXmlStreamAttribute& attr = stream.attributes().at(j);
if (attr.name() == "id") {
@@ -534,7 +687,7 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
{
int folder = 0;
QTreeWidgetItem* item = new_item();//import_file(url);
QTreeWidgetItem* item = new_item();
Media* m = new Media();
for (int j=0;j<stream.attributes().size();j++) {
@@ -591,7 +744,7 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
}
}*/
set_media_of_tree(item, m);
set_footage_of_tree(item, m);
if (folder == 0) {
ui->treeWidget->addTopLevelItem(item);
@@ -823,10 +976,11 @@ void Project::load_project() {
for (int i=0;i<loaded_folders.size();i++) {
QTreeWidgetItem* folder = loaded_folders.at(i);
int parent = folder->data(0, Qt::UserRole + 4).toInt();
if (parent > 0) {
ui->treeWidget->takeTopLevelItem(ui->treeWidget->indexOfTopLevelItem(folder));
if (parent > 0) {
find_loaded_folder_by_id(parent)->addChild(folder);
}
} else {
ui->treeWidget->addTopLevelItem(folder);
}
}
cont = load_worker(file, stream, MEDIA_TYPE_FOOTAGE);
@@ -897,7 +1051,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int
} else {
int folder = root ? 0 : parent->data(0, Qt::UserRole + 3).toInt();
if (type == MEDIA_TYPE_FOOTAGE) {
Media* m = get_media_from_tree(item);
Media* m = get_footage_from_tree(item);
m->save_id = media_id;
stream.writeStartElement("footage");
stream.writeAttribute("id", QString::number(media_id));
+14 -7
View File
@@ -36,8 +36,9 @@ extern QStringList recent_projects;
extern QString recent_proj_file;
int get_type_from_tree(QTreeWidgetItem* item);
Media* get_media_from_tree(QTreeWidgetItem* item);
void set_media_of_tree(QTreeWidgetItem* item, Media* media);
void* get_media_from_tree(QTreeWidgetItem* item);
Media* get_footage_from_tree(QTreeWidgetItem* item);
void set_footage_of_tree(QTreeWidgetItem* item, Media* media);
Sequence* get_sequence_from_tree(QTreeWidgetItem* item);
void set_sequence_of_tree(QTreeWidgetItem* item, Sequence* sequence);
void set_item_to_folder(QTreeWidgetItem* item);
@@ -51,17 +52,19 @@ public:
~Project();
bool is_focused();
void clear();
QTreeWidgetItem* import_file(QString url, QString imported_filename);
void import_file(QTreeWidgetItem* item, Media* m, QString url, QString imported_filename);
void new_sequence(TimelineAction* ta, Sequence* s, bool open, QTreeWidgetItem* parent);
QString get_next_sequence_name();
void delete_media(QTreeWidgetItem* item);
void process_file_list(QStringList& files);
void process_file_list(bool recursive, QStringList& files, QTreeWidgetItem *parent, QTreeWidgetItem* replace);
void replace_media(QTreeWidgetItem* item, QString filename);
QTreeWidgetItem* get_selected_folder();
void new_project();
void load_project();
void save_project(bool autorecovery);
QTreeWidgetItem* new_folder();
QTreeWidgetItem* new_folder(QString name);
void save_recent_projects();
@@ -69,9 +72,12 @@ public:
SourceTable* source_table;
public slots:
void import_dialog();
void import_dialog();
void delete_selected_media();
void duplicate_selected();
void delete_clips_using_selected_media();
void replace_selected_file();
void replace_clip_media();
private:
Ui::Project *ui;
QTreeWidgetItem* new_item();
@@ -89,9 +95,10 @@ private:
QVector<Sequence*> loaded_sequences;
QTreeWidgetItem* find_loaded_folder_by_id(int id);
void add_recent_project(QString url);
void get_media_from_table(QList<QTreeWidgetItem*> items, QList<QTreeWidgetItem*>& list, int type);
void get_all_media_from_table(QList<QTreeWidgetItem*> items, QList<QTreeWidgetItem*>& list, int type);
void start_preview_generator(QTreeWidgetItem* item, Media* media);
void list_all_sequences_worker(QVector<Sequence*>* list, QTreeWidgetItem* parent);
QString get_file_name_from_path(const QString &path);
private slots:
void rename_media(QTreeWidgetItem* item, int column);
void clear_recent_projects();
+1 -2
View File
@@ -264,8 +264,7 @@ void Timeline::repaint_timeline() {
}
void Timeline::redraw_all_clips(bool changed) {
if (changed) {
project_changed = true;
if (changed) {
if (!playing) reset_all_audio();
panel_viewer->viewer_widget->update();
}
-1
View File
@@ -182,7 +182,6 @@ void reset_cache(Clip* c, long target_frame) {
// seeks to nearest keyframe (target_frame represents internal clip frame)
av_seek_frame(c->formatCtx, ms->file_index, (int64_t) qFloor(clip_frame_to_seconds(c, target_frame) / timebase), AVSEEK_FLAG_BACKWARD);
qDebug() << target_frame;
// play up to the frame we actually want
long retrieved_frame = 0;
+1 -1
View File
@@ -246,7 +246,7 @@ void retrieve_next_frame_raw_data(Clip* c, AVFrame* output) {
}
bool is_clip_active(Clip* c, long playhead) {
return c->timeline_in < playhead + ceil(c->sequence->frame_rate) && c->timeline_out > playhead && c->enabled;
return c->timeline_in < playhead + ceil(c->sequence->frame_rate) && c->timeline_out > playhead && c->enabled;
}
void set_sequence(Sequence* s) {
+44 -38
View File
@@ -31,7 +31,8 @@ TimelineAction::TimelineAction() :
done(false),
change_seq(false),
ripple_enabled(false),
change_in_out(false)
change_in_out(false),
old_project_changed(project_changed)
{}
TimelineAction::~TimelineAction() {
@@ -129,8 +130,9 @@ void TimelineAction::delete_clip(Sequence* s, int clip) {
new_action(s, TA_DELETE, clip, 0, 0);
}
void TimelineAction::add_media(QTreeWidgetItem* item) {
void TimelineAction::add_media(QTreeWidgetItem* item, QTreeWidgetItem *parent) {
media_to_add.append(item);
media_to_add_parents.append(parent);
}
void TimelineAction::delete_media(QTreeWidgetItem* item) {
@@ -242,20 +244,20 @@ void TimelineAction::undo() {
}
// delete added clips
if (clips_to_add.size() > 0) {
int delete_count = 0;
for (int i=0;i<clips_to_add.size();i++) {
sequence_to_add_clips_to.at(i)->destroy_clip(added_indexes.at(i)-delete_count, true);
delete_count++;
}
}
int delete_count = 0;
for (int i=0;i<clips_to_add.size();i++) {
sequence_to_add_clips_to.at(i)->destroy_clip(added_indexes.at(i)-delete_count, true);
delete_count++;
}
// remove any added media
if (media_to_add.size() > 0) {
for (int i=0;i<media_to_add.size();i++) {
panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(media_to_add.at(i)));
}
}
for (int i=0;i<media_to_add.size();i++) {
if (media_to_add_parents.at(i) == NULL) {
panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(media_to_add.at(i)));
} else {
media_to_add_parents.at(i)->removeChild(media_to_add.at(i));
}
}
// un-change workarea
if (change_in_out) {
@@ -269,6 +271,8 @@ void TimelineAction::undo() {
set_sequence(old_seq);
}
project_changed = old_project_changed;
done = false;
}
@@ -411,34 +415,34 @@ void TimelineAction::redo() {
}
// delete media
if (deleted_media.size() > 0) {
for (int i=0;i<deleted_media.size();i++) {
QTreeWidgetItem* item = deleted_media.at(i);
deleted_media_parents.append(item->parent());
for (int i=0;i<deleted_media.size();i++) {
QTreeWidgetItem* item = deleted_media.at(i);
deleted_media_parents.append(item->parent());
// if we're deleting the open sequence, close it
if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE &&
get_sequence_from_tree(item) == sequence &&
!change_seq) {
old_seq = sequence;
new_seq = NULL;
change_seq = true;
}
// if we're deleting the open sequence, close it
if (get_type_from_tree(item) == MEDIA_TYPE_SEQUENCE &&
get_sequence_from_tree(item) == sequence &&
!change_seq) {
old_seq = sequence;
new_seq = NULL;
change_seq = true;
}
if (item->parent() == NULL) {
panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item));
} else {
item->parent()->removeChild(item);
}
}
}
if (item->parent() == NULL) {
panel_project->source_table->takeTopLevelItem(panel_project->source_table->indexOfTopLevelItem(item));
} else {
item->parent()->removeChild(item);
}
}
// add any new media
if (media_to_add.size() > 0) {
for (int i=0;i<media_to_add.size();i++) {
panel_project->source_table->addTopLevelItem(media_to_add.at(i));
}
}
for (int i=0;i<media_to_add.size();i++) {
if (media_to_add_parents.at(i) == NULL) {
panel_project->source_table->addTopLevelItem(media_to_add.at(i));
} else {
media_to_add_parents.at(i)->addChild(media_to_add.at(i));
}
}
// change sequence workarea
if (change_in_out) {
@@ -453,6 +457,8 @@ void TimelineAction::redo() {
set_sequence(new_seq);
}
project_changed = true;
done = true;
}
+4 -1
View File
@@ -28,7 +28,7 @@ public:
void set_track(Sequence* s, int clip, int value);
void increase_track(Sequence* s, int clip, int value);
void delete_clip(Sequence* s, int clip);
void add_media(QTreeWidgetItem* item);
void add_media(QTreeWidgetItem* item, QTreeWidgetItem* parent);
void delete_media(QTreeWidgetItem* item);
void ripple(Sequence* s, long point, long length);
void ripple(Sequence* s, long point, long length, QVector<int> &ignore);
@@ -68,6 +68,7 @@ private:
QVector<QTreeWidgetItem*> deleted_media_parents;
QVector<QTreeWidgetItem*> media_to_add;
QVector<QTreeWidgetItem*> media_to_add_parents;
Sequence* ripple_sequence;
bool ripple_enabled;
@@ -87,6 +88,8 @@ private:
void new_action(Sequence* s, int action, int clip, long old_val, long new_val);
void offset_links(QVector<Clip*>& clips, int offset);
bool old_project_changed;
};
class LinkCommand : public QUndoCommand {
+43 -10
View File
@@ -12,6 +12,8 @@
#include <QMimeData>
#include <QHeaderView>
#include <QMenu>
#include <QMessageBox>
#include <QFileInfo>
#include <QDebug>
SourceTable::SourceTable(QWidget* parent) : QTreeWidget(parent) {
@@ -34,24 +36,41 @@ void SourceTable::show_context_menu(const QPoint& pos) {
} else {
if (selectedItems().size() == 1) {
// replace footage
if (get_type_from_tree(selectedItems().at(0)) == MEDIA_TYPE_FOOTAGE) {
menu.addAction("Replace/Relink Media");
int type = get_type_from_tree(selectedItems().at(0));
if (type == MEDIA_TYPE_FOOTAGE) {
QAction* replace_action = menu.addAction("Replace/Relink Media");
connect(replace_action, SIGNAL(triggered(bool)), panel_project, SLOT(replace_selected_file()));
}
menu.addAction("Replace Clips Using This Media");
if (type != MEDIA_TYPE_FOLDER) {
QAction* replace_clip_media = menu.addAction("Replace Clips Using This Media");
connect(replace_clip_media, SIGNAL(triggered(bool)), panel_project, SLOT(replace_clip_media()));
}
}
// duplicate item
bool all_sequences = true;
bool all_footage = true;
for (int i=0;i<selectedItems().size();i++) {
if (get_type_from_tree(selectedItems().at(i)) != MEDIA_TYPE_SEQUENCE) {
all_sequences = false;
break;
all_sequences = false;
}
if (get_type_from_tree(selectedItems().at(i)) != MEDIA_TYPE_FOOTAGE) {
all_footage = false;
}
}
// ONLY sequences are selected
if (all_sequences) {
// ONLY sequences are selected
QAction* duplicate_action = menu.addAction("Duplicate");
connect(duplicate_action, SIGNAL(triggered(bool)), panel_project, SLOT(duplicate_selected()));
}
}
// ONLY footage is selected
if (all_footage) {
QAction* delete_footage_from_sequences = menu.addAction("Delete All Clips Using This Media");
connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), panel_project, SLOT(delete_clips_using_selected_media()));
}
// delete media
QAction* delete_action = menu.addAction("Delete");
@@ -127,22 +146,36 @@ void SourceTable::dragMoveEvent(QDragMoveEvent *event) {
void SourceTable::dropEvent(QDropEvent* event) {
const QMimeData* mimeData = event->mimeData();
QTreeWidgetItem* drop_item = itemAt(event->pos());
if (mimeData->hasUrls()) {
// drag files in from outside
QList<QUrl> urls = mimeData->urls();
if (!urls.isEmpty()) {
QStringList paths;
for (int i=0;i<urls.size();i++) {
qDebug() << urls.at(i).toLocalFile();
for (int i=0;i<urls.size();i++) {
paths.append(urls.at(i).toLocalFile());
}
panel_project->process_file_list(paths);
bool replace = false;
if (urls.size() == 1
&& drop_item != NULL
&& get_type_from_tree(drop_item) == MEDIA_TYPE_FOOTAGE
&& !QFileInfo(paths.at(0)).isDir()
&& QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + drop_item->text(0) + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
replace = true;
panel_project->replace_media(drop_item, paths.at(0));
}
if (!replace) {
QTreeWidgetItem* parent = NULL;
if (drop_item != NULL && get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER) {
parent = drop_item;
}
panel_project->process_file_list(NULL, paths, parent, NULL);
}
}
event->acceptProposedAction();
} else {
// dragging files within project
QVector<QTreeWidgetItem*> move_items;
QTreeWidgetItem* drop_item = itemAt(event->pos());
// if we dragged to the root OR dragged to a folder
if (drop_item == NULL || (drop_item != NULL && get_type_from_tree(drop_item) == MEDIA_TYPE_FOLDER)) {
QList<QTreeWidgetItem*> selected_items = selectedItems();
+48 -42
View File
@@ -79,7 +79,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) {
switch (get_type_from_tree(item)) {
case MEDIA_TYPE_FOOTAGE:
{
Media* m = get_media_from_tree(item);
Media* m = get_footage_from_tree(item);
if (m->ready) {
if (!got_video_values) {
for (int j=0;j<m->video_tracks.size();j++) {
@@ -140,7 +140,7 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) {
switch (item_type) {
case MEDIA_TYPE_FOOTAGE:
m = get_media_from_tree(item);
m = get_footage_from_tree(item);
media = m;
can_import = m->ready;
break;
@@ -815,12 +815,12 @@ void TimelineWidget::update_ghosts(QPoint& mouse_pos) {
validator = g.ghost_length + frame_diff;
if (validator < 1) frame_diff += (1 - validator);
// prevent clip length exceeding media length
if (c->media_type == MEDIA_TYPE_SEQUENCE
// prevent clip length exceeding media length
if (c->media_type == MEDIA_TYPE_SEQUENCE
|| (c->media_type == MEDIA_TYPE_FOOTAGE && !static_cast<Media*>(c->media)->get_stream_from_file_index(c->media_stream)->infinite_length)) {
validator = g.old_clip_in + g.ghost_length + frame_diff;
if (validator > g.media_length) frame_diff -= validator - g.media_length;
}
}
}
// ripple ops
@@ -1433,30 +1433,22 @@ void TimelineWidget::redraw_clips() {
}
if (clip->media_type == MEDIA_TYPE_FOOTAGE) {
bool draw_checkerboard = false;
QRect checkerboard_rect = clip_rect;
Media* m = static_cast<Media*>(clip->media);
MediaStream* ms = m->get_stream_from_file_index(clip->media_stream);
if (ms == NULL) {
// draw "error lines" if media stream is missing
clip_painter.setPen(QPen(QColor(64, 64, 64), 2));
int limit = clip_rect.width();
int clip_height = clip_rect.bottom()-clip_rect.top();
for (int j=-clip_height;j<limit;j+=15) {
int lines_start_x = clip_rect.left()+j;
int lines_start_y = clip_rect.bottom();
int lines_end_x = lines_start_x + clip_height;
int lines_end_y = clip_rect.top();
if (lines_start_x < clip_rect.left()) {
lines_start_y -= (clip_rect.left() - lines_start_x);
lines_start_x = clip_rect.left();
}
if (lines_end_x > clip_rect.right()) {
lines_end_y -= (clip_rect.right() - lines_end_x);
lines_end_x = clip_rect.right();
}
clip_painter.drawLine(lines_start_x, lines_start_y, lines_end_x, lines_end_y);
}
draw_checkerboard = true;
} else if (ms->preview_done) {
// draw thumbnail/waveform
long media_length = m->get_length_in_frames(clip->sequence->frame_rate);
int waveform_limit = qMin(clip_rect.width(), panel_timeline->getScreenPointFromFrame(media_length - clip->clip_in));
if (waveform_limit < clip_rect.width()) {
draw_checkerboard = true;
checkerboard_rect.setLeft(clip_rect.left() + waveform_limit);
}
if (clip->track < 0) {
int thumb_y = clip_painter.fontMetrics().height()+CLIP_TEXT_PADDING+CLIP_TEXT_PADDING;
int thumb_height = clip_rect.height()-thumb_y;
@@ -1465,33 +1457,47 @@ void TimelineWidget::redraw_clips() {
QRect thumb_rect(thumb_x, clip_rect.y()+thumb_y, thumb_width, thumb_height);
clip_painter.drawImage(thumb_rect, ms->video_preview);
}
} else if (clip_rect.height() > TRACK_MIN_HEIGHT) {
} else if (clip_rect.height() > TRACK_MIN_HEIGHT) {
int divider = ms->audio_channels*2;
clip_painter.setPen(QColor(80, 80, 80));
int channel_height = clip_rect.height()/ms->audio_channels;
long media_length = m->get_length_in_frames(clip->sequence->frame_rate);
for (int i=0;i<clip_rect.width();i++) {
for (int i=0;i<waveform_limit;i++) {
int waveform_index = qFloor((((clip->clip_in + ((double) i/panel_timeline->zoom))/media_length) * ms->audio_preview.size())/divider)*divider;
for (int j=0;j<ms->audio_channels;j++) {
int mid = clip_rect.top()+channel_height*j+(channel_height/2);
int offset = waveform_index+(j*2);
for (int j=0;j<ms->audio_channels;j++) {
int mid = clip_rect.top()+channel_height*j+(channel_height/2);
int offset = waveform_index+(j*2);
qint8 min = (double)ms->audio_preview.at(offset) / 128.0 * (channel_height/2);
qint8 max = (double)ms->audio_preview.at(offset+1) / 128.0 * (channel_height/2);
clip_painter.drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max);
/*if (offset >= 0 && offset < ms->audio_preview.size()) {
} else {
QMessageBox::critical(this, "AAAAAAAAAAAAA", "Here's that terrible, no-good bug again!!! I've stepped around it but plz report the following to Matt:\n\n" + QString::number(offset) + " vs " + QString::number(clip->media_stream->audio_preview.size()), QMessageBox::Ok);
break;
}*/
}
}
qint8 min = (double)ms->audio_preview.at(offset) / 128.0 * (channel_height/2);
qint8 max = (double)ms->audio_preview.at(offset+1) / 128.0 * (channel_height/2);
clip_painter.drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max);
}
}
}
}
if (draw_checkerboard) {
// draw "error lines" if media stream is missing
clip_painter.setPen(QPen(QColor(64, 64, 64), 2));
int limit = checkerboard_rect.width();
int clip_height = checkerboard_rect.bottom()-checkerboard_rect.top();
for (int j=-clip_height;j<limit;j+=15) {
int lines_start_x = checkerboard_rect.left()+j;
int lines_start_y = checkerboard_rect.bottom();
int lines_end_x = lines_start_x + clip_height;
int lines_end_y = checkerboard_rect.top();
if (lines_start_x < checkerboard_rect.left()) {
lines_start_y -= (checkerboard_rect.left() - lines_start_x);
lines_start_x = checkerboard_rect.left();
}
if (lines_end_x > checkerboard_rect.right()) {
lines_end_y -= (checkerboard_rect.right() - lines_end_x);
lines_end_x = checkerboard_rect.right();
}
clip_painter.drawLine(lines_start_x, lines_start_y, lines_end_x, lines_end_y);
}
}
}
// top left bevel
+15 -12
View File
@@ -110,19 +110,22 @@ void ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
case MEDIA_TYPE_FOOTAGE:
{
Media* m = static_cast<Media*>(c->media);
if (m->ready
&& m->get_stream_from_file_index(c->media_stream) != 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
if (!c->open) {
open_clip(c, multithreaded);
}
if (m->ready) {
if (m->get_stream_from_file_index(c->media_stream) != 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
if (!c->open) {
open_clip(c, multithreaded);
}
clip_is_active = true;
} else if (c->open) {
close_clip(c);
}
clip_is_active = true;
} else if (c->open) {
close_clip(c);
}
} else {
texture_failed = true;
}
}
break;
case MEDIA_TYPE_SEQUENCE: