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