This feature is work in progress. Currently it is very slow and should not be enabled by default.
option(WITH_LOCALMYLIST "Build LocalMyList feature plugin" ON)
add_feature_info(FeatureLocalMyList WITH_LOCALMYLIST "automatically mark files as watched that are in your LocalMyList")
+option(WITH_ANNOTATIONS "Build annotations feature plugin" ON)
+add_feature_info(FeatureAnnotations WITH_ANNOTATIONS "annotate certain features in the video")
add_subdirectory(pluginapi)
add_subdirectory(core)
--- /dev/null
+#include "annotationmodel.h"
+
+AnnotationModel::AnnotationModel(QObject *parent) : QAbstractListModel{parent} {}
+
+void AnnotationModel::setAnnotations(
+ const PlayerFeaturePlauginInterface::AnnotationList &data) {
+ beginResetModel();
+ m_data = data;
+ endResetModel();
+}
+
+QHash<int, QByteArray> AnnotationModel::roleNames() const {
+static QHash<int, QByteArray> roles{
+ {Qt::DisplayRole, "text"},
+ {AnnotationX, "annotationX"},
+ {AnnotationY, "annotationY"},
+ {AnnotationW, "annotationW"},
+ {AnnotationH, "annotationH"},
+ {AnnotationColor, "annotationColor"},
+ {AnnotationText, "annotationText"},
+ };
+ return roles;
+}
+
+int AnnotationModel::rowCount(const QModelIndex &parent) const {
+ if (parent.isValid())
+ return 0;
+ return m_data.size();
+}
+
+QVariant AnnotationModel::data(const QModelIndex &index, int role) const {
+
+ const PlayerFeaturePlauginInterface::Annotation &annotation =
+ m_data.at(index.row());
+ switch (role) {
+ case Qt::DisplayRole:
+ case AnnotationText:
+ return annotation.text;
+ case AnnotationX:
+ return annotation.x;
+ case AnnotationY:
+ return annotation.y;
+ case AnnotationW:
+ return annotation.w;
+ case AnnotationH:
+ return annotation.h;
+ case AnnotationColor:
+ return annotation.color;
+ }
+ return {};
+}
--- /dev/null
+#ifndef ANNOTATIONMODEL_H
+#define ANNOTATIONMODEL_H
+
+#include <QAbstractListModel>
+#include "aniplayer/playerfeatureplugininterface.h"
+
+class AnnotationModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ enum TrackRoles {
+ AnnotationX = Qt::UserRole + 1,
+ AnnotationY,
+ AnnotationW,
+ AnnotationH,
+ AnnotationColor,
+ AnnotationText,
+ };
+
+ explicit AnnotationModel(QObject *parent = nullptr);
+
+ void setAnnotations(const PlayerFeaturePlauginInterface::AnnotationList &);
+
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex{}) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+private:
+ PlayerFeaturePlauginInterface::AnnotationList m_data;
+};
+
+#endif // ANNOTATIONMODEL_H
m_featurePluginManager->setPluginDirectories(featurePluginPaths);
m_featurePluginManager->setPluginPrefix("feature");
// TODO feature plugins should be looked up and not preferred.
- m_featurePluginManager->setPreferredPlugins({"localmylist"});
+ m_featurePluginManager->setPreferredPlugins({"localmylist", "annotations"});
m_featurePluginManager->loadAll();
}
#include <QLoggingCategory>
#include <QQmlApplicationEngine>
+#include <QMetaObject>
#ifdef Q_OS_WIN
#include <Windows.h>
m_chapterModel = new ChapterModel{this};
Q_CHECK_PTR(m_chapterModel);
+
+ m_annotationModel = new AnnotationModel{this};
+ Q_CHECK_PTR(m_annotationModel);
}
Player::~Player() {
QAbstractItemModel *Player::chapterModel() const { return m_chapterModel; }
+QAbstractItemModel *Player::annotationModel() const {
+ return m_annotationModel;
+}
+
+bool Player::shouldSaveFramesToImage() const
+{
+ return true;
+}
+
+void Player::newFrame(const QImage &image)
+{
+ m_lastFrame = image;
+ // This is called from VideoElement::synchronize which should do as little as
+ // possible
+ QMetaObject::invokeMethod(this, "onNewFrame");
+}
+
void Player::load(const QUrl &resource) {
if (canLoadVideoNow())
m_backend->open(resource);
emit currentSubtitleTrackChanged(track);
}
-void Player::featureShowStatusMessage(const QString &message)
-{
+void Player::featureShowStatusMessage(const QString &message) {
emit statusMessageRequested(message);
}
+void Player::featureSetAnnotations(const AnnotationList &annotations) {
+ m_annotationModel->setAnnotations(annotations);
+}
+
void Player::reqisterQmlTypes() {
qRegisterMetaType<TimeStamp>("TimeStamp");
qRegisterMetaType<TrackIndex>("StreamIndex");
qmlRegisterType<Player>("org.aptx.aniplayer", 1, 0, "Player");
}
+void Player::onNewFrame() {
+ emit frameChanged(m_lastFrame);
+}
+
bool Player::canLoadVideoNow() const {
return m_backendInstanceReady && m_renderer && m_rendererReady;
}
#include <QObject>
#include <QQmlEngine>
#include <QUrl>
+#include <QImage>
#include "aniplayer/backendpluginbase.h"
#include "aniplayer/playerfeatureplugininterface.h"
#include "chaptermodel.h"
#include "trackmodel.h"
+#include "annotationmodel.h"
class Player : public QObject,
public PlayerPluginInterface,
NOTIFY subtitleTrackModelChanged)
Q_PROPERTY(QAbstractItemModel *chapterModel READ chapterModel NOTIFY
chapterModelChanged)
+ Q_PROPERTY(QAbstractItemModel *annotationModel READ annotationModel NOTIFY
+ annotationModelChanged)
public:
using TrackIndex = int;
QAbstractItemModel *audioTrackModel() const;
QAbstractItemModel *subtitleTrackModel() const;
QAbstractItemModel *chapterModel() const;
+ QAbstractItemModel *annotationModel() const;
+
+ bool shouldSaveFramesToImage() const;
+ void newFrame(const QImage &);
signals:
void stateChanged(PlayState state);
void audioTrackModelChanged(QAbstractItemModel *audioTrackModel);
void subtitleTrackModelChanged(QAbstractItemModel *subtitleTrackModel);
void chapterModelChanged(QAbstractItemModel *chapterModel);
+ void annotationModelChanged(QAbstractItemModel *annotationModel);
void statusMessageRequested(const QString &message);
+ void frameChanged(const QImage &frame);
+
public slots:
// Basic Play state
void load(const QUrl &resource);
void backendCurrentSubtitleTrackChanged(TrackIndex) override;
void featureShowStatusMessage(const QString &message) override;
+ void featureSetAnnotations(const AnnotationList &) override;
public:
static void reqisterQmlTypes();
+private slots:
+ void onNewFrame();
+
private:
bool canLoadVideoNow() const;
void loadNextFile();
TrackModel *m_audioTrackModel;
TrackModel *m_subtitleTrackModel;
ChapterModel *m_chapterModel;
+ AnnotationModel *m_annotationModel;
+ QImage m_lastFrame;
+ bool m_saveToImage = true;
bool m_muted = false;
bool m_backendInstanceReady = false;
bool m_rendererReady = false;
#include "player.h"
#include <QLoggingCategory>
+#include <QOpenGLFramebufferObject>
Q_LOGGING_CATEGORY(videoElementCategory, "VideoElement")
void VideoElement::Renderer::render() {
if (m_renderer)
m_renderer->render(framebufferObject());
+ if (m_savetoImage)
+ m_image = framebufferObject()->toImage(false);
}
void VideoElement::Renderer::synchronize(QQuickFramebufferObject *object) {
- auto ve = static_cast<VideoElement *>(object);
+ const auto ve = static_cast<VideoElement *>(object);
+
+ if (ve->source()) {
+ m_savetoImage = ve->source()->shouldSaveFramesToImage();
+ if (m_savetoImage)
+ ve->source()->newFrame(m_image);
+ }
+
if (!ve->rendererUpdateRequired)
return;
ve->rendererUpdateRequired = false;
#include "player.h"
#include "aniplayer/playerplugininterface.h"
+#include <QImage>
+
class VideoElement : public QQuickFramebufferObject,
public VideoUpdateInterface {
Q_OBJECT
private:
VideoRendererBase *m_renderer = nullptr;
+ bool m_savetoImage = false;
+ QImage m_image;
};
Player *m_source = nullptr;
if (WITH_LOCALMYLIST)
add_subdirectory(feature_localmylist)
endif()
+
+if (WITH_ANNOTATIONS)
+ add_subdirectory(feature_annotations)
+endif()
--- /dev/null
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+project(feature_annotations)
+
+find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
+ Core
+ Gui
+)
+
+find_package(dlib CONFIG REQUIRED)
+
+set(feature_annotations_LIBS
+ Qt5::Core
+ Qt5::Gui
+ dlib::dlib
+ pluginapi
+)
+
+set(feature_annotations_SOURCES
+ featureannotations.cpp
+)
+
+set(feature_annotations_HEADERS
+ featureannotations.h
+ feature_annotations_global.h
+)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTORCC ON)
+
+add_library(feature_annotations MODULE
+ ${feature_annotations_SOURCES}
+ ${feature_annotations_HEADERS}
+)
+
+set_property(TARGET feature_annotations PROPERTY CXX_STANDARD 14)
+set_property(TARGET feature_annotations PROPERTY OUTPUT_NAME "feature_annotations")
+target_link_libraries(feature_annotations ${feature_annotations_LIBS})
+
+add_definitions(-DFEATURE_ANNOTATIONS_LIBRARY)
+
+if(WIN32)
+ set(INSTALL_DESTINATION "aniplayer/uiplugins")
+else()
+ set(INSTALL_DESTINATION "lib${LIB_SUFFIX}/aniplayer/uiplugins")
+endif()
+
+install(TARGETS feature_annotations
+ LIBRARY DESTINATION ${INSTALL_DESTINATION}
+ ARCHIVE DESTINATION ${INSTALL_DESTINATION}
+)
+install(FILES $<TARGET_PDB_FILE:feature_annotations>
+ DESTINATION ${INSTALL_DESTINATION} OPTIONAL
+)
--- /dev/null
+#ifndef FEATURE_ANNOTATIONS_GLOBAL_H
+#define FEATURE_ANNOTATIONS_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(FEATURE_ANNOTATIONS_LIBRARY)
+# define FEATURE_ANNOTATIONSSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define FEATURE_ANNOTATIONSSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // FEATURE_ANNOTATIONS_GLOBAL_H
--- /dev/null
+#include "featureannotations.h"
+
+#include <QLoggingCategory>
+#include <QImage>
+
+Q_LOGGING_CATEGORY(annotationsCategory, "Annotations")
+
+#include <dlib/dnn.h>
+#include <dlib/image_saver/image_saver.h>
+
+// random windows.h defines
+#undef interface
+
+using namespace std;
+using namespace dlib;
+
+
+template <long num_filters, typename SUBNET> using con5d = con<num_filters,5,5,2,2,SUBNET>;
+template <long num_filters, typename SUBNET> using con5 = con<num_filters,5,5,1,1,SUBNET>;
+
+template <typename SUBNET> using downsampler_r = relu<affine<con5d<32, relu<affine<con5d<32, relu<affine<con5d<16,SUBNET>>>>>>>>>;
+template <typename SUBNET> using rcon5_r = relu<affine<con5<45,SUBNET>>>;
+
+using detection_net_type = loss_mmod<con<1,9,9,1,1,rcon5_r<rcon5_r<rcon5_r<downsampler_r<input_rgb_image_pyramid<pyramid_down<6>>>>>>>>;
+
+namespace {
+detection_net_type net;
+}
+
+FeatureAnnoations::FeatureAnnoations(QObject *parent) : QObject{parent} {
+ deserialize("mmod_network.dat") >> net;
+}
+
+FeaturePluginInstance *
+FeatureAnnoations::createInstance(QObject *instance,
+ PlayerFeaturePlauginInterface *interface) {
+ return new FeatureAnnoationsInstance(instance, interface);
+}
+
+FeatureAnnoationsInstance::FeatureAnnoationsInstance(
+ QObject *instance, PlayerFeaturePlauginInterface *player, QObject *)
+ : FeaturePluginInstance{instance, player} {
+ qCDebug(annotationsCategory) << "Registering with instance" << instance;
+
+ connect(instance, SIGNAL(frameChanged(const QImage &)),
+ this, SLOT(onFrameChanged(const QImage &)));
+
+
+ m_fpsTimer.setInterval(1000);
+ connect(&m_fpsTimer, &QTimer::timeout, [&]() {
+ qCDebug(annotationsCategory) << "Did" << m_fpsCounter
+ << "frames per seoncd";
+ m_fpsCounter = 0;
+ });
+ m_fpsTimer.start();
+}
+
+void FeatureAnnoationsInstance::onFrameChanged(const QImage &image)
+{
+ qCDebug(annotationsCategory) << "Frame changed! " << image.size() << image.format();
+ if (image.size().isEmpty()) return;
+ try {
+ matrix<rgb_pixel> img{image.size().height(), image.size().width()};
+
+ const auto *imageData = reinterpret_cast<const QRgb *>(image.bits());
+ auto size = img.size();
+
+ std::transform(imageData, imageData + size, img.begin(),
+ [](const QRgb &d) {
+ return rgb_pixel{static_cast<unsigned char>(qRed(d)),
+ static_cast<unsigned char>(qGreen(d)),
+ static_cast<unsigned char>(qBlue(d))};
+ });
+
+ //dlib::save_bmp(img, "testimage.bmp");
+
+ auto dets = net(img);
+ PlayerFeaturePlauginInterface::AnnotationList al;
+ for (auto&& d : dets)
+ al << PlayerFeaturePlauginInterface::Annotation{
+ static_cast<double>(d.rect.left()) / image.size().width(),
+ static_cast<double>(d.rect.top()) / image.size().height(),
+ static_cast<double>(d.rect.right() - d.rect.left()) / image.size().width(),
+ static_cast<double>(d.rect.bottom() - d.rect.top()) / image.size().height(),
+ "red",
+ ""
+ };
+ m_playerInterface->featureSetAnnotations(al);
+ ++m_fpsCounter;
+
+ } catch(const std::exception &ex) {
+ qCDebug(annotationsCategory) << "Exception: " << ex.what();
+ }
+}
--- /dev/null
+#ifndef FeatureAnnoations_H
+#define FeatureAnnoations_H
+
+#include <QHash>
+#include <QObject>
+#include <QUrl>
+#include <QTimer>
+
+#include <QtPlugin>
+
+#include "aniplayer/featurepluginbase.h"
+#include "aniplayer/playerfeatureplugininterface.h"
+#include "feature_annotations_global.h"
+
+class FEATURE_ANNOTATIONSSHARED_EXPORT FeatureAnnoations
+ : public QObject,
+ public FeaturePluginBase {
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID FILE
+ "feature_annotations.json")
+ Q_INTERFACES(FeaturePluginBase)
+public:
+ FeatureAnnoations(QObject *parent = nullptr);
+
+ FeaturePluginInstance *
+ createInstance(QObject *instance, PlayerFeaturePlauginInterface *) override;
+};
+
+class FeatureAnnoationsInstance : public QObject, public FeaturePluginInstance {
+ Q_OBJECT
+public:
+ FeatureAnnoationsInstance(QObject *instance, PlayerFeaturePlauginInterface *, QObject *parent = nullptr);
+
+private slots:
+ void onFrameChanged(const QImage &);
+
+private:
+ int m_fid;
+ QString m_path;
+ double m_duration;
+ QTimer m_fpsTimer;
+ int m_fpsCounter = 0;
+};
+
+#endif // FeatureAnnoations_H
anchors.fill: parent
}
+ Item {
+ id: annotationView
+ anchors.fill: parent
+ Repeater {
+
+ model: player.annotationModel
+ Rectangle {
+ x: annotationX * annotationView.width
+ y: annotationY * annotationView.height
+ width: annotationW * annotationView.width
+ height: annotationH * annotationView.height
+ border.color: annotationColor
+ color: "transparent"
+ }
+ }
+ }
+
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.AllButtons