diff --git a/src/kdenlivesettings.kcfg b/src/kdenlivesettings.kcfg
index 1e1e05cf4..889037601 100644
--- a/src/kdenlivesettings.kcfg
+++ b/src/kdenlivesettings.kcfg
@@ -1,1041 +1,1046 @@
Bin treeview headers state.
Bin sorting column.
0
Bin view mode.
0
Bin default zoom.
4
User added clip file extensions.
Open last project on startup.
false
Enable autosave.
true
Select tab position in dockwidgets.
1
Check if document profile is same as first imported clip.
true
Export image using source resolution.
true
Use KDE central job management to track render jobs.
false
Default color clip duration.
00:00:05:00
Default image clip duration.
00:00:05:00
Default image sequence frame duration.
00:00:00:01
Default image sequence frame duration.
00:00:03:00
Automatically import image sequences.
false
Add a filter to disable default image scaling.
false
Automatically import all streams in multi streams clips.
false
Ignore libav / ffmpeg codec checking.
false
Get h264 metadata using exiftool.
false
Get h264 metadata using exiftool.
false
Default title clip duration.
00:00:05:00
Default transition duration.
00:00:03:00
Disable parameters when the effect is disabled.
true
Default fps filter for project profile.
Default scanning filter for project profile.
Default OpenGL backend (Qt::AAUseOpenGLES).
16
Default number of video tracks.
2
Default number of audio tracks.
2
Enable proxy clips.
false
Enable proxy clips.
false
Auto generate proxy for new clips.
false
index for the external proxy clips combo.
Pattern to find external proxy clips.
Auto generate proxy for new image clips.
false
Minimum source size for proxy creation.
1000
Minimum source size for proxy creation.
2000
Rescale size for image proxy creation.
800
File extension for proxy clips.
default proxy encoding profile.
0
Proxy clips transcoding parameters.
File extension for timeline preview.
default preview encoding profile.
0
Timeline preview encoding parameters.
Enable parallel processing for rendering.
true
Enables vaapi hw accel in encoders.
false
Enables nvenc hw accel in encoders.
false
Enables nvenc hw accel scaling.
false
Default width for timeline track headers.
0
Default interpolation for keyframes.
1
Default size of video chunks for timeline preview.
25
Automatically regenerate dirty zones of timeline preview.
false
+
+ Should we enable all audio streams by default.
+ 0
+
+
Display video thumbnails in timeline.
true
Display audio thumbnails in timeline.
true
Use FFmpeg to create audio thumbnails (10x faster than MLT).
true
Display clip markers comments in timeline.
true
Display all channels in audio thumbnails.
false
Auto scroll timeline while playing.
true
Vertical drag in timeline ruler zooms.
false
Tracks height in pixel.
0
Display audio tracks grouped below video tracks.
1
Raise property pane when selecting timeline clips.
true
Raise property pane when selecting timeline transitions.
true
Raise property pane when selecting timeline tracks.
true
Use Movit for GPU accelerated display and effects.
false
Detected audio backed.
sdl2_audio
Audio backend index used for sound output.
0
Audio backend used for sound output.
sdl2_audio
SDL Audio driver used for sound output.
0
SDL Audio device used for sound output.
Audio driver used for sound output.
Video driver used for output.
Audio device for SDL output.
Background color for OpenGL monitor.
#535353
Volume used for SDL output.
100
Allow framedropping in monitor playback.
true
Monitor gamma (rbg / rec 709).
1
Use Blackmagic device for video out.
false
Blackmagic video output device.
0
Mlt framework install path.
Mlt melt renderer install path.
FFmpeg / Libav binary path.
FFplay / avplay binary path.
FFprobe / avprobe binary path.
Mlt processing thread count.
1
Proxy creation processing thread count.
2
FFmpeg encoding thread count.
0
Default folder for tmp files.
/tmp/
Default folder for project files.
Don't use default XDG folders to store project files.
false
Save captured files to project folder by default.
true
Default folder for captured files.
$HOME
Open Library in default system folder.
true
Default folder for library.
Default image editing application.
Default audio editing application.
Default audio capture system.
default:
Audio capture volume
100
Audio capture channels
2
Audio capture sample rate
48000
Default video capture system.
0
Detected v4l devices.
0
Default video4linux capture format.
/dev/video0
Audio device for v4l capture.
Number of audio channels.
2
Audio device for v4l capture.
default
Default video4linux format.
Default video4linux file extension.
Selected capture format.
0
Should we capture video.
true
Should we also capture audio.
false
default v4l encoding profile.
0
default screen grab encoding profile.
0
Default video4linux format.
Default screen grab file extension.
capture type.
0
follow mouse in region capture.
false
x offset for video capture.
0
y offset for video capture.
0
default width for video capture.
1280
default height for video capture.
720
fps for video rec.
15.0
Hide frame around capture zone.
true
Hide mouse cursor.
false
Enable Blackmagic decklink support.
false
default HDMI capture device.
0
default HDMI encoding profile.
0
default HDMI capture filename.
capture
Default Decklink encoding parameters.
Default Decklink capture file extension.
Enable jog shuttle device.
false
Path to shuttle device.
Available shuttle device names.
Available shuttle device paths.
JogShuttle button to actions mappings.
Number of lines to use for the grid.
3
Should a background indicating the changes caused by curve manipulation be shown.
false
Show handles for all points or only for the selected one.
true
...
0
...
true
Currently displayed page in properties panel
0
Current project fps.
25
Default project format.
Show descriptions in project tree view.
true
Show ratings in project tree view.
false
Show dates in project tree view.
false
Show timecodes as frame number instead of hh:mm:ss:ff.
false
Snap movements to clips, guides and markers.
true
New transitions are automatic transitions.
true
When editing a composite transition, move timeline cursor for better preview.
true
When editing an effect with position, seek to the keyframe pos.
false
Show builtin effect stack.
false
Show small effect description in effects list.
false
Show avformat render parameters in rendering dialog.
false
Show overlay info on monitor (in / out points, markers,...).
0x15
Show overlay info on monitor (in / out points, markers,...).
0x05
Divide monitor resolution by this factor to speedup preview.
1
index of current guides overlay for clip monitor.
0
index of current guides overlay for project monitor.
0
Show audio overlay info on monitor.
false
Show monitor's audio level widget.
0x07
Show on monitor adjustable effect parameter (geometry, ..).
true
Audio driver selected automatically.
Is MLT compiled with Qt4 Kdenlive title support.
true
Collapse audio mixer (only show master channel).
false
List of available MLT producers.
Name of the chosen widget style.
Show thumbnails in slideshow dialog.
false
Show dock widget titlebars to allow un/docking.
true
Color to preselect in the color clip dialog.
#000000
Keep aspect ratio in render dialog rescale widget.
true
default width for rendering rescale.
320
default width for rendering rescale.
240
True if slideshow default method is MIME type.
true
Update parameters while monitor scene changes.
false
Send stopmotion frames to scopes for live analysis.
false
Send frames to audiospectrum scope for live analysis.
true
Show sequence thumbnails in stopmotion widget.
true
Interval between each capture (in seconds).
5
Seconds before triggering a capture notification.
3
Send a notification a few seconds before capture.
false
Should we loop in stop motion playback.
false
Should we play a sound to notify of captured frame in stop motion.
false
Number of frames to play back in stop motion playback.
10
Effect applied to stopmotion frame overlay.
0
Connect the corners in the widget for the c0rners effect with lines.
false
Show previous keyframe in monitor.
false
Show keyframe path in monitor.
true
Show additional controls in the c0rners on-monitor widget.
false
Do not validate the video files when loading a project for the sake of speed.
false
Display audio levels.
true
Should we display video frames while capturing.
true
Add cut clips to project after transcoding.
true
Default category for newly created clip markers.
0
Name of the chosen deinterlacer.
onefield
Name of the chosen interpol method.
nearest
List of favorite effects ids.
volume,lift_gamma_gain,qtblend
List of favorite transitions ids.
wipe,qtblend
Last opened tab in effects list.
0
Force breeze icon theme for consistent look.
false
Force breeze icon color theme for consistent look.
false
Lock size ratio in effects.
true
Name of last selected title template
Show guides in titler.
false
Number of horizontal guides in titler.
2
Number of vertical guides in titler.
3
color titler guides.
#ff0000
Default background for titler.
0
Show titler background.
false
diff --git a/src/lib/audio/audioStreamInfo.cpp b/src/lib/audio/audioStreamInfo.cpp
index 41b67e099..280374d30 100644
--- a/src/lib/audio/audioStreamInfo.cpp
+++ b/src/lib/audio/audioStreamInfo.cpp
@@ -1,187 +1,216 @@
/*
Copyright (C) 2012 Simon A. Eugster (Granjow)
This file is part of kdenlive. See www.kdenlive.org.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
*/
#include "audioStreamInfo.h"
+#include "kdenlivesettings.h"
#include "kdenlive_debug.h"
#include
#include
#include
AudioStreamInfo::AudioStreamInfo(const std::shared_ptr &producer, int audioStreamIndex)
: m_audioStreamIndex(audioStreamIndex)
, m_ffmpegAudioIndex(0)
, m_samplingRate(48000)
, m_channels(2)
, m_bitRate(0)
{
// Fetch audio streams
int streams = producer->get_int("meta.media.nb_streams");
int streamIndex = 1;
for (int ix = 0; ix < streams; ix++) {
char property[200];
snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
QString type = producer->get(property);
if (type == QLatin1String("audio")) {
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.channels", ix);
int chan = producer->get_int(property);
memset(property, 0, 200);
snprintf(property, sizeof(property), "kdenlive:streamname.%d", ix);
QString channelDescription = producer->get(property);
if (channelDescription.isEmpty()) {
channelDescription = QString("%1|").arg(streamIndex++);
switch (chan) {
case 1:
channelDescription.append(i18n("Mono "));
break;
case 2:
channelDescription.append(i18n("Stereo "));
break;
default:
channelDescription.append(i18n("%1 channels ", chan));
break;
}
// Frequency
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.sample_rate", ix);
QString frequency(producer->get(property));
if (frequency.endsWith(QLatin1String("000"))) {
frequency.chop(3);
frequency.append(i18n("kHz "));
} else {
frequency.append(i18n("Hz "));
}
channelDescription.append(frequency);
memset(property, 0, 200);
snprintf(property, sizeof(property), "meta.media.%d.codec.name", ix);
channelDescription.append(producer->get(property));
} else {
streamIndex++;
}
m_audioStreams.insert(ix, channelDescription);
}
}
QString active = producer->get("kdenlive:active_streams");
updateActiveStreams(active);
-
+ if (m_audioStreams.count() > 1 && active.isEmpty()) {
+ // initialize enabled streams
+ QStringList streamString;
+ for (int streamIx : m_activeStreams) {
+ streamString << QString::number(streamIx);
+ }
+ producer->set("kdenlive:active_streams", streamString.join(QLatin1Char(';')).toUtf8().constData());
+ }
+
if (audioStreamIndex > -1) {
QByteArray key;
key = QStringLiteral("meta.media.%1.codec.sample_fmt").arg(audioStreamIndex).toLocal8Bit();
m_samplingFormat = QString::fromLatin1(producer->get(key.data()));
key = QStringLiteral("meta.media.%1.codec.sample_rate").arg(audioStreamIndex).toLocal8Bit();
m_samplingRate = producer->get_int(key.data());
key = QStringLiteral("meta.media.%1.codec.bit_rate").arg(audioStreamIndex).toLocal8Bit();
m_bitRate = producer->get_int(key.data());
key = QStringLiteral("meta.media.%1.codec.channels").arg(audioStreamIndex).toLocal8Bit();
m_channels = producer->get_int(key.data());
setAudioIndex(producer, m_audioStreamIndex);
}
}
AudioStreamInfo::~AudioStreamInfo() = default;
int AudioStreamInfo::samplingRate() const
{
return m_samplingRate;
}
int AudioStreamInfo::channels() const
{
return m_channels;
}
QMap AudioStreamInfo::streams() const
{
return m_audioStreams;
}
QMap AudioStreamInfo::activeStreams() const
{
QMap active;
QMapIterator i(m_audioStreams);
if (m_activeStreams.size() == 1 && m_activeStreams.contains(INT_MAX)) {
active.insert(INT_MAX, i18n("Merged streams"));
} else {
while (i.hasNext()) {
i.next();
if (m_activeStreams.contains(i.key())) {
active.insert(i.key(), i.value());
}
}
}
return active;
}
int AudioStreamInfo::bitrate() const
{
return m_bitRate;
}
int AudioStreamInfo::audio_index() const
{
return m_audioStreamIndex;
}
int AudioStreamInfo::ffmpeg_audio_index() const
{
return m_ffmpegAudioIndex;
}
void AudioStreamInfo::dumpInfo() const
{
qCDebug(KDENLIVE_LOG) << "Info for audio stream " << m_audioStreamIndex << "\n\tChannels: " << m_channels << "\n\tSampling rate: " << m_samplingRate
<< "\n\tBit rate: " << m_bitRate;
}
void AudioStreamInfo::setAudioIndex(const std::shared_ptr &producer, int ix)
{
m_audioStreamIndex = ix;
if (ix > -1) {
int streams = producer->get_int("meta.media.nb_streams");
QList audioStreams;
for (int i = 0; i < streams; ++i) {
QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit();
QString type = producer->get(propertyName.data());
if (type == QLatin1String("audio")) {
audioStreams << i;
}
}
if (audioStreams.count() > 1 && m_audioStreamIndex < audioStreams.count()) {
m_ffmpegAudioIndex = audioStreams.indexOf(m_audioStreamIndex);
}
}
}
void AudioStreamInfo::updateActiveStreams(const QString &activeStreams)
{
// -1 = disable all audio
- // empty = enable all audio
+ // empty = enable all audio or first depending on config
m_activeStreams.clear();
if (activeStreams.isEmpty()) {
- m_activeStreams = m_audioStreams.keys();
+ switch (KdenliveSettings::multistream()) {
+ case 1:
+ // Enable first stream only
+ m_activeStreams << m_audioStreams.firstKey();
+ break;
+ case 2:
+ // Enable the first two streams only
+ {
+ QList str = m_audioStreams.keys();
+ while (!str.isEmpty()) {
+ m_activeStreams << str.takeFirst();
+ if (m_activeStreams.size() == 2) {
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ // Enable all streams
+ m_activeStreams = m_audioStreams.keys();
+ break;
+ }
return;
}
QStringList st = activeStreams.split(QLatin1Char(';'));
for (const QString &s : st) {
m_activeStreams << s.toInt();
}
}
void AudioStreamInfo::renameStream(int ix, const QString streamName)
{
if (m_audioStreams.contains(ix)) {
m_audioStreams.insert(ix, streamName);
}
}
-
diff --git a/src/mltcontroller/clippropertiescontroller.cpp b/src/mltcontroller/clippropertiescontroller.cpp
index c8f36186b..2e6e807c4 100644
--- a/src/mltcontroller/clippropertiescontroller.cpp
+++ b/src/mltcontroller/clippropertiescontroller.cpp
@@ -1,1491 +1,1489 @@
/*
Copyright (C) 2015 Jean-Baptiste Mardelle
This file is part of Kdenlive. See www.kdenlive.org.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "clippropertiescontroller.h"
#include "bin/model/markerlistmodel.hpp"
#include "clipcontroller.h"
#include "core.h"
#include "dialogs/profilesdialog.h"
#include "doc/kdenlivedoc.h"
#include "kdenlivesettings.h"
#include "profiles/profilerepository.hpp"
#include "project/projectmanager.h"
#include "timecodedisplay.h"
#include
#include "widgets/choosecolorwidget.h"
#include
#include
#ifdef KF5_USE_FILEMETADATA
#include
#include
#include
#include
#endif
#include
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AnalysisTree::AnalysisTree(QWidget *parent)
: QTreeWidget(parent)
{
setRootIsDecorated(false);
setColumnCount(2);
setAlternatingRowColors(true);
setHeaderHidden(true);
setDragEnabled(true);
}
// virtual
QMimeData *AnalysisTree::mimeData(const QList list) const
{
QString mimeData;
for (QTreeWidgetItem *item : list) {
if ((item->flags() & Qt::ItemIsDragEnabled) != 0) {
mimeData.append(item->text(1));
}
}
auto *mime = new QMimeData;
mime->setData(QStringLiteral("kdenlive/geometry"), mimeData.toUtf8());
return mime;
}
#ifdef KF5_USE_FILEMETADATA
class ExtractionResult : public KFileMetaData::ExtractionResult
{
public:
ExtractionResult(const QString &filename, const QString &mimetype, QTreeWidget *tree)
: KFileMetaData::ExtractionResult(filename, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData)
, m_tree(tree)
{
}
void append(const QString & /*text*/) override {}
void addType(KFileMetaData::Type::Type /*type*/) override {}
void add(KFileMetaData::Property::Property property, const QVariant &value) override
{
bool decode = false;
switch (property) {
case KFileMetaData::Property::ImageMake:
case KFileMetaData::Property::ImageModel:
case KFileMetaData::Property::ImageDateTime:
case KFileMetaData::Property::BitRate:
case KFileMetaData::Property::TrackNumber:
case KFileMetaData::Property::ReleaseYear:
case KFileMetaData::Property::Composer:
case KFileMetaData::Property::Genre:
case KFileMetaData::Property::Artist:
case KFileMetaData::Property::Album:
case KFileMetaData::Property::Title:
case KFileMetaData::Property::Comment:
case KFileMetaData::Property::Copyright:
case KFileMetaData::Property::PhotoFocalLength:
case KFileMetaData::Property::PhotoExposureTime:
case KFileMetaData::Property::PhotoFNumber:
case KFileMetaData::Property::PhotoApertureValue:
case KFileMetaData::Property::PhotoWhiteBalance:
case KFileMetaData::Property::PhotoGpsLatitude:
case KFileMetaData::Property::PhotoGpsLongitude:
decode = true;
break;
default:
break;
}
if (decode) {
KFileMetaData::PropertyInfo info(property);
if (info.valueType() == QVariant::DateTime) {
new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << value.toDateTime().toString(Qt::DefaultLocaleShortDate));
} else if (info.valueType() == QVariant::Int) {
int val = value.toInt();
if (property == KFileMetaData::Property::BitRate) {
// Adjust unit for bitrate
new QTreeWidgetItem(m_tree, QStringList() << info.displayName()
<< QString::number(val / 1000) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s"));
} else {
new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << QString::number(val));
}
} else if (info.valueType() == QVariant::Double) {
new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << QString::number(value.toDouble()));
} else {
new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << value.toString());
}
}
}
private:
QTreeWidget *m_tree;
};
#endif
ClipPropertiesController::ClipPropertiesController(ClipController *controller, QWidget *parent)
: QWidget(parent)
, m_controller(controller)
, m_tc(Timecode(Timecode::HH_MM_SS_HH, pCore->getCurrentFps()))
, m_id(controller->binId())
, m_type(controller->clipType())
, m_properties(new Mlt::Properties(controller->properties()))
, m_audioStream(nullptr)
, m_textEdit(nullptr)
, m_audioStreamsView(nullptr)
{
m_controller->mirrorOriginalProperties(m_sourceProperties);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
auto *lay = new QVBoxLayout;
lay->setContentsMargins(0, 0, 0, 0);
m_clipLabel = new QLabel(controller->clipName());
lay->addWidget(m_clipLabel);
m_tabWidget = new QTabWidget(this);
lay->addWidget(m_tabWidget);
setLayout(lay);
m_tabWidget->setDocumentMode(true);
m_tabWidget->setTabPosition(QTabWidget::East);
auto *forcePage = new QScrollArea(this);
auto *forceAudioPage = new QScrollArea(this);
m_propertiesPage = new QWidget(this);
m_markersPage = new QWidget(this);
m_metaPage = new QWidget(this);
m_analysisPage = new QWidget(this);
// Clip properties
auto *propsBox = new QVBoxLayout;
m_propertiesTree = new QTreeWidget(this);
m_propertiesTree->setRootIsDecorated(false);
m_propertiesTree->setColumnCount(2);
m_propertiesTree->setAlternatingRowColors(true);
m_propertiesTree->sortByColumn(0, Qt::AscendingOrder);
m_propertiesTree->setHeaderHidden(true);
propsBox->addWidget(m_propertiesTree);
fillProperties();
m_propertiesPage->setLayout(propsBox);
// Clip markers
auto *mBox = new QVBoxLayout;
m_markerTree = new QTreeView;
m_markerTree->setRootIsDecorated(false);
m_markerTree->setAlternatingRowColors(true);
m_markerTree->setHeaderHidden(true);
m_markerTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_markerTree->setModel(controller->getMarkerModel().get());
m_markerTree->setObjectName("markers_list");
mBox->addWidget(m_markerTree);
auto *bar = new QToolBar;
bar->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add marker"), this, SLOT(slotAddMarker()));
bar->addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("Delete marker"), this, SLOT(slotDeleteMarker()));
bar->addAction(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit marker"), this, SLOT(slotEditMarker()));
bar->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Export markers"), this, SLOT(slotSaveMarkers()));
bar->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Import markers"), this, SLOT(slotLoadMarkers()));
mBox->addWidget(bar);
m_markersPage->setLayout(mBox);
connect(m_markerTree, &QAbstractItemView::doubleClicked, this, &ClipPropertiesController::slotSeekToMarker);
// metadata
auto *m2Box = new QVBoxLayout;
auto *metaTree = new QTreeWidget;
metaTree->setRootIsDecorated(true);
metaTree->setColumnCount(2);
metaTree->setAlternatingRowColors(true);
metaTree->setHeaderHidden(true);
m2Box->addWidget(metaTree);
slotFillMeta(metaTree);
m_metaPage->setLayout(m2Box);
// Clip analysis
auto *aBox = new QVBoxLayout;
m_analysisTree = new AnalysisTree(this);
aBox->addWidget(new QLabel(i18n("Analysis data")));
aBox->addWidget(m_analysisTree);
auto *bar2 = new QToolBar;
bar2->addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("Delete analysis"), this, SLOT(slotDeleteAnalysis()));
bar2->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Export analysis"), this, SLOT(slotSaveAnalysis()));
bar2->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Import analysis"), this, SLOT(slotLoadAnalysis()));
aBox->addWidget(bar2);
slotFillAnalysisData();
m_analysisPage->setLayout(aBox);
// Force properties
auto *vbox = new QVBoxLayout;
vbox->setSpacing(0);
// Force Audio properties
auto *audioVbox = new QVBoxLayout;
audioVbox->setSpacing(0);
if (m_type == ClipType::Text || m_type == ClipType::SlideShow || m_type == ClipType::TextTemplate) {
QPushButton *editButton = new QPushButton(i18n("Edit Clip"), this);
connect(editButton, &QAbstractButton::clicked, this, &ClipPropertiesController::editClip);
vbox->addWidget(editButton);
}
if (m_type == ClipType::Color || m_type == ClipType::Image || m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::TextTemplate) {
// Edit duration widget
m_originalProperties.insert(QStringLiteral("out"), m_properties->get("out"));
int kdenlive_length = m_properties->time_to_frames(m_properties->get("kdenlive:duration"));
if (kdenlive_length > 0) {
m_originalProperties.insert(QStringLiteral("kdenlive:duration"), m_properties->get("kdenlive:duration"));
}
m_originalProperties.insert(QStringLiteral("length"), m_properties->get("length"));
auto *hlay = new QHBoxLayout;
QCheckBox *box = new QCheckBox(i18n("Duration"), this);
box->setObjectName(QStringLiteral("force_duration"));
hlay->addWidget(box);
auto *timePos = new TimecodeDisplay(m_tc, this);
timePos->setObjectName(QStringLiteral("force_duration_value"));
timePos->setValue(kdenlive_length > 0 ? kdenlive_length : m_properties->get_int("length"));
int original_length = m_properties->get_int("kdenlive:original_length");
if (original_length > 0) {
box->setChecked(true);
} else {
timePos->setEnabled(false);
}
hlay->addWidget(timePos);
vbox->addLayout(hlay);
connect(box, &QAbstractButton::toggled, timePos, &QWidget::setEnabled);
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
connect(timePos, &TimecodeDisplay::timeCodeEditingFinished, this, &ClipPropertiesController::slotDurationChanged);
connect(this, &ClipPropertiesController::updateTimeCodeFormat, timePos, &TimecodeDisplay::slotUpdateTimeCodeFormat);
connect(this, SIGNAL(modified(int)), timePos, SLOT(setValue(int)));
// connect(this, static_cast(&ClipPropertiesController::modified), timePos, &TimecodeDisplay::setValue);
}
if (m_type == ClipType::TextTemplate) {
// Edit text widget
QString currentText = m_properties->get("templatetext");
m_originalProperties.insert(QStringLiteral("templatetext"), currentText);
m_textEdit = new QTextEdit(this);
m_textEdit->setAcceptRichText(false);
m_textEdit->setPlainText(currentText);
m_textEdit->setPlaceholderText(i18n("Enter template text here"));
vbox->addWidget(m_textEdit);
QPushButton *button = new QPushButton(i18n("Apply"), this);
vbox->addWidget(button);
connect(button, &QPushButton::clicked, this, &ClipPropertiesController::slotTextChanged);
} else if (m_type == ClipType::Color) {
// Edit color widget
m_originalProperties.insert(QStringLiteral("resource"), m_properties->get("resource"));
mlt_color color = m_properties->get_color("resource");
ChooseColorWidget *choosecolor = new ChooseColorWidget(i18n("Color"), QColor::fromRgb(color.r, color.g, color.b).name(), "", false, this);
vbox->addWidget(choosecolor);
// connect(choosecolor, SIGNAL(displayMessage(QString,int)), this, SIGNAL(displayMessage(QString,int)));
connect(choosecolor, &ChooseColorWidget::modified, this, &ClipPropertiesController::slotColorModified);
connect(this, static_cast(&ClipPropertiesController::modified), choosecolor,
&ChooseColorWidget::slotColorModified);
}
if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Image) {
// Aspect ratio
int force_ar_num = m_properties->get_int("force_aspect_num");
int force_ar_den = m_properties->get_int("force_aspect_den");
m_originalProperties.insert(QStringLiteral("force_aspect_den"), (force_ar_den == 0) ? QString() : QString::number(force_ar_den));
m_originalProperties.insert(QStringLiteral("force_aspect_num"), (force_ar_num == 0) ? QString() : QString::number(force_ar_num));
auto *hlay = new QHBoxLayout;
QCheckBox *box = new QCheckBox(i18n("Aspect ratio"), this);
box->setObjectName(QStringLiteral("force_ar"));
vbox->addWidget(box);
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
auto *spin1 = new QSpinBox(this);
spin1->setMaximum(8000);
spin1->setObjectName(QStringLiteral("force_aspect_num_value"));
hlay->addWidget(spin1);
hlay->addWidget(new QLabel(QStringLiteral(":")));
auto *spin2 = new QSpinBox(this);
spin2->setMinimum(1);
spin2->setMaximum(8000);
spin2->setObjectName(QStringLiteral("force_aspect_den_value"));
hlay->addWidget(spin2);
if (force_ar_num == 0) {
// use current ratio
int num = m_properties->get_int("meta.media.sample_aspect_num");
int den = m_properties->get_int("meta.media.sample_aspect_den");
if (den == 0) {
num = 1;
den = 1;
}
spin1->setEnabled(false);
spin2->setEnabled(false);
spin1->setValue(num);
spin2->setValue(den);
} else {
box->setChecked(true);
spin1->setEnabled(true);
spin2->setEnabled(true);
spin1->setValue(force_ar_num);
spin2->setValue(force_ar_den);
}
connect(spin2, static_cast(&QSpinBox::valueChanged), this, &ClipPropertiesController::slotAspectValueChanged);
connect(spin1, static_cast(&QSpinBox::valueChanged), this, &ClipPropertiesController::slotAspectValueChanged);
connect(box, &QAbstractButton::toggled, spin1, &QWidget::setEnabled);
connect(box, &QAbstractButton::toggled, spin2, &QWidget::setEnabled);
vbox->addLayout(hlay);
// Proxy
QString proxy = m_properties->get("kdenlive:proxy");
m_originalProperties.insert(QStringLiteral("kdenlive:proxy"), proxy);
hlay = new QHBoxLayout;
auto *bg = new QGroupBox(this);
bg->setCheckable(false);
bg->setFlat(true);
auto *groupLay = new QHBoxLayout;
groupLay->setContentsMargins(0, 0, 0, 0);
auto *pbox = new QCheckBox(i18n("Proxy clip"), this);
pbox->setTristate(true);
// Proxy codec label
QLabel *lab = new QLabel(this);
pbox->setObjectName(QStringLiteral("kdenlive:proxy"));
bool hasProxy = proxy.length() > 2;
if (hasProxy) {
bg->setToolTip(proxy);
bool proxyReady = (QFileInfo(proxy).fileName() == QFileInfo(m_properties->get("resource")).fileName());
if (proxyReady) {
pbox->setCheckState(Qt::Checked);
lab->setText(m_properties->get(QString("meta.media.%1.codec.name").arg(m_properties->get_int("video_index")).toUtf8().constData()));
} else {
pbox->setCheckState(Qt::PartiallyChecked);
}
} else {
pbox->setCheckState(Qt::Unchecked);
}
pbox->setEnabled(pCore->projectManager()->current()->getDocumentProperty(QStringLiteral("enableproxy")).toInt() != 0);
connect(pbox, &QCheckBox::stateChanged, [this, pbox](int state) {
emit requestProxy(state == Qt::PartiallyChecked);
if (state == Qt::Checked) {
QSignalBlocker bk(pbox);
pbox->setCheckState(Qt::Unchecked);
}
});
connect(this, &ClipPropertiesController::enableProxy, pbox, &QCheckBox::setEnabled);
connect(this, &ClipPropertiesController::proxyModified, [this, pbox, bg, lab](const QString &pxy) {
bool hasProxyClip = pxy.length() > 2;
QSignalBlocker bk(pbox);
pbox->setCheckState(hasProxyClip ? Qt::Checked : Qt::Unchecked);
bg->setEnabled(pbox->isChecked());
bg->setToolTip(pxy);
lab->setText(hasProxyClip ? m_properties->get(QString("meta.media.%1.codec.name").arg(m_properties->get_int("video_index")).toUtf8().constData())
: QString());
});
hlay->addWidget(pbox);
bg->setEnabled(pbox->checkState() == Qt::Checked);
groupLay->addWidget(lab);
// Delete button
auto *tb = new QToolButton(this);
tb->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
tb->setAutoRaise(true);
connect(tb, &QToolButton::clicked, [this, proxy]() { emit deleteProxy(); });
tb->setToolTip(i18n("Delete proxy file"));
groupLay->addWidget(tb);
// Folder button
tb = new QToolButton(this);
auto *pMenu = new QMenu(this);
tb->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
tb->setToolTip(i18n("Proxy options"));
tb->setMenu(pMenu);
tb->setAutoRaise(true);
tb->setPopupMode(QToolButton::InstantPopup);
QAction *ac = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open folder"), this);
connect(ac, &QAction::triggered, [this]() {
QString pxy = m_properties->get("kdenlive:proxy");
QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(pxy).path()));
});
pMenu->addAction(ac);
ac = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Play proxy clip"), this);
connect(ac, &QAction::triggered, [this]() {
QString pxy = m_properties->get("kdenlive:proxy");
QDesktopServices::openUrl(QUrl::fromLocalFile(pxy));
});
pMenu->addAction(ac);
ac = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy file location to clipboard"), this);
connect(ac, &QAction::triggered, [this]() {
QString pxy = m_properties->get("kdenlive:proxy");
QGuiApplication::clipboard()->setText(pxy);
});
pMenu->addAction(ac);
groupLay->addWidget(tb);
bg->setLayout(groupLay);
hlay->addWidget(bg);
vbox->addLayout(hlay);
}
if (m_type == ClipType::AV || m_type == ClipType::Video) {
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
// Fps
QString force_fps = m_properties->get("force_fps");
m_originalProperties.insert(QStringLiteral("force_fps"), force_fps.isEmpty() ? QStringLiteral("-") : force_fps);
auto *hlay = new QHBoxLayout;
QCheckBox *box = new QCheckBox(i18n("Frame rate"), this);
box->setObjectName(QStringLiteral("force_fps"));
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
auto *spin = new QDoubleSpinBox(this);
spin->setMaximum(1000);
connect(spin, SIGNAL(valueChanged(double)), this, SLOT(slotValueChanged(double)));
// connect(spin, static_cast(&QDoubleSpinBox::valueChanged), this, &ClipPropertiesController::slotValueChanged);
spin->setObjectName(QStringLiteral("force_fps_value"));
if (force_fps.isEmpty()) {
spin->setValue(controller->originalFps());
} else {
spin->setValue(locale.toDouble(force_fps));
}
connect(box, &QAbstractButton::toggled, spin, &QWidget::setEnabled);
box->setChecked(!force_fps.isEmpty());
spin->setEnabled(!force_fps.isEmpty());
hlay->addWidget(box);
hlay->addWidget(spin);
vbox->addLayout(hlay);
// Scanning
QString force_prog = m_properties->get("force_progressive");
m_originalProperties.insert(QStringLiteral("force_progressive"), force_prog.isEmpty() ? QStringLiteral("-") : force_prog);
hlay = new QHBoxLayout;
box = new QCheckBox(i18n("Scanning"), this);
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
box->setObjectName(QStringLiteral("force_progressive"));
auto *combo = new QComboBox(this);
combo->addItem(i18n("Interlaced"), 0);
combo->addItem(i18n("Progressive"), 1);
connect(combo, static_cast(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
combo->setObjectName(QStringLiteral("force_progressive_value"));
if (!force_prog.isEmpty()) {
combo->setCurrentIndex(force_prog.toInt());
}
connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
box->setChecked(!force_prog.isEmpty());
combo->setEnabled(!force_prog.isEmpty());
hlay->addWidget(box);
hlay->addWidget(combo);
vbox->addLayout(hlay);
// Field order
QString force_tff = m_properties->get("force_tff");
m_originalProperties.insert(QStringLiteral("force_tff"), force_tff.isEmpty() ? QStringLiteral("-") : force_tff);
hlay = new QHBoxLayout;
box = new QCheckBox(i18n("Field order"), this);
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
box->setObjectName(QStringLiteral("force_tff"));
combo = new QComboBox(this);
combo->addItem(i18n("Bottom first"), 0);
combo->addItem(i18n("Top first"), 1);
connect(combo, static_cast(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
combo->setObjectName(QStringLiteral("force_tff_value"));
if (!force_tff.isEmpty()) {
combo->setCurrentIndex(force_tff.toInt());
}
connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
box->setChecked(!force_tff.isEmpty());
combo->setEnabled(!force_tff.isEmpty());
hlay->addWidget(box);
hlay->addWidget(combo);
vbox->addLayout(hlay);
// Autorotate
QString autorotate = m_properties->get("autorotate");
m_originalProperties.insert(QStringLiteral("autorotate"), autorotate);
hlay = new QHBoxLayout;
box = new QCheckBox(i18n("Disable autorotate"), this);
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
box->setObjectName(QStringLiteral("autorotate"));
box->setChecked(autorotate == QLatin1String("0"));
hlay->addWidget(box);
vbox->addLayout(hlay);
// Decoding threads
QString threads = m_properties->get("threads");
m_originalProperties.insert(QStringLiteral("threads"), threads);
hlay = new QHBoxLayout;
hlay->addWidget(new QLabel(i18n("Threads")));
auto *spinI = new QSpinBox(this);
spinI->setMaximum(4);
spinI->setMinimum(1);
spinI->setObjectName(QStringLiteral("threads_value"));
if (!threads.isEmpty()) {
spinI->setValue(threads.toInt());
} else {
spinI->setValue(1);
}
connect(spinI, static_cast(&QSpinBox::valueChanged), this,
static_cast(&ClipPropertiesController::slotValueChanged));
hlay->addWidget(spinI);
vbox->addLayout(hlay);
// Video index
if (!m_videoStreams.isEmpty()) {
QString vix = m_sourceProperties.get("video_index");
m_originalProperties.insert(QStringLiteral("video_index"), vix);
hlay = new QHBoxLayout;
KDualAction *ac = new KDualAction(i18n("Disable video"), i18n("Enable video"), this);
ac->setInactiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-show-video")));
ac->setActiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-hide-video")));
auto *tbv = new QToolButton(this);
tbv->setToolButtonStyle(Qt::ToolButtonIconOnly);
tbv->setDefaultAction(ac);
tbv->setAutoRaise(true);
hlay->addWidget(tbv);
hlay->addWidget(new QLabel(i18n("Video stream")));
auto *videoStream = new QComboBox(this);
int ix = 1;
for (int stream : m_videoStreams) {
videoStream->addItem(i18n("Video stream %1", ix), stream);
ix++;
}
if (!vix.isEmpty() && vix.toInt() > -1) {
videoStream->setCurrentIndex(videoStream->findData(QVariant(vix)));
}
ac->setActive(vix.toInt() == -1);
videoStream->setEnabled(vix.toInt() > -1);
videoStream->setVisible(m_videoStreams.size() > 1);
connect(ac, &KDualAction::activeChanged, [this, videoStream](bool activated) {
QMap properties;
int vindx = -1;
if (activated) {
videoStream->setEnabled(false);
} else {
videoStream->setEnabled(true);
vindx = videoStream->currentData().toInt();
}
properties.insert(QStringLiteral("video_index"), QString::number(vindx));
properties.insert(QStringLiteral("set.test_image"), vindx > -1 ? QStringLiteral("0") : QStringLiteral("1"));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
QObject::connect(videoStream, static_cast(&QComboBox::currentIndexChanged), [this, videoStream]() {
QMap properties;
properties.insert(QStringLiteral("video_index"), QString::number(videoStream->currentData().toInt()));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
hlay->addWidget(videoStream);
vbox->addLayout(hlay);
}
// Audio index
QMap audioStreamsInfo = m_controller->audioStreams();
if (!audioStreamsInfo.isEmpty()) {
QList enabledStreams = m_controller->activeStreams().keys();
QString vix = m_sourceProperties.get("audio_index");
m_originalProperties.insert(QStringLiteral("audio_index"), vix);
QStringList streamString;
for (int streamIx : enabledStreams) {
streamString << QString::number(streamIx);
}
m_originalProperties.insert(QStringLiteral("kdenlive:active_streams"), streamString.join(QLatin1Char(';')));
hlay = new QHBoxLayout;
KDualAction *ac = new KDualAction(i18n("Disable audio"), i18n("Enable audio"), this);
ac->setInactiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-show-audio")));
ac->setActiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-hide-audio")));
auto *tbv = new QToolButton(this);
tbv->setToolButtonStyle(Qt::ToolButtonIconOnly);
tbv->setDefaultAction(ac);
tbv->setAutoRaise(true);
hlay->addWidget(tbv);
hlay->addWidget(new QLabel(i18n("Audio streams")));
audioVbox->addLayout(hlay);
m_audioStreamsView = new QListWidget(this);
m_audioStreamsView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
audioVbox->addWidget(m_audioStreamsView);
QMapIterator i(audioStreamsInfo);
while (i.hasNext()) {
i.next();
QListWidgetItem *item = new QListWidgetItem(i.value(), m_audioStreamsView);
item->setData(Qt::UserRole, i.key());
// Store oringinal name
item->setData(Qt::UserRole + 1, i.value());
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
if (enabledStreams.contains(i.key())) {
item->setCheckState(Qt::Checked);
} else {
item->setCheckState(Qt::Unchecked);
}
}
if (audioStreamsInfo.count() > 1) {
QListWidgetItem *item = new QListWidgetItem(i18n("Merge all streams"), m_audioStreamsView);
item->setData(Qt::UserRole, INT_MAX);
item->setData(Qt::UserRole + 1, item->text());
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
if (enabledStreams.contains(INT_MAX)) {
item->setCheckState(Qt::Checked);
} else {
item->setCheckState(Qt::Unchecked);
}
}
connect(m_audioStreamsView, &QListWidget::itemChanged, [this] (QListWidgetItem *item) {
if (!item) {
return;
}
bool checked = item->checkState() == Qt::Checked;
int streamId = item->data(Qt::UserRole).toInt();
bool streamModified = false;
QString currentStreams = m_originalProperties.value(QStringLiteral("kdenlive:active_streams"));
QStringList activeStreams = currentStreams.split(QLatin1Char(';'));
if (activeStreams.contains(QString::number(streamId))) {
if (!checked) {
// Stream was unselected
activeStreams.removeAll(QString::number(streamId));
streamModified = true;
}
} else if (checked) {
// Stream was selected
if (streamId == INT_MAX) {
// merge all streams should not have any other stream selected
activeStreams.clear();
} else {
activeStreams.removeAll(QString::number(INT_MAX));
}
activeStreams << QString::number(streamId);
activeStreams.sort();
streamModified = true;
}
if (streamModified) {
if (activeStreams.isEmpty()) {
activeStreams << QStringLiteral("-1");
}
QMap properties;
properties.insert(QStringLiteral("kdenlive:active_streams"), activeStreams.join(QLatin1Char(';')));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
} else if (item->text() != item->data(Qt::UserRole + 1).toString()) {
// Rename event
QString txt = item->text();
int row = m_audioStreamsView->row(item) + 1;
if (!txt.startsWith(QString("%1|").arg(row))) {
txt.prepend(QString("%1|").arg(row));
}
m_controller->renameAudioStream(streamId, txt);
QSignalBlocker bk(m_audioStreamsView);
item->setText(txt);
item->setData(Qt::UserRole + 1, txt);
}
});
ac->setActive(vix.toInt() == -1);
connect(ac, &KDualAction::activeChanged, [this, audioStreamsInfo](bool activated) {
QMap properties;
int vindx = -1;
if (activated) {
properties.insert(QStringLiteral("kdenlive:active_streams"), QStringLiteral("-1"));
} else {
properties.insert(QStringLiteral("kdenlive:active_streams"), QString());
vindx = audioStreamsInfo.firstKey();
}
properties.insert(QStringLiteral("audio_index"), QString::number(vindx));
properties.insert(QStringLiteral("set.test_audio"), vindx > -1 ? QStringLiteral("0") : QStringLiteral("1"));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
// Audio sync
hlay = new QHBoxLayout;
hlay->addWidget(new QLabel(i18n("Audio sync")));
auto *spinSync = new QSpinBox(this);
spinSync->setSuffix(i18n("ms"));
spinSync->setRange(-1000, 1000);
spinSync->setValue(qRound(1000 * m_sourceProperties.get_double("video_delay")));
spinSync->setObjectName(QStringLiteral("video_delay"));
if (spinSync->value() != 0) {
m_originalProperties.insert(QStringLiteral("video_delay"), locale.toString(m_sourceProperties.get_double("video_delay")));
}
//QObject::connect(spinSync, static_cast(&QSpinBox::valueChanged), [this, spinSync]() {
QObject::connect(spinSync, &QSpinBox::editingFinished, [this, spinSync, locale]() {
QMap properties;
properties.insert(QStringLiteral("video_delay"), locale.toString(spinSync->value() / 1000.));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
});
hlay->addWidget(spinSync);
audioVbox->addLayout(hlay);
}
// Colorspace
hlay = new QHBoxLayout;
box = new QCheckBox(i18n("Colorspace"), this);
box->setObjectName(QStringLiteral("force_colorspace"));
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
combo = new QComboBox(this);
combo->setObjectName(QStringLiteral("force_colorspace_value"));
combo->addItem(ProfileRepository::getColorspaceDescription(601), 601);
combo->addItem(ProfileRepository::getColorspaceDescription(709), 709);
combo->addItem(ProfileRepository::getColorspaceDescription(240), 240);
int force_colorspace = m_properties->get_int("force_colorspace");
m_originalProperties.insert(QStringLiteral("force_colorspace"), force_colorspace == 0 ? QStringLiteral("-") : QString::number(force_colorspace));
int colorspace = controller->videoCodecProperty(QStringLiteral("colorspace")).toInt();
if (force_colorspace > 0) {
box->setChecked(true);
combo->setEnabled(true);
combo->setCurrentIndex(combo->findData(force_colorspace));
} else if (colorspace > 0) {
combo->setEnabled(false);
combo->setCurrentIndex(combo->findData(colorspace));
} else {
combo->setEnabled(false);
}
connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
connect(combo, static_cast(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
hlay->addWidget(box);
hlay->addWidget(combo);
vbox->addLayout(hlay);
// Full luma
QString force_luma = m_properties->get("set.force_full_luma");
m_originalProperties.insert(QStringLiteral("set.force_full_luma"), force_luma);
hlay = new QHBoxLayout;
box = new QCheckBox(i18n("Full luma range"), this);
connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
box->setObjectName(QStringLiteral("set.force_full_luma"));
box->setChecked(!force_luma.isEmpty());
hlay->addWidget(box);
vbox->addLayout(hlay);
hlay->addStretch(10);
}
// Force properties page
QWidget *forceProp = new QWidget(this);
forceProp->setLayout(vbox);
forcePage->setWidget(forceProp);
forcePage->setWidgetResizable(true);
// Force audio properties page
QWidget *forceAudioProp = new QWidget(this);
forceAudioProp->setLayout(audioVbox);
forceAudioPage->setWidget(forceAudioProp);
forceAudioPage->setWidgetResizable(true);
vbox->addStretch(10);
m_tabWidget->addTab(m_propertiesPage, QString());
m_tabWidget->addTab(forcePage, QString());
m_tabWidget->addTab(forceAudioPage, QString());
m_tabWidget->addTab(m_markersPage, QString());
m_tabWidget->addTab(m_metaPage, QString());
m_tabWidget->addTab(m_analysisPage, QString());
m_tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("edit-find")));
m_tabWidget->setTabToolTip(0, i18n("File info"));
m_tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("document-edit")));
m_tabWidget->setTabToolTip(1, i18n("Properties"));
m_tabWidget->setTabIcon(2, QIcon::fromTheme(QStringLiteral("audio-volume-high")));
m_tabWidget->setTabToolTip(2, i18n("Audio Properties"));
m_tabWidget->setTabIcon(3, QIcon::fromTheme(QStringLiteral("bookmark-new")));
m_tabWidget->setTabToolTip(3, i18n("Markers"));
m_tabWidget->setTabIcon(4, QIcon::fromTheme(QStringLiteral("view-grid")));
m_tabWidget->setTabToolTip(4, i18n("Metadata"));
m_tabWidget->setTabIcon(5, QIcon::fromTheme(QStringLiteral("visibility")));
m_tabWidget->setTabToolTip(5, i18n("Analysis"));
m_tabWidget->setCurrentIndex(KdenliveSettings::properties_panel_page());
if (m_type == ClipType::Color) {
m_tabWidget->setTabEnabled(0, false);
}
connect(m_tabWidget, &QTabWidget::currentChanged, this, &ClipPropertiesController::updateTab);
}
ClipPropertiesController::~ClipPropertiesController() = default;
void ClipPropertiesController::updateTab(int ix)
{
KdenliveSettings::setProperties_panel_page(ix);
}
void ClipPropertiesController::slotRefreshTimeCode()
{
emit updateTimeCodeFormat();
}
void ClipPropertiesController::slotReloadProperties()
{
mlt_color color;
m_properties.reset(new Mlt::Properties(m_controller->properties()));
m_clipLabel->setText(m_properties->get("kdenlive:clipname"));
switch (m_type) {
case ClipType::Color:
m_originalProperties.insert(QStringLiteral("resource"), m_properties->get("resource"));
m_originalProperties.insert(QStringLiteral("out"), m_properties->get("out"));
m_originalProperties.insert(QStringLiteral("length"), m_properties->get("length"));
emit modified(m_properties->get_int("length"));
color = m_properties->get_color("resource");
emit modified(QColor::fromRgb(color.r, color.g, color.b));
break;
case ClipType::TextTemplate:
m_textEdit->setPlainText(m_properties->get("templatetext"));
break;
case ClipType::Image:
case ClipType::AV:
case ClipType::Video: {
QString proxy = m_properties->get("kdenlive:proxy");
if (proxy != m_originalProperties.value(QStringLiteral("kdenlive:proxy"))) {
m_originalProperties.insert(QStringLiteral("kdenlive:proxy"), proxy);
emit proxyModified(proxy);
}
if (m_audioStreamsView && m_audioStreamsView->count() > 0) {
int audio_ix = m_properties->get_int("audio_index");
m_originalProperties.insert(QStringLiteral("kdenlive:active_streams"), m_properties->get("kdenlive:active_streams"));
if (audio_ix != m_originalProperties.value(QStringLiteral("audio_index")).toInt()) {
QSignalBlocker bk(m_audioStream);
m_originalProperties.insert(QStringLiteral("audio_index"), QString::number(audio_ix));
- // update combo
- //m_audioStream->setCurrentIndex(m_audioStream->findData(audio_ix));
}
QList enabledStreams = m_controller->activeStreams().keys();
qDebug()<<"=== GOT ACTIVE STREAMS: "<count(); ix++) {
QListWidgetItem *item = m_audioStreamsView->item(ix);
int stream = item->data(Qt::UserRole).toInt();
item->setCheckState(enabledStreams.contains(stream) ? Qt::Checked : Qt::Unchecked);
}
}
break;
}
default:
break;
}
}
void ClipPropertiesController::slotColorModified(const QColor &newcolor)
{
QMap properties;
properties.insert(QStringLiteral("resource"), newcolor.name(QColor::HexArgb));
QMap oldProperties;
oldProperties.insert(QStringLiteral("resource"), m_properties->get("resource"));
emit updateClipProperties(m_id, oldProperties, properties);
}
void ClipPropertiesController::slotDurationChanged(int duration)
{
QMap properties;
// kdenlive_length is the default duration for image / title clips
int kdenlive_length = m_properties->time_to_frames(m_properties->get("kdenlive:duration"));
int current_length = m_properties->get_int("length");
if (kdenlive_length > 0) {
// special case, image/title clips store default duration in kdenlive:duration property
properties.insert(QStringLiteral("kdenlive:duration"), m_properties->frames_to_time(duration));
if (duration > current_length) {
properties.insert(QStringLiteral("length"), m_properties->frames_to_time(duration));
properties.insert(QStringLiteral("out"), m_properties->frames_to_time(duration - 1));
}
} else {
properties.insert(QStringLiteral("length"), m_properties->frames_to_time(duration));
properties.insert(QStringLiteral("out"), m_properties->frames_to_time(duration - 1));
}
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::slotEnableForce(int state)
{
auto *box = qobject_cast(sender());
if (!box) {
return;
}
QString param = box->objectName();
QMap properties;
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
if (state == Qt::Unchecked) {
// The force property was disable, remove it / reset default if necessary
if (param == QLatin1String("force_duration")) {
// special case, reset original duration
auto *timePos = findChild(param + QStringLiteral("_value"));
timePos->setValue(m_properties->get_int("kdenlive:original_length"));
int original = m_properties->get_int("kdenlive:original_length");
m_properties->set("kdenlive:original_length", (char *)nullptr);
slotDurationChanged(original);
return;
}
if (param == QLatin1String("kdenlive:transparency")) {
properties.insert(param, QString());
} else if (param == QLatin1String("force_ar")) {
properties.insert(QStringLiteral("force_aspect_den"), QString());
properties.insert(QStringLiteral("force_aspect_num"), QString());
properties.insert(QStringLiteral("force_aspect_ratio"), QString());
} else if (param == QLatin1String("autorotate")) {
properties.insert(QStringLiteral("autorotate"), QString());
} else {
properties.insert(param, QString());
}
} else {
// A force property was set
if (param == QLatin1String("force_duration")) {
int original_length = m_properties->get_int("kdenlive:original_length");
if (original_length == 0) {
int kdenlive_length = m_properties->time_to_frames(m_properties->get("kdenlive:duration"));
m_properties->set("kdenlive:original_length", kdenlive_length > 0 ? m_properties->get("kdenlive:duration") : m_properties->get("length"));
}
} else if (param == QLatin1String("force_fps")) {
auto *spin = findChild(param + QStringLiteral("_value"));
if (!spin) {
return;
}
properties.insert(param, locale.toString(spin->value()));
} else if (param == QLatin1String("threads")) {
auto *spin = findChild(param + QStringLiteral("_value"));
if (!spin) {
return;
}
properties.insert(param, QString::number(spin->value()));
} else if (param == QLatin1String("force_colorspace") || param == QLatin1String("force_progressive") || param == QLatin1String("force_tff")) {
auto *combo = findChild(param + QStringLiteral("_value"));
if (!combo) {
return;
}
properties.insert(param, QString::number(combo->currentData().toInt()));
} else if (param == QLatin1String("set.force_full_luma")) {
properties.insert(param, QStringLiteral("1"));
} else if (param == QLatin1String("autorotate")) {
properties.insert(QStringLiteral("autorotate"), QStringLiteral("0"));
} else if (param == QLatin1String("force_ar")) {
auto *spin = findChild(QStringLiteral("force_aspect_num_value"));
auto *spin2 = findChild(QStringLiteral("force_aspect_den_value"));
if ((spin == nullptr) || (spin2 == nullptr)) {
return;
}
properties.insert(QStringLiteral("force_aspect_den"), QString::number(spin2->value()));
properties.insert(QStringLiteral("force_aspect_num"), QString::number(spin->value()));
properties.insert(QStringLiteral("force_aspect_ratio"), locale.toString((double)spin->value() / spin2->value()));
}
}
if (properties.isEmpty()) {
return;
}
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::slotValueChanged(double value)
{
auto *box = qobject_cast(sender());
if (!box) {
return;
}
QString param = box->objectName().section(QLatin1Char('_'), 0, -2);
QMap properties;
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
properties.insert(param, locale.toString(value));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::slotValueChanged(int value)
{
auto *box = qobject_cast(sender());
if (!box) {
return;
}
QString param = box->objectName().section(QLatin1Char('_'), 0, -2);
QMap properties;
properties.insert(param, QString::number(value));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::slotAspectValueChanged(int)
{
auto *spin = findChild(QStringLiteral("force_aspect_num_value"));
auto *spin2 = findChild(QStringLiteral("force_aspect_den_value"));
if ((spin == nullptr) || (spin2 == nullptr)) {
return;
}
QMap properties;
properties.insert(QStringLiteral("force_aspect_den"), QString::number(spin2->value()));
properties.insert(QStringLiteral("force_aspect_num"), QString::number(spin->value()));
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
properties.insert(QStringLiteral("force_aspect_ratio"), locale.toString((double)spin->value() / spin2->value()));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::slotComboValueChanged()
{
auto *box = qobject_cast(sender());
if (!box) {
return;
}
QString param = box->objectName().section(QLatin1Char('_'), 0, -2);
QMap properties;
properties.insert(param, QString::number(box->currentData().toInt()));
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::fillProperties()
{
m_clipProperties.clear();
QList propertyMap;
m_propertiesTree->setSortingEnabled(false);
#ifdef KF5_USE_FILEMETADATA
// Read File Metadata through KDE's metadata system
KFileMetaData::ExtractorCollection metaDataCollection;
QMimeDatabase mimeDatabase;
QMimeType mimeType;
mimeType = mimeDatabase.mimeTypeForFile(m_controller->clipUrl());
for (KFileMetaData::Extractor *plugin : metaDataCollection.fetchExtractors(mimeType.name())) {
ExtractionResult extractionResult(m_controller->clipUrl(), mimeType.name(), m_propertiesTree);
plugin->extract(&extractionResult);
}
#endif
// Get MLT's metadata
if (m_type == ClipType::Image) {
int width = m_sourceProperties.get_int("meta.media.width");
int height = m_sourceProperties.get_int("meta.media.height");
propertyMap.append(QStringList() << i18n("Image size") << QString::number(width) + QLatin1Char('x') + QString::number(height));
}
if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Audio) {
int vindex = m_sourceProperties.get_int("video_index");
int default_audio = m_sourceProperties.get_int("audio_index");
// Find maximum stream index values
m_videoStreams.clear();
int aStreams = m_sourceProperties.get_int("meta.media.nb_streams");
for (int ix = 0; ix < aStreams; ++ix) {
char property[200];
snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
QString type = m_sourceProperties.get(property);
if (type == QLatin1String("video")) {
m_videoStreams << ix;
}
}
m_clipProperties.insert(QStringLiteral("default_video"), QString::number(vindex));
m_clipProperties.insert(QStringLiteral("default_audio"), QString::number(default_audio));
if (vindex > -1) {
// We have a video stream
QString codecInfo = QString("meta.media.%1.codec.").arg(vindex);
QString streamInfo = QString("meta.media.%1.stream.").arg(vindex);
QString property = codecInfo + QStringLiteral("long_name");
QString codec = m_sourceProperties.get(property.toUtf8().constData());
if (!codec.isEmpty()) {
propertyMap.append({i18n("Video codec"), codec});
}
int width = m_sourceProperties.get_int("meta.media.width");
int height = m_sourceProperties.get_int("meta.media.height");
propertyMap.append({i18n("Frame size"), QString::number(width) + QLatin1Char('x') + QString::number(height)});
property = streamInfo + QStringLiteral("frame_rate");
QString fpsValue = m_sourceProperties.get(property.toUtf8().constData());
if (!fpsValue.isEmpty()) {
propertyMap.append({i18n("Frame rate"), fpsValue});
} else {
int rate_den = m_sourceProperties.get_int("meta.media.frame_rate_den");
if (rate_den > 0) {
double fps = ((double)m_sourceProperties.get_int("meta.media.frame_rate_num")) / rate_den;
propertyMap.append({i18n("Frame rate"), QString::number(fps, 'f', 2)});
}
}
property = codecInfo + QStringLiteral("bit_rate");
int bitrate = m_sourceProperties.get_int(property.toUtf8().constData()) / 1000;
if (bitrate > 0) {
propertyMap.append({i18n("Video bitrate"), QString::number(bitrate) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")});
}
int scan = m_sourceProperties.get_int("meta.media.progressive");
propertyMap.append({i18n("Scanning"), (scan == 1 ? i18n("Progressive") : i18n("Interlaced"))});
property = codecInfo + QStringLiteral("sample_aspect_ratio");
double par = m_sourceProperties.get_double(property.toUtf8().constData());
if (qFuzzyIsNull(par)) {
// Read media aspect ratio
par = m_sourceProperties.get_double("aspect_ratio");
}
propertyMap.append({i18n("Pixel aspect ratio"), QString::number(par, 'f', 3)});
property = codecInfo + QStringLiteral("pix_fmt");
propertyMap.append({i18n("Pixel format"), m_sourceProperties.get(property.toUtf8().constData())});
property = codecInfo + QStringLiteral("colorspace");
int colorspace = m_sourceProperties.get_int(property.toUtf8().constData());
propertyMap.append({i18n("Colorspace"), ProfileRepository::getColorspaceDescription(colorspace)});
}
if (default_audio > -1) {
propertyMap.append({i18n("Audio streams"), QString::number(m_controller->audioStreamsCount())});
QString codecInfo = QString("meta.media.%1.codec.").arg(default_audio);
QString property = codecInfo + QStringLiteral("long_name");
QString codec = m_sourceProperties.get(property.toUtf8().constData());
if (!codec.isEmpty()) {
propertyMap.append({i18n("Audio codec"), codec});
}
property = codecInfo + QStringLiteral("channels");
int channels = m_sourceProperties.get_int(property.toUtf8().constData());
propertyMap.append({i18n("Audio channels"), QString::number(channels)});
property = codecInfo + QStringLiteral("sample_rate");
int srate = m_sourceProperties.get_int(property.toUtf8().constData());
propertyMap.append({i18n("Audio frequency"), QString::number(srate) + QLatin1Char(' ') + i18nc("Herz", "Hz")});
property = codecInfo + QStringLiteral("bit_rate");
int bitrate = m_sourceProperties.get_int(property.toUtf8().constData()) / 1000;
if (bitrate > 0) {
propertyMap.append({i18n("Audio bitrate"), QString::number(bitrate) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")});
}
}
}
qint64 filesize = m_sourceProperties.get_int64("kdenlive:file_size");
if (filesize > 0) {
QLocale locale(QLocale::system()); // use the user's locale for getting proper separators!
propertyMap.append({i18n("File size"), KIO::convertSize((size_t)filesize) + QStringLiteral(" (") + locale.toString(filesize) + QLatin1Char(')')});
}
for (int i = 0; i < propertyMap.count(); i++) {
auto *item = new QTreeWidgetItem(m_propertiesTree, propertyMap.at(i));
item->setToolTip(1, propertyMap.at(i).at(1));
}
m_propertiesTree->setSortingEnabled(true);
m_propertiesTree->resizeColumnToContents(0);
}
void ClipPropertiesController::slotSeekToMarker()
{
auto markerModel = m_controller->getMarkerModel();
auto current = m_markerTree->currentIndex();
if (!current.isValid()) return;
GenTime pos(markerModel->data(current, MarkerListModel::PosRole).toDouble());
emit seekToFrame(pos.frames(pCore->getCurrentFps()));
}
void ClipPropertiesController::slotEditMarker()
{
auto markerModel = m_controller->getMarkerModel();
auto current = m_markerTree->currentIndex();
if (!current.isValid()) return;
GenTime pos(markerModel->data(current, MarkerListModel::PosRole).toDouble());
markerModel->editMarkerGui(pos, this, false, m_controller);
}
void ClipPropertiesController::slotDeleteMarker()
{
auto markerModel = m_controller->getMarkerModel();
QModelIndexList indexes = m_markerTree->selectionModel()->selectedIndexes();
QList positions;
for (auto &ix : indexes) {
if (ix.isValid()) {
positions << GenTime(markerModel->data(ix, MarkerListModel::PosRole).toDouble());
}
}
if (!positions.isEmpty()) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
for (GenTime pos : positions) {
markerModel->removeMarker(pos, undo, redo);
}
pCore->pushUndo(undo, redo, i18n("Delete marker"));
}
}
void ClipPropertiesController::slotAddMarker()
{
auto markerModel = m_controller->getMarkerModel();
GenTime pos(m_controller->originalProducer()->position(), m_tc.fps());
markerModel->editMarkerGui(pos, this, true, m_controller, true);
}
void ClipPropertiesController::slotSaveMarkers()
{
QScopedPointer fd(new QFileDialog(this, i18n("Save Clip Markers"), pCore->projectManager()->current()->projectDataFolder()));
fd->setMimeTypeFilters(QStringList() << QStringLiteral("application/json") << QStringLiteral("text/plain"));
fd->setFileMode(QFileDialog::AnyFile);
fd->setAcceptMode(QFileDialog::AcceptSave);
if (fd->exec() != QDialog::Accepted) {
return;
}
QStringList selection = fd->selectedFiles();
QString url;
if (!selection.isEmpty()) {
url = selection.first();
}
if (url.isEmpty()) {
return;
}
QFile file(url);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::error(this, i18n("Cannot open file %1", QUrl::fromLocalFile(url).fileName()));
return;
}
file.write(m_controller->getMarkerModel()->toJson().toUtf8());
file.close();
}
void ClipPropertiesController::slotLoadMarkers()
{
QScopedPointer fd(new QFileDialog(this, i18n("Load Clip Markers"), pCore->projectManager()->current()->projectDataFolder()));
fd->setMimeTypeFilters(QStringList() << QStringLiteral("application/json") << QStringLiteral("text/plain"));
fd->setFileMode(QFileDialog::ExistingFile);
if (fd->exec() != QDialog::Accepted) {
return;
}
QStringList selection = fd->selectedFiles();
QString url;
if (!selection.isEmpty()) {
url = selection.first();
}
if (url.isEmpty()) {
return;
}
QFile file(url);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
KMessageBox::error(this, i18n("Cannot open file %1", QUrl::fromLocalFile(url).fileName()));
return;
}
QString fileContent = QString::fromUtf8(file.readAll());
file.close();
bool res = m_controller->getMarkerModel()->importFromJson(fileContent, false);
if (!res) {
KMessageBox::error(this, i18n("An error occurred while parsing the marker file"));
}
}
void ClipPropertiesController::slotFillMeta(QTreeWidget *tree)
{
tree->clear();
if (m_type != ClipType::AV && m_type != ClipType::Video && m_type != ClipType::Image) {
// Currently, we only use exiftool on video files
return;
}
int exifUsed = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:exiftool"));
if (exifUsed == 1) {
Mlt::Properties subProperties;
subProperties.pass_values(*m_properties, "kdenlive:meta.exiftool.");
if (subProperties.count() > 0) {
QTreeWidgetItem *exif = new QTreeWidgetItem(tree, QStringList() << i18n("Exif") << QString());
exif->setExpanded(true);
for (int i = 0; i < subProperties.count(); i++) {
new QTreeWidgetItem(exif, QStringList() << subProperties.get_name(i) << subProperties.get(i));
}
}
} else if (KdenliveSettings::use_exiftool()) {
QString url = m_controller->clipUrl();
// Check for Canon THM file
url = url.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".THM");
if (QFile::exists(url)) {
// Read the exif metadata embedded in the THM file
QProcess p;
QStringList args;
args << QStringLiteral("-g") << QStringLiteral("-args") << url;
p.start(QStringLiteral("exiftool"), args);
p.waitForFinished();
QString res = p.readAllStandardOutput();
m_controller->setProducerProperty(QStringLiteral("kdenlive:exiftool"), 1);
QTreeWidgetItem *exif = nullptr;
QStringList list = res.split(QLatin1Char('\n'));
for (const QString &tagline : list) {
if (tagline.startsWith(QLatin1String("-File")) || tagline.startsWith(QLatin1String("-ExifTool"))) {
continue;
}
QString tag = tagline.section(QLatin1Char(':'), 1).simplified();
if (tag.startsWith(QLatin1String("ImageWidth")) || tag.startsWith(QLatin1String("ImageHeight"))) {
continue;
}
if (!tag.section(QLatin1Char('='), 0, 0).isEmpty() && !tag.section(QLatin1Char('='), 1).simplified().isEmpty()) {
if (!exif) {
exif = new QTreeWidgetItem(tree, QStringList() << i18n("Exif") << QString());
exif->setExpanded(true);
}
m_controller->setProducerProperty("kdenlive:meta.exiftool." + tag.section(QLatin1Char('='), 0, 0),
tag.section(QLatin1Char('='), 1).simplified());
new QTreeWidgetItem(exif, QStringList() << tag.section(QLatin1Char('='), 0, 0) << tag.section(QLatin1Char('='), 1).simplified());
}
}
} else {
if (m_type == ClipType::Image || m_controller->codec(false) == QLatin1String("h264")) {
QProcess p;
QStringList args;
args << QStringLiteral("-g") << QStringLiteral("-args") << m_controller->clipUrl();
p.start(QStringLiteral("exiftool"), args);
p.waitForFinished();
QString res = p.readAllStandardOutput();
if (m_type != ClipType::Image) {
m_controller->setProducerProperty(QStringLiteral("kdenlive:exiftool"), 1);
}
QTreeWidgetItem *exif = nullptr;
QStringList list = res.split(QLatin1Char('\n'));
for (const QString &tagline : list) {
if (m_type != ClipType::Image && !tagline.startsWith(QLatin1String("-H264"))) {
continue;
}
QString tag = tagline.section(QLatin1Char(':'), 1);
if (tag.startsWith(QLatin1String("ImageWidth")) || tag.startsWith(QLatin1String("ImageHeight"))) {
continue;
}
if (!exif) {
exif = new QTreeWidgetItem(tree, QStringList() << i18n("Exif") << QString());
exif->setExpanded(true);
}
if (m_type != ClipType::Image) {
// Do not store image exif metadata in project file, would be too much noise
m_controller->setProducerProperty("kdenlive:meta.exiftool." + tag.section(QLatin1Char('='), 0, 0),
tag.section(QLatin1Char('='), 1).simplified());
}
new QTreeWidgetItem(exif, QStringList() << tag.section(QLatin1Char('='), 0, 0) << tag.section(QLatin1Char('='), 1).simplified());
}
}
}
}
int magic = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:magiclantern"));
if (magic == 1) {
Mlt::Properties subProperties;
subProperties.pass_values(*m_properties, "kdenlive:meta.magiclantern.");
QTreeWidgetItem *magicL = nullptr;
for (int i = 0; i < subProperties.count(); i++) {
if (!magicL) {
magicL = new QTreeWidgetItem(tree, QStringList() << i18n("Magic Lantern") << QString());
QIcon icon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("meta_magiclantern.png")));
magicL->setIcon(0, icon);
magicL->setExpanded(true);
}
new QTreeWidgetItem(magicL, QStringList() << subProperties.get_name(i) << subProperties.get(i));
}
} else if (m_type != ClipType::Image && KdenliveSettings::use_magicLantern()) {
QString url = m_controller->clipUrl();
url = url.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".LOG");
if (QFile::exists(url)) {
QFile file(url);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
m_controller->setProducerProperty(QStringLiteral("kdenlive:magiclantern"), 1);
QTreeWidgetItem *magicL = nullptr;
while (!file.atEnd()) {
QString line = file.readLine().simplified();
if (line.startsWith('#') || line.isEmpty() || !line.contains(QLatin1Char(':'))) {
continue;
}
if (line.startsWith(QLatin1String("CSV data"))) {
break;
}
m_controller->setProducerProperty("kdenlive:meta.magiclantern." + line.section(QLatin1Char(':'), 0, 0).simplified(),
line.section(QLatin1Char(':'), 1).simplified());
if (!magicL) {
magicL = new QTreeWidgetItem(tree, QStringList() << i18n("Magic Lantern") << QString());
QIcon icon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("meta_magiclantern.png")));
magicL->setIcon(0, icon);
magicL->setExpanded(true);
}
new QTreeWidgetItem(magicL, QStringList()
<< line.section(QLatin1Char(':'), 0, 0).simplified() << line.section(QLatin1Char(':'), 1).simplified());
}
}
}
// if (!meta.isEmpty())
// clip->setMetadata(meta, "Magic Lantern");
// clip->setProperty("magiclantern", "1");
}
tree->resizeColumnToContents(0);
}
void ClipPropertiesController::slotFillAnalysisData()
{
m_analysisTree->clear();
Mlt::Properties subProperties;
subProperties.pass_values(*m_properties, "kdenlive:clipanalysis.");
if (subProperties.count() > 0) {
for (int i = 0; i < subProperties.count(); i++) {
new QTreeWidgetItem(m_analysisTree, QStringList() << subProperties.get_name(i) << subProperties.get(i));
}
}
m_analysisTree->resizeColumnToContents(0);
}
void ClipPropertiesController::slotDeleteAnalysis()
{
QTreeWidgetItem *current = m_analysisTree->currentItem();
if (!current) {
return;
}
emit editAnalysis(m_id, "kdenlive:clipanalysis." + current->text(0), QString());
}
void ClipPropertiesController::slotSaveAnalysis()
{
const QString url =
QFileDialog::getSaveFileName(this, i18n("Save Analysis Data"), QFileInfo(m_controller->clipUrl()).absolutePath(), i18n("Text File (*.txt)"));
if (url.isEmpty()) {
return;
}
KSharedConfigPtr config = KSharedConfig::openConfig(url, KConfig::SimpleConfig);
KConfigGroup analysisConfig(config, "Analysis");
QTreeWidgetItem *current = m_analysisTree->currentItem();
analysisConfig.writeEntry(current->text(0), current->text(1));
}
void ClipPropertiesController::slotLoadAnalysis()
{
const QString url =
QFileDialog::getOpenFileName(this, i18n("Open Analysis Data"), QFileInfo(m_controller->clipUrl()).absolutePath(), i18n("Text File (*.txt)"));
if (url.isEmpty()) {
return;
}
KSharedConfigPtr config = KSharedConfig::openConfig(url, KConfig::SimpleConfig);
KConfigGroup transConfig(config, "Analysis");
// read the entries
QMap profiles = transConfig.entryMap();
QMapIterator i(profiles);
while (i.hasNext()) {
i.next();
emit editAnalysis(m_id, "kdenlive:clipanalysis." + i.key(), i.value());
}
}
void ClipPropertiesController::slotTextChanged()
{
QMap properties;
properties.insert(QStringLiteral("templatetext"), m_textEdit->toPlainText());
emit updateClipProperties(m_id, m_originalProperties, properties);
m_originalProperties = properties;
}
void ClipPropertiesController::slotDeleteSelectedMarkers()
{
if (m_tabWidget->currentIndex() == 3) {
slotDeleteMarker();
}
}
void ClipPropertiesController::slotSelectAllMarkers()
{
if (m_tabWidget->currentIndex() == 3) {
m_markerTree->selectAll();
}
}
diff --git a/src/ui/configtimeline_ui.ui b/src/ui/configtimeline_ui.ui
index cad9bb59f..e2731e2b2 100644
--- a/src/ui/configtimeline_ui.ui
+++ b/src/ui/configtimeline_ui.ui
@@ -1,211 +1,237 @@
ConfigTimeline_UI
0
0
- 391
- 450
+ 488
+ 460
- -
-
-
- Autoscroll while playing
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 23
-
-
-
-
- -
-
-
- Zoom using vertical drag in ruler
-
-
-
- -
-
-
- Display clip markers comments
-
-
-
-
Use FFmpeg for audio thumbnails (faster)
-
-
Track height
-
10
999
10
-
Qt::Horizontal
40
20
- -
+
-
+
+
+ Create new transitions as automatic transitions
+
+
+
+ -
Raise properties pane when selecting in timeline
-
Clips
-
Transitions
-
Tracks
- -
-
-
- Create new transitions as automatic transitions
-
-
-
-
Thumbnails
-
Video
-
-
Audio
-
false
Separate channels
-
Qt::Horizontal
40
20
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 23
+
+
+
+
+ -
+
+
+ Zoom using vertical drag in ruler
+
+
+
+ -
+
+
+ Autoscroll while playing
+
+
+
+ -
+
+
+ Display clip markers comments
+
+
+
+ -
+
+
+ For clips with multiple audio streams, enable
+
+
+
+ -
+
+ -
+
+ All streams
+
+
+ -
+
+ First stream
+
+
+ -
+
+ First 2 streams
+
+
+
+
kcfg_videothumbnails
kcfg_audiothumbnails
kcfg_displayallchannels
kcfg_ffmpegaudiothumbnails
kcfg_showmarkers
kcfg_autoscroll
kcfg_verticalzoom
kcfg_automatictransitions
kcfg_trackheight
kcfg_audiothumbnails
toggled(bool)
kcfg_displayallchannels
setEnabled(bool)
35
66
105
66
diff --git a/src/utils/thumbnailcache.cpp b/src/utils/thumbnailcache.cpp
index 21eb8fb66..4acc55f63 100644
--- a/src/utils/thumbnailcache.cpp
+++ b/src/utils/thumbnailcache.cpp
@@ -1,325 +1,325 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "thumbnailcache.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include
#include
#include
std::unique_ptr ThumbnailCache::instance;
std::once_flag ThumbnailCache::m_onceFlag;
class ThumbnailCache::Cache_t
{
public:
Cache_t(int maxCost)
: m_maxCost(maxCost)
{
}
bool contains(const QString &key) const { return m_cache.count(key) > 0; }
void remove(const QString &key)
{
if (!contains(key)) {
return;
}
auto it = m_cache.at(key);
m_currentCost -= (*it).second.second;
m_data.erase(it);
m_cache.erase(key);
}
void insert(const QString &key, const QImage &img, int cost)
{
if (cost > m_maxCost) {
return;
}
m_data.push_front({key, {img, cost}});
auto it = m_data.begin();
m_cache[key] = it;
m_currentCost += cost;
while (m_currentCost > m_maxCost) {
remove(m_data.back().first);
}
}
QImage get(const QString &key)
{
if (!contains(key)) {
return QImage();
}
// when a get operation occurs, we put the corresponding list item in front to remember last access
std::pair> data;
auto it = m_cache.at(key);
std::swap(data, (*it)); // take data out without copy
QImage result = data.second.first; // a copy occurs here
m_data.erase(it); // delete old iterator
m_cache[key] = m_data.emplace(m_data.begin(), std::move(data)); // reinsert without copy and store iterator
return result;
}
void clear()
{
m_data.clear();
m_cache.clear();
m_currentCost = 0;
}
protected:
int m_maxCost;
int m_currentCost{0};
std::list>> m_data; // the data is stored as (key,(image, cost))
std::unordered_map m_cache;
};
ThumbnailCache::ThumbnailCache()
: m_volatileCache(new Cache_t(10000000))
{
}
std::unique_ptr &ThumbnailCache::get()
{
std::call_once(m_onceFlag, [] { instance.reset(new ThumbnailCache()); });
return instance;
}
bool ThumbnailCache::hasThumbnail(const QString &binId, int pos, bool volatileOnly) const
{
QMutexLocker locker(&m_mutex);
bool ok = false;
auto key = pos < 0 ? getAudioKey(binId, &ok).first() : getKey(binId, pos, &ok);
if (ok && m_volatileCache->contains(key)) {
return true;
}
if (!ok || volatileOnly) {
return false;
}
QDir thumbFolder = getDir(pos < 0, &ok);
return ok && thumbFolder.exists(key);
}
QImage ThumbnailCache::getAudioThumbnail(const QString &binId, bool volatileOnly) const
{
QMutexLocker locker(&m_mutex);
bool ok = false;
auto key = getAudioKey(binId, &ok).first();
if (ok && m_volatileCache->contains(key)) {
return m_volatileCache->get(key);
}
if (!ok || volatileOnly) {
return QImage();
}
QDir thumbFolder = getDir(true, &ok);
if (ok && thumbFolder.exists(key)) {
m_storedOnDisk[binId].push_back(-1);
return QImage(thumbFolder.absoluteFilePath(key));
}
return QImage();
}
const QList ThumbnailCache::getAudioThumbPath(const QString &binId) const
{
QMutexLocker locker(&m_mutex);
bool ok = false;
auto key = getAudioKey(binId, &ok);
QDir thumbFolder = getDir(true, &ok);
QList pathList;
if (ok) {
for (const QString &p : key) {
if (thumbFolder.exists(p)) {
pathList <contains(key)) {
return m_volatileCache->get(key);
}
if (!ok || volatileOnly) {
return QImage();
}
QDir thumbFolder = getDir(false, &ok);
if (ok && thumbFolder.exists(key)) {
m_storedOnDisk[binId].push_back(pos);
return QImage(thumbFolder.absoluteFilePath(key));
}
return QImage();
}
void ThumbnailCache::storeThumbnail(const QString &binId, int pos, const QImage &img, bool persistent)
{
QMutexLocker locker(&m_mutex);
bool ok = false;
const QString key = getKey(binId, pos, &ok);
if (!ok) {
return;
}
if (persistent) {
QDir thumbFolder = getDir(false, &ok);
if (ok) {
if (!img.save(thumbFolder.absoluteFilePath(key))) {
qDebug() << ".............\n!!!!!!!! ERROR SAVING THUMB in: "<contains(key)) {
m_volatileCache->remove(key);
} else {
m_storedVolatile[binId].push_back(pos);
}
m_volatileCache->insert(key, img, (int)img.sizeInBytes());
}
} else {
m_volatileCache->insert(key, img, (int)img.sizeInBytes());
m_storedVolatile[binId].push_back(pos);
}
}
void ThumbnailCache::saveCachedThumbs(QStringList keys)
{
bool ok;
QDir thumbFolder = getDir(false, &ok);
if (!ok) {
return;
}
for (const QString &key : keys) {
if (!thumbFolder.exists(key) && m_volatileCache->contains(key)) {
QImage img = m_volatileCache->get(key);
if (!img.save(thumbFolder.absoluteFilePath(key))) {
qDebug() << "// Error writing thumbnails to " << thumbFolder.absolutePath();
break;
}
}
}
}
void ThumbnailCache::invalidateThumbsForClip(const QString &binId, bool reloadAudio)
{
QMutexLocker locker(&m_mutex);
if (m_storedVolatile.find(binId) != m_storedVolatile.end()) {
bool ok = false;
for (int pos : m_storedVolatile.at(binId)) {
auto key = getKey(binId, pos, &ok);
if (ok) {
m_volatileCache->remove(key);
}
}
m_storedVolatile.erase(binId);
}
bool ok = false;
// Video thumbs
QDir thumbFolder = getDir(false, &ok);
QDir audioThumbFolder = getDir(true, &ok);
if (ok && m_storedOnDisk.find(binId) != m_storedOnDisk.end()) {
// Remove persistent cache
for (int pos : m_storedOnDisk.at(binId)) {
if (pos < 0) {
if (reloadAudio) {
auto key = getAudioKey(binId, &ok);
if (ok) {
for (const QString &p : key) {
QFile::remove(audioThumbFolder.absoluteFilePath(p));
}
}
}
} else {
auto key = getKey(binId, pos, &ok);
if (ok) {
QFile::remove(thumbFolder.absoluteFilePath(key));
}
}
}
m_storedOnDisk.erase(binId);
}
}
void ThumbnailCache::clearCache()
{
QMutexLocker locker(&m_mutex);
m_volatileCache->clear();
m_storedVolatile.clear();
m_storedOnDisk.clear();
}
// static
QString ThumbnailCache::getKey(const QString &binId, int pos, bool *ok)
{
if (binId.isEmpty()) {
*ok = false;
return QString();
}
auto binClip = pCore->projectItemModel()->getClipByBinID(binId);
*ok = binClip != nullptr;
return *ok ? binClip->hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".png") : QString();
}
// static
QStringList ThumbnailCache::getAudioKey(const QString &binId, bool *ok)
{
auto binClip = pCore->projectItemModel()->getClipByBinID(binId);
*ok = binClip != nullptr;
if (ok) {
QString streams = binClip->getProducerProperty(QStringLiteral("kdenlive:active_streams"));
- if (streams.isEmpty() || streams == QString::number(INT_MAX)) {
+ if (streams == QString::number(INT_MAX)) {
// activate all audio streams
QList streamIxes = binClip->audioStreams().keys();
if (streamIxes.size() > 1) {
QStringList streamsList;
for (const int st : streamIxes) {
streamsList << QString("%1_%2.png").arg(binClip->hash()).arg(st);
}
return streamsList;
}
}
if (streams.size() < 2) {
int audio = binClip->getProducerIntProperty(QStringLiteral("audio_index"));
if (audio > -1) {
return {QString("%1_%2.png").arg(binClip->hash()).arg(audio)};
}
return {binClip->hash() + QStringLiteral(".png")};
}
QStringList streamsList;
QStringList streamIndexes = streams.split(QLatin1Char(';'));
for (const QString st : streamIndexes) {
streamsList << QString("%1_%2.png").arg(binClip->hash()).arg(st);
}
return streamsList;
}
return {};
}
// static
QDir ThumbnailCache::getDir(bool audio, bool *ok)
{
return pCore->currentDoc()->getCacheDir(audio ? CacheAudio : CacheThumbs, ok);
}