From 12f9b1acfc614e9ddeb27ced0c2c05c245b780e9 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 23 Jul 2022 15:22:57 -0700 Subject: [PATCH] export: allow exporting subtitles to sidecar files --- app/codec/encoder.cpp | 31 ++++++++++++- app/codec/encoder.h | 15 ++++++ app/dialog/export/export.cpp | 40 +++++++++++++--- app/dialog/export/export.h | 2 + app/dialog/export/exportformatcombobox.cpp | 7 +++ app/dialog/export/exportformatcombobox.h | 3 +- app/dialog/export/exportsubtitlestab.cpp | 27 ++++++++++- app/dialog/export/exportsubtitlestab.h | 15 +++++- app/task/export/export.cpp | 54 +++++++++++++++++++--- app/task/export/export.h | 4 +- app/task/export/exportparams.cpp | 12 ----- app/task/export/exportparams.h | 5 -- app/task/render/render.cpp | 7 ++- 13 files changed, 183 insertions(+), 39 deletions(-) diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 7abf5d39f..31b187a5a 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -93,7 +93,8 @@ EncodingParams::EncodingParams() : video_color_range_(kYUVDefault), audio_enabled_(false), audio_bit_rate_(0), - subtitles_enabled_(false) + subtitles_enabled_(false), + subtitles_are_sidecar_(false) { } @@ -117,6 +118,29 @@ void EncodingParams::EnableSubtitles(const ExportCodec::Codec &scodec) subtitles_codec_ = scodec; } +void EncodingParams::EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec) +{ + subtitles_enabled_ = true; + subtitles_are_sidecar_ = true; + subtitle_sidecar_fmt_ = sfmt; + subtitles_codec_ = scodec; +} + +void EncodingParams::DisableVideo() +{ + video_enabled_ = false; +} + +void EncodingParams::DisableAudio() +{ + audio_enabled_ = false; +} + +void EncodingParams::DisableSubtitles() +{ + subtitles_enabled_ = false; +} + void EncodingParams::Save(QXmlStreamWriter *writer) const { writer->writeTextElement(QStringLiteral("filename"), filename_); @@ -217,6 +241,11 @@ Encoder *Encoder::CreateFromFormat(ExportFormat::Format f, const EncodingParams return CreateFromID(GetTypeFromFormat(f), params); } +Encoder *Encoder::CreateFromParams(const EncodingParams ¶ms) +{ + return CreateFromFormat(params.format(), params); +} + QStringList Encoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const { return QStringList(); diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 7b95fa7fb..e4c6db587 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -58,6 +58,14 @@ public: void EnableVideo(const VideoParams& video_params, const ExportCodec::Codec& vcodec); void EnableAudio(const AudioParams& audio_params, const ExportCodec::Codec &acodec); void EnableSubtitles(const ExportCodec::Codec &scodec); + void EnableSidecarSubtitles(const ExportFormat::Format &sfmt, const ExportCodec::Codec &scodec); + + void DisableVideo(); + void DisableAudio(); + void DisableSubtitles(); + + const ExportFormat::Format &format() const { return format_; } + void set_format(const ExportFormat::Format &format) { format_ = format; } void set_video_option(const QString& key, const QString& value) { video_opts_.insert(key, value); } void set_video_bit_rate(const int64_t& rate) { video_bit_rate_ = rate; } @@ -92,6 +100,8 @@ public: void set_audio_bit_rate(const int64_t& b) { audio_bit_rate_ = b; } bool subtitles_enabled() const { return subtitles_enabled_; } + bool subtitles_are_sidecar() const { return subtitles_are_sidecar_; } + ExportFormat::Format subtitle_sidecar_fmt() const { return subtitle_sidecar_fmt_; } ExportCodec::Codec subtitles_codec() const { return subtitles_codec_; } const rational& GetExportLength() const { return export_length_; } @@ -101,6 +111,7 @@ public: private: QString filename_; + ExportFormat::Format format_; bool video_enabled_; ExportCodec::Codec video_codec_; @@ -121,6 +132,8 @@ private: int64_t audio_bit_rate_; bool subtitles_enabled_; + bool subtitles_are_sidecar_; + ExportFormat::Format subtitle_sidecar_fmt_; ExportCodec::Codec subtitles_codec_; rational export_length_; @@ -152,6 +165,8 @@ public: static Encoder *CreateFromFormat(ExportFormat::Format f, const EncodingParams ¶ms); + static Encoder *CreateFromParams(const EncodingParams ¶ms); + virtual QStringList GetPixelFormatsForCodec(ExportCodec::Codec c) const; virtual std::vector GetSampleFormatsForCodec(ExportCodec::Codec c) const; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index b512c3910..986d3a169 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -32,7 +32,6 @@ #include "common/digit.h" #include "common/qtutils.h" -#include "core.h" #include "dialog/task/task.h" #include "node/project/project.h" #include "node/project/sequence/sequence.h" @@ -250,6 +249,12 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preview_viewer_->ConnectViewerNode(viewer_node_); preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); + + // We don't check if the codec supports subtitles because we can always export to a sidecar file + bool has_subtitle_codecs = SequenceHasSubtitles(); + connect(subtitles_enabled_, &QCheckBox::toggled, subtitle_tab_, &QWidget::setEnabled); + subtitles_enabled_->setChecked(has_subtitle_codecs); + subtitles_enabled_->setEnabled(has_subtitle_codecs); } rational ExportDialog::GetSelectedTimebase() const @@ -445,9 +450,9 @@ void ExportDialog::FormatChanged(ExportFormat::Format current_format) audio_enabled_->setChecked(has_audio_codecs); audio_enabled_->setEnabled(has_audio_codecs); - bool has_subtitle_codecs = subtitle_tab_->SetFormat(current_format); - subtitles_enabled_->setChecked(has_subtitle_codecs); - subtitles_enabled_->setEnabled(has_subtitle_codecs); + if (subtitles_enabled_->isEnabled()) { + subtitle_tab_->SetFormat(current_format); + } } void ExportDialog::ResolutionChanged() @@ -503,6 +508,20 @@ void ExportDialog::SetDefaultFilename() filename_edit_->setText(file_location); } +bool ExportDialog::SequenceHasSubtitles() const +{ + if (Sequence *s = dynamic_cast(viewer_node_)) { + TrackList *tl = s->track_list(Track::kSubtitle); + for (Track *t : tl->GetTracks()) { + if (!t->IsMuted() && !t->Blocks().empty()) { + return true; + } + } + } + + return false; +} + ExportParams ExportDialog::GenerateParams() const { VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()), @@ -519,7 +538,7 @@ ExportParams ExportDialog::GenerateParams() const audio_tab_->sample_format_combobox()->GetSampleFormat()); ExportParams params; - params.set_encoder(Encoder::GetTypeFromFormat(format_combobox_->GetFormat())); + params.set_format(format_combobox_->GetFormat()); params.SetFilename(filename_edit_->text().trimmed()); params.SetExportLength(viewer_node_->GetLength()); @@ -561,8 +580,15 @@ ExportParams ExportDialog::GenerateParams() const params.set_audio_bit_rate(audio_tab_->bit_rate_slider()->GetValue() * 1000); } - if (subtitles_enabled_->isChecked()) { - params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec()); + if (subtitles_enabled_->isEnabled() + && subtitles_enabled_->isChecked()) { + if (!subtitle_tab_->GetSidecarEnabled()) { + // Export subtitles embedded in container + params.EnableSubtitles(subtitle_tab_->GetSubtitleCodec()); + } else { + // Export subtitles to a sidecar file + params.EnableSidecarSubtitles(subtitle_tab_->GetSidecarFormat(), subtitle_tab_->GetSubtitleCodec()); + } } return params; diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index df16c4850..9cd7d274a 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -63,6 +63,8 @@ private: void LoadPresets(); void SetDefaultFilename(); + bool SequenceHasSubtitles() const; + ExportParams GenerateParams() const; ViewerOutput* viewer_node_; diff --git a/app/dialog/export/exportformatcombobox.cpp b/app/dialog/export/exportformatcombobox.cpp index c185b27a8..dcbcdd01d 100644 --- a/app/dialog/export/exportformatcombobox.cpp +++ b/app/dialog/export/exportformatcombobox.cpp @@ -46,6 +46,13 @@ ExportFormatComboBox::ExportFormatComboBox(Mode mode, QWidget *parent) : continue; } break; + case kShowSubtitlesOnly: + if (!ExportFormat::GetVideoCodecs(f).isEmpty() + || ExportFormat::GetSubtitleCodecs(f).isEmpty() + || !ExportFormat::GetAudioCodecs(f).isEmpty()) { + continue; + } + break; } QString format_name = ExportFormat::GetName(f); diff --git a/app/dialog/export/exportformatcombobox.h b/app/dialog/export/exportformatcombobox.h index 2f60867af..c90479e72 100644 --- a/app/dialog/export/exportformatcombobox.h +++ b/app/dialog/export/exportformatcombobox.h @@ -34,7 +34,8 @@ public: enum Mode { kShowAllFormats, kShowAudioOnly, - kShowVideoOnly + kShowVideoOnly, + kShowSubtitlesOnly }; ExportFormatComboBox(Mode mode, QWidget *parent = nullptr); diff --git a/app/dialog/export/exportsubtitlestab.cpp b/app/dialog/export/exportsubtitlestab.cpp index 27c40fc16..330942810 100644 --- a/app/dialog/export/exportsubtitlestab.cpp +++ b/app/dialog/export/exportsubtitlestab.cpp @@ -1,7 +1,6 @@ #include "exportsubtitlestab.h" #include -#include namespace olive { @@ -15,22 +14,46 @@ ExportSubtitlesTab::ExportSubtitlesTab(QWidget *parent) : int row = 0; + sidecar_checkbox_ = new QCheckBox(tr("Export to sidecar file")); + layout->addWidget(sidecar_checkbox_, row, 0, 1, 2); + + row++; + + sidecar_format_label_ = new QLabel(tr("Sidecar Format:")); + sidecar_format_label_->setVisible(false); + layout->addWidget(sidecar_format_label_, row, 0); + + sidecar_format_combobox_ = new ExportFormatComboBox(ExportFormatComboBox::kShowSubtitlesOnly); + sidecar_format_combobox_->setVisible(false); + layout->addWidget(sidecar_format_combobox_, row, 1); + + row++; + layout->addWidget(new QLabel(tr("Codec:")), row, 0); codec_combobox_ = new QComboBox(); layout->addWidget(codec_combobox_, row, 1); outer_layout->addStretch(); + + connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_label_, &QWidget::setVisible); + connect(sidecar_checkbox_, &QCheckBox::toggled, sidecar_format_combobox_, &QWidget::setVisible); } int ExportSubtitlesTab::SetFormat(ExportFormat::Format format) { auto scodecs = ExportFormat::GetSubtitleCodecs(format); - setEnabled(!scodecs.isEmpty()); + + sidecar_checkbox_->setChecked(scodecs.empty()); + sidecar_checkbox_->setEnabled(!scodecs.empty()); + + scodecs = ExportFormat::GetSubtitleCodecs(sidecar_format_combobox_->GetFormat()); + codec_combobox_->clear(); foreach (ExportCodec::Codec scodec, scodecs) { codec_combobox_->addItem(ExportCodec::GetCodecName(scodec), scodec); } + return scodecs.size(); } diff --git a/app/dialog/export/exportsubtitlestab.h b/app/dialog/export/exportsubtitlestab.h index a88f4d904..faa5d4bdf 100644 --- a/app/dialog/export/exportsubtitlestab.h +++ b/app/dialog/export/exportsubtitlestab.h @@ -21,10 +21,12 @@ #ifndef EXPORTSUBTITLESTAB_H #define EXPORTSUBTITLESTAB_H +#include #include +#include #include "codec/exportformat.h" -#include "render/subtitleparams.h" +#include "dialog/export/exportformatcombobox.h" namespace olive { @@ -34,6 +36,12 @@ class ExportSubtitlesTab : public QWidget public: ExportSubtitlesTab(QWidget *parent = nullptr); + bool GetSidecarEnabled() const { return sidecar_checkbox_->isChecked(); } + void SetSidecarEnabled(bool e) { sidecar_checkbox_->setEnabled(e); } + + ExportFormat::Format GetSidecarFormat() const { return sidecar_format_combobox_->GetFormat(); } + void SetSidecarFormat(ExportFormat::Format f) { sidecar_format_combobox_->SetFormat(f); } + int SetFormat(ExportFormat::Format format); ExportCodec::Codec GetSubtitleCodec() @@ -42,6 +50,11 @@ public: } private: + QCheckBox *sidecar_checkbox_; + + QLabel *sidecar_format_label_; + ExportFormatComboBox *sidecar_format_combobox_; + QComboBox *codec_combobox_; }; diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index e471d6862..3df5f1240 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -58,7 +58,14 @@ bool ExportTask::Run() params_.SetFilename(FileFunctions::GetSafeTemporaryFilename(real_filename)); } - encoder_ = Encoder::CreateFromID(params_.encoder(), params_); + // If we're exporting to a sidecar subtitle file, disable the subtitles in the main encoder + bool subtitles_enabled = params_.subtitles_enabled(); + ExportParams sidecar_params = params_; + if (subtitles_enabled && params_.subtitles_are_sidecar()) { + params_.DisableSubtitles(); + } + + encoder_ = std::shared_ptr(Encoder::CreateFromParams(params_)); if (!encoder_) { SetError(tr("Failed to create encoder")); @@ -67,10 +74,38 @@ bool ExportTask::Run() if (!encoder_->Open()) { SetError(tr("Failed to open file: %1").arg(encoder_->GetError())); - encoder_->deleteLater(); return false; } + if (subtitles_enabled && params_.subtitles_are_sidecar()) { + // Construct sidecar params + sidecar_params.DisableVideo(); + sidecar_params.DisableAudio(); + + QString sidecar_filename; + { + QFileInfo fi(real_filename); + sidecar_filename = fi.completeBaseName(); + sidecar_filename.append('.'); + sidecar_filename.append(ExportFormat::GetExtension(sidecar_params.subtitle_sidecar_fmt())); + sidecar_filename = fi.dir().filePath(sidecar_filename); + } + sidecar_params.SetFilename(sidecar_filename); + + subtitle_encoder_ = std::shared_ptr(Encoder::CreateFromFormat(sidecar_params.subtitle_sidecar_fmt(), sidecar_params)); + if (!subtitle_encoder_) { + SetError(tr("Failed to create subtitle encoder")); + return false; + } + + if (!subtitle_encoder_->Open()) { + SetError(tr("Failed to open subtitle sidecar file: %1").arg(sidecar_filename)); + return false; + } + } else { + subtitle_encoder_ = encoder_; + } + if (params_.has_custom_range()) { // Render custom range only range = params_.custom_range(); @@ -121,7 +156,7 @@ bool ExportTask::Run() audio_range = {range}; } - if (params_.subtitles_enabled()) { + if (subtitles_enabled) { subtitle_range = range; } @@ -132,13 +167,18 @@ bool ExportTask::Run() bool success = true; encoder_->Close(); - if (!encoder_->GetError().isEmpty()) { SetError(encoder_->GetError()); success = false; } - delete encoder_; + if (subtitle_encoder_ != encoder_) { + subtitle_encoder_->Close(); + if (!subtitle_encoder_->GetError().isEmpty()) { + SetError(subtitle_encoder_->GetError()); + success = false; + } + } // If cancelled, delete the file we made, which is always a file we created since we write to a // temp file during the actual encoding process @@ -209,8 +249,8 @@ bool ExportTask::AudioDownloaded(const TimeRange &range, const SampleBuffer &sam bool ExportTask::EncodeSubtitle(const SubtitleBlock *sub) { - if (!encoder_->WriteSubtitle(sub)) { - SetError(encoder_->GetError()); + if (!subtitle_encoder_->WriteSubtitle(sub)) { + SetError(subtitle_encoder_->GetError()); return false; } else { return true; diff --git a/app/task/export/export.h b/app/task/export/export.h index bf696978e..4bed7cd8b 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -60,7 +60,9 @@ private: ExportParams params_; - Encoder* encoder_; + std::shared_ptr encoder_; + + std::shared_ptr subtitle_encoder_; ColorProcessorPtr color_processor_; diff --git a/app/task/export/exportparams.cpp b/app/task/export/exportparams.cpp index dd7154044..dfe79686d 100644 --- a/app/task/export/exportparams.cpp +++ b/app/task/export/exportparams.cpp @@ -28,16 +28,6 @@ ExportParams::ExportParams() : { } -const Encoder::Type &ExportParams::encoder() const -{ - return encoder_id_; -} - -void ExportParams::set_encoder(const Encoder::Type &id) -{ - encoder_id_ = id; -} - bool ExportParams::has_custom_range() const { return has_custom_range_; @@ -104,8 +94,6 @@ void ExportParams::Save(QXmlStreamWriter *writer) const { writer->writeStartElement(QStringLiteral("export")); - writer->writeTextElement(QStringLiteral("encoder"), QString::number(encoder_id_)); - writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_)); writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); diff --git a/app/task/export/exportparams.h b/app/task/export/exportparams.h index b72597a6c..46f213f9c 100644 --- a/app/task/export/exportparams.h +++ b/app/task/export/exportparams.h @@ -39,9 +39,6 @@ public: ExportParams(); - const Encoder::Type& encoder() const; - void set_encoder(const Encoder::Type& id); - bool has_custom_range() const; const TimeRange& custom_range() const; void set_custom_range(const TimeRange& custom_range); @@ -59,8 +56,6 @@ public: virtual void Save(QXmlStreamWriter* writer) const override; private: - Encoder::Type encoder_id_; - VideoScalingMethod video_scaling_method_; bool has_custom_range_; diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 23fa2e387..d427b9608 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -95,8 +95,7 @@ bool RenderTask::Render(ColorManager* manager, // Subtitle loop, loops over all blocks in sequence on all tracks if (!subtitle_range.length().isNull()) { - Sequence *sequence = dynamic_cast(viewer_); - if (sequence) { + if (Sequence *sequence = dynamic_cast(viewer_)) { TrackList *list = sequence->track_list(Track::kSubtitle); QVector block_indexes(list->GetTrackCount(), 0); @@ -106,6 +105,10 @@ bool RenderTask::Render(ColorManager* manager, for (int i=0; iGetTrackAt(i); + if (this_track->IsMuted()) { + continue; + } + int &this_block_index = block_indexes[i]; if (this_block_index >= this_track->Blocks().size()) { continue;