Skip to content

Commit

Permalink
Add MediaSequence and MediaMixer, and effects subclasses.
Browse files Browse the repository at this point in the history
Extract pre-application initialization.
Make Interval a value-type.
emit MediaClip.clipEnded after rendering last frame.
Connect Qt.exit signal.
Add MediaManager.frameRendered signal.
Add mediafxviewer tool.
Enable qmlformat lint.
  • Loading branch information
rectalogic committed Jan 10, 2024
1 parent e3fe5ec commit 03ebf47
Show file tree
Hide file tree
Showing 37 changed files with 1,071 additions and 101 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ jobs:
with:
clang-format-version: '15'
- name: qmlformat
if: ${{ false }}
run: builders/Linux/docker-run.sh bash -c 'cd /mediafx && qmlformat -i $(git ls-files "**/*.qml") && git diff --exit-code'
- name: qmllint
if: ${{ false }}
Expand Down
2 changes: 1 addition & 1 deletion .qmlformat.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
FunctionsSpacing=
IndentWidth=4
NewlineType=unix
NormalizeOrder=1
NormalizeOrder=
ObjectsSpacing=
UseTabs=
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ project(mediaFX VERSION 1.0.0 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Qml Quick ShaderTools QUIET OPTIONAL_COMPONENTS Quick3D WebEngineQuick)

qt_standard_project_setup()
qt_policy(SET QTP0001 NEW)
enable_testing(true)

set(CMAKE_CXX_STANDARD 20)
Expand All @@ -38,3 +39,4 @@ endif()

add_subdirectory(src/MediaFX)
add_subdirectory(tests)
add_subdirectory(tools/viewer)
18 changes: 16 additions & 2 deletions src/MediaFX/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
# You should have received a copy of the GNU General Public License along with mediaFX.
# If not, see <https://www.gnu.org/licenses/>.

qt_policy(SET QTP0001 NEW)

find_package(PkgConfig)
pkg_search_module(ffms2 REQUIRED IMPORTED_TARGET ffms2)

option(EVENT_LOGGER "Enable event logger" OFF)

qt_add_library(mediafx STATIC
application.cpp
session.cpp
media_manager.cpp
encoder.cpp
Expand Down Expand Up @@ -51,7 +50,22 @@ set_property(TARGET mediafxtool PROPERTY OUTPUT_NAME mediafx)
qt_add_qml_module(mediafx
URI MediaFX
QML_FILES
qml/sequence.js
qml/MediaSequence.qml
qml/MultiEffectState.qml
qml/ShaderEffectState.qml
qml/effects/MediaMixer.qml
qml/effects/CrossFadeMixer.qml
qml/effects/LumaMixer.qml
qml/effects/LumaGradientMixer.qml
qml/effects/WipeMixer.qml
)
qt_add_shaders(mediafx "shaders"
PREFIX
"/"
FILES
qml/effects/crossfade.frag
qml/effects/luma.frag
)

target_link_libraries(mediafx PUBLIC PkgConfig::ffms2 Qt6::Core Qt6::Gui Qt6::GuiPrivate Qt6::Multimedia Qt6::Qml Qt6::Quick)
Expand Down
34 changes: 34 additions & 0 deletions src/MediaFX/application.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2024 Andrew Wason
*
* This file is part of mediaFX.
*
* mediaFX 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.
*
* mediaFX 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 mediaFX.
* If not, see <https://www.gnu.org/licenses/>.
*/

#include "application.h"
#include <QCoreApplication>
#ifdef WEBENGINEQUICK
#include <QtWebEngineQuick>
#endif

void initializeMediaFX()
{
#ifdef WEBENGINEQUICK
#if !defined(QT_NO_OPENGL)
// https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html#rendering-to-opengl-surface
// https://doc.qt.io/qt-6/qtwebengine-overview.html#embedding-web-content-into-qt-quick-applications
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endif
QtWebEngineQuick::initialize();
#endif
}
22 changes: 22 additions & 0 deletions src/MediaFX/application.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2024 Andrew Wason
*
* This file is part of mediaFX.
*
* mediaFX 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.
*
* mediaFX 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 mediaFX.
* If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#define qSL QStringLiteral

void initializeMediaFX();
4 changes: 4 additions & 0 deletions src/MediaFX/interval.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@

#include <QDebug>
#include <QDebugStateSaver>
#include <QtQmlIntegration>
#include <QtTypes>
#include <chrono>
#include <utility>
using namespace std::chrono;

class Interval {
Q_GADGET
QML_VALUE_TYPE(interval)
Q_PROPERTY(int start READ start FINAL)
Q_PROPERTY(int end READ end FINAL)
Q_PROPERTY(int duration READ duration FINAL)

public:
constexpr Interval() noexcept = default;
Expand All @@ -48,6 +51,7 @@ class Interval {

constexpr qint64 start() const noexcept { return duration_cast<milliseconds>(s).count(); }
constexpr qint64 end() const noexcept { return duration_cast<milliseconds>(e).count(); }
constexpr qint64 duration() const noexcept { return duration_cast<milliseconds>(e - s).count(); }

Q_INVOKABLE constexpr bool contains(qint64 time) const noexcept
{
Expand Down
17 changes: 2 additions & 15 deletions src/MediaFX/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* If not, see <https://www.gnu.org/licenses/>.
*/

#include "application.h"
#include "encoder.h"
#ifdef EVENTLOGGER
#include "event_logger.h"
Expand All @@ -28,12 +29,6 @@
#include <QStringLiteral>
#include <QUrl>

#ifdef WEBENGINEQUICK
#include <QtWebEngineQuick>
#endif

#define qSL QStringLiteral

const auto ffmpegPreamble = qSL("-f rawvideo -video_size ${MEDIAFX_FRAMESIZE} -pixel_format rgb0 -framerate ${MEDIAFX_FRAMERATE} -i pipe:${MEDIAFX_VIDEOFD}");

int main(int argc, char* argv[])
Expand All @@ -43,15 +38,7 @@ int main(int argc, char* argv[])
const_cast<char*>("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM=1"));
#endif

#ifdef WEBENGINEQUICK
#if !defined(QT_NO_OPENGL)
// https://doc.qt.io/qt-6/qml-qtwebengine-webengineview.html#rendering-to-opengl-surface
// https://doc.qt.io/qt-6/qtwebengine-overview.html#embedding-web-content-into-qt-quick-applications
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#endif
QtWebEngineQuick::initialize();
#endif

initializeMediaFX();
QGuiApplication app(argc, argv);

#ifdef EVENTLOGGER
Expand Down
28 changes: 15 additions & 13 deletions src/MediaFX/media_clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ void MediaClip::setClipStart(qint64 ms)
}
m_clipStart = ms;
emit clipStartChanged();
emit clipDurationChanged();
}

void MediaClip::setClipEnd(qint64 ms)
Expand All @@ -75,13 +76,23 @@ void MediaClip::setClipEnd(qint64 ms)
}
m_clipEnd = ms;
emit clipEndChanged();
emit clipDurationChanged();
}

void MediaClip::render()
{
if (!isActive())
return;

emit clipCurrentTimeChanged();

if (m_audioTrack)
m_audioTrack->render(m_currentFrameTime);
if (m_videoTrack)
m_videoTrack->render(m_currentFrameTime);

m_currentFrameTime = m_currentFrameTime.nextInterval(MediaManager::singletonInstance()->frameDuration());

if (m_currentFrameTime.start() >= m_clipEnd) {
if (m_audioTrack)
m_audioTrack->stop();
Expand All @@ -90,15 +101,6 @@ void MediaClip::render()
emit clipEnded();
return;
}

emit clipCurrentTimeChanged();

if (m_audioTrack)
m_audioTrack->render(m_currentFrameTime);
if (m_videoTrack)
m_videoTrack->render(m_currentFrameTime);

m_currentFrameTime = m_currentFrameTime.nextInterval(MediaManager::singletonInstance()->session()->frameDuration());
}

void MediaClip::setActive(bool active)
Expand All @@ -123,7 +125,7 @@ void MediaClip::loadMedia()
{
if (!source().isValid()) {
qmlWarning(this) << "MediaClip requires source Url";
emit MediaManager::singletonInstance()->session()->exitApp(1);
emit MediaManager::singletonInstance()->window()->engine()->exit(1);
return;
}
ErrorInfo errorInfo;
Expand All @@ -132,14 +134,14 @@ void MediaClip::loadMedia()
FFMS_Indexer* indexer = FFMS_CreateIndexer(sourceFileUtf8.data(), &errorInfo);
if (!indexer) {
qmlWarning(this) << "MediaClip FFMS_CreateIndexer failed:" << errorInfo;
emit MediaManager::singletonInstance()->session()->exitApp(1);
emit MediaManager::singletonInstance()->window()->engine()->exit(1);
return;
}

FFMS_Index* index = FFMS_DoIndexing2(indexer, FFMS_IEH_ABORT, &errorInfo);
if (!index) {
qmlWarning(this) << "MediaClip FFMS_DoIndexing2 failed:" << errorInfo;
emit MediaManager::singletonInstance()->session()->exitApp(1);
emit MediaManager::singletonInstance()->window()->engine()->exit(1);
return;
}

Expand Down Expand Up @@ -167,5 +169,5 @@ void MediaClip::componentComplete()
if (clipEnd() < 0) {
setClipEnd(std::max(m_audioTrack ? m_audioTrack->duration() : 0, m_videoTrack ? m_videoTrack->duration() : 0));
}
m_currentFrameTime = Interval(milliseconds(clipStart()), milliseconds(clipStart()) + MediaManager::singletonInstance()->session()->frameDuration());
m_currentFrameTime = Interval(milliseconds(clipStart()), milliseconds(clipStart()) + MediaManager::singletonInstance()->frameDuration());
}
4 changes: 4 additions & 0 deletions src/MediaFX/media_clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ class MediaClip : public QObject, public QQmlParserStatus {
Q_PROPERTY(QUrl source READ source WRITE setSource REQUIRED FINAL)
Q_PROPERTY(int clipStart READ clipStart WRITE setClipStart NOTIFY clipStartChanged FINAL)
Q_PROPERTY(int clipEnd READ clipEnd WRITE setClipEnd NOTIFY clipEndChanged FINAL)
Q_PROPERTY(int clipDuration READ clipDuration NOTIFY clipDurationChanged FINAL)
Q_PROPERTY(bool active READ isActive NOTIFY activeChanged FINAL)
Q_PROPERTY(Interval clipCurrentTime READ clipCurrentTime NOTIFY clipCurrentTimeChanged FINAL)
QML_ELEMENT

signals:
void clipStartChanged();
void clipEndChanged();
void clipDurationChanged();
void activeChanged(bool);
void clipCurrentTimeChanged();
void clipEnded();
Expand All @@ -59,6 +61,8 @@ class MediaClip : public QObject, public QQmlParserStatus {
qint64 clipEnd() const { return m_clipEnd; };
void setClipEnd(qint64 ms);

qint64 clipDuration() const { return m_clipEnd - m_clipStart; };

const Interval& clipCurrentTime() const { return m_currentFrameTime; };

void setActive(bool active);
Expand Down
11 changes: 7 additions & 4 deletions src/MediaFX/media_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@

#include "media_manager.h"
#include "media_clip.h"
#include "session.h"
#include <QObject>
#include <QQuickView>
#include <chrono>
using namespace std::chrono;

MediaManager::MediaManager(Session* session, QObject* parent)
MediaManager::MediaManager(const microseconds& frameDuration, QQuickView* quickView, QObject* parent)
: QObject(parent)
, m_session(session)
, m_frameDuration(frameDuration)
, m_quickView(quickView)
{
connect(this, &MediaManager::finishEncoding, [this](bool immediate) { this->setEncodingState(immediate ? EncodingState::Stopped : EncodingState::Stopping); emit this->session()->exitApp(0); });
connect(this, &MediaManager::finishEncoding, [this]() { this->finishedEncoding = true; });
}

MediaManager* MediaManager::singletonInstance()
Expand Down
33 changes: 19 additions & 14 deletions src/MediaFX/media_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,47 +17,52 @@

#pragma once

#include "interval.h"
#include <QJSEngine>
#include <QList>
#include <QObject>
#include <QQuickView>
#include <QtAssert>
#include <QtQml>
#include <QtQmlIntegration>
#include <chrono>
class MediaClip;
class Session;
using namespace std::chrono;

class MediaManager : public QObject {
Q_OBJECT
Q_PROPERTY(QQuickView* window READ window FINAL)

public:
using QObject::QObject;
MediaManager(Session* session, QObject* parent = nullptr);
MediaManager(const microseconds& frameDuration, QQuickView* quickView, QObject* parent = nullptr);

static MediaManager* singletonInstance();

Session* session() { return m_session; };
Q_INVOKABLE Interval createInterval(qint64 start, qint64 end) const
{
return Interval(start, end);
};

QQuickView* window() const { return m_quickView; };
const microseconds& frameDuration() { return m_frameDuration; };

void registerClip(MediaClip* clip);
void unregisterClip(MediaClip* clip);

void render();

enum EncodingState {
Encoding,
Stopping,
Stopped,
};

EncodingState encodingState() const { return m_encodingState; };
void setEncodingState(EncodingState state) { m_encodingState = state; };
bool isFinishedEncoding() const { return finishedEncoding; }

signals:
void finishEncoding(bool immediate = true);
void finishEncoding();
void frameRendered();

private:
Session* m_session;
microseconds m_frameDuration;
QQuickView* m_quickView;
QList<MediaClip*> activeClips;
EncodingState m_encodingState = Encoding;
bool finishedEncoding = false;
};

// https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON
Expand Down
Loading

0 comments on commit 03ebf47

Please sign in to comment.