diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 33dd5ca9e68a7d1d0c0e57dc90b5b0717c5359b1..33ffa7ea068d6885e8b06b437c69bfcb9e031db1 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -1189,12 +1189,14 @@ HEADERS += \ src/VideoStreaming/VideoStreaming.h \ src/VideoStreaming/VideoSurface.h \ src/VideoStreaming/VideoSurface_p.h \ + src/VideoStreaming/SubtitleWriter.h \ SOURCES += \ src/VideoStreaming/VideoItem.cc \ src/VideoStreaming/VideoReceiver.cc \ src/VideoStreaming/VideoStreaming.cc \ src/VideoStreaming/VideoSurface.cc \ + src/VideoStreaming/SubtitleWriter.cc \ contains (CONFIG, DISABLE_VIDEOSTREAMING) { message("Skipping support for video streaming (manual override from command line)") diff --git a/src/FlightDisplay/VideoManager.cc b/src/FlightDisplay/VideoManager.cc index 24c76808cf7c6b737c51d28aefd104d87fcb8b54..97736420725536251ae825e3d5afbbdc9c50ae8e 100644 --- a/src/FlightDisplay/VideoManager.cc +++ b/src/FlightDisplay/VideoManager.cc @@ -81,6 +81,7 @@ VideoManager::setToolbox(QGCToolbox *toolbox) _updateSettings(); if(isGStreamer()) { startVideo(); + _subtitleWriter.setVideoReceiver(_videoReceiver); } else { stopVideo(); } diff --git a/src/FlightDisplay/VideoManager.h b/src/FlightDisplay/VideoManager.h index 918b1a2695058d7707c698c2b0099847c4da2961..7d9ad610b3e72aab41dd4ccfddeea32a67b86b96 100644 --- a/src/FlightDisplay/VideoManager.h +++ b/src/FlightDisplay/VideoManager.h @@ -20,6 +20,7 @@ #include "QGCLoggingCategory.h" #include "VideoReceiver.h" #include "QGCToolbox.h" +#include "SubtitleWriter.h" Q_DECLARE_LOGGING_CATEGORY(VideoManagerLog) @@ -105,6 +106,7 @@ private: void _updateSettings (); private: + SubtitleWriter _subtitleWriter; bool _isTaisync = false; VideoReceiver* _videoReceiver = nullptr; VideoReceiver* _thermalVideoReceiver = nullptr; diff --git a/src/VideoStreaming/CMakeLists.txt b/src/VideoStreaming/CMakeLists.txt index 9024b35bbdfd0cd404553940949e1a7894410623..439ba45c5e9fa8c7e238312d7a8b38bcaa3c9764 100644 --- a/src/VideoStreaming/CMakeLists.txt +++ b/src/VideoStreaming/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(VideoStreaming VideoReceiver.cc VideoStreaming.cc VideoSurface.cc + SubtitleWriter.cc ${EXTRA_SRC} ) diff --git a/src/VideoStreaming/SubtitleWriter.cc b/src/VideoStreaming/SubtitleWriter.cc new file mode 100644 index 0000000000000000000000000000000000000000..338851db0ac938e921890e788eb98c7f29b47bed --- /dev/null +++ b/src/VideoStreaming/SubtitleWriter.cc @@ -0,0 +1,193 @@ +/**************************************************************************** + * + * (c) 2009-2019 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + + +/** + * @file + * @brief QGC Video Subtitle Writer + * @author Willian Galvani + */ + +#include "SubtitleWriter.h" +#include "SettingsManager.h" +#include "VideoReceiver.h" +#include "VideoManager.h" +#include "QGCApplication.h" +#include "QGCCorePlugin.h" +#include +#include +#include + +QGC_LOGGING_CATEGORY(SubtitleWriterLog, "SubtitleWriterLog") + +const int SubtitleWriter::_sampleRate = 1; // Sample rate in Hz for getting telemetry data, most players do weird stuff when > 1Hz + +SubtitleWriter::SubtitleWriter(QObject* parent) + : QObject(parent) +{ +} + +void SubtitleWriter::setVideoReceiver(VideoReceiver* videoReceiver) +{ + if(!videoReceiver) { + qCWarning(SubtitleWriterLog) << "Invalid VideoReceiver pointer! Aborting subtitle capture!"; + return; + } + _videoReceiver = videoReceiver; + // Only start writing subtitles once the recording pipeline actually starts + connect(_videoReceiver, &VideoReceiver::gotFirstRecordingKeyFrame, this, &SubtitleWriter::_startCapturingTelemetry); + + // Captures recordingChanged() signals to stop writing subtitles + connect(_videoReceiver, &VideoReceiver::recordingChanged, this, &SubtitleWriter::_onVideoRecordingChanged); + + // Timer for telemetry capture and writing to file + connect(&_timer, &QTimer::timeout, this, &SubtitleWriter::_captureTelemetry); +} + +void SubtitleWriter::_onVideoRecordingChanged() +{ + // Stop capturing data if recording stopped + if(!_videoReceiver->recording()) { + qCDebug(SubtitleWriterLog) << "Stopping writing"; + _timer.stop(); + _file.close(); + } +} + +void SubtitleWriter::_startCapturingTelemetry() +{ + if(!_videoReceiver) { + qCWarning(SubtitleWriterLog) << "Invalid VideoReceiver pointer! Aborting subtitle capture!"; + _timer.stop(); + return; + } + + // Get the facts displayed in the values widget and capture them, removing the "Vehicle." prefix. + QSettings settings; + settings.beginGroup("ValuesWidget"); + _values = settings.value("large").toStringList().replaceInStrings(QStringLiteral("Vehicle."), QString()); + _values += settings.value("small").toStringList().replaceInStrings(QStringLiteral("Vehicle."), QString()); + + _startTime = QDateTime::currentDateTime(); + + QFileInfo videoFile(_videoReceiver->videoFile()); + QString subtitleFilePath = QStringLiteral("%1/%2.ass").arg(videoFile.path(), videoFile.completeBaseName()); + qCDebug(SubtitleWriterLog) << "Writing overlay to file:" << subtitleFilePath; + _file.setFileName(subtitleFilePath); + + if (!_file.open(QIODevice::ReadWrite)) { + qCWarning(SubtitleWriterLog) << "Unable to write subtitle data to file"; + return; + } + + QTextStream stream( &_file ); + + // This is file header + stream << QStringLiteral( + "[Script Info]\n" + "Title: QGroundControl Subtitle Telemetry file\n" + "ScriptType: v4.00+\n" + "WrapStyle: 0\n" + "ScaledBorderAndShadow: yes\n" + "YCbCr Matrix: TV.601\n" + "PlayResX: 1920\n" + "PlayResY: 1080\n" + "\n" + "[V4+ Styles]\n" + "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" + "Style: Default,Monospace,30,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,1,10,10,10,1\n" + "\n" + "[Events]\n" + "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" + ); + + // TODO: Find a good way to input title + //stream << QStringLiteral("Dialogue: 0,0:00:00.00,999:00:00.00,Default,,0,0,0,,{\\pos(5,35)}%1\n"); + + _timer.start(1000/_sampleRate); +} + +void SubtitleWriter::_captureTelemetry() +{ + if(!_videoReceiver) { + qCWarning(SubtitleWriterLog) << "Invalid VideoReceiver pointer! Aborting subtitle capture!"; + _timer.stop(); + return; + } + + static const float nRows = 3; // number of rows used for displaying data + static const int offsetFactor = 700; // Used to simulate a larger resolution and reduce the borders in the layout + + auto *vehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle(); + + if (!vehicle) { + qCWarning(SubtitleWriterLog) << "Attempting to capture fact data with no active vehicle!"; + return; + } + // Each list corresponds to a column in the subtitles + QStringList namesStrings; + QStringList valuesStrings; + + // Make a list of "factname:" strings and other with the values, so one can be aligned left and the other right + for (const auto& i : _values) { + valuesStrings << QStringLiteral("%2 %3").arg(vehicle->getFact(i)->cookedValueString()) + .arg(vehicle->getFact(i)->cookedUnits()); + namesStrings << QStringLiteral("%1:").arg(vehicle->getFact(i)->shortDescription()); + } + + // The time to start displaying this subtitle text + QTime start = QTime(0, 0).addMSecs(_startTime.time().msecsTo(QDateTime::currentDateTime().time())); + + // The time to stop displaying this subtitle text + QTime end = start.addMSecs(1000/_sampleRate); + + // This splits the screen in N parts and uses the N-1 internal parts to align the subtitles to. + // Should we try to get the resolution from the pipeline? This seems to work fine with other resolutions too. + static const int rowWidth = (1920 + offsetFactor)/(nRows+1); + int nValuesByRow = ceil(_values.length() / nRows); + + QList dataColumns; + QStringList stringColumns; + + // These templates are used for the data columns, one right-aligned for names and one for + // the facts values. The arguments expected are: start time, end time, xposition, and string content. + QString namesLine = QStringLiteral("Dialogue: 0,%2,%3,Default,,0,0,0,,{\\an3\\pos(%1,1075)}%4\n"); + QString valuesLine = QStringLiteral("Dialogue: 0,%2,%3,Default,,0,0,0,,{\\pos(%1,1075)}%4\n"); + + // Split values into N columns and create a subtitle entry for each column + for (int i=0; i + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + + +/** + * @file + * @brief QGC Video Subtitle Writer + * @author Willian Galvani + */ + +#pragma once + +#include "QGCLoggingCategory.h" +#include "VideoReceiver.h" +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(SubtitleWriterLog) + +class SubtitleWriter : public QObject +{ + Q_OBJECT + +public: + explicit SubtitleWriter(QObject* parent = nullptr); + ~SubtitleWriter() = default; + + void setVideoReceiver(VideoReceiver* videoReceiver); + +private slots: + // Fires with every "videoRecordingChanged() signal, stops capturing telemetry if video stopped." + void _onVideoRecordingChanged(); + + // Captures a snapshot of telemetry data from vehicle into the subtitles file. + void _captureTelemetry(); + + // starts capturing vehicle telemetry. + void _startCapturingTelemetry(); + +private: + QTimer _timer; + QStringList _values; + QDateTime _startTime; + QFile _file; + + VideoReceiver* _videoReceiver; + + static const int _sampleRate; +}; diff --git a/src/VideoStreaming/VideoReceiver.cc b/src/VideoStreaming/VideoReceiver.cc index 53b1d753e4f89dfd45d8141b9b92359f9a2ffdae..f6c2500f4ee4570dc615663dbae4e23e0ed20c20 100644 --- a/src/VideoStreaming/VideoReceiver.cc +++ b/src/VideoStreaming/VideoReceiver.cc @@ -828,6 +828,7 @@ VideoReceiver::_keyframeWatch(GstPad* pad, GstPadProbeInfo* info, gpointer user_ gst_element_sync_state_with_parent(pThis->_sink->filesink); qCDebug(VideoReceiverLog) << "Got keyframe, stop dropping buffers"; + pThis->gotFirstRecordingKeyFrame(); } } diff --git a/src/VideoStreaming/VideoReceiver.h b/src/VideoStreaming/VideoReceiver.h index 017b937b00f769ec8aecaa67308109184194a839..7f3b3402176d32bd70eb68078940ec7da5915d83 100644 --- a/src/VideoStreaming/VideoReceiver.h +++ b/src/VideoStreaming/VideoReceiver.h @@ -79,6 +79,7 @@ signals: void msgErrorReceived (); void msgEOSReceived (); void msgStateChangedReceived (); + void gotFirstRecordingKeyFrame (); #endif public slots: