Commit f2525c72 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Add mediainfo based recording timecode option in clip monitor

Related to #886
parent 470c06fc
Pipeline #45238 passed with stage
in 10 minutes and 15 seconds
......@@ -3508,14 +3508,14 @@ void Bin::slotOpenClip()
if (KdenliveSettings::defaultimageapp().isEmpty()) {
KMessageBox::sorry(QApplication::activeWindow(), i18n("Please set a default application to open images in the Settings dialog"));
} else {
QProcess::startDetached(KdenliveSettings::defaultimageapp(), QStringList() << clip->url());
QProcess::startDetached(KdenliveSettings::defaultimageapp(), {clip->url()});
}
break;
case ClipType::Audio:
if (KdenliveSettings::defaultaudioapp().isEmpty()) {
KMessageBox::sorry(QApplication::activeWindow(), i18n("Please set a default application to open audio files in the Settings dialog"));
} else {
QProcess::startDetached(KdenliveSettings::defaultaudioapp(), QStringList() << clip->url());
QProcess::startDetached(KdenliveSettings::defaultaudioapp(), {clip->url()});
}
break;
default:
......
......@@ -637,6 +637,52 @@ void ProjectClip::createDisabledMasterProducer()
}
}
int ProjectClip::getRecordTime()
{
if (m_masterProducer && (m_clipType == ClipType::AV || m_clipType == ClipType::Video || m_clipType == ClipType::Audio)) {
int recTime = m_masterProducer->get_int("kdenlive:record_date");
if (recTime > 0) {
return recTime;
}
if (recTime < 0) {
// Cannot read record date on this clip, abort
return 0;
}
// Try to get record date metadata
if (KdenliveSettings::mediainfopath().isEmpty()) {
}
QProcess extractInfo;
extractInfo.start(KdenliveSettings::mediainfopath(), {url(),QStringLiteral("--output=XML")});
extractInfo.waitForFinished();
if(extractInfo.exitStatus() != QProcess::NormalExit || extractInfo.exitCode() != 0) {
KMessageBox::error(QApplication::activeWindow(), i18n("Cannot extract metadata from %1\n%2", url(),
QString(extractInfo.readAllStandardError())));
return 0;
}
QDomDocument doc;
doc.setContent(extractInfo.readAllStandardOutput());
QDomNodeList nodes = doc.documentElement().elementsByTagName(QStringLiteral("Recorded_Date"));
if (!nodes.isEmpty()) {
QString recDate = nodes.at(0).toElement().text();
if (!recDate.isEmpty()) {
if (recDate.contains(QLatin1Char('+'))) {
recDate = recDate.section(QLatin1Char('+'), 0, 0);
} else if (recDate.contains(QLatin1Char('-'))) {
recDate = recDate.section(QLatin1Char('-'), 0, 0);
}
QDateTime date = QDateTime::fromString(recDate, "yyyy-MM-dd hh:mm:ss");
recTime = date.time().msecsSinceStartOfDay();
m_masterProducer->set("kdenlive:record_date", recTime);
return recTime;
}
} else {
m_masterProducer->set("kdenlive:record_date", -1);
return 0;
}
}
return 0;
}
std::shared_ptr<Mlt::Producer> ProjectClip::getTimelineProducer(int trackId, int clipId, PlaylistState::ClipState state, int audioStream, double speed, bool secondPlaylist)
{
if (!m_masterProducer) {
......
......@@ -247,6 +247,7 @@ public:
static const QByteArray getFolderHash(QDir dir, QString fileName);
/** @brief Check if the clip is included in timeline and reset its occurrences on producer reload. */
void updateTimelineOnReload();
int getRecordTime();
protected:
friend class ClipModel;
......
......@@ -124,6 +124,7 @@ KdenliveSettingsDialog::KdenliveSettingsDialog(QMap<QString, QString> mappable_a
m_configEnv.ffmpegurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffmpegpath"));
m_configEnv.ffplayurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffplaypath"));
m_configEnv.ffprobeurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffprobepath"));
m_configEnv.mediainfourl->lineEdit()->setObjectName(QStringLiteral("kcfg_mediainfopath"));
m_configEnv.tmppathurl->setMode(KFile::Directory);
m_configEnv.tmppathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_currenttmpfolder"));
m_configEnv.capturefolderurl->setMode(KFile::Directory);
......@@ -876,6 +877,9 @@ void KdenliveSettingsDialog::updateSettings()
m_configEnv.ffplayurl->setText(KdenliveSettings::ffplaypath());
m_configEnv.ffprobeurl->setText(KdenliveSettings::ffprobepath());
}
if (m_configEnv.mediainfourl->text().isEmpty()) {
m_configEnv.mediainfourl->setText(KdenliveSettings::mediainfopath());
}
if (m_configTimeline.kcfg_trackheight->value() == 0) {
QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
......
......@@ -458,6 +458,11 @@
<label>FFprobe / avprobe binary path.</label>
<default></default>
</entry>
<entry name="mediainfopath" type="Path">
<label>mediaInfo binary path.</label>
<default></default>
</entry>
<entry name="mltthreads" type="Int">
<label>Mlt processing thread count.</label>
......@@ -793,6 +798,11 @@
<label>Show timecodes as frame number instead of hh:mm:ss:ff.</label>
<default>false</default>
</entry>
<entry name="rectimecode" type="Bool">
<label>Show recorded timecode in clip monitor, in format hh:mm:ss:ff.</label>
<default>false</default>
</entry>
<entry name="snaptopoints" type="Bool">
<label>Snap movements to clips, guides and markers.</label>
......
......@@ -46,6 +46,7 @@
#include <KDualAction>
#include <KFileWidget>
#include <KMessageWidget>
#include <KMessageBox>
#include <KRecentDirs>
#include <KSelectAction>
#include <KWindowConfig>
......@@ -432,6 +433,7 @@ Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *paren
connect(this, &Monitor::scopesClear, m_glMonitor, &GLWidget::releaseAnalyse, Qt::DirectConnection);
connect(m_glMonitor, &GLWidget::analyseFrame, this, &Monitor::frameUpdated);
m_timePos = new TimecodeDisplay(pCore->timecode(), this);
if (id == Kdenlive::ProjectMonitor) {
// TODO: reimplement
......@@ -441,6 +443,7 @@ Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *paren
} else if (id == Kdenlive::ClipMonitor) {
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::saveZone, this, &Monitor::updateClipZone);
}
m_glMonitor->getControllerProxy()->setTimeCode(m_timePos);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekNextKeyframe, this, &Monitor::seekToNextKeyframe);
connect(m_glMonitor->getControllerProxy(), &MonitorProxy::seekPreviousKeyframe, this, &Monitor::seekToPreviousKeyframe);
......@@ -454,7 +457,7 @@ Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *paren
m_toolbar->addAction(m_sceneVisibilityAction);
m_toolbar->addSeparator();
m_timePos = new TimecodeDisplay(pCore->timecode(), this);
connect(m_timePos, &TimecodeDisplay::timeCodeUpdated, m_glMonitor->getControllerProxy(), &MonitorProxy::timecodeChanged);
m_toolbar->addWidget(m_timePos);
auto *configButton = new QToolButton(m_toolbar);
......@@ -646,6 +649,14 @@ void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QA
QAction *switchAudioMonitor = m_configMenu->addAction(i18n("Show Audio Levels"), this, SLOT(slotSwitchAudioMonitor()));
switchAudioMonitor->setCheckable(true);
switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0);
if (m_id == Kdenlive::ClipMonitor) {
QAction *recordTimecode = new QAction(i18n("Show Record Timecode"), this);
recordTimecode->setCheckable(true);
connect(recordTimecode, &QAction::triggered, this, &Monitor::slotSwitchRecTimecode);
recordTimecode->setChecked(KdenliveSettings::rectimecode());
m_configMenu->addAction(recordTimecode);
}
// For some reason, the frame in QAbstracSpinBox (base class of TimeCodeDisplay) needs to be displayed once, then hidden
// or it will never appear (supposed to appear on hover).
......@@ -1608,6 +1619,9 @@ void Monitor::slotOpenClip(const std::shared_ptr<ProjectClip> &controller, int i
// we are in record mode, don't display clip
return;
}
if (KdenliveSettings::rectimecode()) {
m_timePos->setOffset(m_controller->getRecordTime());
}
if (m_controller->statusReady()) {
m_timePos->setRange(0, (int)m_controller->frameDuration() - 1);
m_glMonitor->setRulerInfo((int)m_controller->frameDuration() - 1, controller->getMarkerModel());
......@@ -2460,3 +2474,33 @@ void Monitor::updateMultiTrackView(int tid)
root->setProperty("activeTrack", tid);
}
}
void Monitor::slotSwitchRecTimecode(bool enable)
{
qDebug()<<"=== SLOT SWITCH REC: "<<enable;
if (!enable) {
m_timePos->setOffset(0);
KdenliveSettings::setRectimecode(false);
return;
}
if (KdenliveSettings::mediainfopath().isEmpty() || !QFileInfo::exists(KdenliveSettings::mediainfopath())) {
// Try to find binary
const QStringList mltpath({QFileInfo(KdenliveSettings::rendererpath()).canonicalPath(), qApp->applicationDirPath()});
QString mediainfopath = QStandardPaths::findExecutable(QStringLiteral("mediainfo"), mltpath);
if (mediainfopath.isEmpty()) {
mediainfopath = QStandardPaths::findExecutable(QStringLiteral("mediainfo"));
}
if (mediainfopath.isEmpty()) {
//TODO: propose to install mediainfo
KMessageBox::sorry(this, i18n("The MediaInfo application is required for the recording timecode feature, please install it and re-enable the feature in Kdenlive"));
return;
}
KdenliveSettings::setMediainfopath(mediainfopath);
}
qDebug()<<"========== READY TO READ OFFSET\n\n.............::";
KdenliveSettings::setRectimecode(true);
if (m_controller) {
qDebug()<<"=== GOT TIMECODE OFFSET: "<<m_controller->getRecordTime();
m_timePos->setOffset(m_controller->getRecordTime());
}
}
......@@ -281,6 +281,8 @@ private slots:
void processSeek(int pos);
/** @brief Check and display dropped frames */
void checkDrops();
/** @brief En/Disable the show record timecode feature in clip monitor */
void slotSwitchRecTimecode(bool enable);
public slots:
void slotSetScreen(int screenIndex);
......
......@@ -42,6 +42,7 @@ MonitorProxy::MonitorProxy(GLWidget *parent)
, m_clipType(0)
, m_clipId(-1)
, m_seekFinished(true)
, m_td(nullptr)
{
}
......@@ -374,3 +375,16 @@ bool MonitorProxy::autoKeyframe() const
{
return KdenliveSettings::autoKeyframe();
}
const QString MonitorProxy::timecode() const
{
if (m_td) {
return m_td->displayText();
}
return QString();
}
void MonitorProxy::setTimeCode(TimecodeDisplay *td)
{
m_td = td;
}
......@@ -32,6 +32,7 @@
#include <QObject>
class GLWidget;
class TimecodeDisplay;
class MonitorProxy : public QObject
{
......@@ -44,6 +45,7 @@ class MonitorProxy : public QObject
Q_PROPERTY(int zoneOut READ zoneOut WRITE setZoneOut NOTIFY zoneChanged)
Q_PROPERTY(int rulerHeight READ rulerHeight WRITE setRulerHeight NOTIFY rulerHeightChanged)
Q_PROPERTY(QString markerComment READ markerComment NOTIFY markerCommentChanged)
Q_PROPERTY(QString timecode READ timecode NOTIFY timecodeChanged)
Q_PROPERTY(QList <int> audioStreams MEMBER m_audioStreams NOTIFY audioThumbChanged)
Q_PROPERTY(QList <int> audioChannels MEMBER m_audioChannels NOTIFY audioThumbChanged)
Q_PROPERTY(int overlayType READ overlayType WRITE setOverlayType NOTIFY overlayTypeChanged)
......@@ -72,6 +74,7 @@ public:
int overlayType() const;
void setOverlayType(int ix);
QString markerComment() const;
const QString timecode() const;
/** brief: update position and end seeking if we reached the requested seek position.
* returns true if the position was unchanged, false otherwise
* */
......@@ -107,6 +110,8 @@ public:
void setAudioThumb(const QList <int> streamIndexes = QList <int>(), QList <int> channels = QList <int>());
void setAudioStream(const QString &name);
void setRulerHeight(int height);
/** @brief Store a reference to the timecode display */
void setTimeCode(TimecodeDisplay *td);
signals:
void positionChanged(int);
......@@ -136,6 +141,7 @@ signals:
void audioThumbNormalizeChanged();
void profileChanged();
void autoKeyframeChanged();
void timecodeChanged();
private:
GLWidget *q;
......@@ -152,6 +158,7 @@ private:
int m_clipId;
bool m_seekFinished;
QPoint m_undoZone;
TimecodeDisplay *m_td;
};
#endif
......@@ -406,7 +406,7 @@ Item {
background: Rectangle {
color: "#66000000"
}
text: controller.toTimecode(controller.position)
text: controller.timecode
visible: root.showTimecode
anchors {
right: parent.right
......
......@@ -133,7 +133,7 @@ Item {
background: Rectangle {
color: "#66000000"
}
text: controller.toTimecode(controller.position)
text: controller.timecode
visible: root.showTimecode
anchors {
right: parent.right
......
......@@ -52,6 +52,7 @@ TimecodeDisplay::TimecodeDisplay(const Timecode &t, QWidget *parent)
, m_minimum(0)
, m_maximum(-1)
, m_value(0)
, m_offset(0)
{
const QFont ft = QFontDatabase::systemFont(QFontDatabase::FixedFont);
lineEdit()->setFont(ft);
......@@ -218,7 +219,8 @@ void TimecodeDisplay::setValue(int value)
return;
}
m_value = value;
lineEdit()->setText(m_timecode.getTimecodeFromFrames(value - m_minimum));
lineEdit()->setText(m_timecode.getTimecodeFromFrames(m_offset + value - m_minimum));
emit timeCodeUpdated();
}
}
......@@ -233,7 +235,7 @@ void TimecodeDisplay::slotEditingFinished()
if (m_frametimecode) {
setValue(lineEdit()->text().toInt() + m_minimum);
} else {
setValue(m_timecode.getFrameCount(lineEdit()->text()) + m_minimum);
setValue(m_timecode.getFrameCount(lineEdit()->text()) + m_minimum - m_offset);
}
emit timeCodeEditingFinished(m_value);
}
......@@ -242,3 +244,13 @@ const QString TimecodeDisplay::displayText() const
{
return lineEdit()->displayText();
}
void TimecodeDisplay::setOffset(int offset)
{
m_offset = GenTime(offset/1000.).frames(m_timecode.fps());
// Update timecode display
if (!m_frametimecode) {
lineEdit()->setText(m_timecode.getTimecodeFromFrames(m_offset + m_value - m_minimum));
emit timeCodeUpdated();
}
}
......@@ -85,6 +85,10 @@ public:
void stepBy(int steps) override;
const QString displayText() const;
/** @brief Sets an offset for timecode display only, Used to show recording time instead of absolute timecode
* @param offset the offset in msecs */
void setOffset(int offset);
private:
/** timecode for widget */
......@@ -94,6 +98,7 @@ private:
int m_minimum;
int m_maximum;
int m_value;
int m_offset;
public slots:
/** @brief Sets the value.
......@@ -112,6 +117,7 @@ private slots:
signals:
void timeCodeEditingFinished(int value = -1);
void timeCodeUpdated();
protected:
void keyPressEvent(QKeyEvent *e) override;
......
......@@ -49,15 +49,28 @@
<item row="1" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>MLT environment</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1" colspan="2">
<widget class="KUrlRequester" name="ffmpegurl"/>
<item row="6" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1" colspan="2">
<widget class="KUrlRequester" name="ffprobeurl"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
......@@ -66,12 +79,8 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>FFmpeg</string>
</property>
</widget>
<item row="1" column="1" colspan="2">
<widget class="KUrlRequester" name="ffplayurl"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_14">
......@@ -80,25 +89,8 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Melt path</string>
</property>
</widget>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
<item row="0" column="1" colspan="2">
<widget class="KUrlRequester" name="ffmpegurl"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
......@@ -107,17 +99,35 @@
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="KUrlRequester" name="ffplayurl"/>
<item row="4" column="1" colspan="2">
<widget class="KUrlRequester" name="rendererpathurl"/>
</item>
<item row="3" column="1" colspan="2">
<widget class="KUrlRequester" name="mltpathurl"/>
</item>
<item row="4" column="1" colspan="2">
<widget class="KUrlRequester" name="rendererpathurl"/>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Melt path</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="KUrlRequester" name="ffprobeurl"/>
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>FFmpeg</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>MediaInfo</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="KUrlRequester" name="mediainfourl"/>
</item>
</layout>
</widget>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment