importtool: create a sequence automatically from dropped footage if one isn't

open already
This commit is contained in:
itsmattkc
2020-02-21 17:28:17 +11:00
parent 042c4616c7
commit bac33e3d16
7 changed files with 306 additions and 184 deletions
+45 -27
View File
@@ -44,8 +44,6 @@
#include "project/projectimportmanager.h"
#include "project/projectloadmanager.h"
#include "project/projectsavemanager.h"
#include "project/item/footage/footage.h"
#include "project/item/sequence/sequence.h"
#include "render/backend/indexmanager.h"
#include "render/backend/opengl/opengltexturecache.h"
#include "render/colormanager.h"
@@ -312,34 +310,19 @@ void Core::CreateNewFolder()
void Core::CreateNewSequence()
{
// Locate the most recently focused Project panel (assume that's the panel the user wants to import into)
ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused<ProjectPanel>();
Project* active_project;
Project* active_project = GetActiveProject();
if (active_project_panel == nullptr // Check that we found a Project panel
|| (active_project = active_project_panel->project()) == nullptr) { // and that we could find an active Project
if (!active_project) {
QMessageBox::critical(main_window_, tr("Failed to create new sequence"), tr("Failed to find active Project panel"));
return;
}
// Get the selected folder in this panel
Folder* folder = active_project_panel->GetSelectedFolder();
// Create new sequence
SequencePtr new_sequence = std::make_shared<Sequence>();
SequencePtr new_sequence = CreateNewSequenceForProject(active_project);
// Set all defaults for the sequence
new_sequence->set_default_parameters();
// Get default name for this sequence (in the format "Sequence N", the first that doesn't exist)
int sequence_number = 1;
QString sequence_name;
do {
sequence_name = tr("Sequence %1").arg(sequence_number);
sequence_number++;
} while (active_project->root()->ChildExistsWithName(sequence_name));
new_sequence->set_name(sequence_name);
SequenceDialog sd(new_sequence.get(), SequenceDialog::kNew, main_window_);
// Make sure SequenceDialog doesn't make an undo command for editing the sequence, since we make an undo command for
@@ -348,8 +331,8 @@ void Core::CreateNewSequence()
if (sd.exec() == QDialog::Accepted) {
// Create an undoable command
ProjectViewModel::AddItemCommand* aic = new ProjectViewModel::AddItemCommand(active_project_panel->model(),
folder,
ProjectViewModel::AddItemCommand* aic = new ProjectViewModel::AddItemCommand(GetActiveProjectModel(),
GetSelectedFolderInActiveProject(),
new_sequence);
new_sequence->add_default_nodes();
@@ -452,16 +435,35 @@ void Core::SaveAutorecovery()
Project *Core::GetActiveProject()
{
// Locate the most recently focused Project panel (assume that's the panel the user wants to import into)
ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused<ProjectPanel>();
// If we couldn't find one, return nullptr
if (active_project_panel == nullptr) {
if (active_project_panel) {
return active_project_panel->project();
} else {
return nullptr;
}
}
// Otherwise, return the project panel's project (which may be nullptr but in most cases shouldn't be)
return active_project_panel->project();
ProjectViewModel *Core::GetActiveProjectModel()
{
ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused<ProjectPanel>();
if (active_project_panel) {
return active_project_panel->model();
} else {
return nullptr;
}
}
Folder *Core::GetSelectedFolderInActiveProject()
{
ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused<ProjectPanel>();
if (active_project_panel) {
return active_project_panel->GetSelectedFolder();
} else {
return nullptr;
}
}
void Core::SetProjectModified()
@@ -643,6 +645,22 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref
Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value;
}
SequencePtr Core::CreateNewSequenceForProject(Project* project) const
{
SequencePtr new_sequence = std::make_shared<Sequence>();
// Get default name for this sequence (in the format "Sequence N", the first that doesn't exist)
int sequence_number = 1;
QString sequence_name;
do {
sequence_name = tr("Sequence %1").arg(sequence_number);
sequence_number++;
} while (project->root()->ChildExistsWithName(sequence_name));
new_sequence->set_name(sequence_name);
return new_sequence;
}
void Core::OpenProject()
{
QString file = QFileDialog::getOpenFileName(main_window_,
+8
View File
@@ -27,6 +27,7 @@
#include "common/rational.h"
#include "project/item/footage/footage.h"
#include "project/item/sequence/sequence.h"
#include "project/project.h"
#include "project/projectviewmodel.h"
#include "task/task.h"
@@ -121,6 +122,8 @@ public:
* The active Project file, or nullptr if the heuristic couldn't find one.
*/
Project* GetActiveProject();
ProjectViewModel* GetActiveProjectModel();
Folder* GetSelectedFolderInActiveProject();
/**
* @brief Sets state to "modified" so that the GUI will prompt the user to save before closing
@@ -174,6 +177,11 @@ public:
static QVariant GetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference);
static void SetPreferenceForRenderMode(RenderMode::Mode mode, const QString& preference, const QVariant& value);
/**
* @brief Create a new sequence named appropriately for the active project
*/
SequencePtr CreateNewSequenceForProject(Project *project) const;
public slots:
/**
* @brief Starts an open file dialog to load a project from file
+5
View File
@@ -265,6 +265,11 @@ void Sequence::set_default_parameters()
Config::Current()["DefaultSequenceAudioLayout"].toULongLong()));
}
ViewerOutput *Sequence::viewer_output() const
{
return viewer_output_;
}
void Sequence::NameChangedEvent(const QString &name)
{
viewer_output_->set_media_name(name);
+2
View File
@@ -71,6 +71,8 @@ public:
void set_default_parameters();
ViewerOutput* viewer_output() const;
protected:
virtual void NameChangedEvent(const QString& name) override;
+4 -12
View File
@@ -616,30 +616,22 @@ void TimelineWidget::ViewMouseDoubleClicked(TimelineViewMouseEvent *event)
void TimelineWidget::ViewDragEntered(TimelineViewMouseEvent *event)
{
if (GetConnectedNode()) {
import_tool_->DragEnter(event);
}
import_tool_->DragEnter(event);
}
void TimelineWidget::ViewDragMoved(TimelineViewMouseEvent *event)
{
if (GetConnectedNode()) {
import_tool_->DragMove(event);
}
import_tool_->DragMove(event);
}
void TimelineWidget::ViewDragLeft(QDragLeaveEvent *event)
{
if (GetConnectedNode()) {
import_tool_->DragLeave(event);
}
import_tool_->DragLeave(event);
}
void TimelineWidget::ViewDragDropped(TimelineViewMouseEvent *event)
{
if (GetConnectedNode()) {
import_tool_->DragDrop(event);
}
import_tool_->DragDrop(event);
}
void TimelineWidget::AddBlock(Block *block, TrackReference track)
@@ -202,7 +202,12 @@ private:
virtual void DragDrop(TimelineViewMouseEvent *event) override;
private:
void FootageToGhosts(rational ghost_start, const QList<Footage*>& footage, const rational &dest_tb, const int &track_start);
QList<Footage*> dragged_footage_;
int import_pre_buffer_;
};
class EditTool : public Tool
+237 -145
View File
@@ -30,6 +30,7 @@
#include "node/distort/transform/transform.h"
#include "node/input/media/audio/audio.h"
#include "node/input/media/video/video.h"
#include "project/item/sequence/sequence.h"
#include "widget/nodeview/nodeviewundo.h"
Timeline::TrackType TrackTypeFromStreamType(Stream::Type stream_type)
@@ -78,10 +79,6 @@ void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event)
// Set drag start position
drag_start_ = event->GetCoordinates();
// Set ghosts to start where the cursor entered
rational ghost_start = drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_);
snap_points_.clear();
while (!stream.atEnd()) {
@@ -92,62 +89,20 @@ void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event)
// Check if Item is Footage
if (item->type() == Item::kFootage) {
// If the Item is Footage, we can create a Ghost from it
Footage* footage = static_cast<Footage*>(item);
dragged_footage_.append(static_cast<Footage*>(item));
// Each stream is offset by one track per track "type", we keep track of them in this vector
QVector<int> track_offsets(Timeline::kTrackTypeCount);
track_offsets.fill(drag_start_.GetTrack().index());
rational footage_duration;
// Loop through all streams in footage
foreach (StreamPtr stream, footage->streams()) {
Timeline::TrackType track_type = TrackTypeFromStreamType(stream->type());
// Check if this stream has a compatible TrackList
if (track_type == Timeline::kTrackTypeNone || !stream->enabled()) {
continue;
}
TimelineViewGhostItem* ghost = new TimelineViewGhostItem();
if (stream->type() == Stream::kImage) {
// Stream is essentially length-less - use config's default image length
footage_duration = Config::Current()["DefaultStillLength"].value<rational>();
} else {
// Use duration from file
int64_t stream_duration = stream->duration();
// Rescale to timeline timebase
stream_duration = qCeil(static_cast<double>(stream_duration) * stream->timebase().toDouble() / parent()->timebase_dbl());
// Convert to rational time
footage_duration = rational(parent()->timebase().numerator() * stream_duration,
parent()->timebase().denominator());
}
ghost->SetIn(ghost_start);
ghost->SetOut(ghost_start + footage_duration);
ghost->SetTrack(TrackReference(track_type, track_offsets.at(track_type)));
// Increment track count for this track type
track_offsets[track_type]++;
snap_points_.append(ghost->In());
snap_points_.append(ghost->Out());
ghost->setData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(stream));
ghost->SetMode(Timeline::kMove);
parent()->AddGhost(ghost);
}
// Stack each ghost one after the other
ghost_start += footage_duration;
}
}
if (parent()->GetConnectedNode()) {
FootageToGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_),
dragged_footage_,
parent()->timebase(),
drag_start_.GetTrack().index());
}
event->accept();
} else {
// FIXME: Implement dropping from file
@@ -157,45 +112,48 @@ void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event)
void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
{
if (parent()->HasGhosts()) {
rational time_movement = event->GetFrame() - drag_start_.GetFrame();
int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index();
if (!dragged_footage_.isEmpty()) {
// If snapping is enabled, check for snap points
if (Core::instance()->snapping()) {
SnapPoint(snap_points_, &time_movement);
if (parent()->HasGhosts()) {
rational time_movement = event->GetFrame() - drag_start_.GetFrame();
int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index();
// If snapping is enabled, check for snap points
if (Core::instance()->snapping()) {
SnapPoint(snap_points_, &time_movement);
}
time_movement = ValidateFrameMovement(time_movement, parent()->ghost_items_);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
rational earliest_ghost = RATIONAL_MAX;
// Move ghosts to the mouse cursor
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
ghost->SetInAdjustment(time_movement);
ghost->SetOutAdjustment(time_movement);
ghost->SetTrackAdjustment(track_movement);
TrackReference adjusted_track = ghost->GetAdjustedTrack();
ghost->SetYCoords(parent()->GetTrackY(adjusted_track), parent()->GetTrackHeight(adjusted_track));
earliest_ghost = qMin(earliest_ghost, ghost->GetAdjustedIn());
}
// Generate tooltip (showing earliest in point of imported clip)
int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
Timecode::CurrentDisplay());
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
// of the cursor)
QToolTip::hideText();
QToolTip::showText(QCursor::pos(),
tooltip_text,
parent());
}
time_movement = ValidateFrameMovement(time_movement, parent()->ghost_items_);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
rational earliest_ghost = RATIONAL_MAX;
// Move ghosts to the mouse cursor
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
ghost->SetInAdjustment(time_movement);
ghost->SetOutAdjustment(time_movement);
ghost->SetTrackAdjustment(track_movement);
TrackReference adjusted_track = ghost->GetAdjustedTrack();
ghost->SetYCoords(parent()->GetTrackY(adjusted_track), parent()->GetTrackHeight(adjusted_track));
earliest_ghost = qMin(earliest_ghost, ghost->GetAdjustedIn());
}
// Generate tooltip (showing earliest in point of imported clip)
int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
Timecode::CurrentDisplay());
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
// of the cursor)
QToolTip::hideText();
QToolTip::showText(QCursor::pos(),
tooltip_text,
parent());
event->accept();
} else {
event->ignore();
@@ -204,8 +162,9 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
void TimelineWidget::ImportTool::DragLeave(QDragLeaveEvent* event)
{
if (parent()->HasGhosts()) {
if (!dragged_footage_.isEmpty()) {
parent()->ClearGhosts();
dragged_footage_.clear();
event->accept();
} else {
@@ -215,75 +174,144 @@ void TimelineWidget::ImportTool::DragLeave(QDragLeaveEvent* event)
void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event)
{
if (parent()->HasGhosts()) {
if (!dragged_footage_.isEmpty()) {
QUndoCommand* command = new QUndoCommand();
NodeGraph* dst_graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
QVector<Block*> block_items(parent()->ghost_items_.size());
NodeGraph* dst_graph;
ViewerOutput* viewer_node;
Sequence* open_sequence = nullptr;
for (int i=0;i<parent()->ghost_items_.size();i++) {
TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
if (parent()->GetConnectedNode()) {
viewer_node = parent()->GetConnectedNode();
dst_graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
} else {
Project* active_project = Core::instance()->GetActiveProject();
StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
if (active_project) {
SequencePtr new_sequence = Core::instance()->CreateNewSequenceForProject(active_project);
ClipBlock* clip = new ClipBlock();
clip->set_length_and_media_out(ghost->Length());
clip->set_block_name(footage_stream->footage()->name());
new NodeAddCommand(dst_graph, clip, command);
new_sequence->set_default_parameters();
switch (footage_stream->type()) {
case Stream::kVideo:
case Stream::kImage:
{
VideoInput* video_input = new VideoInput();
video_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, video_input, command);
new NodeEdgeAddCommand(video_input->output(), clip->texture_input(), command);
bool found_video_params = false;
bool found_audio_params = false;
TransformDistort* transform = new TransformDistort();
new NodeAddCommand(dst_graph, transform, command);
new NodeEdgeAddCommand(transform->output(), video_input->matrix_input(), command);
foreach (Footage* f, dragged_footage_) {
foreach (StreamPtr s, f->streams()) {
if (!found_video_params && s->type() == Stream::kVideo) {
//OpacityNode* opacity = new OpacityNode();
//NodeParam::ConnectEdge(opacity->texture_output(), clip->texture_input());
//NodeParam::ConnectEdge(media->texture_output(), opacity->texture_input());
break;
}
case Stream::kAudio:
{
AudioInput* audio_input = new AudioInput();
audio_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, audio_input, command);
VideoStream* vs = static_cast<VideoStream*>(s.get());
VolumeNode* volume_node = new VolumeNode();
new NodeAddCommand(dst_graph, volume_node, command);
if (vs->frame_rate() != 0) {
new_sequence->set_video_params(VideoParams(vs->width(), vs->height(), vs->frame_rate().flipped()));
found_video_params = true;
}
new NodeEdgeAddCommand(audio_input->output(), volume_node->samples_input(), command);
new NodeEdgeAddCommand(volume_node->output(), clip->texture_input(), command);
break;
}
default:
break;
}
} else if (!found_audio_params && s->type() == Stream::kAudio) {
if (event->GetModifiers() & Qt::ControlModifier) {
//emit parent()->RequestInsertBlockAtTime(clip, ghost->GetAdjustedIn());
AudioStream* as = static_cast<AudioStream*>(s.get());
new_sequence->set_audio_params(AudioParams(as->sample_rate(), as->channel_layout()));
found_audio_params = true;
}
if (found_video_params && found_audio_params) {
break;
}
}
if (found_video_params && found_audio_params) {
break;
}
}
new_sequence->add_default_nodes();
new ProjectViewModel::AddItemCommand(Core::instance()->GetActiveProjectModel(),
Core::instance()->GetSelectedFolderInActiveProject(),
new_sequence,
command);
FootageToGhosts(0, dragged_footage_, new_sequence->video_params().time_base(), 0);
dst_graph = new_sequence.get();
viewer_node = new_sequence->viewer_output();
// Set this as the sequence to open
open_sequence = new_sequence.get();
} else {
new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(ghost->GetAdjustedTrack().type()),
ghost->GetAdjustedTrack().index(),
clip,
ghost->GetAdjustedIn(),
command);
dst_graph = nullptr;
}
}
block_items.replace(i, clip);
if (dst_graph) {
// Link any clips so far that share the same Footage with this one
for (int j=0;j<i;j++) {
StreamPtr footage_compare = parent()->ghost_items_.at(j)->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
QVector<Block*> block_items(parent()->ghost_items_.size());
if (footage_compare->footage() == footage_stream->footage()) {
Block::Link(block_items.at(j), clip);
for (int i=0;i<parent()->ghost_items_.size();i++) {
TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
ClipBlock* clip = new ClipBlock();
clip->set_length_and_media_out(ghost->Length());
clip->set_block_name(footage_stream->footage()->name());
new NodeAddCommand(dst_graph, clip, command);
switch (footage_stream->type()) {
case Stream::kVideo:
case Stream::kImage:
{
VideoInput* video_input = new VideoInput();
video_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, video_input, command);
new NodeEdgeAddCommand(video_input->output(), clip->texture_input(), command);
TransformDistort* transform = new TransformDistort();
new NodeAddCommand(dst_graph, transform, command);
new NodeEdgeAddCommand(transform->output(), video_input->matrix_input(), command);
//OpacityNode* opacity = new OpacityNode();
//NodeParam::ConnectEdge(opacity->texture_output(), clip->texture_input());
//NodeParam::ConnectEdge(media->texture_output(), opacity->texture_input());
break;
}
case Stream::kAudio:
{
AudioInput* audio_input = new AudioInput();
audio_input->SetFootage(footage_stream);
new NodeAddCommand(dst_graph, audio_input, command);
VolumeNode* volume_node = new VolumeNode();
new NodeAddCommand(dst_graph, volume_node, command);
new NodeEdgeAddCommand(audio_input->output(), volume_node->samples_input(), command);
new NodeEdgeAddCommand(volume_node->output(), clip->texture_input(), command);
break;
}
default:
break;
}
if (event->GetModifiers() & Qt::ControlModifier) {
//emit parent()->RequestInsertBlockAtTime(clip, ghost->GetAdjustedIn());
} else {
new TrackPlaceBlockCommand(viewer_node->track_list(ghost->GetAdjustedTrack().type()),
ghost->GetAdjustedTrack().index(),
clip,
ghost->GetAdjustedIn(),
command);
}
block_items.replace(i, clip);
// Link any clips so far that share the same Footage with this one
for (int j=0;j<i;j++) {
StreamPtr footage_compare = parent()->ghost_items_.at(j)->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
if (footage_compare->footage() == footage_stream->footage()) {
Block::Link(block_items.at(j), clip);
}
}
}
}
@@ -291,9 +319,73 @@ void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event)
Core::instance()->undo_stack()->pushIfHasChildren(command);
parent()->ClearGhosts();
dragged_footage_.clear();
if (open_sequence) {
Sequence::Open(open_sequence);
}
event->accept();
} else {
event->ignore();
}
}
void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QList<Footage *> &footage_list, const rational& dest_tb, const int& track_start)
{
foreach (Footage* footage, footage_list) {
// Each stream is offset by one track per track "type", we keep track of them in this vector
QVector<int> track_offsets(Timeline::kTrackTypeCount);
track_offsets.fill(track_start);
rational footage_duration;
// Loop through all streams in footage
foreach (StreamPtr stream, footage->streams()) {
Timeline::TrackType track_type = TrackTypeFromStreamType(stream->type());
// Check if this stream has a compatible TrackList
if (track_type == Timeline::kTrackTypeNone || !stream->enabled()) {
continue;
}
TimelineViewGhostItem* ghost = new TimelineViewGhostItem();
if (stream->type() == Stream::kImage) {
// Stream is essentially length-less - use config's default image length
footage_duration = Config::Current()["DefaultStillLength"].value<rational>();
} else {
// Use duration from file
int64_t stream_duration = stream->duration();
// Rescale to timeline timebase
stream_duration = qCeil(static_cast<double>(stream_duration) * stream->timebase().toDouble() / dest_tb.toDouble());
// Convert to rational time
footage_duration = rational(dest_tb.numerator() * stream_duration,
dest_tb.denominator());
}
ghost->SetIn(ghost_start);
ghost->SetOut(ghost_start + footage_duration);
ghost->SetTrack(TrackReference(track_type, track_offsets.at(track_type)));
// Increment track count for this track type
track_offsets[track_type]++;
snap_points_.append(ghost->In());
snap_points_.append(ghost->Out());
ghost->setData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(stream));
ghost->SetMode(Timeline::kMove);
parent()->AddGhost(ghost);
}
// Stack each ghost one after the other
ghost_start += footage_duration;
}
}