[krita/krita/3.1] /: Implement Audio Channel support
Dmitry Kazakov
null at kde.org
Wed Jan 4 12:00:03 UTC 2017
Git commit 2e88c449097d5826d0ee4c344b168ede1ae6c105 by Dmitry Kazakov.
Committed on 04/01/2017 at 11:59.
Pushed by dkazakov into branch 'krita/3.1'.
Implement Audio Channel support
It is quite primitive yet (it doesn't have any visualisation), but it
works! Just select the file using a button on the timeline and it'll
work fine: with both playback and scrubbing.
TODO: icons for the button!
CC:kimageshop at kde.org
Conflicts:
plugins/dockers/animation/timeline_frames_model.h
plugins/dockers/animation/timeline_frames_view.cpp
M +5 -0 CMakeLists.txt
A +4 -0 config-qtmultimedia.h.cmake
M +33 -0 libs/image/kis_image_animation_interface.cpp
M +26 -0 libs/image/kis_image_animation_interface.h
M +6 -0 libs/ui/CMakeLists.txt
A +107 -0 libs/ui/KisSyncedAudioPlayback.cpp [License: UNKNOWN] *
A +29 -0 libs/ui/KisSyncedAudioPlayback.h [License: UNKNOWN] *
M +77 -0 libs/ui/canvas/kis_animation_player.cpp
M +6 -0 libs/ui/canvas/kis_animation_player.h
M +10 -0 libs/ui/kis_config.cc
M +3 -0 libs/ui/kis_config.h
M +27 -0 libs/ui/kra/kis_kra_loader.cpp
M +1 -0 libs/ui/kra/kis_kra_loader.h
M +29 -0 libs/ui/kra/kis_kra_saver.cpp
M +1 -0 libs/ui/kra/kis_kra_saver.h
M +4 -1 plugins/dockers/animation/kis_time_based_item_model.cpp
M +26 -0 plugins/dockers/animation/timeline_frames_model.cpp
M +7 -0 plugins/dockers/animation/timeline_frames_model.h
M +118 -0 plugins/dockers/animation/timeline_frames_view.cpp
M +5 -0 plugins/dockers/animation/timeline_frames_view.h
The files marked with a * at the end have a non valid license. Please read: http://techbase.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.
https://commits.kde.org/krita/2e88c449097d5826d0ee4c344b168ede1ae6c105
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b383517eb12..693d5e0a716 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -242,6 +242,8 @@ find_package(Qt5 ${MIN_QT_VERSION}
Svg
Test
Concurrent
+ OPTIONAL_COMPONENTS
+ Multimedia
)
@@ -292,6 +294,9 @@ else()
set(HAVE_XCB FALSE)
endif()
+macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA)
+configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h )
+
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_STRICT_ITERATORS
diff --git a/config-qtmultimedia.h.cmake b/config-qtmultimedia.h.cmake
new file mode 100644
index 00000000000..ca655089f5f
--- /dev/null
+++ b/config-qtmultimedia.h.cmake
@@ -0,0 +1,4 @@
+/* config-qtmultimedia.h. Generated by cmake from config-gsl.h.cmake */
+
+/* Defines if you have Qt Multimedia component */
+#cmakedefine HAVE_QT_MULTIMEDIA 1
diff --git a/libs/image/kis_image_animation_interface.cpp b/libs/image/kis_image_animation_interface.cpp
index 09cdc21f3ec..7a2ba32fae5 100644
--- a/libs/image/kis_image_animation_interface.cpp
+++ b/libs/image/kis_image_animation_interface.cpp
@@ -18,6 +18,8 @@
#include "kis_image_animation_interface.h"
+#include <QFileInfo>
+
#include "kis_global.h"
#include "kis_image.h"
#include "kis_regenerate_frame_stroke_strategy.h"
@@ -37,6 +39,7 @@ struct KisImageAnimationInterface::Private
externalFrameActive(false),
frameInvalidationBlocked(false),
cachedLastFrameValue(-1),
+ audioChannelMuted(false),
m_currentTime(0),
m_currentUITime(0)
{
@@ -50,6 +53,8 @@ struct KisImageAnimationInterface::Private
playbackRange(rhs.playbackRange),
framerate(rhs.framerate),
cachedLastFrameValue(-1),
+ audioChannelFileName(rhs.audioChannelFileName),
+ audioChannelMuted(rhs.audioChannelMuted),
m_currentTime(rhs.m_currentTime),
m_currentUITime(rhs.m_currentUITime)
{
@@ -63,6 +68,8 @@ struct KisImageAnimationInterface::Private
KisTimeRange playbackRange;
int framerate;
int cachedLastFrameValue;
+ QString audioChannelFileName;
+ bool audioChannelMuted;
KisSwitchTimeStrokeStrategy::SharedTokenWSP switchToken;
@@ -156,6 +163,32 @@ int KisImageAnimationInterface::framerate() const
return m_d->framerate;
}
+QString KisImageAnimationInterface::audioChannelFileName() const
+{
+ return m_d->audioChannelFileName;
+}
+
+void KisImageAnimationInterface::setAudioChannelFileName(const QString &fileName)
+{
+ QFileInfo info(fileName);
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(fileName.isEmpty() || info.isAbsolute());
+ m_d->audioChannelFileName = fileName.isEmpty() ? fileName : info.absoluteFilePath();
+
+ emit sigAudioChannelChanged();
+}
+
+bool KisImageAnimationInterface::isAudioMuted() const
+{
+ return m_d->audioChannelMuted;
+}
+
+void KisImageAnimationInterface::setAudioMuted(bool value)
+{
+ m_d->audioChannelMuted = value;
+ emit sigAudioChannelChanged();
+}
+
void KisImageAnimationInterface::setFramerate(int fps)
{
m_d->framerate = fps;
diff --git a/libs/image/kis_image_animation_interface.h b/libs/image/kis_image_animation_interface.h
index b596c692924..c41b8ee0e43 100644
--- a/libs/image/kis_image_animation_interface.h
+++ b/libs/image/kis_image_animation_interface.h
@@ -122,6 +122,27 @@ public:
int framerate() const;
+ /**
+ * @return **absolute** file name of the audio channel file
+ */
+ QString audioChannelFileName() const;
+
+ /**
+ * Sets **absolute** file name of the audio channel file. Dont' try to pass
+ * a relative path, it'll assert!
+ */
+ void setAudioChannelFileName(const QString &fileName);
+
+ /**
+ * @return is the audio channel is currently muted
+ */
+ bool isAudioMuted() const;
+
+ /**
+ * Mutes the audio channel
+ */
+ void setAudioMuted(bool value);
+
public Q_SLOTS:
void setFramerate(int fps);
public:
@@ -158,6 +179,11 @@ Q_SIGNALS:
void sigFullClipRangeChanged();
void sigPlaybackRangeChanged();
+ /**
+ * Emitted when the audio channel of the document is changed
+ */
+ void sigAudioChannelChanged();
+
private:
struct Private;
const QScopedPointer<Private> m_d;
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index c871daaf8d4..bb8210831b2 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -392,6 +392,7 @@ if(WIN32)
)
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
+
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
@@ -399,6 +400,7 @@ endif()
canvas/kis_animation_player.cpp
kis_animation_exporter.cpp
kis_animation_importer.cpp
+ KisSyncedAudioPlayback.cpp
)
if(UNIX)
@@ -502,6 +504,10 @@ target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::Ite
kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES}
)
+if (HAVE_QT_MULTIMEDIA)
+ target_link_libraries(kritaui Qt5::Multimedia)
+endif()
+
if (HAVE_KIO)
target_link_libraries(kritaui KF5::KIOCore)
endif()
diff --git a/libs/ui/KisSyncedAudioPlayback.cpp b/libs/ui/KisSyncedAudioPlayback.cpp
new file mode 100644
index 00000000000..afe9785d520
--- /dev/null
+++ b/libs/ui/KisSyncedAudioPlayback.cpp
@@ -0,0 +1,107 @@
+#include "KisSyncedAudioPlayback.h"
+
+#include "config-qtmultimedia.h"
+
+#ifdef HAVE_QT_MULTIMEDIA
+#include <QtMultimedia/QMediaPlayer>
+#else
+class QIODevice;
+
+#include <QUrl>
+
+namespace {
+
+struct QMediaPlayer {
+ enum State
+ {
+ StoppedState,
+ PlayingState,
+ PausedState
+ };
+
+ State state() const { return StoppedState; }
+
+ void play() {}
+ void stop() {}
+
+ qint64 position() const { return 0; }
+ qreal playbackRate() const { return 1.0; }
+ void setPosition(qint64) {}
+ void setPlaybackRate(qreal) {}
+ void setVolume(int) {}
+ void setMedia(const QUrl&, QIODevice * device = 0) { Q_UNUSED(device);}
+};
+}
+#endif
+
+
+
+#include <QFileInfo>
+
+
+
+struct KisSyncedAudioPlayback::Private
+{
+ QMediaPlayer player;
+ qint64 tolerance = 40;
+};
+
+
+KisSyncedAudioPlayback::KisSyncedAudioPlayback(const QString &fileName)
+ : QObject(0),
+ m_d(new Private)
+{
+ QFileInfo fileInfo(fileName);
+ Q_ASSERT(fileInfo.exists());
+
+ m_d->player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ m_d->player.setVolume(50);
+}
+
+KisSyncedAudioPlayback::~KisSyncedAudioPlayback()
+{
+}
+
+void KisSyncedAudioPlayback::setSoundOffsetTolerance(qint64 value)
+{
+ m_d->tolerance = value;
+}
+
+void KisSyncedAudioPlayback::syncWithVideo(qint64 position)
+{
+ if (qAbs(position - m_d->player.position()) > m_d->tolerance) {
+ m_d->player.setPosition(position);
+ }
+}
+
+bool KisSyncedAudioPlayback::isPlaying() const
+{
+ return m_d->player.state() == QMediaPlayer::PlayingState;
+}
+
+void KisSyncedAudioPlayback::setSpeed(qreal value)
+{
+ if (qFuzzyCompare(value, m_d->player.playbackRate())) return;
+
+ if (m_d->player.state() == QMediaPlayer::PlayingState) {
+ const qint64 oldPosition = m_d->player.position();
+
+ m_d->player.stop();
+ m_d->player.setPlaybackRate(value);
+ m_d->player.setPosition(oldPosition);
+ m_d->player.play();
+ } else {
+ m_d->player.setPlaybackRate(value);
+ }
+}
+
+void KisSyncedAudioPlayback::play(qint64 startPosition)
+{
+ m_d->player.setPosition(startPosition);
+ m_d->player.play();
+}
+
+void KisSyncedAudioPlayback::stop()
+{
+ m_d->player.stop();
+}
diff --git a/libs/ui/KisSyncedAudioPlayback.h b/libs/ui/KisSyncedAudioPlayback.h
new file mode 100644
index 00000000000..77b2496c01f
--- /dev/null
+++ b/libs/ui/KisSyncedAudioPlayback.h
@@ -0,0 +1,29 @@
+#ifndef KISSYNCEDAUDIOPLAYBACK_H
+#define KISSYNCEDAUDIOPLAYBACK_H
+
+#include <QScopedPointer>
+#include <QObject>
+
+class KisSyncedAudioPlayback : public QObject
+{
+ Q_OBJECT
+public:
+ KisSyncedAudioPlayback(const QString &fileName);
+ virtual ~KisSyncedAudioPlayback();
+
+ void setSoundOffsetTolerance(qint64 value);
+ void syncWithVideo(qint64 position);
+
+ bool isPlaying() const;
+
+public Q_SLOTS:
+ void setSpeed(qreal value);
+ void play(qint64 startPosition);
+ void stop();
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISSYNCEDAUDIOPLAYBACK_H
diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp
index 116a7f35699..6a32f99df76 100644
--- a/libs/ui/canvas/kis_animation_player.cpp
+++ b/libs/ui/canvas/kis_animation_player.cpp
@@ -35,11 +35,16 @@
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_signal_compressor.h"
+#include <KisDocument.h>
+#include <QFileInfo>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/rolling_mean.hpp>
+#include "KisSyncedAudioPlayback.h"
+#include "kis_signal_compressor_with_param.h"
+
using namespace boost::accumulators;
typedef accumulator_set<qreal, stats<tag::rolling_mean> > FpsAccumulator;
@@ -95,6 +100,9 @@ public:
KisSignalCompressor playbackStatisticsCompressor;
+ QScopedPointer<KisSyncedAudioPlayback> syncedAudio;
+ QScopedPointer<KisSignalCompressorWithParam<int> > audioSyncScrubbingCompressor;
+
void stopImpl(bool doUpdates);
int incFrame(int frame, int inc) {
@@ -104,6 +112,13 @@ public:
}
return frame;
}
+
+ qint64 frameToMSec(int value) {
+ return qreal(value) / fps * 1000.0;
+ }
+ int msecToFrame(qint64 value) {
+ return qreal(value) * fps / 1000.0;
+ }
};
KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
@@ -127,6 +142,17 @@ KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
connect(&m_d->playbackStatisticsCompressor, SIGNAL(timeout()),
this, SIGNAL(sigPlaybackStatisticsUpdated()));
+
+ using namespace std::placeholders;
+ std::function<void (int)> callback(
+ std::bind(&KisAnimationPlayer::slotSyncScrubbingAudio, this, _1));
+
+ KisConfig cfg;
+ m_d->audioSyncScrubbingCompressor.reset(
+ new KisSignalCompressorWithParam<int>(cfg.scribbingAudioUpdatesDelay(), callback, KisSignalCompressor::FIRST_ACTIVE));
+
+ connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioChannelChanged()), SLOT(slotAudioChannelChanged()));
+ slotAudioChannelChanged();
}
KisAnimationPlayer::~KisAnimationPlayer()
@@ -138,6 +164,24 @@ void KisAnimationPlayer::slotUpdateDropFramesMode()
m_d->dropFramesMode = cfg.animationDropFrames();
}
+void KisAnimationPlayer::slotSyncScrubbingAudio(int msecTime)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
+ m_d->syncedAudio->syncWithVideo(msecTime);
+}
+
+void KisAnimationPlayer::slotAudioChannelChanged()
+{
+
+ QString fileName = m_d->canvas->image()->animationInterface()->audioChannelFileName();
+ QFileInfo info(fileName);
+ if (info.exists() && !m_d->canvas->image()->animationInterface()->isAudioMuted()) {
+ m_d->syncedAudio.reset(new KisSyncedAudioPlayback(info.absoluteFilePath()));
+ } else {
+ m_d->syncedAudio.reset();
+ }
+}
+
void KisAnimationPlayer::connectCancelSignals()
{
m_d->cancelStrokeConnections.addConnection(
@@ -187,6 +231,11 @@ void KisAnimationPlayer::slotUpdatePlaybackTimer()
m_d->expectedInterval = qreal(1000) / m_d->fps / m_d->playbackSpeed;
m_d->lastTimerInterval = m_d->expectedInterval;
+
+ if (m_d->syncedAudio) {
+ m_d->syncedAudio->setSpeed(m_d->playbackSpeed);
+ }
+
m_d->timer->start(m_d->expectedInterval);
if (m_d->playbackTime.isValid()) {
@@ -207,10 +256,18 @@ void KisAnimationPlayer::play()
m_d->lastPaintedFrame = m_d->firstFrame;
connectCancelSignals();
+
+ if (m_d->syncedAudio) {
+ m_d->syncedAudio->play(m_d->frameToMSec(m_d->firstFrame));
+ }
}
void KisAnimationPlayer::Private::stopImpl(bool doUpdates)
{
+ if (syncedAudio) {
+ syncedAudio->stop();
+ }
+
q->disconnectCancelSignals();
timer->stop();
@@ -258,6 +315,17 @@ void KisAnimationPlayer::slotUpdate()
uploadFrame(-1);
}
+void KisAnimationPlayer::setScrubState(bool value, int currentFrame)
+{
+ if (!m_d->syncedAudio || isPlaying()) return;
+
+ if (value) {
+ m_d->syncedAudio->play(m_d->frameToMSec(currentFrame));
+ } else {
+ m_d->syncedAudio->stop();
+ }
+}
+
void KisAnimationPlayer::uploadFrame(int frame)
{
if (frame < 0) {
@@ -287,6 +355,15 @@ void KisAnimationPlayer::uploadFrame(int frame)
m_d->playbackStatisticsCompressor.start();
}
+ if (m_d->syncedAudio) {
+ const int msecTime = m_d->frameToMSec(frame);
+ if (isPlaying()) {
+ slotSyncScrubbingAudio(msecTime);
+ } else {
+ m_d->audioSyncScrubbingCompressor->start(msecTime);
+ }
+ }
+
if (m_d->canvas->frameCache() && m_d->canvas->frameCache()->uploadFrame(frame)) {
m_d->canvas->updateCanvas();
diff --git a/libs/ui/canvas/kis_animation_player.h b/libs/ui/canvas/kis_animation_player.h
index 06e1d357168..6456e97855d 100644
--- a/libs/ui/canvas/kis_animation_player.h
+++ b/libs/ui/canvas/kis_animation_player.h
@@ -50,6 +50,8 @@ public:
qreal realFps() const;
qreal framesDroppedPortion() const;
+ void setScrubState(bool value, int currentFrame);
+
public Q_SLOTS:
void slotUpdate();
void slotCancelPlayback();
@@ -58,6 +60,10 @@ public Q_SLOTS:
void slotUpdatePlaybackTimer();
void slotUpdateDropFramesMode();
+private Q_SLOTS:
+ void slotSyncScrubbingAudio(int msecTime);
+ void slotAudioChannelChanged();
+
Q_SIGNALS:
void sigFrameChanged();
void sigPlaybackStopped();
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc
index 252791b5217..2f6fcc2fc4b 100644
--- a/libs/ui/kis_config.cc
+++ b/libs/ui/kis_config.cc
@@ -1675,6 +1675,16 @@ void KisConfig::setScribbingUpdatesDelay(int value)
m_cfg.writeEntry("scribbingUpdatesDelay", value);
}
+int KisConfig::scribbingAudioUpdatesDelay(bool defaultValue) const
+{
+ return (defaultValue ? 200 : m_cfg.readEntry("scribbingAudioUpdatesDelay", 200));
+}
+
+void KisConfig::setScribbingAudioUpdatesDelay(int value)
+{
+ m_cfg.writeEntry("scribbingAudioUpdatesDelay", value);
+}
+
bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false);
diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h
index 7736fe101d7..c852831b934 100644
--- a/libs/ui/kis_config.h
+++ b/libs/ui/kis_config.h
@@ -479,6 +479,9 @@ public:
int scribbingUpdatesDelay(bool defaultValue = false) const;
void setScribbingUpdatesDelay(int value);
+ int scribbingAudioUpdatesDelay(bool defaultValue = false) const;
+ void setScribbingAudioUpdatesDelay(int value);
+
bool switchSelectionCtrlAlt(bool defaultValue = false) const;
void setSwitchSelectionCtrlAlt(bool value);
diff --git a/libs/ui/kra/kis_kra_loader.cpp b/libs/ui/kra/kis_kra_loader.cpp
index 590acf52a59..44cbe223346 100644
--- a/libs/ui/kra/kis_kra_loader.cpp
+++ b/libs/ui/kra/kis_kra_loader.cpp
@@ -341,6 +341,8 @@ KisImageSP KisKraLoader::loadXML(const KoXmlElement& element)
loadGuides(e);
} else if (e.tagName() == "assistants") {
loadAssistantsList(e);
+ } else if (e.tagName() == "audio") {
+ loadAudio(e, image);
}
}
@@ -1102,3 +1104,28 @@ void KisKraLoader::loadGuides(const KoXmlElement& elem)
guides.loadFromXml(domElement);
m_d->document->setGuidesConfig(guides);
}
+
+void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image)
+{
+ QDomDocument dom;
+ KoXml::asQDomElement(dom, elem);
+ QDomElement qElement = dom.firstChildElement();
+
+ QString fileName;
+ if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) {
+ fileName = QDir::toNativeSeparators(fileName);
+
+ QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir();
+ qDebug() << ppVar(baseDirectory);
+
+ fileName = baseDirectory.absoluteFilePath(fileName);
+
+ QFileInfo info(fileName);
+
+ if (!info.exists()) {
+ m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName);
+ } else {
+ image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath());
+ }
+ }
+}
diff --git a/libs/ui/kra/kis_kra_loader.h b/libs/ui/kra/kis_kra_loader.h
index a4e756aacc0..968052103b9 100644
--- a/libs/ui/kra/kis_kra_loader.h
+++ b/libs/ui/kra/kis_kra_loader.h
@@ -103,6 +103,7 @@ private:
void loadAssistantsList(const KoXmlElement& elem);
void loadGrid(const KoXmlElement& elem);
void loadGuides(const KoXmlElement& elem);
+ void loadAudio(const KoXmlElement& elem, KisImageSP image);
private:
struct Private;
diff --git a/libs/ui/kra/kis_kra_saver.cpp b/libs/ui/kra/kis_kra_saver.cpp
index 535806efc82..7dee8b80202 100644
--- a/libs/ui/kra/kis_kra_saver.cpp
+++ b/libs/ui/kra/kis_kra_saver.cpp
@@ -57,6 +57,9 @@
#include "kis_guides_config.h"
#include "KisProofingConfiguration.h"
+#include <QFileInfo>
+#include <QDir>
+
using namespace KRA;
@@ -126,6 +129,7 @@ QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageWSP image)
saveAssistantsList(doc,imageElement);
saveGrid(doc,imageElement);
saveGuides(doc,imageElement);
+ saveAudio(doc,imageElement);
QDomElement animationElement = doc.createElement("animation");
KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate());
@@ -418,3 +422,28 @@ bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element)
return true;
}
+bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element)
+{
+ QString fileName = m_d->doc->image()->animationInterface()->audioChannelFileName();
+ if (fileName.isEmpty()) return true;
+
+ if (!QFileInfo::exists(fileName)) {
+ m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName);
+ return false;
+ }
+
+ const QDir documentDir = QFileInfo(m_d->doc->localFilePath()).absoluteDir();
+ KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false);
+
+ fileName = documentDir.relativeFilePath(fileName);
+ fileName = QDir::fromNativeSeparators(fileName);
+
+ KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false);
+
+ QDomElement audioElement = doc.createElement("audio");
+ KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName);
+ element.appendChild(audioElement);
+
+ return true;
+}
+
diff --git a/libs/ui/kra/kis_kra_saver.h b/libs/ui/kra/kis_kra_saver.h
index 2f8ef5b7919..44af477c3b8 100644
--- a/libs/ui/kra/kis_kra_saver.h
+++ b/libs/ui/kra/kis_kra_saver.h
@@ -53,6 +53,7 @@ private:
bool saveAssistantsList(QDomDocument& doc, QDomElement& element);
bool saveGrid(QDomDocument& doc, QDomElement& element);
bool saveGuides(QDomDocument& doc, QDomElement& element);
+ bool saveAudio(QDomDocument& doc, QDomElement& element);
bool saveNodeKeyframes(KoStore *store, QString location, const KisNode *node);
struct Private;
Private * const m_d;
diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp
index 99ff688ca15..620cb9512f5 100644
--- a/plugins/dockers/animation/kis_time_based_item_model.cpp
+++ b/plugins/dockers/animation/kis_time_based_item_model.cpp
@@ -54,7 +54,6 @@ struct KisTimeBasedItemModel::Private
QScopedPointer<KisSignalCompressorWithParam<int> > scrubbingCompressor;
-
int baseNumFrames() const {
if (image.isNull()) return 0;
@@ -345,6 +344,10 @@ void KisTimeBasedItemModel::setScrubState(bool active)
m_d->scrubInProgress = true;
}
+ if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) {
+ m_d->animationPlayer->setScrubState(active, m_d->activeFrameIndex);
+ }
+
if (m_d->scrubInProgress && !active) {
m_d->scrubInProgress = false;
diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp
index dc5d4ef97ea..704a7a3e67f 100644
--- a/plugins/dockers/animation/timeline_frames_model.cpp
+++ b/plugins/dockers/animation/timeline_frames_model.cpp
@@ -220,6 +220,7 @@ void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade,
KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade;
if (m_d->dummiesFacade) {
+ m_d->image->animationInterface()->disconnect(this);
m_d->image->disconnect(this);
m_d->dummiesFacade->disconnect(this);
}
@@ -236,6 +237,8 @@ void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade,
SLOT(slotDummyChanged(KisNodeDummy*)));
connect(m_d->image->animationInterface(),
SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded()));
+ connect(m_d->image->animationInterface(),
+ SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged()));
}
if (m_d->dummiesFacade != oldDummiesFacade) {
@@ -244,6 +247,7 @@ void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade,
if (m_d->dummiesFacade) {
emit sigInfiniteTimelineUpdateNeeded();
+ emit sigAudioChannelChanged();
}
}
@@ -644,3 +648,25 @@ bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex)
return result;
}
+
+QString TimelineFramesModel::audioChannelFileName() const
+{
+ return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString();
+}
+
+void TimelineFramesModel::setAudioChannelFileName(const QString &fileName)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image);
+ m_d->image->animationInterface()->setAudioChannelFileName(fileName);
+}
+
+bool TimelineFramesModel::isAudioMuted() const
+{
+ return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false;
+}
+
+void TimelineFramesModel::setAudioMuted(bool value)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image);
+ m_d->image->animationInterface()->setAudioMuted(value);
+}
diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h
index 101326bf3be..3269c17bc10 100644
--- a/plugins/dockers/animation/timeline_frames_model.h
+++ b/plugins/dockers/animation/timeline_frames_model.h
@@ -51,6 +51,12 @@ public:
bool createFrame(const QModelIndex &dstIndex);
bool copyFrame(const QModelIndex &dstIndex);
+ QString audioChannelFileName() const;
+ void setAudioChannelFileName(const QString &fileName);
+
+ bool isAudioMuted() const;
+ void setAudioMuted(bool value);
+
void setLastClickedIndex(const QModelIndex &index);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
@@ -113,6 +119,7 @@ Q_SIGNALS:
void requestCurrentNodeChanged(KisNodeSP node);
void sigInfiniteTimelineUpdateNeeded();
void sigEnsureRowVisible(int row);
+ void sigAudioChannelChanged();
private:
struct Private;
diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp
index cf9dcbf308a..220a07b896f 100644
--- a/plugins/dockers/animation/timeline_frames_view.cpp
+++ b/plugins/dockers/animation/timeline_frames_view.cpp
@@ -28,6 +28,7 @@
#include <QPainter>
+#include <QFileInfo>
#include <QApplication>
#include <QHeaderView>
#include <QDropEvent>
@@ -54,6 +55,11 @@
#include "kis_time_range.h"
#include "kis_color_label_selector_widget.h"
+#include <KoFileDialog.h>
+#include <QDesktopServices>
+
+#include "config-qtmultimedia.h"
+
typedef QPair<QRect, QModelIndex> QItemViewPaintPair;
typedef QList<QItemViewPaintPair> QItemViewPaintPairs;
@@ -84,11 +90,17 @@ struct TimelineFramesView::Private
QToolButton *addLayersButton;
KisAction *showHideLayerAction;
+ QToolButton *audioOptionsButton;
+
KisColorLabelSelectorWidget *colorSelector;
QWidgetAction *colorSelectorAction;
KisColorLabelSelectorWidget *multiframeColorSelector;
QWidgetAction *multiframeColorSelectorAction;
+ QMenu *audioOptionsMenu;
+ QAction *openAudioAction;
+ QAction *audioMuteAction;
+
QMenu *layerEditingMenu;
QMenu *existingLayersMenu;
@@ -149,6 +161,9 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount()));
connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount()));
+
+ /********** New Layer Menu ***********************************************************/
+
m_d->addLayersButton = new QToolButton(this);
m_d->addLayersButton->setAutoRaise(true);
m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer"));
@@ -176,6 +191,36 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
m_d->addLayersButton->setMenu(m_d->layerEditingMenu);
+ /********** Audio Channel Menu *******************************************************/
+
+ m_d->audioOptionsButton = new QToolButton(this);
+ m_d->audioOptionsButton->setAutoRaise(true);
+ m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
+ m_d->audioOptionsButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup);
+
+ m_d->audioOptionsMenu = new QMenu(this);
+
+#ifndef HAVE_QT_MULTIMEDIA
+ m_d->audioOptionsMenu->addSection(i18nc("@item:inmenu", "Audio playback is not suported in this build!"));
+#endif
+
+ m_d->openAudioAction= new QAction("XXX", this);
+ connect(m_d->openAudioAction, SIGNAL(triggered()), this, SLOT(slotSelectAudioChannelFile()));
+ m_d->audioOptionsMenu->addAction(m_d->openAudioAction);
+
+ m_d->audioMuteAction = new QAction(i18nc("@item:inmenu", "Mute"), this);
+ m_d->audioMuteAction->setCheckable(true);
+ connect(m_d->audioMuteAction, SIGNAL(triggered(bool)), SLOT(slotAudioChannelMute(bool)));
+
+ m_d->audioOptionsMenu->addAction(m_d->audioMuteAction);
+
+ m_d->audioOptionsMenu->addAction(i18nc("@item:inmenu", "Remove audio"), this, SLOT(slotAudioChannelRemove()));
+
+ m_d->audioOptionsButton->setMenu(m_d->audioOptionsMenu);
+
+ /********** Frame Editing Context Menu ***********************************************/
+
m_d->frameCreationMenu = new QMenu(this);
m_d->frameCreationMenu->addAction(KisAnimationUtils::addFrameActionName, this, SLOT(slotNewFrame()));
m_d->frameCreationMenu->addAction(KisAnimationUtils::duplicateFrameActionName, this, SLOT(slotCopyFrame()));
@@ -198,6 +243,8 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
m_d->multipleFrameEditingMenu->addAction(KisAnimationUtils::removeFramesActionName, this, SLOT(slotRemoveFrame()));
m_d->multipleFrameEditingMenu->addAction(m_d->multiframeColorSelectorAction);
+ /********** Zoom Button **************************************************************/
+
m_d->zoomDragButton = new KisZoomButton(this);
m_d->zoomDragButton->setAutoRaise(true);
m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
@@ -240,11 +287,13 @@ void TimelineFramesView::updateGeometries()
const int minimalSize = availableHeight - 2 * margin;
resizeToMinimalSize(m_d->addLayersButton, minimalSize);
+ resizeToMinimalSize(m_d->audioOptionsButton, minimalSize);
resizeToMinimalSize(m_d->zoomDragButton, minimalSize);
int x = 2 * margin;
int y = (availableHeight - minimalSize) / 2;
m_d->addLayersButton->move(x, 2 * y);
+ m_d->audioOptionsButton->move(x + minimalSize + 2 * margin, 2 * y);
const int availableWidth = m_d->layersHeader->width();
@@ -271,10 +320,15 @@ void TimelineFramesView::setModel(QAbstractItemModel *model)
connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()),
this, SLOT(slotUpdateInfiniteFramesCount()));
+ connect(m_d->model, SIGNAL(sigAudioChannelChanged()),
+ this, SLOT(slotUpdateAudioActions()));
+
connect(selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
&m_d->selectionChangedCompressor, SLOT(start()));
connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int)));
+
+ slotUpdateAudioActions();
}
void TimelineFramesView::setFramesPerSecond(int fps)
@@ -322,6 +376,70 @@ void TimelineFramesView::slotColorLabelChanged(int label)
config.setDefaultFrameColorLabel(label);
}
+void TimelineFramesView::slotSelectAudioChannelFile()
+{
+ if (!m_d->model) return;
+
+ KoFileDialog dialog(this, KoFileDialog::ImportFiles, "ImportAudio");
+
+ QString defaultDir = QDesktopServices::storageLocation(QDesktopServices::MusicLocation);
+
+ const QString currentFile = m_d->model->audioChannelFileName();
+ QDir baseDir = QFileInfo(currentFile).absoluteDir();
+ if (baseDir.exists()) {
+ defaultDir = baseDir.absolutePath();
+ }
+
+ dialog.setDefaultDir(defaultDir);
+
+ QStringList mimeTypes;
+ mimeTypes << "audio/mpeg";
+ mimeTypes << "audio/ogg";
+ mimeTypes << "audio/vorbis";
+ mimeTypes << "audio/vnd.wave";
+
+ dialog.setMimeTypeFilters(mimeTypes);
+ dialog.setCaption(i18nc("@titile:window", "Open Audio"));
+
+ const QString result = dialog.filename();
+ const QFileInfo info(result);
+
+ if (info.exists()) {
+ m_d->model->setAudioChannelFileName(info.absoluteFilePath());
+ }
+}
+
+void TimelineFramesView::slotAudioChannelMute(bool value)
+{
+ if (!m_d->model) return;
+
+ if (value != m_d->model->isAudioMuted()) {
+ m_d->model->setAudioMuted(value);
+ }
+}
+
+void TimelineFramesView::slotAudioChannelRemove()
+{
+ if (!m_d->model) return;
+ m_d->model->setAudioChannelFileName(QString());
+}
+
+void TimelineFramesView::slotUpdateAudioActions()
+{
+ if (!m_d->model) return;
+
+ const QString currentFile = m_d->model->audioChannelFileName();
+
+ if (currentFile.isEmpty()) {
+ m_d->openAudioAction->setText(i18nc("@item:inmenu", "Open audio..."));
+ } else {
+ QFileInfo info(currentFile);
+ m_d->openAudioAction->setText(i18nc("@item:inmenu", "Change audio (%1)...", info.fileName()));
+ }
+
+ m_d->audioMuteAction->setChecked(m_d->model->isAudioMuted());
+}
+
void TimelineFramesView::slotUpdateInfiniteFramesCount()
{
if (horizontalScrollBar()->isSliderDown()) return;
diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h
index 4a77116edc3..1590bb6c221 100644
--- a/plugins/dockers/animation/timeline_frames_view.h
+++ b/plugins/dockers/animation/timeline_frames_view.h
@@ -73,6 +73,11 @@ private Q_SLOTS:
void slotEnsureRowVisible(int row);
+ void slotSelectAudioChannelFile();
+ void slotAudioChannelMute(bool value);
+ void slotAudioChannelRemove();
+ void slotUpdateAudioActions();
+
private:
void setFramesPerSecond(int fps);
More information about the kimageshop
mailing list