/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
***/
#include "sequence.h"
#include
#include "timelinefunctions.h"
#include "panels/panels.h"
#include "global/clipboard.h"
#include "global/config.h"
#include "global/debug.h"
Sequence::Sequence() :
playhead(0),
using_workarea(false),
workarea_in(0),
workarea_out(0),
wrapper_sequence(false)
{
// Set up tracks
track_lists_.resize(Track::kTypeCount);
for (int i=0;i(i));
}
}
SequencePtr Sequence::copy() {
SequencePtr s = std::make_shared();
s->name = QCoreApplication::translate("Sequence", "%1 (copy)").arg(name);
s->width = width;
s->height = height;
s->frame_rate = frame_rate;
s->audio_frequency = audio_frequency;
s->audio_layout = audio_layout;
// deep copy all of the sequence's clips
for (int i=0;itrack_lists_[i] = track_lists_.at(i)->copy(s.get());
}
// copy all of the sequence's markers
s->markers = markers;
return s;
}
void Sequence::Save(QXmlStreamWriter &stream)
{
// Provide unique IDs for each Clip
QVector all_clips = GetAllClips();
for (int i=0;iload_id = i;
}
stream.writeStartElement("sequence");
stream.writeAttribute("id", QString::number(save_id));
stream.writeAttribute("name", name);
stream.writeAttribute("width", QString::number(width));
stream.writeAttribute("height", QString::number(height));
stream.writeAttribute("framerate", QString::number(frame_rate, 'f', 10));
stream.writeAttribute("afreq", QString::number(audio_frequency));
stream.writeAttribute("alayout", QString::number(audio_layout));
if (this == Timeline::GetTopSequence().get()) {
stream.writeAttribute("open", "1");
}
stream.writeAttribute("workarea", QString::number(using_workarea));
stream.writeAttribute("workareaIn", QString::number(workarea_in));
stream.writeAttribute("workareaOut", QString::number(workarea_out));
QVector transition_save_cache;
QVector transition_clip_save_cache;
for (int j=0;jSave(stream);
}
for (int j=0;jTrackCount();i++) {
end_frame = qMax(track_list->TrackAt(i)->GetEndFrame(), end_frame);
}
}
return end_frame;
}
QVector Sequence::GetAllClips()
{
QVector all_clips;
for (int j=0;jTrackCount();i++) {
all_clips.append(track_list->TrackAt(i)->GetAllClips());
}
}
return all_clips;
}
TrackList *Sequence::GetTrackList(Track::Type type)
{
return track_lists_.at(type);
}
void Sequence::Close()
{
QVector all_clips = GetAllClips();
for (int i=0;iClose(true);
}
}
void Sequence::RefreshClipsUsingMedia(Media *m) {
QVector all_clips = GetAllClips();
for (int i=0;imedia() == m) {
c->Close(true);
c->refresh();
}
}
}
QVector Sequence::SelectedClips(bool containing)
{
QVector selected_clips;
for (int i=0;iTrackCount();j++) {
Track* t = tl->TrackAt(j);
selected_clips.append(t->GetSelectedClips(containing));
}
}
return selected_clips;
}
void Sequence::AddClipsFromGhosts(ComboAction* ca, const QVector& ghosts)
{
// add clips
long earliest_point = LONG_MAX;
QVector added_clips;
for (int i=0;i(g.track->Sibling(g.track_movement));
c->set_media(g.media, g.media_stream);
c->set_timeline_in(g.in);
c->set_timeline_out(g.out);
c->set_clip_in(g.clip_in);
if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) {
Footage* m = c->media()->to_footage();
if (m->video_tracks.size() == 0) {
// audio only (greenish)
c->set_color(128, 192, 128);
} else if (m->audio_tracks.size() == 0) {
// video only (orangeish)
c->set_color(192, 160, 128);
} else {
// video and audio (blueish)
c->set_color(128, 128, 192);
}
c->set_name(m->name);
} else if (c->media()->get_type() == MEDIA_TYPE_SEQUENCE) {
// sequence (red?ish?)
c->set_color(192, 128, 128);
c->set_name(c->media()->to_sequence()->name);
}
c->refresh();
added_clips.append(c);
}
ca->append(new AddClipCommand(added_clips));
// link clips from the same media
for (int i=0;imedia() == cc->media()) {
c->linked.append(cc.get());
}
}
if (olive::config.add_default_effects_to_clips) {
if (c->type() == Track::kTypeVideo) {
// add default video effects
c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_TRANSFORM, EFFECT_TYPE_EFFECT)));
} else if (c->type() == Track::kTypeAudio) {
// add default audio effects
c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_VOLUME, EFFECT_TYPE_EFFECT)));
c->effects.append(Effect::Create(c.get(), Effect::GetInternalMeta(EFFECT_INTERNAL_PAN, EFFECT_TYPE_EFFECT)));
}
}
}
if (olive::config.enable_seek_to_import) {
panel_sequence_viewer->seek(earliest_point);
}
olive::timeline::snapped = false;
}
void Sequence::MoveClip(Clip *c, ComboAction *ca, long iin, long iout, long iclip_in, Track *itrack, bool verify_transitions, bool relative)
{
ClipPtr clip_ptr = c->track()->GetClipObjectFromRawPtr(c);
ca->append(new MoveClipAction(clip_ptr, iin, iout, iclip_in, itrack, relative));
if (verify_transitions) {
// if this is a shared transition, and the corresponding clip will be moved away somehow
if (c->opening_transition != nullptr
&& c->opening_transition->secondary_clip != nullptr
&& c->opening_transition->secondary_clip->timeline_out() != iin) {
// separate transition
ca->append(new SetPointer(reinterpret_cast(&c->opening_transition->secondary_clip), nullptr));
ca->append(new AddTransitionCommand(nullptr,
c->opening_transition->secondary_clip,
c->opening_transition,
nullptr,
0));
}
if (c->closing_transition != nullptr
&& c->closing_transition->secondary_clip != nullptr
&& c->closing_transition->parent_clip->timeline_in() != iout) {
// separate transition
ca->append(new SetPointer(reinterpret_cast(&c->closing_transition->secondary_clip), nullptr));
ca->append(new AddTransitionCommand(nullptr,
c,
c->closing_transition,
nullptr,
0));
}
}
}
void Sequence::EditToPoint(bool in, bool ripple)
{
QVector all_clips = GetAllClips();
if (all_clips.size() > 0) {
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
for (int i=0;itimeline_out(), sequence_end);
if (c->timeline_in() == playhead)
playhead_falls_on_in = true;
if (c->timeline_out() == playhead)
playhead_falls_on_out = true;
if (c->timeline_in() > playhead)
next_cut = qMin(c->timeline_in(), next_cut);
if (c->timeline_out() > playhead)
next_cut = qMin(c->timeline_out(), next_cut);
if (c->timeline_in() < playhead)
prev_cut = qMax(c->timeline_in(), prev_cut);
if (c->timeline_out() < playhead)
prev_cut = qMax(c->timeline_out(), prev_cut);
}
next_cut = qMin(sequence_end, next_cut);
QVector areas;
ComboAction* ca = new ComboAction();
bool push_undo = true;
long seek = playhead;
if ((in && (playhead_falls_on_out || (playhead_falls_on_in && playhead == 0)))
|| (!in && (playhead_falls_on_in || (playhead_falls_on_out && playhead == sequence_end)))) { // one frame mode
if (ripple) {
// set up deletion areas based on track count
long in_point = playhead;
if (!in) {
in_point--;
seek--;
}
if (in_point >= 0) {
for (int i=0;iTrackCount();j++) {
areas.append(Selection(in_point, in_point+1, tl->TrackAt(j)));
}
}
// trim and move clips around the in point
DeleteAreas(ca, areas, true);
if (ripple) {
Ripple(ca, in_point, -1);
}
} else {
push_undo = false;
}
} else {
push_undo = false;
}
} else {
// set up deletion areas based on track count
long area_in, area_out;
if (in) {
seek = prev_cut;
area_in = prev_cut;
area_out = playhead;
} else {
area_in = playhead;
area_out = next_cut;
}
if (area_in == area_out) {
push_undo = false;
} else {
for (int i=0;iTrackCount();j++) {
areas.append(Selection(area_in, area_out, tl->TrackAt(j)));
}
}
// trim and move clips around the in point
DeleteAreas(ca, areas, true);
if (ripple) {
Ripple(ca, area_in, area_in - area_out);
}
}
}
if (push_undo) {
olive::undo_stack.push(ca);
update_ui(true);
if (seek != playhead && ripple) {
panel_sequence_viewer->seek(seek);
}
} else {
delete ca;
}
} else {
panel_sequence_viewer->seek(0);
}
}
bool Sequence::SnapPoint(long *l, double zoom, bool use_playhead, bool use_markers, bool use_workarea)
{
olive::timeline::snapped = false;
if (olive::timeline::snapping) {
if (use_playhead && !panel_sequence_viewer->playing) {
// snap to playhead
if (olive::timeline::SnapToPoint(playhead, l, zoom)) return true;
}
// snap to marker
if (use_markers) {
for (int i=0;i all_clips = GetAllClips();
for (int i=0;itimeline_in(), l, zoom)) {
return true;
} else if (olive::timeline::SnapToPoint(c->timeline_out(), l, zoom)) {
return true;
} else if (c->opening_transition != nullptr
&& olive::timeline::SnapToPoint(c->timeline_in() + c->opening_transition->get_true_length(), l, zoom)) {
return true;
} else if (c->closing_transition != nullptr
&& olive::timeline::SnapToPoint(c->timeline_out() - c->closing_transition->get_true_length(), l, zoom)) {
return true;
} else {
// try to snap to clip markers
for (int j=0;jget_markers().size();j++) {
if (olive::timeline::SnapToPoint(c->get_markers().at(j).frame + c->timeline_in() - c->clip_in(), l, zoom)) {
return true;
}
}
}
}
}
return false;
}
void Sequence::DeleteInToOut(bool ripple)
{
if (using_workarea) {
QVector areas_to_delete;
for (int i=0;iTrackCount();j++) {
areas_to_delete.append(Selection(workarea_in, workarea_out, tl->TrackAt(j)));
}
}
ComboAction* ca = new ComboAction();
DeleteAreas(ca, areas_to_delete, true);
if (ripple) Ripple(ca,
workarea_in,
workarea_in - workarea_out);
ca->append(new SetTimelineInOutCommand(this, false, 0, 0));
olive::undo_stack.push(ca);
update_ui(true);
}
}
void Sequence::DeleteClipsUsingMedia(const QVector& media)
{
QVector all_clips = GetAllClips();
ComboAction* ca = new ComboAction();
bool deleted = false;
for (int j=0;jmedia() == media.at(j)) {
ca->append(new DeleteClipAction(c));
deleted = true;
}
}
}
if (deleted) {
olive::undo_stack.push(ca);
update_ui(true);
} else {
delete ca;
}
}
void Sequence::Ripple(ComboAction *ca, long point, long length, const QVector &ignore)
{
ca->append(new RippleAction(this, point, length, ignore));
}
void Sequence::ChangeTrackHeightsRelatively(int diff)
{
for (int i=0;iTrackCount();j++) {
Track* t = tl->TrackAt(j);
t->set_height(t->height() + diff);
}
}
}
void Sequence::ToggleLinksOnSelected()
{
QVector selected_clips = SelectedClips();
bool link = true;
QVector link_clips;
for (int i=0;ilinked.size() > 0) {
link = false; // prioritize unlinking
for (int j=0;jlinked.size();j++) { // add links to the command
if (!link_clips.contains(c->linked.at(j))) {
link_clips.append(c->linked.at(j));
}
}
}
}
if (!link_clips.isEmpty()) {
olive::undo_stack.push(new LinkCommand(link_clips, link));
}
}
void Sequence::Split()
{
ComboAction* ca = new ComboAction();
bool split_occurred = false;
// See if there are any selected clips at the current playhead to split
QVector selected_clips = SelectedClips(true);
if (selected_clips.size() > 0) {
// see if whole clips are selected
QVector pre_clips;
QVector post_clips;
for (int i=0;iappend(new AddClipCommand(post_clips));
}
}
// If we weren't able to split any selected clips above, see if there are arbitrary selections to split
if (!split_occurred) {
TrackList* track_list;
Track* track;
foreach (track_list, track_lists_) {
QVector