QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
SubtitleWriter.cc
Go to the documentation of this file.
1#include "SubtitleWriter.h"
2#include "Fact.h"
3#include "FactValueGrid.h"
8
9#include <QtCore/QDateTime>
10#include <QtCore/QFileInfo>
11#include <QtCore/QString>
12
13QGC_LOGGING_CATEGORY(SubtitleWriterLog, "Video.SubtitleWriter")
14
16 : QObject(parent)
17{
18 // qCDebug(SubtitleWriterLog) << Q_FUNC_INFO << this;
19
20 (void) connect(&_timer, &QTimer::timeout, this, &SubtitleWriter::_captureTelemetry);
21}
22
24{
25 // qCDebug(SubtitleWriterLog) << Q_FUNC_INFO << this;
26}
27
28void SubtitleWriter::startCapturingTelemetry(const QString &videoFile, QSize size)
29{
30 _size = size;
31 _facts.clear();
32
33 // Gather the facts currently displayed into _facts
34 FactValueGrid *grid = new FactValueGrid();
35 (void) grid->setProperty("settingsGroup", HorizontalFactValueGrid::telemetryBarSettingsGroup);
36 grid->componentComplete();
37 for (int colIndex = 0; colIndex < grid->columns()->count(); colIndex++) {
38 const QmlObjectListModel *list = grid->columns()->value<const QmlObjectListModel*>(colIndex);
39 for (int rowIndex = 0; rowIndex < list->count(); rowIndex++) {
40 const InstrumentValueData *value = list->value<InstrumentValueData*>(rowIndex);
41 if (value->fact()) {
42 _facts += value->fact();
43 }
44 }
45 }
46 grid->deleteLater();
47
48 // One subtitle always starts where the previous ended
49 _lastEndTime = QTime(0, 0);
50
51 const QFileInfo videoFileInfo(videoFile);
52 const QString subtitleFilePath = QStringLiteral("%1/%2.ass").arg(videoFileInfo.path(), videoFileInfo.completeBaseName());
53 qCDebug(SubtitleWriterLog) << "Writing overlay to file:" << subtitleFilePath;
54 _file.setFileName(subtitleFilePath);
55
56 if (!_file.open(QIODevice::ReadWrite)) {
57 qCWarning(SubtitleWriterLog) << "Unable to write subtitle data to file";
58 return;
59 }
60
61 QTextStream stream(&_file);
62
63 // Calculate the scaled font size based on the recording width
64 static constexpr int baseWidth = 640;
65 static constexpr int baseFontSize = 12;
66 const int scaledFontSize = (_size.width() * baseFontSize) / baseWidth;
67
68 // This is file header
69 stream << QStringLiteral(
70 "[Script Info]\n"
71 "Title: QGroundControl Subtitle Telemetry file\n"
72 "ScriptType: v4.00+\n"
73 "WrapStyle: 0\n"
74 "ScaledBorderAndShadow: yes\n"
75 "YCbCr Matrix: TV.601\n"
76 "PlayResX: %1\n"
77 "PlayResY: %2\n"
78 "\n"
79 "[V4+ Styles]\n"
80 "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"
81 "Style: Default,Monospace,%3,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,1,10,10,10,1\n"
82 "\n"
83 "[Events]\n"
84 "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
85 ).arg(_size.width()).arg(_size.height()).arg(scaledFontSize);
86
87 // TODO: Find a good way to input title
88 // stream << QStringLiteral("Dialogue: 0,0:00:00.00,999:00:00.00,Default,,0,0,0,,{\\pos(5,35)}%1\n");
89
90 _timer.start(1000 / _kSampleRate);
91}
92
94{
95 qCDebug(SubtitleWriterLog) << "Stopping writing";
96 _timer.stop();
97 _file.close();
98}
99
100void SubtitleWriter::_captureTelemetry()
101{
102 if (!MultiVehicleManager::instance()->activeVehicle()) {
103 qCWarning(SubtitleWriterLog) << "Attempting to capture fact data with no active vehicle!";
104 return;
105 }
106
107 // Each list corresponds to a column in the subtitles
108 QStringList namesStrings;
109 QStringList valuesStrings;
110
111 // Make a list of "factname:" strings and other with the values, so one can be aligned left and the other right
112 for (const Fact *fact : std::as_const(_facts)) {
113 valuesStrings << QStringLiteral("%2 %3").arg(fact->cookedValueString(), fact->cookedUnits());
114 namesStrings << QStringLiteral("%1:").arg(fact->shortDescription());
115 }
116
117 // The time to start displaying this subtitle text
118 const QTime start = _lastEndTime;
119
120 // The time to stop displaying this subtitle text
121 const QTime end = start.addMSecs(1000 / _kSampleRate);
122 _lastEndTime = end;
123
124 // This splits the screen in N parts and uses the N-1 internal parts to align the subtitles to.
125 // Should we try to get the resolution from the pipeline? This seems to work fine with other resolutions too.
126 static constexpr int offsetFactor = 100; // Used to reduce the borders in the layout
127 static constexpr float nRows = 3; // number of rows used for displaying data
128 static const int rowWidth = (_size.width() + offsetFactor) / (nRows + 1);
129 const int nValuesByRow = ceil(_facts.length() / nRows);
130
131 QStringList stringColumns;
132
133 // These templates are used for the data columns, one right-aligned for names and one for
134 // the facts values. The arguments expected are: start time, end time, xposition, and string content.
135 static const QString namesLine = QStringLiteral("Dialogue: 0,%3,%4,Default,,0,0,0,,{\\an3\\pos(%1,%2)}%5\n");
136 static const QString valuesLine = QStringLiteral("Dialogue: 0,%3,%4,Default,,0,0,0,,{\\pos(%1,%2)}%5\n");
137
138 // Split values into N columns and create a subtitle entry for each column
139 for (int i = 0; i < nRows; i++) {
140 const QStringList currentColumnNameStrings = namesStrings.mid(i * nValuesByRow, nValuesByRow);
141 const QStringList currentColumnValueStrings = valuesStrings.mid(i * nValuesByRow, nValuesByRow);
142
143 // Fill templates for names of column i
144 const QString names = namesLine.arg(QString::number((-offsetFactor / 2) + (rowWidth * (i + 1)) - 10),
145 QString::number(_size.height() - 30),
146 start.toString("H:mm:ss.zzz").chopped(2),
147 end.toString("H:mm:ss.zzz").chopped(2),
148 currentColumnNameStrings.join("\\N"));
149 stringColumns << names;
150
151 // Fill templates for values of column i
152 const QString values = valuesLine.arg(QString::number((-offsetFactor / 2) + (rowWidth * (i + 1))),
153 QString::number(_size.height() - 30),
154 start.toString("H:mm:ss.zzz").chopped(2),
155 end.toString("H:mm:ss.zzz").chopped(2),
156 currentColumnValueStrings.join("\\N"));
157 stringColumns << values;
158 }
159
160 // Write the date to the corner
161 stringColumns << QStringLiteral("Dialogue: 0,%1,%2,Default,,0,0,0,,{\\pos(10,35)}%3\n").arg(
162 start.toString("H:mm:ss.zzz").chopped(2),
163 end.toString("H:mm:ss.zzz").chopped(2),
164 QDateTime::currentDateTime().toString(QLocale::system().dateFormat(QLocale::ShortFormat)));
165 // Write new data
166 QTextStream stream(&_file);
167 for (const QString &col : std::as_const(stringColumns)) {
168 stream << col;
169 }
170}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QmlObjectListModel * columns(void) const
void componentComplete(void) final
A Fact is used to hold a single value within the system.
Definition Fact.h:19
Fact * fact(void) const
T value(int index) const
int count() const override final
void stopCapturingTelemetry()
void startCapturingTelemetry(const QString &videoFile, QSize size)