Commit 6cc741be authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle

Merge branch '2012'

parents 3bec5a06 5baea514
Pipeline #41501 canceled with stage
......@@ -53,6 +53,17 @@ SubtitleModel::SubtitleModel(Mlt::Tractor *tractor, std::shared_ptr<TimelineItem
m_tractor->attach(*m_subtitleFilter.get());
}
setup();
QSize frameSize = pCore->getCurrentFrameDisplaySize();
int fontSize = frameSize.height() / 15;
int fontMargin = frameSize.height() - (2 *fontSize);
scriptInfoSection = QString("[Script Info]\n; This is a Sub Station Alpha v4 script.\n;\nScriptType: v4.00\nCollisions: Normal\nPlayResX: %1\nPlayResY: %2\nTimer: 100.0000\n").arg(frameSize.width()).arg(frameSize.height());
styleSection = QString("[V4 Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding\nStyle: Default,Consolas,%1,16777215,65535,255,0,-1,0,1,2,2,6,40,40,%2,0,1\n").arg(fontSize).arg(fontMargin);
eventSection = QStringLiteral("[Events]\n");
styleName = QStringLiteral("Default");
connect(this, &SubtitleModel::modelChanged, [this]() {
jsontoSubtitle(toJson());
});
}
void SubtitleModel::setup()
......@@ -67,12 +78,7 @@ void SubtitleModel::setup()
connect(this, &SubtitleModel::modelReset, this, &SubtitleModel::modelChanged);
}
std::shared_ptr<SubtitleModel> SubtitleModel::getModel()
{
return pCore->projectManager()->getSubtitleModel();
}
void SubtitleModel::importSubtitle(const QString filePath, int offset)
void SubtitleModel::importSubtitle(const QString filePath, int offset, bool externalImport)
{
QString start,end,comment;
QString timeLine;
......@@ -86,7 +92,7 @@ void SubtitleModel::importSubtitle(const QString filePath, int offset)
if (filePath.isEmpty())
return;
GenTime subtitleOffset(offset, pCore->getCurrentFps());
if (filePath.contains(".srt")) {
if (filePath.endsWith(".srt")) {
QFile srtFile(filePath);
if (!srtFile.exists() || !srtFile.open(QIODevice::ReadOnly)) {
qDebug() << " File not found " << filePath;
......@@ -98,7 +104,7 @@ void SubtitleModel::importSubtitle(const QString filePath, int offset)
QString line;
while (stream.readLineInto(&line)) {
line = line.simplified();
if (line.compare("")) {
if (!line.isEmpty()) {
if (!turn) {
// index=atoi(line.toStdString().c_str());
turn++;
......@@ -133,7 +139,7 @@ void SubtitleModel::importSubtitle(const QString filePath, int offset)
}
}
srtFile.close();
} else if (filePath.contains(".ass")) {
} else if (filePath.endsWith(".ass")) {
qDebug()<< "ass File";
QString startTime,endTime,text;
QString EventFormat, section;
......@@ -147,9 +153,12 @@ void SubtitleModel::importSubtitle(const QString filePath, int offset)
QTextStream stream(&assFile);
QString line;
qDebug() << " correct ass file " << filePath;
scriptInfoSection.clear();
styleSection.clear();
eventSection.clear();
while (stream.readLineInto(&line)) {
line = line.simplified();
if (line.compare("")) {
if (!line.isEmpty()) {
if (!turn) {
//qDebug() << " turn = 0 " << line;
//check if it is script info, event,or v4+ style
......@@ -168,12 +177,15 @@ void SubtitleModel::importSubtitle(const QString filePath, int offset)
turn++;
//qDebug()<< "turn" << turn;
continue;
} else {
} else if (line.contains("Events")) {
turn++;
section = "Events";
eventSection += line +"\n";
//qDebug()<< "turn" << turn;
continue;
} else {
//unknown section
}
}
if (section.contains("Script Info")) {
......@@ -230,7 +242,9 @@ void SubtitleModel::importSubtitle(const QString filePath, int offset)
}
assFile.close();
}
jsontoSubtitle(toJson());
if (externalImport) {
jsontoSubtitle(toJson());
}
}
void SubtitleModel::parseSubtitle(const QString subPath)
......@@ -241,7 +255,7 @@ void SubtitleModel::parseSubtitle(const QString subPath)
}
QString filePath = m_subtitleFilter->get("av.filename");
m_subFilePath = filePath;
importSubtitle(filePath);
importSubtitle(filePath, 0, false);
//jsontoSubtitle(toJson());
}
......@@ -374,6 +388,39 @@ SubtitledTime SubtitleModel::getSubtitle(GenTime startFrame) const
return SubtitledTime(GenTime(), QString(), GenTime());
}
QString SubtitleModel::getText(int id) const
{
if (m_timeline->m_allSubtitles.find( id ) == m_timeline->m_allSubtitles.end()) {
return QString();
}
GenTime start = m_timeline->m_allSubtitles.at(id);
return m_subtitleList.at(start).first;
}
bool SubtitleModel::setText(int id, const QString text)
{
if (m_timeline->m_allSubtitles.find( id ) == m_timeline->m_allSubtitles.end()) {
return false;
}
GenTime start = m_timeline->m_allSubtitles.at(id);
GenTime end = m_subtitleList.at(start).second;
QString oldText = m_subtitleList.at(start).first;
m_subtitleList[start].first = text;
Fun local_redo = [this, start, end, text]() {
editSubtitle(start, text, end);
pCore->refreshProjectRange({start.frames(pCore->getCurrentFps()), end.frames(pCore->getCurrentFps())});
return true;
};
Fun local_undo = [this, start, end, oldText]() {
editSubtitle(start, oldText, end);
pCore->refreshProjectRange({start.frames(pCore->getCurrentFps()), end.frames(pCore->getCurrentFps())});
return true;
};
local_redo();
pCore->pushUndo(local_undo, local_redo, i18n("Edit subtitle"));
return true;
}
std::unordered_set<int> SubtitleModel::getItemsInRange(int startFrame, int endFrame) const
{
GenTime startTime(startFrame, pCore->getCurrentFps());
......@@ -484,15 +531,14 @@ void SubtitleModel::removeSnapPoint(GenTime startpos)
void SubtitleModel::editEndPos(GenTime startPos, GenTime newEndPos, bool refreshModel)
{
qDebug()<<"Changing the sub end timings in model";
auto model = getModel();
if (model->m_subtitleList.count(startPos) <= 0) {
if (m_subtitleList.count(startPos) <= 0) {
//is not present in model only
return;
}
int row = static_cast<int>(std::distance(model->m_subtitleList.begin(), model->m_subtitleList.find(startPos)));
model->m_subtitleList[startPos].second = newEndPos;
int row = static_cast<int>(std::distance(m_subtitleList.begin(), m_subtitleList.find(startPos)));
m_subtitleList[startPos].second = newEndPos;
// Trigger update of the qml view
emit model->dataChanged(model->index(row), model->index(row), {EndFrameRole});
emit dataChanged(index(row), index(row), {EndFrameRole});
if (refreshModel) {
emit modelChanged();
}
......@@ -580,12 +626,11 @@ void SubtitleModel::editSubtitle(GenTime startPos, QString newSubtitleText, GenT
return;
}
qDebug()<<"Editing existing subtitle in model";
auto model = getModel();
int row = static_cast<int>(std::distance(model->m_subtitleList.begin(), model->m_subtitleList.find(startPos)));
model->m_subtitleList[startPos].first = newSubtitleText ;
model->m_subtitleList[startPos].second = endPos;
int row = static_cast<int>(std::distance(m_subtitleList.begin(), m_subtitleList.find(startPos)));
m_subtitleList[startPos].first = newSubtitleText ;
m_subtitleList[startPos].second = endPos;
qDebug()<<startPos.frames(pCore->getCurrentFps())<<m_subtitleList[startPos].first<<m_subtitleList[startPos].second.frames(pCore->getCurrentFps());
emit model->dataChanged(model->index(row), model->index(row), QVector<int>() << SubtitleRole);
emit dataChanged(index(row), index(row), QVector<int>() << SubtitleRole);
emit modelChanged();
return;
}
......@@ -658,6 +703,42 @@ int SubtitleModel::getIdForStartPos(GenTime startTime) const
return -1;
}
GenTime SubtitleModel::getStartPosForId(int id) const
{
if (m_timeline->m_allSubtitles.count(id) == 0) {
return GenTime();
};
return m_timeline->m_allSubtitles.at(id);
}
int SubtitleModel::getPreviousSub(int id) const
{
GenTime start = getStartPosForId(id);
int row = static_cast<int>(std::distance(m_subtitleList.begin(), m_subtitleList.find(start)));
if (row > 0) {
row--;
auto it = m_subtitleList.begin();
std::advance(it, row);
const GenTime res = it->first;
return getIdForStartPos(res);
}
return -1;
}
int SubtitleModel::getNextSub(int id) const
{
GenTime start = getStartPosForId(id);
int row = static_cast<int>(std::distance(m_subtitleList.begin(), m_subtitleList.find(start)));
if (row < static_cast<int>(m_subtitleList.size()) - 1) {
row++;
auto it = m_subtitleList.begin();
std::advance(it, row);
const GenTime res = it->first;
return getIdForStartPos(res);
}
return -1;
}
QString SubtitleModel::toJson()
{
//qDebug()<< "to JSON";
......@@ -681,8 +762,10 @@ void SubtitleModel::jsontoSubtitle(const QString &data, QString updatedFileName)
if (outFile.isEmpty()) {
outFile = pCore->currentDoc()->subTitlePath(); // use srt format as default unless file is imported (m_subFilePath)
}
if (!outFile.contains(".ass"))
bool assFormat = outFile.endsWith(".ass");
if (!assFormat) {
qDebug()<< "srt file import"; // if imported file isn't .ass, it is .srt format
}
QFile outF(outFile);
//qDebug()<< "Import from JSON";
......@@ -696,7 +779,7 @@ void SubtitleModel::jsontoSubtitle(const QString &data, QString updatedFileName)
auto list = json.array();
if (outF.open(QIODevice::WriteOnly)) {
QTextStream out(&outF);
if (outFile.contains(".ass")) {
if (assFormat) {
out<<scriptInfoSection<<endl;
out<<styleSection<<endl;
out<<eventSection;
......@@ -754,11 +837,10 @@ void SubtitleModel::jsontoSubtitle(const QString &data, QString updatedFileName)
.arg(seconds, 2, 10, QChar('0'))
.arg(millisec,3,10,QChar('0'));
line++;
if (outFile.contains(".ass")) {
if (assFormat) {
//Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text
out <<"Dialogue: 0,"<<startTimeString<<","<<endTimeString<<","<<styleName<<",,0000,0000,0000,,"<<dialogue<<endl;
}
if (outFile.contains(".srt")) {
} else {
out<<line<<"\n"<<startTimeStringSRT<<" --> "<<endTimeStringSRT<<"\n"<<dialogue<<"\n"<<endl;
}
......@@ -783,6 +865,16 @@ void SubtitleModel::updateSub(int id, QVector <int> roles)
emit dataChanged(index(row), index(row), roles);
}
int SubtitleModel::getRowForId(int id) const
{
if (m_timeline->m_allSubtitles.find( id ) == m_timeline->m_allSubtitles.end()) {
return -1;
}
GenTime startPos = m_timeline->m_allSubtitles.at(id);
int row = static_cast<int>(std::distance(m_subtitleList.begin(), m_subtitleList.find(startPos)));
return row;
}
int SubtitleModel::getSubtitlePlaytime(int id) const
{
GenTime startPos = m_timeline->m_allSubtitles.at(id);
......
......@@ -105,7 +105,7 @@ public:
void moveSubtitle(GenTime oldPos, GenTime newPos, bool updateModel, bool updateView);
/** @brief Function that imports a subtitle file */
void importSubtitle(const QString filePath, int offset = 0);
void importSubtitle(const QString filePath, int offset = 0, bool externalImport = false);
/** @brief Exports the subtitle model to json */
QString toJson();
......@@ -121,6 +121,11 @@ public:
/** @brief Cut a subtitle */
void cutSubtitle(int position);
bool cutSubtitle(int position, Fun &undo, Fun &redo);
QString getText(int id) const;
int getRowForId(int id) const;
GenTime getStartPosForId(int id) const;
int getPreviousSub(int id) const;
int getNextSub(int id) const;
public slots:
/** @brief Function that parses through a subtitle file */
......@@ -128,6 +133,8 @@ public slots:
/** @brief Import model to a temporary subtitle file to which the Subtitle effect is applied*/
void jsontoSubtitle(const QString &data, QString updatedFileName = QString());
/** @brief Update a subtitle text*/
bool setText(int id, const QString text);
private:
std::shared_ptr<TimelineItemModel> m_timeline;
......@@ -152,8 +159,6 @@ signals:
void modelChanged();
protected:
/** @brief Helper function that retrieves a pointer to the subtitle model*/
static std::shared_ptr<SubtitleModel> getModel();
/** @brief Add time as snap in the registered snap model */
void addSnapPoint(GenTime startpos);
/** @brief Remove time as snap in the registered snap model */
......
......@@ -29,6 +29,7 @@ the Free Software Foundation, either version 3 of the License, or
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinewidget.h"
#include "dialogs/subtitleedit.h"
#include <mlt++/MltRepository.h>
#include <KMessageBox>
......@@ -52,7 +53,7 @@ Core::Core()
void Core::prepareShutdown()
{
m_guiConstructed = false;
m_mainWindow->getCurrentTimeline()->controller()->prepareClose();
//m_mainWindow->getCurrentTimeline()->controller()->prepareClose();
projectItemModel()->blockSignals(true);
QThreadPool::globalInstance()->clear();
}
......@@ -179,6 +180,7 @@ void Core::initGUI(const QUrl &Url, const QString &clipsToLoad)
m_projectManager = new ProjectManager(this);
m_binWidget = new Bin(m_projectItemModel, m_mainWindow);
m_library = new LibraryWidget(m_projectManager, m_mainWindow);
m_subtitleWidget = new SubtitleEdit(m_mainWindow);
m_mixerWidget = new MixerManager(m_mainWindow);
connect(m_library, SIGNAL(addProjectClips(QList<QUrl>)), m_binWidget, SLOT(droppedUrls(QList<QUrl>)));
connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
......@@ -268,6 +270,21 @@ void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone)
m_binWidget->selectClipById(clipId, frame, zone);
}
void Core::selectTimelineItem(int id)
{
if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
m_mainWindow->getCurrentTimeline()->controller()->getModel()->requestAddToSelection(id, true);
}
}
std::shared_ptr<SubtitleModel> Core::getSubtitleModel()
{
if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getSubtitleModel();
}
return nullptr;
}
std::shared_ptr<JobManager> Core::jobManager()
{
return m_jobManager;
......@@ -278,6 +295,11 @@ LibraryWidget *Core::library()
return m_library;
}
SubtitleEdit *Core::subtitleWidget()
{
return m_subtitleWidget;
}
MixerManager *Core::mixer()
{
return m_mixerWidget;
......
......@@ -20,6 +20,7 @@ the Free Software Foundation, either version 3 of the License, or
#include <QUrl>
#include <memory>
#include <QPoint>
#include <QTextEdit>
#include <KSharedDataCache>
#include <unordered_set>
#include "timecode.h"
......@@ -38,6 +39,8 @@ class MonitorManager;
class ProfileModel;
class ProjectItemModel;
class ProjectManager;
class SubtitleEdit;
class SubtitleModel;
namespace Mlt {
class Repository;
......@@ -107,12 +110,16 @@ public:
Bin *bin();
/** @brief Select a clip in the Bin from its id. */
void selectBinClip(const QString &id, int frame = -1, const QPoint &zone = QPoint());
/** @brief Selects an item in the current timeline (clip, composition, subtitle). */
void selectTimelineItem(int id);
/** @brief Returns a pointer to the model of the project bin. */
std::shared_ptr<ProjectItemModel> projectItemModel();
/** @brief Returns a pointer to the job manager. Please do not store it. */
std::shared_ptr<JobManager> jobManager();
/** @brief Returns a pointer to the library. */
LibraryWidget *library();
/** @brief Returns a pointer to the subtitle edit. */
SubtitleEdit *subtitleWidget();
/** @brief Returns a pointer to the audio mixer. */
MixerManager *mixer();
......@@ -230,6 +237,8 @@ public:
void addGuides(QList <int> guides);
/** @brief Temporarily un/plug a list of clips in timeline. */
void temporaryUnplug(QList<int> clipIds, bool hide);
/** @brief Returns the current doc's subtitle model. */
std::shared_ptr<SubtitleModel> getSubtitleModel();
KSharedDataCache audioThumbCache;
......@@ -247,6 +256,7 @@ private:
std::shared_ptr<JobManager> m_jobManager;
Bin *m_binWidget{nullptr};
LibraryWidget *m_library{nullptr};
SubtitleEdit *m_subtitleWidget{nullptr};
MixerManager *m_mixerWidget{nullptr};
/** @brief Current project's profile path */
QString m_currentProfile;
......
......@@ -6,6 +6,7 @@ set(kdenlive_SRCS
dialogs/markerdialog.cpp
dialogs/profilesdialog.cpp
dialogs/renderwidget.cpp
dialogs/subtitleedit.cpp
dialogs/titletemplatedialog.cpp
dialogs/wizard.cpp
dialogs/splash.cpp
......
/***************************************************************************
* Copyright (C) 2020 by Jean-Baptiste Mardelle *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* 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 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include "subtitleedit.h"
#include "bin/model/subtitlemodel.hpp"
#include "monitor/monitor.h"
#include "core.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "QTextEdit"
#include <QEvent>
#include <QKeyEvent>
#include <QToolButton>
ShiftEnterFilter::ShiftEnterFilter(QObject *parent)
: QObject(parent)
{}
bool ShiftEnterFilter::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast <QKeyEvent*> (event);
if((keyEvent->modifiers() & Qt::ShiftModifier) && ((keyEvent->key() == Qt::Key_Enter) || (keyEvent->key() == Qt::Key_Return))) {
emit triggerUpdate();
return true;
}
}
return QObject::eventFilter(obj, event);
}
SubtitleEdit::SubtitleEdit(QWidget *parent)
: QWidget(parent)
, m_model(nullptr)
, m_activeSub(-1)
{
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setupUi(this);
buttonApply->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
auto *keyFilter = new ShiftEnterFilter(this);
subText->installEventFilter(keyFilter);
connect(keyFilter, &ShiftEnterFilter::triggerUpdate, this, &SubtitleEdit::updateSubtitle);
connect(subText, &QPlainTextEdit::textChanged, [this]() {
buttonApply->setEnabled(true);
});
connect(buttonApply, &QToolButton::clicked, this, &SubtitleEdit::updateSubtitle);
connect(buttonPrev, &QToolButton::clicked, this, &SubtitleEdit::goToPrevious);
connect(buttonNext, &QToolButton::clicked, this, &SubtitleEdit::goToNext);
sub_list->setVisible(false);
}
void SubtitleEdit::setModel(std::shared_ptr<SubtitleModel> model)
{
m_model = model;
m_activeSub = -1;
subText->setEnabled(false);
buttonApply->setEnabled(false);
if (m_model == nullptr) {
QSignalBlocker bk(subText);
subText->clear();
} else {
connect(m_model.get(), &SubtitleModel::dataChanged, [this](const QModelIndex &start, const QModelIndex &, const QVector <int>&roles) {
if (m_activeSub > -1 && start.row() == m_model->getRowForId(m_activeSub) && roles.contains(SubtitleModel::SubtitleRole)) {
setActiveSubtitle(m_activeSub);
}
});
}
}
void SubtitleEdit::updateSubtitle()
{
if (m_activeSub > -1 && m_model) {
m_model->setText(m_activeSub, subText->toPlainText());
}
}
void SubtitleEdit::setActiveSubtitle(int id)
{
m_activeSub = id;
if (m_model && id > -1) {
subText->setEnabled(true);
buttonApply->setEnabled(false);
QSignalBlocker bk(subText);
subText->setPlainText(m_model->getText(id));
} else {
subText->setEnabled(false);
buttonApply->setEnabled(false);
QSignalBlocker bk(subText);
subText->clear();
}
}
void SubtitleEdit::goToPrevious()
{
if (m_model) {
int id = m_model->getPreviousSub(m_activeSub);
if (id > -1) {
if (buttonApply->isEnabled()) {
updateSubtitle();
}
GenTime prev = m_model->getStartPosForId(id);
pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(prev.frames(pCore->getCurrentFps()));
pCore->selectTimelineItem(id);
}
}
}
void SubtitleEdit::goToNext()
{
if (m_model) {
int id = m_model->getNextSub(m_activeSub);
if (id > -1) {
if (buttonApply->isEnabled()) {
updateSubtitle();
}
GenTime prev = m_model->getStartPosForId(id);
pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(prev.frames(pCore->getCurrentFps()));
pCore->selectTimelineItem(id);
}
}
}
/***************************************************************************
* Copyright (C) 2020 by Jean-Baptiste Mardelle *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* 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 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#ifndef SUBTITLEEDIT_H
#define SUBTITLEEDIT_H
#include "ui_editsub_ui.h"
#include "definitions.h"
class SubtitleModel;
class ShiftEnterFilter : public QObject
{
Q_OBJECT
public:
ShiftEnterFilter(QObject *parent = 0);
protected:
virtual bool eventFilter(QObject *obj, QEvent *event) override;
signals:
void triggerUpdate();
};
/**
* @class SubtitleEdit: Subtitle edit widget
* @brief A dialog for editing markers and guides.
* @author Jean-Baptiste Mardelle
*/
class SubtitleEdit : public QWidget, public Ui::SubEdit_UI
{
Q_OBJECT
public:
explicit SubtitleEdit(QWidget *parent = nullptr);
void setModel(std::shared_ptr<SubtitleModel> model);