Merge branch 'master' into multicam

This commit is contained in:
itsmattkc
2022-10-01 13:51:04 -07:00
16 changed files with 230 additions and 45 deletions
+2 -2
View File
@@ -1490,7 +1490,7 @@ bool Core::LabelNodes(const QVector<Node *> &nodes, MultiUndoCommand *parent)
return false;
}
Sequence *Core::CreateNewSequenceForProject(Project* project) const
Sequence *Core::CreateNewSequenceForProject(const QString &format, Project* project)
{
Sequence* new_sequence = new Sequence();
@@ -1498,7 +1498,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const
int sequence_number = 1;
QString sequence_name;
do {
sequence_name = tr("Sequence %1").arg(sequence_number);
sequence_name = format.arg(sequence_number);
sequence_number++;
} while (project->root()->ChildExistsWithName(sequence_name));
new_sequence->SetLabel(sequence_name);
+5 -1
View File
@@ -255,7 +255,11 @@ public:
/**
* @brief Create a new sequence named appropriately for the active project
*/
Sequence* CreateNewSequenceForProject(Project *project) const;
static Sequence* CreateNewSequenceForProject(const QString &format, Project *project);
static Sequence* CreateNewSequenceForProject(Project *project)
{
return CreateNewSequenceForProject(tr("Sequence %1"), project);
}
/**
* @brief Opens a project from the recently opened list
+2 -2
View File
@@ -232,7 +232,7 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range)
{
TimeRange thumb_range = range.Intersected(max_range);
if (GetAdjustedThumbnailRange(&thumb_range)) {
emit connected->thumbnail_cache()->Request(thumb_range);
connected->thumbnail_cache()->Request(thumb_range);
}
}
@@ -296,7 +296,7 @@ void ClipBlock::RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_
}
if (request) {
emit cache->Request(r);
cache->Request(r);
}
}
+27
View File
@@ -403,6 +403,33 @@ Block *Track::NearestBlockAfter(const rational &time) const
return nullptr;
}
bool Track::IsRangeFree(const TimeRange &range) const
{
Block *b = NearestBlockBeforeOrAt(range.in());
if (!b) {
// No block here, assume track is empty here
return true;
}
if (!dynamic_cast<GapBlock*>(b)) {
// There's a block at or around the start point that isn't a gap, range is not free
return false;
}
while ((b = b->next())) {
if (b->in() >= range.out()) {
// This block is after the range, no longer relevant
break;
} else if (!dynamic_cast<GapBlock*>(b)) {
// Found a block in this range, range is not free
return false;
}
}
// If we get here, we couldn't find anything in the way of this range
return true;
}
void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
{
TimeRange limited;
+5
View File
@@ -316,6 +316,11 @@ public:
*/
Block* NearestBlockAfter(const rational& time) const;
/*
* @brief Returns whether a time range is empty or only has a gap
*/
bool IsRangeFree(const TimeRange &range) const;
const QVector<Block *> &Blocks() const
{
return blocks_;
+5 -5
View File
@@ -224,16 +224,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
if (Node *connected = GetConnectedOutput(from, element)) {
if (from == kTextureInput) {
//emit connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
//connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly);
if (autocache_input_video_) {
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()));
emit connected->video_frame_cache()->Request(range.Intersected(max_range));
connected->video_frame_cache()->Request(range.Intersected(max_range));
}
} else if (from == kSamplesInput) {
TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()));
emit connected->waveform_cache()->Request(range.Intersected(max_range));
connected->waveform_cache()->Request(range.Intersected(max_range));
if (autocache_input_audio_) {
emit connected->audio_playback_cache()->Request(range.Intersected(max_range));
connected->audio_playback_cache()->Request(range.Intersected(max_range));
}
}
}
@@ -393,7 +393,7 @@ void ViewerOutput::ConnectedToPreviewEvent()
TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()));
TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range);
for (const TimeRange &r : invalid) {
emit connected->waveform_cache()->Request(r);
connected->waveform_cache()->Request(r);
}
}
}
+5
View File
@@ -98,6 +98,11 @@ public:
timeline_widget()->ShowSpeedDurationDialogForSelectedClips();
}
void NestSelectedClips()
{
timeline_widget()->NestSelectedClips();
}
void InsertFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
+7
View File
@@ -223,6 +223,13 @@ void PlaybackCache::InvalidateAll()
Invalidate(TimeRange(0, RATIONAL_MAX));
}
void PlaybackCache::Request(const TimeRange &r)
{
requested_.insert(r);
emit Requested(r);
}
void PlaybackCache::Validate(const TimeRange &r, bool signal)
{
validated_.insert(r);
+17 -1
View File
@@ -98,15 +98,29 @@ public:
const QVector<Passthrough> &GetPassthroughs() const { return passthroughs_; }
void ClearRequestRange(const olive::TimeRange &r)
{
requested_.remove(r);
}
void ResignalRequests()
{
for (const TimeRange &r : requested_) {
emit Requested(r);
}
}
public slots:
void InvalidateAll();
void Request(const olive::TimeRange &r);
signals:
void Invalidated(const olive::TimeRange& r);
void Validated(const olive::TimeRange& r);
void Request(const olive::TimeRange& r);
void Requested(const olive::TimeRange& r);
void CancelAll();
@@ -124,6 +138,8 @@ protected:
private:
TimeRangeList validated_;
TimeRangeList requested_;
QUuid uuid_;
bool saving_enabled_;
+37 -17
View File
@@ -109,6 +109,8 @@ void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range)
{
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
cache->ClearRequestRange(range);
VideoInvalidatedFromNode(cache, range);
}
@@ -116,6 +118,8 @@ void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range)
{
PlaybackCache *cache = static_cast<PlaybackCache*>(sender());
cache->ClearRequestRange(range);
AudioInvalidatedFromNode(cache, range);
}
@@ -250,7 +254,10 @@ void PreviewAutoCacher::VideoRendered()
void PreviewAutoCacher::ProcessUpdateQueue()
{
// Iterate everything that happened to the graph and do the same thing on our end
foreach (const QueuedJob& job, graph_update_queue_) {
while (!graph_update_queue_.empty()) {
QueuedJob job = graph_update_queue_.front();
graph_update_queue_.pop_front();
switch (job.type) {
case QueuedJob::kNodeAdded:
AddNode(job.node);
@@ -272,7 +279,6 @@ void PreviewAutoCacher::ProcessUpdateQueue()
break;
}
}
graph_update_queue_.clear();
// Indicate that we have synchronized to this point, which is compared with the graph change
// time to see if our copied graph is up to date
@@ -380,22 +386,22 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy)
void PreviewAutoCacher::ConnectToNodeCache(Node *node)
{
connect(node->video_frame_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::VideoInvalidatedFromCache);
connect(node->thumbnail_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::VideoInvalidatedFromCache);
connect(node->audio_playback_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::AudioInvalidatedFromCache);
connect(node->waveform_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::AudioInvalidatedFromCache);
@@ -408,27 +414,32 @@ void PreviewAutoCacher::ConnectToNodeCache(Node *node)
&PlaybackCache::CancelAll,
this,
&PreviewAutoCacher::CancelForCache);
node->video_frame_cache()->ResignalRequests();
node->thumbnail_cache()->ResignalRequests();
node->audio_playback_cache()->ResignalRequests();
node->waveform_cache()->ResignalRequests();
}
void PreviewAutoCacher::DisconnectFromNodeCache(Node *node)
{
disconnect(node->video_frame_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::VideoInvalidatedFromCache);
disconnect(node->thumbnail_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::VideoInvalidatedFromCache);
disconnect(node->audio_playback_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::AudioInvalidatedFromCache);
disconnect(node->waveform_cache(),
&PlaybackCache::Request,
&PlaybackCache::Requested,
this,
&PreviewAutoCacher::AudioInvalidatedFromCache);
@@ -478,6 +489,8 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base();
}
cache->ClearRequestRange(range);
TimeRangeListFrameIterator iterator({range}, using_tb);
pending_video_jobs_.push_back({node, cache, range, iterator});
video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_);
@@ -487,6 +500,9 @@ void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeR
void PreviewAutoCacher::StartCachingAudioRange(PlaybackCache *cache, const TimeRange &range)
{
Node *node = cache->parent();
cache->ClearRequestRange(range);
pending_audio_jobs_.push_back({node, cache, range});
audio_cache_data_[cache].job_tracker.insert(range, graph_changed_time_);
TryRender();
@@ -498,6 +514,8 @@ void PreviewAutoCacher::VideoInvalidatedFromNode(PlaybackCache *cache, const Tim
// want to dedicate all our rendering power to realtime feedback for the user
//CancelVideoTasks(node);
cache->ClearRequestRange(range);
// If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames
if (!NodeInputDragger::IsInputBeingDragged()) {
StartCachingVideoRange(cache, range);
@@ -510,6 +528,8 @@ void PreviewAutoCacher::AudioInvalidatedFromNode(PlaybackCache *cache, const Tim
// cancelled, so some areas may end up unrendered forever
// ClearAudioQueue();
cache->ClearRequestRange(range);
// If we're auto-caching audio or require realtime waveforms, we'll have to render this
StartCachingAudioRange(cache, range);
}
@@ -565,37 +585,37 @@ void PreviewAutoCacher::SetRendersPaused(bool e)
void PreviewAutoCacher::NodeAdded(Node *node)
{
graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
graph_update_queue_.push_back({QueuedJob::kNodeAdded, node, NodeInput(), nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::NodeRemoved(Node *node)
{
graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
graph_update_queue_.push_back({QueuedJob::kNodeRemoved, node, NodeInput(), nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::EdgeAdded(Node *output, const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output});
graph_update_queue_.push_back({QueuedJob::kEdgeAdded, nullptr, input, output});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::EdgeRemoved(Node *output, const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output});
graph_update_queue_.push_back({QueuedJob::kEdgeRemoved, nullptr, input, output});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, nullptr});
graph_update_queue_.push_back({QueuedJob::kValueChanged, nullptr, input, nullptr});
UpdateGraphChangeValue();
}
void PreviewAutoCacher::ValueHintChanged(const NodeInput &input)
{
graph_update_queue_.append({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
graph_update_queue_.push_back({QueuedJob::kValueHintChanged, nullptr, input, nullptr});
UpdateGraphChangeValue();
}
@@ -603,7 +623,7 @@ void PreviewAutoCacher::TryRender()
{
delayed_requeue_timer_.stop();
if (!graph_update_queue_.isEmpty()) {
if (!graph_update_queue_.empty()) {
// Check if we have jobs running in other threads that shouldn't be interrupted right now
// NOTE: We don't check for downloads because, while they run in another thread, they don't
// require any access to the graph and therefore don't risk race conditions.
+1 -1
View File
@@ -165,7 +165,7 @@ private:
Project copied_project_;
QVector<QueuedJob> graph_update_queue_;
std::list<QueuedJob> graph_update_queue_;
QHash<Node*, Node*> copy_map_;
QHash<NodeGraph*, NodeGraph*> graph_map_;
ViewerOutput* copied_viewer_node_;
+1 -1
View File
@@ -293,7 +293,7 @@ void MenuShared::EnableDisableTriggered()
void MenuShared::NestTriggered()
{
qDebug() << "FIXME: Stub";
PanelManager::instance()->MostRecentlyFocused<TimelinePanel>()->NestSelectedClips();
}
void MenuShared::DefaultTransitionTriggered()
+98 -4
View File
@@ -539,12 +539,16 @@ void TimelineWidget::DecreaseTrackHeight()
void TimelineWidget::InsertFootageAtPlayhead(const QVector<ViewerOutput*>& footage)
{
import_tool_->PlaceAt(footage, GetTime(), true);
auto command = new MultiUndoCommand();
import_tool_->PlaceAt(footage, GetTime(), true, command);
Core::instance()->undo_stack()->push(command);
}
void TimelineWidget::OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage)
{
import_tool_->PlaceAt(footage, GetTime(), false);
auto command = new MultiUndoCommand();
import_tool_->PlaceAt(footage, GetTime(), false, command);
Core::instance()->undo_stack()->push(command);
}
void TimelineWidget::ToggleLinksOnSelected()
@@ -788,13 +792,14 @@ void TimelineWidget::RecordingCallback(const QString &filename, const TimeRange
task.Start();
MultiUndoCommand *import_command = task.GetCommand();
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
if (task.GetImportedFootage().empty()) {
qCritical() << "Failed to import recorded audio file" << filename;
} else {
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, track.index());
import_tool_->PlaceAt({task.GetImportedFootage().front()}, time.in(), false, import_command, track.index());
}
Core::instance()->undo_stack()->pushIfHasChildren(import_command);
}
void TimelineWidget::EnableRecordingOverlay(const TimelineCoordinate &coord)
@@ -839,6 +844,95 @@ void TimelineWidget::AddTentativeSubtitleTrack()
}
}
void TimelineWidget::NestSelectedClips()
{
if (!GetConnectedNode()) {
return;
}
QVector<Block*> blocks = this->selected_blocks_;
if (blocks.empty()) {
return;
}
QVector<Track::Reference> tracks(blocks.size());
QVector<TimeRange> times(blocks.size());
QVector<int> track_offset(Track::kCount, INT_MAX);
rational start_time = RATIONAL_MAX;
rational end_time = RATIONAL_MIN;
for (int i=0; i<blocks.size(); i++) {
Block *b = blocks.at(i);
Track::Reference tf = b->track()->ToReference();;
tracks[i] = tf;
times[i] = b->range();
int &to = track_offset[tf.type()];
to = std::min(to, tf.index());
start_time = std::min(start_time, b->in());
end_time = std::max(end_time, b->out());
}
auto move_to_nest_command = new MultiUndoCommand();
// Remove blocks from this sequence
ReplaceBlocksWithGaps(blocks, false, move_to_nest_command);
// Create new sequence
Project *project = this->GetConnectedNode()->project();
Sequence *nest = Core::CreateNewSequenceForProject(tr("Nested Sequence %1"), project);
nest->SetVideoParams(GetConnectedNode()->GetVideoParams());
nest->SetAudioParams(GetConnectedNode()->GetAudioParams());
move_to_nest_command->add_child(new NodeAddCommand(project, nest));
// Add to same folder
move_to_nest_command->add_child(new FolderAddChild(this->GetConnectedNode()->folder(), nest));
// Place blocks in new sequence
for (int i=0; i<blocks.size(); i++) {
Block *b = blocks.at(i);
const TimeRange &range = times.at(i);
Track::Reference track = tracks.at(i);
move_to_nest_command->add_child(new TrackPlaceBlockCommand(nest->track_list(track.type()),
track.index() - track_offset.at(track.type()),
b, range.in() - start_time));
}
// Do this command now, because we later do checks and actions that rely on these having been done
move_to_nest_command->redo_now();
auto meta_command = new MultiUndoCommand();
meta_command->add_child(move_to_nest_command);
// Find first free track index
bool empty = false;
int index = -1;
while (!empty) {
index++;
empty = true;
for (int i=0; i<Track::kCount; i++) {
if (track_offset.at(i) == INT_MAX) {
// No clips on this track
continue;
}
TrackList *list = sequence()->track_list(static_cast<Track::Type>(i));
if (index < list->GetTrackCount() && !list->GetTrackAt(index)->IsRangeFree(TimeRange(start_time, end_time))) {
empty = false;
break;
}
}
}
// Place new sequence in this sequence
import_tool_->PlaceAt({nest}, start_time, false, meta_command, index);
Core::instance()->undo_stack()->push(meta_command);
}
void TimelineWidget::ClearTentativeSubtitleTrack()
{
if (subtitle_show_command_) {
@@ -112,6 +112,8 @@ public:
void AddTentativeSubtitleTrack();
void NestSelectedClips();
/**
* @brief Timelines should always be connected to sequences
*/
+13 -8
View File
@@ -183,7 +183,9 @@ void ImportTool::DragLeave(QDragLeaveEvent* event)
void ImportTool::DragDrop(TimelineViewMouseEvent *event)
{
if (!dragged_footage_.isEmpty()) {
DropGhosts(event->GetModifiers() & Qt::ControlModifier);
auto command = new MultiUndoCommand();
DropGhosts(event->GetModifiers() & Qt::ControlModifier, command);
Core::instance()->undo_stack()->pushIfHasChildren(command);
event->accept();
} else {
@@ -191,7 +193,7 @@ void ImportTool::DragDrop(TimelineViewMouseEvent *event)
}
}
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, int track_offset)
void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset)
{
DraggedFootageData refs;
@@ -199,10 +201,10 @@ void ImportTool::PlaceAt(const QVector<ViewerOutput *> &footage, const rational
refs.append({f, f->GetEnabledStreamsAsReferences()});
}
PlaceAt(refs, start, insert, track_offset);
PlaceAt(refs, start, insert, command, track_offset);
}
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, int track_offset)
void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &start, bool insert, MultiUndoCommand *command, int track_offset)
{
dragged_footage_ = footage;
@@ -211,7 +213,7 @@ void ImportTool::PlaceAt(const DraggedFootageData &footage, const rational &star
}
PrepGhosts(start, track_offset);
DropGhosts(insert);
DropGhosts(insert, command);
}
void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData &sorted, const rational& dest_tb, const int& track_start)
@@ -292,9 +294,9 @@ void ImportTool::PrepGhosts(const rational& frame, const int& track_index)
}
}
void ImportTool::DropGhosts(bool insert)
void ImportTool::DropGhosts(bool insert, MultiUndoCommand *parent_command)
{
MultiUndoCommand* command = new MultiUndoCommand();
auto command = new MultiUndoCommand();
if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) {
command->add_child(c);
@@ -500,7 +502,10 @@ void ImportTool::DropGhosts(bool insert)
command->add_child(new OpenSequenceCommand(sequence));
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
// Do command now because RequestInvalidatedFromConnected relies on track type, which will be
// "none" before this command is done because it won't be connected to any track
command->redo_now();
parent_command->add_child(command);
while (!imported_clips.empty()) {
imported_clips.front()->RequestInvalidatedFromConnected();
+3 -3
View File
@@ -37,8 +37,8 @@ public:
using DraggedFootageData = QVector<QPair<ViewerOutput*, QVector<Track::Reference> > >;
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, int track_offset = 0);
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, int track_offset = 0);
void PlaceAt(const QVector<ViewerOutput *> &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0);
void PlaceAt(const DraggedFootageData &footage, const rational& start, bool insert, MultiUndoCommand *command, int track_offset = 0);
enum DropWithoutSequenceBehavior {
kDWSAsk,
@@ -52,7 +52,7 @@ private:
void PrepGhosts(const rational &frame, const int &track_index);
void DropGhosts(bool insert);
void DropGhosts(bool insert, MultiUndoCommand *parent_command);
TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track);