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

Add option in track headers to disable normalizing of audio thumbnails.

Related to #798
parent 41e87260
Pipeline #36252 passed with stage
in 15 minutes and 29 seconds
......@@ -208,6 +208,36 @@ QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &
void ProjectClip::updateAudioThumbnail()
{
if (m_hasAudio && m_service.startsWith(QLatin1String("avformat"))) {
int audioMax = getProducerIntProperty(QStringLiteral("kdenlive:audio_max"));
if (audioMax == 0) {
// Calculate max audio level with ffmpeg
QProcess ffmpeg;
QStringList args;
args << QStringLiteral("-i") << clipUrl() << QStringLiteral("-vn") << QStringLiteral("-af") << QStringLiteral("volumedetect") << QStringLiteral("-f") << QStringLiteral("null");
#ifdef Q_OS_WIN
args << QStringLiteral("-");
#else
args << QStringLiteral("/dev/stdout");
#endif
qDebug()<<"=== STARTING COMMAND: "<<args;
QObject::connect(&ffmpeg, &QProcess::readyReadStandardOutput, [&ffmpeg, this]() {
QString output = ffmpeg.readAllStandardOutput();
if (output.contains(QLatin1String("max_volume"))) {
qDebug()<<"===== GOT FFFMPEG OUTPUT:\n"<<output<<"\n.........................";
output = output.section(QLatin1String("max_volume:"), 1).simplified();
output = output.section(QLatin1Char(' '), 0, 0);
int aMax = qMax(1, qAbs(qRound(output.toDouble())));
qDebug()<<"===== GOT CLIP AMAX: "<<aMax;
qDebug()<<"===== GOT CLIP FINAL AMAX: "<<aMax;
setProducerProperty(QStringLiteral("kdenlive:audio_max"), aMax);
}
});
ffmpeg.setProcessChannelMode(QProcess::MergedChannels);
ffmpeg.start(KdenliveSettings::ffmpegpath(), args);
ffmpeg.waitForFinished(-1);
}
}
emit audioThumbReady();
if (m_clipType == ClipType::Audio) {
QImage thumb = ThumbnailCache::get()->getThumbnail(m_binId, 0);
......
......@@ -403,6 +403,18 @@ const QVector<uint8_t> ProjectItemModel::getAudioLevelsByBinID(const QString &bi
return QVector<uint8_t>();
}
double ProjectItemModel::getAudioMaxLevel(const QString &binId)
{
READ_LOCK();
for (const auto &clip : m_allItems) {
auto c = std::static_pointer_cast<AbstractProjectItem>(clip.second.lock());
if (c->itemType() == AbstractProjectItem::ClipItem && c->clipId() == binId) {
return qSqrt(std::static_pointer_cast<ProjectClip>(c)->getProducerIntProperty(QStringLiteral("kdenlive:audio_max")));
}
}
return 0;
}
bool ProjectItemModel::hasClip(const QString &binId)
{
READ_LOCK();
......
......@@ -69,6 +69,7 @@ public:
std::shared_ptr<ProjectClip> getClipByBinID(const QString &binId);
/** @brief Returns audio levels for a clip from its id */
const QVector <uint8_t>getAudioLevelsByBinID(const QString &binId, int stream);
double getAudioMaxLevel(const QString &binId);
/** @brief Returns a list of clips using the given url */
QStringList getClipByUrl(const QFileInfo &url) const;
......
......@@ -217,7 +217,7 @@ bool AudioThumbJob::computeWithFFMPEG()
} else if (offset > 250) {
intraOffset = offset / 10;
}
long maxLevel = 1;
long maxAudioLevel = 1;
if (!m_successful) {
m_done = true;
return true;
......@@ -240,7 +240,7 @@ bool AudioThumbJob::computeWithFFMPEG()
break;
}
k /= steps;
maxLevel = qMax(k, maxLevel);
maxAudioLevel = qMax(k, maxAudioLevel);
}
int p = 80 + (i * 20 / m_lengthInFrames);
......@@ -255,7 +255,7 @@ bool AudioThumbJob::computeWithFFMPEG()
return true;
}
for (long &v : ffmpegLevels) {
m_audioLevels << (uint8_t) (255 * v / maxLevel);
m_audioLevels << (uint8_t) (255 * v / maxAudioLevel);
}
m_done = true;
return true;
......@@ -398,7 +398,6 @@ bool AudioThumbJob::commitResult(Fun &undo, Fun &redo)
if (!m_successful) {
return false;
}
auto operation = [clip = m_binClip]() {
clip->updateAudioThumbnail();
return true;
......
......@@ -301,6 +301,11 @@
<label>Display all channels in audio thumbnails.</label>
<default>false</default>
</entry>
<entry name="normalizechannels" type="Bool">
<label>Normalize audio channels in thumbnails.</label>
<default>true</default>
</entry>
<entry name="autoscroll" type="Bool">
<label>Auto scroll timeline while playing.</label>
......
......@@ -595,6 +595,13 @@ void MainWindow::init()
separate_channels->setData("separate_channels");
connect(separate_channels, &QAction::triggered, this, &MainWindow::slotSeparateAudioChannel);
timelineHeadersMenu->addAction(separate_channels);
QAction *normalize_channels = new QAction(QIcon(), i18n("Normalize Audio Thumbnails"), this);
normalize_channels->setCheckable(true);
normalize_channels->setChecked(KdenliveSettings::normalizechannels());
normalize_channels->setData("normalize_channels");
connect(normalize_channels, &QAction::triggered, this, &MainWindow::slotNormalizeAudioChannel);
timelineHeadersMenu->addAction(normalize_channels);
QMenu *thumbsMenu = new QMenu(i18n("Thumbnails"), this);
QActionGroup *thumbGroup = new QActionGroup(this);
......@@ -2616,7 +2623,16 @@ void MainWindow::slotSeparateAudioChannel()
KdenliveSettings::setDisplayallchannels(!KdenliveSettings::displayallchannels());
emit getCurrentTimeline()->controller()->audioThumbFormatChanged();
if (m_clipMonitor) {
emit m_clipMonitor->refreshAudioThumbs();
m_clipMonitor->refreshAudioThumbs();
}
}
void MainWindow::slotNormalizeAudioChannel()
{
KdenliveSettings::setNormalizechannels(!KdenliveSettings::normalizechannels());
emit getCurrentTimeline()->controller()->audioThumbNormalizeChanged();
if (m_clipMonitor) {
m_clipMonitor->normalizeAudioThumbs();
}
}
......
......@@ -393,6 +393,8 @@ private slots:
void slotResizeItemEnd();
void configureNotifications();
void slotSeparateAudioChannel();
/** @brief Normalize audio channels before displaying them */
void slotNormalizeAudioChannel();
void slotInsertTrack();
void slotDeleteTrack();
/** @brief Show context menu to switch current track target audio stream. */
......
......@@ -1196,6 +1196,11 @@ void Monitor::refreshAudioThumbs()
emit m_glMonitor->getControllerProxy()->colorsChanged();
}
void Monitor::normalizeAudioThumbs()
{
emit m_glMonitor->getControllerProxy()->audioThumbNormalizeChanged();
}
void Monitor::checkOverlay(int pos)
{
if (m_qmlManager->sceneType() != MonitorSceneDefault) {
......
......@@ -162,6 +162,8 @@ public:
void reloadActiveStream();
/** @brief Trigger a refresh of audio thumbs colors */
void refreshAudioThumbs();
/** @brief Trigger a refresh of audio thumbs on notrmalization change */
void normalizeAudioThumbs();
protected:
......
......@@ -373,6 +373,11 @@ bool MonitorProxy::audioThumbFormat() const
return KdenliveSettings::displayallchannels();
}
bool MonitorProxy::audioThumbNormalize() const
{
return KdenliveSettings::normalizechannels();
}
void MonitorProxy::switchAutoKeyframe()
{
KdenliveSettings::setAutoKeyframe(!KdenliveSettings::autoKeyframe());
......
......@@ -51,6 +51,7 @@ class MonitorProxy : public QObject
Q_PROPERTY(QColor thumbColor2 READ thumbColor2 NOTIFY colorsChanged)
Q_PROPERTY(bool autoKeyframe READ autoKeyframe NOTIFY autoKeyframeChanged)
Q_PROPERTY(bool audioThumbFormat READ audioThumbFormat NOTIFY audioThumbFormatChanged)
Q_PROPERTY(bool audioThumbNormalize READ audioThumbNormalize NOTIFY audioThumbNormalizeChanged)
/** @brief: Returns true if current clip in monitor has Audio and Video
* */
Q_PROPERTY(bool clipHasAV MEMBER m_hasAV NOTIFY clipHasAVChanged)
......@@ -79,7 +80,8 @@ public:
Q_INVOKABLE void seek(int delta, uint modifiers);
Q_INVOKABLE QColor thumbColor1() const;
Q_INVOKABLE QColor thumbColor2() const;
Q_INVOKABLE bool audioThumbFormat() const;
bool audioThumbFormat() const;
bool audioThumbNormalize() const;
void positionFromConsumer(int pos, bool playing);
void setMarkerComment(const QString &comment);
int zoneIn() const;
......@@ -131,6 +133,7 @@ signals:
void audioThumbChanged();
void colorsChanged();
void audioThumbFormatChanged();
void audioThumbNormalizeChanged();
void profileChanged();
void autoKeyframeChanged();
......
......@@ -274,6 +274,7 @@ Item {
isFirstChunk: false
showItem: audioThumb.visible
format: controller.audioThumbFormat
normalize: controller.audioThumbNormalize
drawInPoint: 0
drawOutPoint: audioThumb.width
waveInPoint: (root.duration - 1) * root.zoomStart * channels
......
......@@ -49,6 +49,7 @@ Row {
isFirstChunk: index == 0
showItem: waveform.visible && (index * waveform.maxWidth < (clipRoot.scrollStart + scrollView.width)) && ((index * waveform.maxWidth + width) > clipRoot.scrollStart)
format: timeline.audioThumbFormat
normalize: timeline.audioThumbNormalize
drawInPoint: Math.max(0, clipRoot.scrollStart - (index * waveform.maxWidth))
drawOutPoint: (clipRoot.scrollStart + scrollView.width - (index * waveform.maxWidth))
waveInPoint: clipRoot.speed < 0 ? (Math.round((clipRoot.maxDuration - 1 - clipRoot.inPoint) * Math.abs(clipRoot.speed) - (index * waveform.maxWidth / clipRoot.timeScale) * Math.abs(clipRoot.speed)) * channels) : (Math.round((clipRoot.inPoint + (index * waveform.maxWidth / clipRoot.timeScale)) * clipRoot.speed) * channels)
......
......@@ -27,7 +27,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QPainterPath>
#include <QQuickPaintedItem>
#include <QElapsedTimer>
#include <QtMath>
#include <cmath>
#include "kdenlivesettings.h"
const QStringList chanelNames{"L", "R", "C", "LFE", "BL", "BR"};
......@@ -89,6 +91,7 @@ class TimelineWaveform : public QQuickPaintedItem
Q_PROPERTY(int waveOutPointWithUpdate MEMBER m_outPoint NOTIFY propertyChanged)
Q_PROPERTY(int audioStream MEMBER m_stream)
Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged)
Q_PROPERTY(bool normalize MEMBER m_normalize NOTIFY propertyChanged)
Q_PROPERTY(bool showItem READ showItem WRITE setShowItem NOTIFY showItemChanged)
Q_PROPERTY(bool isFirstChunk MEMBER m_firstChunk)
......@@ -114,6 +117,7 @@ public:
}
});
connect(this, &TimelineWaveform::propertyChanged, [&]() {
m_audioMax = KdenliveSettings::normalizechannels() ? 0 : pCore->projectItemModel()->getAudioMaxLevel(m_binId);
update();
});
}
......@@ -140,6 +144,7 @@ public:
}
if (m_audioLevels.isEmpty() && m_stream >= 0) {
m_audioLevels = pCore->projectItemModel()->getAudioLevelsByBinID(m_binId, m_stream);
m_audioMax = KdenliveSettings::normalizechannels() ? 0 : pCore->projectItemModel()->getAudioMaxLevel(m_binId);
if (m_audioLevels.isEmpty()) {
return;
}
......@@ -160,6 +165,10 @@ public:
pen.setWidthF(0);
}
painter->setPen(pen);
double scaleFactor = 255;
if (m_audioMax > 0) {
scaleFactor *= m_audioMax;
}
int startPos = m_inPoint / indicesPrPixel;
if (!KdenliveSettings::displayallchannels()) {
// Draw merged channels
......@@ -181,9 +190,9 @@ public:
if (idx + m_channels >= m_audioLevels.length() || idx < 0) {
break;
}
level = m_audioLevels.at(idx) / 255.;
level = m_audioLevels.at(idx) / scaleFactor;
for (int k = 1; k < m_channels; k++) {
level = qMax(level, m_audioLevels.at(idx + k) / 255.);
level = qMax(level, m_audioLevels.at(idx + k) / scaleFactor);
}
if (pathDraw) {
path.lineTo(i, height() - level * height());
......@@ -198,6 +207,7 @@ public:
} else {
double channelHeight = (double)height() / m_channels;
// Draw separate channels
scaleFactor = channelHeight / (2 * scaleFactor);
double i = 0;
double level;
QRectF bgRect(0, 0, width(), channelHeight);
......@@ -240,10 +250,10 @@ public:
idx += channel;
if (idx >= m_audioLevels.length() || idx < 0) break;
if (pathDraw) {
level = m_audioLevels.at(idx) * channelHeight / 510.;
level = m_audioLevels.at(idx) * scaleFactor;
path.lineTo(i, y - level);
} else {
level = m_audioLevels.at(idx) * channelHeight / 510.; // divide height by 510 (2*255) to get height
level = m_audioLevels.at(idx) * scaleFactor; // divide height by 510 (2*255) to get height
painter->drawLine(i, y - level, i, y + level);
}
}
......@@ -278,10 +288,12 @@ private:
QColor m_color;
QColor m_color2;
bool m_format;
bool m_normalize;
bool m_showItem;
int m_channels;
int m_precisionFactor;
int m_stream;
double m_audioMax;
bool m_firstChunk;
};
......
......@@ -595,6 +595,11 @@ bool TimelineController::audioThumbFormat() const
return KdenliveSettings::displayallchannels();
}
bool TimelineController::audioThumbNormalize() const
{
return KdenliveSettings::normalizechannels();
}
bool TimelineController::showWaveforms() const
{
return KdenliveSettings::audiothumbnails();
......
......@@ -48,6 +48,7 @@ class TimelineController : public QObject
Q_PROPERTY(int duration READ duration NOTIFY durationChanged)
Q_PROPERTY(int fullDuration READ fullDuration NOTIFY durationChanged)
Q_PROPERTY(bool audioThumbFormat READ audioThumbFormat NOTIFY audioThumbFormatChanged)
Q_PROPERTY(bool audioThumbNormalize READ audioThumbNormalize NOTIFY audioThumbNormalizeChanged)
Q_PROPERTY(int zoneIn READ zoneIn WRITE setZoneIn NOTIFY zoneChanged)
Q_PROPERTY(int zoneOut READ zoneOut WRITE setZoneOut NOTIFY zoneChanged)
Q_PROPERTY(bool ripple READ ripple NOTIFY rippleChanged)
......@@ -251,6 +252,7 @@ public:
bool showAudioThumbnails() const;
bool showMarkers() const;
bool audioThumbFormat() const;
bool audioThumbNormalize() const;
/* @brief Do we want to display audio thumbnails
*/
Q_INVOKABLE bool showWaveforms() const;
......@@ -634,6 +636,7 @@ signals:
void trackHeightChanged();
void scaleFactorChanged();
void audioThumbFormatChanged();
void audioThumbNormalizeChanged();
void durationChanged();
void audioTargetChanged();
void videoTargetChanged();
......
......@@ -233,7 +233,7 @@ void TimelineWidget::showHeaderMenu()
QList <QAction *> menuActions = m_headerMenu->actions();
QList <QAction *> audioActions;
for (QAction *ac : qAsConst(menuActions)) {
if (ac->data().toString() == QLatin1String("show_track_record") || ac->data().toString() == QLatin1String("separate_channels")) {
if (ac->data().toString() == QLatin1String("show_track_record") || ac->data().toString() == QLatin1String("separate_channels") || ac->data().toString() == QLatin1String("normalize_channels")) {
audioActions << ac;
}
}
......
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