diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index f4f363880..560566029 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -79,4 +79,40 @@ QString QtUtils::GetFormattedDateTime(const QDateTime &dt) return dt.toString(Qt::TextDate); } +QStringList QtUtils::WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width) +{ + QStringList list; + + QStringList lines = s.split('\n'); + + // Iterate every line + for (int i=0; i 1 && QFontMetricsWidth(fm, this_line) >= bounding_width) { + for (int j=this_line.size()-1; j>=0; j--) { + if (this_line.at(j).isSpace()) { + QString chopped = this_line.left(j); + if (QFontMetricsWidth(fm, chopped) < bounding_width) { + list.append(chopped); + + int k = j+1; + while (k < this_line.size() && this_line.at(k).isSpace()) { + k++; + } + this_line.remove(0, k); + break; + } + } + } + } + + if (!this_line.isEmpty()) { + list.append(this_line); + } + } + + return list; +} + } diff --git a/app/common/qtutils.h b/app/common/qtutils.h index 45321c76b..2c5f6b652 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -62,6 +62,8 @@ public: static QString GetFormattedDateTime(const QDateTime &dt); + static QStringList WordWrapString(const QString &s, const QFontMetrics &fm, int bounding_width); + }; } diff --git a/app/node/project/sequence/sequence.cpp b/app/node/project/sequence/sequence.cpp index 0cb443188..8829089dd 100644 --- a/app/node/project/sequence/sequence.cpp +++ b/app/node/project/sequence/sequence.cpp @@ -118,6 +118,15 @@ void Sequence::Retranslate() } } +void Sequence::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options) +{ + if (from == kTrackInputFormat.arg(Track::kSubtitle)) { + emit SubtitlesChanged(range); + } + + super::InvalidateCache(range, from, element, options); +} + rational Sequence::VerifyLengthInternal(Track::Type type) const { if (!track_lists_.isEmpty()) { diff --git a/app/node/project/sequence/sequence.h b/app/node/project/sequence/sequence.h index 40cc9f4d6..d07a89c4f 100644 --- a/app/node/project/sequence/sequence.h +++ b/app/node/project/sequence/sequence.h @@ -84,6 +84,8 @@ public: virtual void Retranslate() override; + virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override; + static const QString kTrackInputFormat; virtual bool IsItem() const override @@ -102,6 +104,8 @@ signals: void TrackAdded(Track* track); void TrackRemoved(Track* track); + void SubtitlesChanged(const TimeRange &range); + private: QVector track_lists_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 92b92eead..1568f3237 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -275,6 +275,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { auto_cacher_.SetViewerNode(n); + display_widget_->SetSubtitleTracks(dynamic_cast(n)); } void ViewerWidget::ScaleChangedEvent(const double &s) @@ -1189,6 +1190,13 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos) connect(show_fps_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetShowFPS); } + if (context_menu_widget_ == display_widget_) { + QAction* show_subtitles_action = menu.addAction(tr("Show Subtitles")); + show_subtitles_action->setCheckable(true); + show_subtitles_action->setChecked(display_widget_->GetShowSubtitles()); + connect(show_subtitles_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetShowSubtitles); + } + menu.exec(static_cast(sender())->mapToGlobal(pos)); } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 2d6d0513e..6000e7f0d 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -37,8 +37,10 @@ #include "common/define.h" #include "common/functiontimer.h" #include "common/html.h" +#include "common/qtutils.h" #include "config/config.h" #include "core.h" +#include "node/block/subtitle/subtitle.h" #include "node/gizmo/path.h" #include "node/gizmo/point.h" #include "node/gizmo/polygon.h" @@ -59,6 +61,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : gizmos_(nullptr), current_gizmo_(nullptr), gizmo_drag_started_(false), + show_subtitles_(true), + subtitle_tracks_(nullptr), hand_dragging_(false), deinterlace_(false), show_fps_(false), @@ -189,6 +193,21 @@ void ViewerDisplayWidget::SetTime(const rational &time) } } +void ViewerDisplayWidget::SetSubtitleTracks(Sequence *list) +{ + if (subtitle_tracks_) { + disconnect(subtitle_tracks_, &Sequence::SubtitlesChanged, this, &ViewerDisplayWidget::SubtitlesChanged); + } + + subtitle_tracks_ = list; + + if (subtitle_tracks_) { + connect(subtitle_tracks_, &Sequence::SubtitlesChanged, this, &ViewerDisplayWidget::SubtitlesChanged); + } + + update(); +} + QPointF ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(const QPointF &pos) { /* @@ -582,6 +601,53 @@ void ViewerDisplayWidget::OnPaint() } } } + + // Extraordinarily basic subtitle renderer. Hoping to swap this out with libass at some point. + if (show_subtitles_ && subtitle_tracks_) { + const QVector &subtitle_tracklist = subtitle_tracks_->track_list(Track::kSubtitle)->GetTracks(); + + if (!subtitle_tracklist.empty()) { + QPainter p(inner_widget()); + + QTransform transform = GenerateWorldTransform(); + QRect bounding_box = transform.mapRect(rect()); + + bounding_box.adjust(bounding_box.width()/10, bounding_box.height()/10, -bounding_box.width()/10, -bounding_box.height()/10); + + QFont f = p.font(); + int font_sz = bounding_box.height() / 18; + f.setStyleHint(QFont::SansSerif); + f.setFamily(f.defaultFamily()); + f.setPointSize(font_sz); + f.setWeight(QFont::Bold); + p.setFont(f); + p.setPen(Qt::white); + + QPainterPath path; + + int text_line = 1; + + for (int j=subtitle_tracklist.size()-1; j>=0; j--) { + Track *sub_track = subtitle_tracklist.at(j); + if (!sub_track->IsMuted()) { + if (SubtitleBlock *sub = dynamic_cast(sub_track->BlockAtTime(time_))) { + // Split into lines + QStringList list = QtUtils::WordWrapString(sub->GetText(), p.fontMetrics(), bounding_box.width()); + + for (int i=list.size()-1; i>=0; i--) { + int w = QtUtils::QFontMetricsWidth(p.fontMetrics(), list.at(i)); + path.addText(bounding_box.x() + bounding_box.width()/2 - w/2, bounding_box.y() + bounding_box.height() - p.fontMetrics().height() * text_line + p.fontMetrics().ascent(), p.font(), list.at(i)); + text_line++; + } + } + } + } + + p.setPen(QPen(Qt::black, font_sz / 16)); + p.setBrush(Qt::white); + p.drawPath(path); + } + } } void ViewerDisplayWidget::OnDestroy() @@ -618,12 +684,12 @@ QPointF ViewerDisplayWidget::GetTexturePosition(const double &x, const double &y y / gizmo_params_.height()); } -void ViewerDisplayWidget::DrawTextWithCrudeShadow(QPainter *painter, const QRect &rect, const QString &text) +void ViewerDisplayWidget::DrawTextWithCrudeShadow(QPainter *painter, const QRect &rect, const QString &text, const QTextOption &opt) { painter->setPen(Qt::black); - painter->drawText(rect.adjusted(1, 1, 0, 0), text); + painter->drawText(rect.adjusted(1, 1, 0, 0), text, opt); painter->setPen(Qt::white); - painter->drawText(rect, text); + painter->drawText(rect, text, opt); } rational ViewerDisplayWidget::GetGizmoTime() @@ -807,4 +873,11 @@ void ViewerDisplayWidget::TextEditChanged() gizmo->UpdateInputHtml(html, GetGizmoTime()); } +void ViewerDisplayWidget::SubtitlesChanged(const TimeRange &r) +{ + if (time_ >= r.in() && time_ < r.out()) { + update(); + } +} + } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index 718fd3070..1ccc5b3d1 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -26,6 +26,7 @@ #include "node/color/colormanager/colormanager.h" #include "node/node.h" +#include "node/output/track/tracklist.h" #include "render/color.h" #include "tool/tool.h" #include "viewerplaybacktimer.h" @@ -72,6 +73,7 @@ public: void SetGizmos(Node* node); void SetVideoParams(const VideoParams ¶ms); void SetTime(const rational& time); + void SetSubtitleTracks(Sequence *list); void SetShowWidgetBackground(bool e) { @@ -97,6 +99,9 @@ public: return show_fps_; } + bool GetShowSubtitles() const { return show_subtitles_; } + void SetShowSubtitles(bool e) { show_subtitles_ = e; update(); } + void IncrementSkippedFrames(); void IncrementFrameCount() @@ -239,7 +244,7 @@ private: QPointF GetTexturePosition(const QSize& size); QPointF GetTexturePosition(const double& x, const double& y); - static void DrawTextWithCrudeShadow(QPainter* painter, const QRect& rect, const QString& text); + static void DrawTextWithCrudeShadow(QPainter* painter, const QRect& rect, const QString& text, const QTextOption &opt = QTextOption()); rational GetGizmoTime(); @@ -312,6 +317,9 @@ private: NodeGizmo *current_gizmo_; bool gizmo_drag_started_; + bool show_subtitles_; + Sequence *subtitle_tracks_; + rational time_; /** @@ -365,6 +373,8 @@ private slots: void TextEditChanged(); + void SubtitlesChanged(const TimeRange &r); + }; }