From 83e7707025ac63f5285d71b0980e0349c8b44cdd Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 11 May 2022 13:48:59 -0700 Subject: [PATCH] media: implement importing SRT files --- app/codec/ffmpeg/ffmpegdecoder.cpp | 73 +++++++- app/codec/ffmpeg/ffmpegdecoder.h | 4 + app/common/timecodefunctions.cpp | 17 +- .../footageproperties/footageproperties.cpp | 27 ++- app/node/block/subtitle/subtitle.cpp | 6 +- app/node/output/viewer/viewer.cpp | 39 +++++ app/node/output/viewer/viewer.h | 28 ++- app/node/project/footage/footage.cpp | 49 +++++- app/node/project/footage/footage.h | 1 + .../project/footage/footagedescription.cpp | 10 ++ app/node/project/footage/footagedescription.h | 34 +++- app/node/value.cpp | 6 + app/node/value.h | 7 + app/render/opengl/openglrenderer.cpp | 1 + app/render/subtitleparams.cpp | 54 ++++++ app/render/subtitleparams.h | 70 +++++++- .../nodeparamviewwidgetbridge.cpp | 3 + app/widget/timelinewidget/tool/import.cpp | 162 +++++++++++------- app/widget/timelinewidget/tool/import.h | 2 + 19 files changed, 517 insertions(+), 76 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 165807418..caf9d6857 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -46,6 +46,7 @@ extern "C" { #include "common/timecodefunctions.h" #include "render/framehashcache.h" #include "render/diskmanager.h" +#include "render/subtitleparams.h" namespace olive { @@ -415,7 +416,51 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - qDebug() << "Subtitle probing: Stub"; + // Limit to SRT for now... + if (avstream->codecpar->codec_id == AV_CODEC_ID_SUBRIP) { + SubtitleParams sub; + + AVPacket* pkt = av_packet_alloc(); + { + Instance instance; + instance.Open(filename_c, avstream->index); + + //qDebug() << instance.GetSubtitleHeader(); + + AVSubtitle avsub; + while (instance.GetSubtitle(pkt, &avsub) >= 0) { + for (unsigned int j=0; jass; + + int comma = 0; + for (int k=0; kpts, avstream->time_base), + Timecode::timestamp_to_time(pkt->pts + pkt->duration, avstream->time_base)); + + sub.push_back(Subtitle(time, ass)); + } + avsubtitle_free(&avsub); + } + + instance.Close(); + } + av_packet_free(&pkt); + + desc.AddSubtitleStream(sub); + } } @@ -1120,6 +1165,32 @@ int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame) return ret; } +const char *FFmpegDecoder::Instance::GetSubtitleHeader() const +{ + return reinterpret_cast(codec_ctx_->subtitle_header); +} + +int FFmpegDecoder::Instance::GetSubtitle(AVPacket *pkt, AVSubtitle *sub) +{ + int ret; + + do { + av_packet_unref(pkt); + + ret = av_read_frame(fmt_ctx_, pkt); + } while (pkt->stream_index != avstream_->index && ret >= 0); + + if (ret >= 0) { + int got_sub; + ret = avcodec_decode_subtitle2(codec_ctx_, sub, &got_sub, pkt); + if (!got_sub) { + ret = -1; + } + } + + return ret; +} + void FFmpegDecoder::Instance::Seek(int64_t timestamp) { avcodec_flush_buffers(codec_ctx_); diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 612aa8533..13535cf0f 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -91,6 +91,10 @@ private: */ int GetFrame(AVPacket* pkt, AVFrame* frame); + const char *GetSubtitleHeader() const; + + int GetSubtitle(AVPacket* pkt, AVSubtitle* sub); + void Seek(int64_t timestamp); AVFormatContext* fmt_ctx() const diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index 8402666f7..a0f97efb1 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -20,6 +20,10 @@ #include "timecodefunctions.h" +extern "C" { +#include +} + #include #include "config/config.h" @@ -254,7 +258,14 @@ rational Timecode::snap_time_to_timebase(const rational &time, const rational &t rational Timecode::timestamp_to_time(const int64_t ×tamp, const rational &timebase) { - return rational(timestamp) * timebase; + int64_t num = int64_t(timebase.numerator()) * timestamp; + int64_t den = timebase.denominator(); + + int num_r, den_r; + + av_reduce(&num_r, &den_r, num, den, INT_MAX); + + return rational(num_r, den_r); } QString Timecode::time_to_timecode(const rational &time, const rational &timebase, const Timecode::Display &display, bool show_plus_if_positive) @@ -310,7 +321,7 @@ int64_t Timecode::rescale_timestamp(const int64_t &ts, const rational &source, c return ts; } - return qRound64(static_cast(ts) * source.toDouble() / dest.toDouble()); + return av_rescale_q(ts, source.toAVRational(), dest.toAVRational()); } int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts, const rational &source, const rational &dest) @@ -319,7 +330,7 @@ int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts, const rational &sour return ts; } - return qCeil(static_cast(ts) * source.toDouble() / dest.toDouble()); + return av_rescale_q_rnd(ts, source.toAVRational(), dest.toAVRational(), AV_ROUND_UP); } } diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp index 7cb56c63a..d33517766 100644 --- a/app/dialog/footageproperties/footageproperties.cpp +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -93,6 +93,15 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota description = tr("%1 Hz %2 channels").arg(QString::number(ap.sample_rate()), QString::number(ap.channel_count())); break; } + case Track::kSubtitle: + { + SubtitleParams sp = footage_->GetSubtitleParams(reference.index()); + is_enabled = sp.enabled(); + + // FIXME: Language? + description = tr("Subtitles"); + break; + } default: stacked_widget_->addWidget(new StreamProperties()); description = tr("Unknown"); @@ -106,7 +115,8 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota if (first_usable_stream == -1 && (reference.type() == Track::kVideo - || reference.type() == Track::kAudio)) { + || reference.type() == Track::kAudio + || reference.type() == Track::kSubtitle)) { first_usable_stream = i; } } @@ -163,6 +173,8 @@ void FootagePropertiesDialog::accept() old_stream_enabled = footage_->GetAudioParams(reference.index()).enabled(); break; case Track::kSubtitle: + old_stream_enabled = footage_->GetSubtitleParams(reference.index()).enabled(); + break; case Track::kNone: case Track::kCount: break; @@ -218,6 +230,13 @@ void FootagePropertiesDialog::StreamEnableChangeCommand::redo() break; } case Track::kSubtitle: + { + SubtitleParams sp = footage_->GetSubtitleParams(index_); + old_enabled_ = sp.enabled(); + sp.set_enabled(new_enabled_); + footage_->SetSubtitleParams(sp, index_); + break; + } case Track::kNone: case Track::kCount: break; @@ -242,6 +261,12 @@ void FootagePropertiesDialog::StreamEnableChangeCommand::undo() break; } case Track::kSubtitle: + { + SubtitleParams sp = footage_->GetSubtitleParams(index_); + sp.set_enabled(old_enabled_); + footage_->SetSubtitleParams(sp, index_); + break; + } case Track::kNone: case Track::kCount: break; diff --git a/app/node/block/subtitle/subtitle.cpp b/app/node/block/subtitle/subtitle.cpp index d0930adbb..25bccb6f4 100644 --- a/app/node/block/subtitle/subtitle.cpp +++ b/app/node/block/subtitle/subtitle.cpp @@ -43,7 +43,11 @@ SubtitleBlock::SubtitleBlock() QString SubtitleBlock::Name() const { - return tr("Subtitle"); + if (GetText().isEmpty()) { + return tr("Subtitle"); + } else { + return GetText(); + } } QString SubtitleBlock::id() const diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index ff1ef27a9..536d6de39 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -28,6 +28,7 @@ namespace olive { const QString ViewerOutput::kVideoParamsInput = QStringLiteral("video_param_in"); const QString ViewerOutput::kAudioParamsInput = QStringLiteral("audio_param_in"); +const QString ViewerOutput::kSubtitleParamsInput = QStringLiteral("subtitle_param_in"); const QString ViewerOutput::kTextureInput = QStringLiteral("tex_in"); const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); const QString ViewerOutput::kVideoAutoCacheInput = QStringLiteral("video_autocache_in"); @@ -48,6 +49,8 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + AddInput(kSubtitleParamsInput, NodeValue::kSubtitleParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); + if (create_buffer_inputs) { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); @@ -100,6 +103,7 @@ QString ViewerOutput::duration() const // Get first enabled streams VideoParams video = GetFirstEnabledVideoStream(); AudioParams audio = GetFirstEnabledAudioStream(); + SubtitleParams sub = GetFirstEnabledSubtitleStream(); if (video.is_valid() && video.video_type() != VideoParams::kVideoTypeStill) { // Prioritize video @@ -113,6 +117,8 @@ QString ViewerOutput::duration() const } using_timebase = audio.sample_rate_as_time_base(); + } else if (sub.is_valid()) { + using_timebase = OLIVE_CONFIG("DefaultSequenceFrameRate").value(); } if (using_timebase.isNull()) { @@ -151,6 +157,11 @@ bool ViewerOutput::HasEnabledAudioStreams() const return GetFirstEnabledAudioStream().is_valid(); } +bool ViewerOutput::HasEnabledSubtitleStreams() const +{ + return GetFirstEnabledSubtitleStream().is_valid(); +} + VideoParams ViewerOutput::GetFirstEnabledVideoStream() const { int sz = GetVideoStreamCount(); @@ -181,6 +192,21 @@ AudioParams ViewerOutput::GetFirstEnabledAudioStream() const return AudioParams(); } +SubtitleParams ViewerOutput::GetFirstEnabledSubtitleStream() const +{ + int sz = GetSubtitleStreamCount(); + + for (int i=0; i ViewerOutput::GetEnabledStreamsAsReferences() const } } + { + int sp_sz = GetSubtitleStreamCount(); + + for (int i=0; i(); + } else { + return SubtitleParams(); + } + } + void SetVideoParams(const VideoParams &video, int index = 0) { SetStandardValue(kVideoParamsInput, QVariant::fromValue(video), index); @@ -98,6 +109,11 @@ public: SetStandardValue(kAudioParamsInput, QVariant::fromValue(audio), index); } + void SetSubtitleParams(const SubtitleParams &subs, int index = 0) + { + SetStandardValue(kSubtitleParamsInput, QVariant::fromValue(subs), index); + } + int GetVideoStreamCount() const { return InputArraySize(kVideoParamsInput); @@ -108,16 +124,23 @@ public: return InputArraySize(kAudioParamsInput); } + int GetSubtitleStreamCount() const + { + return InputArraySize(kSubtitleParamsInput); + } + int GetTotalStreamCount() const { - return GetVideoStreamCount() + GetAudioStreamCount(); + return GetVideoStreamCount() + GetAudioStreamCount() + GetSubtitleStreamCount(); } bool HasEnabledVideoStreams() const; bool HasEnabledAudioStreams() const; + bool HasEnabledSubtitleStreams() const; VideoParams GetFirstEnabledVideoStream() const; AudioParams GetFirstEnabledAudioStream() const; + SubtitleParams GetFirstEnabledSubtitleStream() const; const rational &GetLength() const { return last_length_; } const rational &GetVideoLength() const { return video_length_; } @@ -191,6 +214,7 @@ public: static const QString kVideoParamsInput; static const QString kAudioParamsInput; + static const QString kSubtitleParamsInput; static const QString kTextureInput; static const QString kSamplesInput; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 55f4043e4..5ce6afab3 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -121,6 +121,10 @@ void Footage::InputValueChangedEvent(const QString &input, int element) AddStream(Track::kAudio, QVariant::fromValue(footage_info.GetAudioStreams().at(i))); } + for (int i=0; i& GetSubtitleStreams() const + { + return subtitle_streams_; + } + private: - static constexpr unsigned kFootageMetaVersion = 1; + static constexpr unsigned kFootageMetaVersion = 2; QString decoder_; @@ -120,6 +146,8 @@ private: QVector audio_streams_; + QVector subtitle_streams_; + }; } diff --git a/app/node/value.cpp b/app/node/value.cpp index 3b51a4a74..984745102 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -29,6 +29,7 @@ #include "common/bezier.h" #include "common/tohex.h" #include "render/audioparams.h" +#include "render/subtitleparams.h" #include "render/videoparams.h" #include "render/color.h" @@ -130,6 +131,7 @@ QByteArray NodeValue::ValueToBytes(NodeValue::Type type, const QVariant &value) return value.value().toBytes(); // These types have no persistent input + case kSubtitleParams: case kNone: case kTexture: case kSamples: @@ -345,6 +347,8 @@ QString NodeValue::GetPrettyDataTypeName(Type type) return QCoreApplication::translate("NodeValue", "Video Parameters"); case kAudioParams: return QCoreApplication::translate("NodeValue", "Audio Parameters"); + case kSubtitleParams: + return QCoreApplication::translate("NodeValue", "Subtitle Parameters"); case kDataTypeCount: break; @@ -394,6 +398,8 @@ QString NodeValue::GetDataTypeName(Type type) return QStringLiteral("vparam"); case kAudioParams: return QStringLiteral("aparam"); + case kSubtitleParams: + return QStringLiteral("sparam"); case kDataTypeCount: break; } diff --git a/app/node/value.h b/app/node/value.h index 67be1425d..3a2309c4f 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -178,6 +178,13 @@ public: */ kAudioParams, + /** + * Subtitle Parameters type + * + * Resolves to `SubtitleParams` + */ + kSubtitleParams, + /** * End of list */ diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index cfdf00eb1..99e80c104 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -518,6 +518,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video case NodeValue::kFile: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kBezier: case NodeValue::kNone: case NodeValue::kDataTypeCount: diff --git a/app/render/subtitleparams.cpp b/app/render/subtitleparams.cpp index 67118bdd3..442fa3994 100644 --- a/app/render/subtitleparams.cpp +++ b/app/render/subtitleparams.cpp @@ -22,6 +22,8 @@ #include +#include "common/xmlutils.h" + namespace olive { QString SubtitleParams::GenerateASSHeader() @@ -106,4 +108,56 @@ QString SubtitleParams::GenerateASSHeader() return ass_code; } +void SubtitleParams::Load(QXmlStreamReader *reader) +{ + this->clear(); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("streamindex")) { + set_stream_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("enabled")) { + set_enabled(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("subtitles")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("subtitle")) { + rational in, out; + QString text; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + text = reader->readElementText(); + + this->push_back(Subtitle(TimeRange(in, out), text)); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void SubtitleParams::Save(QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("streamindex"), QString::number(stream_index_)); + writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_)); + + writer->writeStartElement(QStringLiteral("subtitles")); + for (auto it=this->cbegin(); it!=this->cend(); it++) { + writer->writeStartElement(QStringLiteral("subtitle")); + writer->writeAttribute(QStringLiteral("in"), it->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), it->time().out().toString()); + writer->writeCharacters(it->text()); + writer->writeEndElement(); // subtitle + } + writer->writeEndElement(); // subtitles +} + } diff --git a/app/render/subtitleparams.h b/app/render/subtitleparams.h index 065247e41..2e9d86c8e 100644 --- a/app/render/subtitleparams.h +++ b/app/render/subtitleparams.h @@ -21,16 +21,84 @@ #ifndef SUBTITLEPARAMS_H #define SUBTITLEPARAMS_H +#include #include +#include +#include + +#include "common/timerange.h" namespace olive { -class SubtitleParams { +class Subtitle +{ public: + Subtitle() = default; + + Subtitle(const TimeRange &time, const QString &text) : + range_(time), + text_(text) + { + } + + const TimeRange &time() const { return range_; } + void set_time(const TimeRange &t) { range_ = t; } + + const QString &text() const { return text_; } + void set_text(const QString &t) { text_ = t; } + +private: + TimeRange range_; + + QString text_; + +}; + +class SubtitleParams : public std::vector +{ +public: + SubtitleParams() + { + stream_index_ = 0; + enabled_ = true; + } + static QString GenerateASSHeader(); + void Load(QXmlStreamReader* reader); + + void Save(QXmlStreamWriter* writer) const; + + bool is_valid() const + { + return !this->empty(); + } + + rational duration() const + { + if (this->empty()) { + return 0; + } else { + return back().time().out(); + } + } + + int stream_index() const { return stream_index_; } + void set_stream_index(int i) { stream_index_ = i; } + + bool enabled() const { return enabled_; } + void set_enabled(bool e) { enabled_ = e; } + +private: + int stream_index_; + + bool enabled_; + }; } +Q_DECLARE_METATYPE(olive::Subtitle) +Q_DECLARE_METATYPE(olive::SubtitleParams) + #endif // SUBTITLEPARAMS_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index aff1fb770..9f0ae6ac8 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -89,6 +89,7 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kSamples: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: @@ -240,6 +241,7 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kSamples: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: @@ -417,6 +419,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kSamples: case NodeValue::kVideoParams: case NodeValue::kAudioParams: + case NodeValue::kSubtitleParams: case NodeValue::kDataTypeCount: break; case NodeValue::kInt: diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index a5cda4a32..d17b9a3d8 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -30,6 +30,7 @@ #include "core.h" #include "dialog/sequence/sequence.h" #include "node/audio/volume/volume.h" +#include "node/block/subtitle/subtitle.h" #include "node/distort/transform/transformdistortnode.h" #include "node/generator/matrix/matrix.h" #include "node/math/math/math.h" @@ -154,6 +155,7 @@ void ImportTool::DragLeave(QDragLeaveEvent* event) { if (!dragged_footage_.isEmpty()) { parent()->ClearGhosts(); + parent()->ClearTentativeSubtitleTrack(); dragged_footage_.clear(); event->accept(); @@ -235,25 +237,27 @@ void ImportTool::FootageToGhosts(rational ghost_start, const DraggedFootageData // Create ghosts foreach (const Track::Reference& ref, it->second) { Track::Type track_type = ref.type(); + Track::Reference dest_track(track_type, track_offsets.at(track_type)); - TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); + if (track_type == Track::kVideo || track_type == Track::kAudio) { + auto ghost = CreateGhost(TimeRange(ghost_start, ghost_start + footage_duration), ghost_in, dest_track); - ghost->SetIn(ghost_start); - ghost->SetOut(ghost_start + footage_duration); - ghost->SetMediaIn(ghost_in); - ghost->SetTrack(Track::Reference(track_type, track_offsets.at(track_type))); + // Increment track count for this track type + track_offsets[track_type]++; - snap_points_.push_back(ghost->GetIn()); - snap_points_.push_back(ghost->GetOut()); + TimelineViewGhostItem::AttachedFootage af = {it->first, ref.ToString()}; + ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(af)); + } else if (track_type == Track::kSubtitle) { + SubtitleParams sp = footage->GetSubtitleParams(ref.index()); - // Increment track count for this track type - track_offsets[track_type]++; + for (const Subtitle &sub : sp) { + auto ghost = CreateGhost(sub.time() + ghost_start, 0, dest_track); - TimelineViewGhostItem::AttachedFootage af = {it->first, ref.ToString()}; - ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(af)); - ghost->SetMode(Timeline::kMove); + ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(sub)); + } - parent()->AddGhost(ghost); + parent()->AddTentativeSubtitleTrack(); + } } // Stack each ghost one after the other @@ -276,6 +280,10 @@ void ImportTool::DropGhosts(bool insert) { MultiUndoCommand* command = new MultiUndoCommand(); + if (MultiUndoCommand *c = parent()->TakeSubtitleSectionCommand()) { + command->add_child(c); + } + NodeGraph* dst_graph = nullptr; Sequence* sequence = this->sequence(); bool open_sequence = false; @@ -384,69 +392,83 @@ void ImportTool::DropGhosts(bool insert) for (int i=0;iGetGhostItems().size();i++) { TimelineViewGhostItem* ghost = parent()->GetGhostItems().at(i); + Block* block = nullptr; - TimelineViewGhostItem::AttachedFootage footage_stream = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + Track::Type track_type = ghost->GetAdjustedTrack().type(); + if (track_type == Track::kVideo || track_type == Track::kAudio) { + TimelineViewGhostItem::AttachedFootage footage_stream = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); - ClipBlock* clip = new ClipBlock(); - clip->set_media_in(ghost->GetMediaIn()); - clip->set_length_and_media_out(ghost->GetLength()); - clip->SetLabel(footage_stream.footage->GetLabel()); - command->add_child(new NodeAddCommand(dst_graph, clip)); + ClipBlock* clip = new ClipBlock(); + block = clip; + clip->set_media_in(ghost->GetMediaIn()); + clip->SetLabel(footage_stream.footage->GetLabel()); + command->add_child(new NodeAddCommand(dst_graph, clip)); - // Position clip in its own context - command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); + // Position clip in its own context + command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0))); - int dep_pos = kDefaultDistanceFromOutput; + int dep_pos = kDefaultDistanceFromOutput; - // Position footage in its context - command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0))); + // Position footage in its context + command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0))); - dep_pos++; + dep_pos++; - switch (Track::Reference::TypeFromString(footage_stream.output)) { - case Track::kVideo: - { - TransformDistortNode* transform = new TransformDistortNode(); - command->add_child(new NodeAddCommand(dst_graph, transform)); + switch (Track::Reference::TypeFromString(footage_stream.output)) { + case Track::kVideo: + { + TransformDistortNode* transform = new TransformDistortNode(); + command->add_child(new NodeAddCommand(dst_graph, transform)); - command->add_child(new NodeSetValueHintCommand(transform, TransformDistortNode::kTextureInput, -1, Node::ValueHint({NodeValue::kTexture}, footage_stream.output))); + command->add_child(new NodeSetValueHintCommand(transform, TransformDistortNode::kTextureInput, -1, Node::ValueHint({NodeValue::kTexture}, footage_stream.output))); - command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); - command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); - break; + command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput))); + command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0))); + break; + } + case Track::kAudio: + { + VolumeNode* volume_node = new VolumeNode(); + command->add_child(new NodeAddCommand(dst_graph, volume_node)); + + command->add_child(new NodeSetValueHintCommand(volume_node, VolumeNode::kSamplesInput, -1, Node::ValueHint({NodeValue::kSamples}, footage_stream.output))); + + command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); + command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); + command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); + break; + } + default: + break; + } + + // Link any clips so far that share the same Footage with this one + for (int j=0;jGetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + + if (footage_compare.footage == footage_stream.footage) { + Block::Link(block_items.at(j), clip); + } + } + } else if (track_type == Track::kSubtitle) { + Subtitle src = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + SubtitleBlock *sub = new SubtitleBlock(); + sub->SetText(src.text()); + block = sub; + + command->add_child(new NodeAddCommand(dst_graph, sub)); + command->add_child(new NodeSetPositionCommand(sub, sub, QPointF(0, 0))); } - case Track::kAudio: - { - VolumeNode* volume_node = new VolumeNode(); - command->add_child(new NodeAddCommand(dst_graph, volume_node)); - command->add_child(new NodeSetValueHintCommand(volume_node, VolumeNode::kSamplesInput, -1, Node::ValueHint({NodeValue::kSamples}, footage_stream.output))); - - command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput))); - command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); - command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0))); - break; - } - default: - break; - } + block->set_length_and_media_out(ghost->GetLength()); command->add_child(new TrackPlaceBlockCommand(sequence->track_list(ghost->GetAdjustedTrack().type()), ghost->GetAdjustedTrack().index(), - clip, + block, ghost->GetAdjustedIn())); - block_items.replace(i, clip); - - // Link any clips so far that share the same Footage with this one - for (int j=0;jGetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage).value(); - - if (footage_compare.footage == footage_stream.footage) { - Block::Link(block_items.at(j), clip); - } - } + block_items.replace(i, block); } } @@ -460,4 +482,24 @@ void ImportTool::DropGhosts(bool insert) dragged_footage_.clear(); } +TimelineViewGhostItem* ImportTool::CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track) +{ + TimelineViewGhostItem* ghost = new TimelineViewGhostItem(); + + ghost->SetIn(range.in()); + ghost->SetOut(range.out()); + ghost->SetMediaIn(media_in); + ghost->SetTrack(track); + + snap_points_.push_back(ghost->GetIn()); + snap_points_.push_back(ghost->GetOut()); + + + ghost->SetMode(Timeline::kMove); + + parent()->AddGhost(ghost); + + return ghost; +} + } diff --git a/app/widget/timelinewidget/tool/import.h b/app/widget/timelinewidget/tool/import.h index d2e59a578..2640f2708 100644 --- a/app/widget/timelinewidget/tool/import.h +++ b/app/widget/timelinewidget/tool/import.h @@ -54,6 +54,8 @@ private: void DropGhosts(bool insert); + TimelineViewGhostItem* CreateGhost(const TimeRange &range, const rational &media_in, const Track::Reference &track); + DraggedFootageData dragged_footage_; int import_pre_buffer_;