[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-kimageshop
Subject:    =?utf-8?q?=5Bgraphics/krita=5D_/=3A_Refactor_metadata_backends_into_their_own_plugins?=
From:       Dmitry Kazakov <null () kde ! org>
Date:       2021-09-04 13:35:59
Message-ID: 20210904133559.1E9E31241554 () leptone ! kde ! org
[Download RAW message or body]

Git commit 4a8195729427cc45f8236eef08f43aa9e04f5e89 by Dmitry Kazakov, on behalf of \
L. E. Segovia. Committed on 04/09/2021 at 13:08.
Pushed by dkazakov into branch 'master'.

Refactor metadata backends into their own plugins

This commit turns the Exif, IPTC, and XMP backends into loadable
KPlugins.

CCMAIL: kimageshop@kde.org

M  +2    -1    libs/metadata/CMakeLists.txt
A  +42   -0    libs/metadata/kis_meta_data_backend_registry.cpp     [License: \
LGPL(v2.1+)] A  +20   -0    libs/metadata/kis_meta_data_backend_registry.h     \
[License: LGPL(v2.1+)] D  +0    -33   libs/metadata/kis_meta_data_io_backend.cc
M  +11   -29   libs/metadata/kis_meta_data_io_backend.h
M  +1    -6    libs/ui/CMakeLists.txt
M  +2    -13   libs/ui/KisApplication.cpp
M  +0    -1    libs/ui/KisApplication.h
M  +15   -15   libs/ui/kis_png_converter.cpp
D  +0    -1    libs/ui/kisexiv2/.krazy
D  +0    -32   libs/ui/kisexiv2/kis_exiv2.h
M  +0    -8    libs/ui/tests/CMakeLists.txt
M  +1    -0    plugins/CMakeLists.txt
M  +7    -7    plugins/impex/heif/HeifExport.cpp
M  +9    -8    plugins/impex/heif/HeifImport.cpp
M  +15   -15   plugins/impex/jpeg/kis_jpeg_converter.cc
M  +2    -2    plugins/impex/jpeg/tests/CMakeLists.txt
M  +3    -3    plugins/impex/jpeg/tests/kis_jpeg_test.cpp
M  +22   -23   plugins/impex/libkra/kis_kra_load_visitor.cpp
M  +13   -13   plugins/impex/libkra/kis_kra_save_visitor.cpp
M  +3    -4    plugins/impex/tiff/tests/kis_tiff_test.cpp
A  +7    -0    plugins/metadata/CMakeLists.txt
R  +40   -45   plugins/metadata/common/kis_exiv2_common.h [from: \
libs/ui/kisexiv2/kis_exiv2.cpp - 081% similarity] R  +0    -0    \
plugins/metadata/common/kis_exiv2_constants.h [from: \
libs/ui/kisexiv2/kis_exiv2_constants.h - 100% similarity] A  +17   -0    \
plugins/metadata/exif/CMakeLists.txt R  +115  -108  \
plugins/metadata/exif/kis_exif_io.cpp [from: libs/ui/kisexiv2/kis_exif_io.cpp - 076% \
similarity] R  +17   -12   plugins/metadata/exif/kis_exif_io.h [from: \
libs/ui/kisexiv2/kis_exif_io.h - 054% similarity] A  +29   -0    \
plugins/metadata/exif/kis_exif_plugin.cpp     [License: GPL(v2.0+)] A  +21   -0    \
plugins/metadata/exif/kis_exif_plugin.h     [License: GPL(v2.0+)] A  +9    -0    \
plugins/metadata/exif/kritaexif.json A  +17   -0    \
plugins/metadata/iptc/CMakeLists.txt R  +59   -58   \
plugins/metadata/iptc/kis_iptc_io.cpp [from: libs/ui/kisexiv2/kis_iptc_io.cpp - 051% \
similarity] R  +20   -12   plugins/metadata/iptc/kis_iptc_io.h [from: \
libs/ui/kisexiv2/kis_iptc_io.h - 056% similarity] A  +29   -0    \
plugins/metadata/iptc/kis_iptc_plugin.cpp     [License: GPL(v2.0+)] A  +21   -0    \
plugins/metadata/iptc/kis_iptc_plugin.h     [License: GPL(v2.0+)] A  +7    -0    \
plugins/metadata/iptc/kritaiptc.json A  +21   -0    \
plugins/metadata/tests/CMakeLists.txt R  +-    --    \
plugins/metadata/tests/data/metadata/hpim3238.exv [from: \
libs/ui/tests/data/metadata/hpim3238.exv - 100% similarity] R  +34   -29   \
plugins/metadata/tests/kis_exif_test.cpp [from: libs/ui/tests/kis_exiv2_test.cpp - \
065% similarity] R  +2    -1    plugins/metadata/tests/kis_exif_test.h [from: \
libs/ui/tests/kis_exiv2_test.h - 073% similarity] A  +17   -0    \
plugins/metadata/xmp/CMakeLists.txt R  +72   -56   \
plugins/metadata/xmp/kis_xmp_io.cpp [from: libs/ui/kisexiv2/kis_xmp_io.cpp - 075% \
similarity] R  +17   -11   plugins/metadata/xmp/kis_xmp_io.h [from: \
libs/ui/kisexiv2/kis_xmp_io.h - 055% similarity] A  +29   -0    \
plugins/metadata/xmp/kis_xmp_plugin.cpp     [License: GPL(v2.0+)] A  +21   -0    \
plugins/metadata/xmp/kis_xmp_plugin.h     [License: GPL(v2.0+)] A  +9    -0    \
plugins/metadata/xmp/kritaxmp.json

https://invent.kde.org/graphics/krita/commit/4a8195729427cc45f8236eef08f43aa9e04f5e89

diff --git a/libs/metadata/CMakeLists.txt b/libs/metadata/CMakeLists.txt
index a0cccc48b8..952ef22466 100644
--- a/libs/metadata/CMakeLists.txt
+++ b/libs/metadata/CMakeLists.txt
@@ -4,7 +4,6 @@ set(kritametadata_LIB_SRCS
     kis_meta_data_filter_p.cc
     kis_meta_data_filter_registry.cc
     kis_meta_data_filter_registry_model.cc
-    kis_meta_data_io_backend.cc
     kis_meta_data_merge_strategy.cc
     kis_meta_data_merge_strategy_p.cc
     kis_meta_data_merge_strategy_registry.cc
@@ -15,6 +14,8 @@ set(kritametadata_LIB_SRCS
     kis_meta_data_type_info.cc
     kis_meta_data_validator.cc
     kis_meta_data_value.cc
+
+    kis_meta_data_backend_registry.cpp
 )
 
 add_library(kritametadata SHARED ${kritametadata_LIB_SRCS} )
diff --git a/libs/metadata/kis_meta_data_backend_registry.cpp \
b/libs/metadata/kis_meta_data_backend_registry.cpp new file mode 100644
index 0000000000..0808a4d29a
--- /dev/null
+++ b/libs/metadata/kis_meta_data_backend_registry.cpp
@@ -0,0 +1,42 @@
+/*
+ *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ *  SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "kis_meta_data_backend_registry.h"
+
+#include <QGlobalStatic>
+
+#include <KoPluginLoader.h>
+
+#include <kis_debug.h>
+
+Q_GLOBAL_STATIC(KisMetadataBackendRegistry, s_instance)
+
+KisMetadataBackendRegistry::KisMetadataBackendRegistry()
+{
+}
+
+KisMetadataBackendRegistry::~KisMetadataBackendRegistry()
+{
+    Q_FOREACH (const QString &id, keys()) {
+        delete get(id);
+    }
+    dbgRegistry << "Deleting KisMetadataBackendRegistry";
+}
+
+void KisMetadataBackendRegistry::init()
+{
+    KoPluginLoader::instance()->load("Krita/Metadata", "(Type == 'Service') and \
([X-Krita-Version] == 28)"); +}
+
+KisMetadataBackendRegistry *KisMetadataBackendRegistry::instance()
+{
+    if (!s_instance.exists()) {
+        dbgRegistry << "initializing KisMetadataBackendRegistry";
+        s_instance->init();
+    }
+    return s_instance;
+}
diff --git a/libs/metadata/kis_meta_data_backend_registry.h \
b/libs/metadata/kis_meta_data_backend_registry.h new file mode 100644
index 0000000000..c50fcb8b2c
--- /dev/null
+++ b/libs/metadata/kis_meta_data_backend_registry.h
@@ -0,0 +1,20 @@
+/*
+ *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ *  SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <KoGenericRegistry.h>
+#include <kis_meta_data_io_backend.h>
+
+#include "kritametadata_export.h"
+
+class KRITAMETADATA_EXPORT KisMetadataBackendRegistry : public \
KoGenericRegistry<KisMetaData::IOBackend *> +{
+public:
+    KisMetadataBackendRegistry();
+    ~KisMetadataBackendRegistry() override;
+    void init();
+    static KisMetadataBackendRegistry *instance();
+};
diff --git a/libs/metadata/kis_meta_data_io_backend.cc \
b/libs/metadata/kis_meta_data_io_backend.cc deleted file mode 100644
index 88a50f04ce..0000000000
--- a/libs/metadata/kis_meta_data_io_backend.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
- *
- *  SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "kis_meta_data_io_backend.h"
-
-using namespace KisMetaData;
-
-#include <QGlobalStatic>
-#include "kis_debug.h"
-
-Q_GLOBAL_STATIC(IOBackendRegistry, s_instance)
-
-
-IOBackendRegistry::IOBackendRegistry()
-{
-}
-
-IOBackendRegistry::~IOBackendRegistry()
-{
-    Q_FOREACH (const QString &id, keys()) {
-        delete get(id);
-    }    
-}
-
-
-IOBackendRegistry* IOBackendRegistry::instance()
-{
-    // XXX: load backend plugins
-    return s_instance;
-}
diff --git a/libs/metadata/kis_meta_data_io_backend.h \
b/libs/metadata/kis_meta_data_io_backend.h index 3de9e21f67..52af754cd1 100644
--- a/libs/metadata/kis_meta_data_io_backend.h
+++ b/libs/metadata/kis_meta_data_io_backend.h
@@ -1,17 +1,17 @@
 /*
-*  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
-*
-*  SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ *  SPDX-License-Identifier: LGPL-2.1-or-later
+ */
 
 #ifndef _KIS_META_DATA_IO_BACKEND_H_
 #define _KIS_META_DATA_IO_BACKEND_H_
 
 #include <kritametadata_export.h>
 
-#include <KoGenericRegistry.h>
-
 class QIODevice;
+class QString;
 
 namespace KisMetaData
 {
@@ -24,15 +24,11 @@ class Store;
 class KRITAMETADATA_EXPORT IOBackend
 {
 public:
-
     /**
      * Tell whether the backend input/output from/to binary data
      * or text (XML or RDF) data.
      */
-    enum BackendType {
-        Binary,
-        Text
-    };
+    enum BackendType { Binary, Text };
 
     enum HeaderType {
         NoHeader, ///< Don't append any header
@@ -40,8 +36,7 @@ public:
     };
 
 public:
-
-    virtual ~IOBackend() {};
+    virtual ~IOBackend(){};
 
     virtual QString id() const = 0;
 
@@ -64,14 +59,14 @@ public:
      *                   which type of header
      * @return true if the save was successful (XXX: actually, all backends always \
                return true...)
      */
-    virtual bool saveTo(Store* store, QIODevice* ioDevice, HeaderType headerType = \
NoHeader) const = 0; +    virtual bool saveTo(Store *store, QIODevice *ioDevice, \
HeaderType headerType = NoHeader) const = 0;  
     /**
      * @param store the list of metadata
      * @return true if this backend is capable of saving all the metadata
      * of the store
      */
-    virtual bool canSaveAllEntries(Store* store) const = 0;
+    virtual bool canSaveAllEntries(Store *store) const = 0;
 
     /**
      * @return true if this backend support loading
@@ -83,21 +78,8 @@ public:
      * @param ioDevice the device from where the metadata will be loaded
      * @return true if the load was successful
      */
-    virtual bool loadFrom(Store* store, QIODevice* ioDevice) const = 0;
-};
-
-class KRITAMETADATA_EXPORT IOBackendRegistry : public KoGenericRegistry<IOBackend*>
-{
-
-public:
-
-    IOBackendRegistry();
-    ~IOBackendRegistry() override;
-    static IOBackendRegistry* instance();
-
+    virtual bool loadFrom(Store *store, QIODevice *ioDevice) const = 0;
 };
-
 }
 
-
 #endif
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index 770eab3a99..5180076e11 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -167,11 +167,6 @@ set(kritaui_LIB_SRCS
     KisChangeCloneLayersCommand.cpp
     KisUiFont.cpp
 
-    kisexiv2/kis_exif_io.cpp
-    kisexiv2/kis_exiv2.cpp
-    kisexiv2/kis_iptc_io.cpp
-    kisexiv2/kis_xmp_io.cpp
-
     opengl/kis_opengl.cpp
     opengl/kis_opengl_canvas2.cpp
     opengl/kis_opengl_canvas_debugger.cpp
@@ -648,7 +643,7 @@ add_library(kritaui SHARED ${kritaui_HEADERS_MOC} \
${kritaui_LIB_SRCS} )  generate_export_header(kritaui BASE_NAME kritaui)
 
 target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n \
                KF5::ItemViews Qt5::Network
-                      kritaversion kritaimpex kritacolor kritaimage kritalibbrush \
kritawidgets kritawidgetutils kritaresources ${PNG_LIBRARIES} LibExiv2::LibExiv2 +    \
kritaversion kritaimpex kritacolor kritaimage kritalibbrush kritawidgets \
kritawidgetutils kritaresources ${PNG_LIBRARIES}  )
 
 if (ANDROID)
diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp
index cd06f77d36..c16c411aa4 100644
--- a/libs/ui/KisApplication.cpp
+++ b/libs/ui/KisApplication.cpp
@@ -63,7 +63,7 @@
 #include <generator/kis_generator.h>
 #include <brushengine/kis_paintop_registry.h>
 #include <kis_meta_data_io_backend.h>
-#include "kisexiv2/kis_exiv2.h"
+#include <kis_meta_data_backend_registry.h>
 #include "KisApplicationArguments.h"
 #include <kis_debug.h>
 #include "kis_action_registry.h"
@@ -347,15 +347,7 @@ void KisApplication::loadPlugins()
     KisPaintOpRegistry::instance();
     KoToolRegistry::instance();
     KoDockRegistry::instance();
-}
-
-void KisApplication::loadGuiPlugins()
-{
-    // XXX_EXIV: make the exiv io backends real plugins
-    setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO..."));
-    processEvents();
-    //    qDebug() << "loading exiv2";
-    KisExiv2::initialize();
+    KisMetadataBackendRegistry::instance();
 }
 
 bool KisApplication::start(const KisApplicationArguments &args)
@@ -428,9 +420,6 @@ bool KisApplication::start(const KisApplicationArguments &args)
         return false;
     }
 
-    // Load the gui plugins
-    loadGuiPlugins();
-
     KisPart *kisPart = KisPart::instance();
     if (needsMainWindow) {
         // show a mainWindow asap, if we want that
diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h
index 8b8ff409b0..19b70c7679 100644
--- a/libs/ui/KisApplication.h
+++ b/libs/ui/KisApplication.h
@@ -85,7 +85,6 @@ public:
     void addResourceTypes();
     bool registerResources();
     void loadPlugins();
-    void loadGuiPlugins();
     void initializeGlobals(const KisApplicationArguments &args);
 
 public Q_SLOTS:
diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp
index 0ec098ff4f..84b941695d 100644
--- a/libs/ui/kis_png_converter.cpp
+++ b/libs/ui/kis_png_converter.cpp
@@ -37,23 +37,23 @@
 #include <KoColor.h>
 #include <KoUnit.h>
 
-#include <kis_config.h>
-#include <kis_painter.h>
+#include "dialogs/kis_dlg_png_import.h"
+#include "kis_clipboard.h"
+#include "kis_undo_stores.h"
 #include <KisDocument.h>
+#include <KoColorModelStandardIds.h>
+#include <kis_config.h>
+#include <kis_cursor_override_hijacker.h>
+#include <kis_group_layer.h>
 #include <kis_image.h>
 #include <kis_iterator_ng.h>
 #include <kis_layer.h>
+#include <kis_meta_data_backend_registry.h>
+#include <kis_meta_data_store.h>
 #include <kis_paint_device.h>
-#include <kis_transaction.h>
 #include <kis_paint_layer.h>
-#include <kis_group_layer.h>
-#include <kis_meta_data_io_backend.h>
-#include <kis_meta_data_store.h>
-#include <KoColorModelStandardIds.h>
-#include "dialogs/kis_dlg_png_import.h"
-#include "kis_clipboard.h"
-#include <kis_cursor_override_hijacker.h>
-#include "kis_undo_stores.h"
+#include <kis_painter.h>
+#include <kis_transaction.h>
 
 #include <kis_assert.h>
 
@@ -240,7 +240,7 @@ QByteArray png_read_raw_profile(png_textp text)
 void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int \
headerSize)  {
     dbgFile << "Decoding " << type << " " << text[0].key;
-    KisMetaData::IOBackend* exifIO = \
KisMetaData::IOBackendRegistry::instance()->value(type); +    KisMetaData::IOBackend \
*exifIO = KisMetadataBackendRegistry::instance()->value(type);  Q_ASSERT(exifIO);
 
     QByteArray rawProfile = png_read_raw_profile(text);
@@ -1244,7 +1244,7 @@ KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* \
iodevice, const Q  if (options.exif) {
             dbgFile << "Trying to save exif information";
 
-            KisMetaData::IOBackend* exifIO = \
KisMetaData::IOBackendRegistry::instance()->value("exif"); +            \
KisMetaData::IOBackend *exifIO = \
KisMetadataBackendRegistry::instance()->value("exif");  Q_ASSERT(exifIO);
 
             QBuffer buffer;
@@ -1254,7 +1254,7 @@ KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* \
iodevice, const Q  // Save IPTC
         if (options.iptc) {
             dbgFile << "Trying to save exif information";
-            KisMetaData::IOBackend* iptcIO = \
KisMetaData::IOBackendRegistry::instance()->value("iptc"); +            \
KisMetaData::IOBackend *iptcIO = \
KisMetadataBackendRegistry::instance()->value("iptc");  Q_ASSERT(iptcIO);
 
             QBuffer buffer;
@@ -1266,7 +1266,7 @@ KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* \
iodevice, const Q  // Save XMP
         if (options.xmp) {
             dbgFile << "Trying to save XMP information";
-            KisMetaData::IOBackend* xmpIO = \
KisMetaData::IOBackendRegistry::instance()->value("xmp"); +            \
KisMetaData::IOBackend *xmpIO = KisMetadataBackendRegistry::instance()->value("xmp"); \
Q_ASSERT(xmpIO);  
             QBuffer buffer;
diff --git a/libs/ui/kisexiv2/.krazy b/libs/ui/kisexiv2/.krazy
deleted file mode 100644
index cbcf2fb7c9..0000000000
--- a/libs/ui/kisexiv2/.krazy
+++ /dev/null
@@ -1 +0,0 @@
-EXCLUDE typedefs
diff --git a/libs/ui/kisexiv2/kis_exiv2.h b/libs/ui/kisexiv2/kis_exiv2.h
deleted file mode 100644
index 7b565a1584..0000000000
--- a/libs/ui/kisexiv2/kis_exiv2.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
- *
- *  SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef _KIS_EXIV2_H_
-#define _KIS_EXIV2_H_
-
-
-#include <kis_meta_data_value.h>
-#include <exiv2/exiv2.hpp>
-#include "kritaui_export.h"
-
-/// Convert an exiv value to a KisMetaData value
-KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr value, bool \
forceSeq, KisMetaData::Value::ValueType arrayType = \
                KisMetaData::Value::UnorderedArray);
-
-/// Convert a KisMetaData to an Exiv value
-Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& value, Exiv2::TypeId \
                type);
-
-/**
- * Convert a KisMetaData to an Exiv value, without knowing the targeted \
                Exiv2::TypeId
- * This function should be used for saving to XMP.
- */
-Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& value);
-
-struct KRITAUI_EXPORT KisExiv2 {
-
-    static void initialize();
-
-};
-#endif
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index 2f08704aa4..b2b7921288 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -272,13 +272,6 @@ endif()
         NAME_PREFIX "libs-ui-"
         ${MACOS_GUI_TEST})
 
-
-    krita_add_broken_unit_test( kis_exiv2_test.cpp
-        TEST_NAME KisExiv2Test
-        LINK_LIBRARIES kritaui Qt5::Test
-        NAME_PREFIX "libs-ui-"
-        ${MACOS_GUI_TEST})
-
     krita_add_broken_unit_test( kis_clipboard_test.cpp
         TEST_NAME KisClipboardTest
         LINK_LIBRARIES kritaui Qt5::Test
@@ -349,7 +342,6 @@ endif()
 
     macos_test_fixrpath(
         ${BROKEN_TESTS}
-        KisExiv2Test
         KisClipboardTest
         KisSelectionManagerTest
         KisNodeManagerTest
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index f22788342b..1d92f7e198 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -19,6 +19,7 @@ add_subdirectory( filters )
 add_subdirectory( flake )
 add_subdirectory( generators )
 add_subdirectory( impex )
+add_subdirectory( metadata )
 add_subdirectory( paintops )
 add_subdirectory( tools )
 add_subdirectory( qt )
diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp
index 48f1339664..c8e514d864 100644
--- a/plugins/impex/heif/HeifExport.cpp
+++ b/plugins/impex/heif/HeifExport.cpp
@@ -37,14 +37,14 @@
 #include <kis_paint_device.h>
 #include <kis_paint_layer.h>
 
-#include <kis_meta_data_store.h>
+#include <kis_exif_info_visitor.h>
+#include <kis_meta_data_backend_registry.h>
 #include <kis_meta_data_entry.h>
-#include <kis_meta_data_value.h>
+#include <kis_meta_data_filter_registry_model.h>
 #include <kis_meta_data_schema.h>
 #include <kis_meta_data_schema_registry.h>
-#include <kis_meta_data_filter_registry_model.h>
-#include <kis_exif_info_visitor.h>
-#include <kis_meta_data_io_backend.h>
+#include <kis_meta_data_store.h>
+#include <kis_meta_data_value.h>
 
 #include "kis_iterator_ng.h"
 
@@ -479,7 +479,7 @@ KisImportExportErrorCode HeifExport::convert(KisDocument \
*document, QIODevice *i  
         if (!metaDataStore->empty()) {
             {
-                KisMetaData::IOBackend* exifIO = \
KisMetaData::IOBackendRegistry::instance()->value("exif"); +                \
KisMetaData::IOBackend *exifIO = \
KisMetadataBackendRegistry::instance()->value("exif");  QBuffer buffer;
                 exifIO->saveTo(metaDataStore.data(), &buffer, \
KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else?  QByteArray \
data = buffer.data(); @@ -490,7 +490,7 @@ KisImportExportErrorCode \
HeifExport::convert(KisDocument *document, QIODevice *i  }
             }
             {
-                KisMetaData::IOBackend* xmpIO = \
KisMetaData::IOBackendRegistry::instance()->value("xmp"); +                \
KisMetaData::IOBackend *xmpIO = KisMetadataBackendRegistry::instance()->value("xmp"); \
                QBuffer buffer;
                 xmpIO->saveTo(metaDataStore.data(), &buffer, \
KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else?  QByteArray \
                data = buffer.data();
diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp
index fe72e747c8..6ae3d788c0 100644
--- a/plugins/impex/heif/HeifImport.cpp
+++ b/plugins/impex/heif/HeifImport.cpp
@@ -30,10 +30,10 @@
 #include <kis_node.h>
 #include <kis_group_layer.h>
 
+#include <kis_meta_data_backend_registry.h>
 #include <kis_meta_data_entry.h>
-#include <kis_meta_data_value.h>
 #include <kis_meta_data_store.h>
-#include <kis_meta_data_io_backend.h>
+#include <kis_meta_data_value.h>
 
 #include "kis_iterator_ng.h"
 
@@ -458,12 +458,13 @@ KisImportExportErrorCode HeifImport::convert(KisDocument \
                *document, QIODevice *i
               size_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | \
(exif_data[2]<<8) | exif_data[3]) + 4;  
               if (exif_data.size()>skip) {
-                KisMetaData::IOBackend* exifIO = \
KisMetaData::IOBackendRegistry::instance()->value("exif"); +                  \
KisMetaData::IOBackend *exifIO = \
KisMetadataBackendRegistry::instance()->value("exif");  
-                // Copy the exif data into the byte array
-                QByteArray ba(reinterpret_cast<char *>(exif_data.data()+skip), \
                static_cast<int>(exif_data.size()-skip));
-                QBuffer buf(&ba);
-                exifIO->loadFrom(layer->metaData(), &buf);
+                  // Copy the exif data into the byte array
+                  QByteArray ba(reinterpret_cast<char *>(exif_data.data() + skip),
+                                static_cast<int>(exif_data.size() - skip));
+                  QBuffer buf(&ba);
+                  exifIO->loadFrom(layer->metaData(), &buf);
               }
             }
           }
@@ -474,7 +475,7 @@ KisImportExportErrorCode HeifImport::convert(KisDocument \
*document, QIODevice *i  
             std::vector<uint8_t> xmp_data = handle.get_metadata(id);
 
-            KisMetaData::IOBackend* xmpIO = \
KisMetaData::IOBackendRegistry::instance()->value("xmp"); +            \
KisMetaData::IOBackend *xmpIO = KisMetadataBackendRegistry::instance()->value("xmp"); \
  // Copy the xmp data into the byte array
             QByteArray ba(reinterpret_cast<char *>(xmp_data.data()), \
                static_cast<int>(xmp_data.size()));
diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc \
b/plugins/impex/jpeg/kis_jpeg_converter.cc index 8b22279bc6..3364ae6eda 100644
--- a/plugins/impex/jpeg/kis_jpeg_converter.cc
+++ b/plugins/impex/jpeg/kis_jpeg_converter.cc
@@ -36,21 +36,21 @@ extern "C" {
 #include <KoUnit.h>
 #include "KoColorModelStandardIds.h"
 
-#include <kis_painter.h>
 #include <KisDocument.h>
-#include <kis_image.h>
-#include <kis_paint_layer.h>
-#include <kis_transaction.h>
 #include <kis_group_layer.h>
+#include <kis_image.h>
+#include <kis_iterator_ng.h>
+#include <kis_jpeg_destination.h>
+#include <kis_jpeg_source.h>
+#include <kis_meta_data_backend_registry.h>
 #include <kis_meta_data_entry.h>
-#include <kis_meta_data_value.h>
 #include <kis_meta_data_store.h>
-#include <kis_meta_data_io_backend.h>
+#include <kis_meta_data_value.h>
 #include <kis_paint_device.h>
+#include <kis_paint_layer.h>
+#include <kis_painter.h>
+#include <kis_transaction.h>
 #include <kis_transform_worker.h>
-#include <kis_jpeg_source.h>
-#include <kis_jpeg_destination.h>
-#include "kis_iterator_ng.h"
 
 #define ICC_MARKER  (JPEG_APP0 + 2) /* JPEG marker code for ICC */
 #define ICC_OVERHEAD_LEN  14    /* size of non-profile data in APP2 */
@@ -311,7 +311,7 @@ KisImportExportErrorCode KisJPEGConverter::decode(QIODevice *io)
                 continue; /* no Exif header */
 
             dbgFile << "Found exif information of length :" << marker->data_length;
-            KisMetaData::IOBackend* exifIO = \
KisMetaData::IOBackendRegistry::instance()->value("exif"); +            \
KisMetaData::IOBackend *exifIO = \
KisMetadataBackendRegistry::instance()->value("exif");  Q_ASSERT(exifIO);
             QByteArray byteArray((const char*)marker->data + 6, marker->data_length \
- 6);  QBuffer buf(&byteArray);
@@ -369,7 +369,7 @@ KisImportExportErrorCode KisJPEGConverter::decode(QIODevice *io)
             }
 
             dbgFile << "Found Photoshop information of length :" << \
                marker->data_length;
-            KisMetaData::IOBackend* iptcIO = \
KisMetaData::IOBackendRegistry::instance()->value("iptc"); +            \
KisMetaData::IOBackend *iptcIO = \
KisMetadataBackendRegistry::instance()->value("iptc");  Q_ASSERT(iptcIO);
             const Exiv2::byte *record = 0;
             uint32_t sizeIptc = 0;
@@ -402,7 +402,7 @@ KisImportExportErrorCode KisJPEGConverter::decode(QIODevice *io)
             }
             dbgFile << "Found XMP Marker of length " << marker->data_length;
             QByteArray byteArray((const char*)marker->data + 29, marker->data_length \
                - 29);
-            KisMetaData::IOBackend* xmpIO = \
KisMetaData::IOBackendRegistry::instance()->value("xmp"); +            \
KisMetaData::IOBackend *xmpIO = KisMetadataBackendRegistry::instance()->value("xmp"); \
Q_ASSERT(xmpIO);  xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray));
             break;
@@ -562,7 +562,7 @@ KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice \
*io, KisPaintLaye  if (options.exif) {
                 dbgFile << "Trying to save exif information";
 
-                KisMetaData::IOBackend* exifIO = \
KisMetaData::IOBackendRegistry::instance()->value("exif"); +                \
KisMetaData::IOBackend *exifIO = \
KisMetadataBackendRegistry::instance()->value("exif");  Q_ASSERT(exifIO);
 
                 QBuffer buffer;
@@ -579,7 +579,7 @@ KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice \
*io, KisPaintLaye  // Save IPTC
             if (options.iptc) {
                 dbgFile << "Trying to save exif information";
-                KisMetaData::IOBackend* iptcIO = \
KisMetaData::IOBackendRegistry::instance()->value("iptc"); +                \
KisMetaData::IOBackend *iptcIO = \
KisMetadataBackendRegistry::instance()->value("iptc");  Q_ASSERT(iptcIO);
 
                 QBuffer buffer;
@@ -596,7 +596,7 @@ KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice \
*io, KisPaintLaye  // Save XMP
             if (options.xmp) {
                 dbgFile << "Trying to save XMP information";
-                KisMetaData::IOBackend* xmpIO = \
KisMetaData::IOBackendRegistry::instance()->value("xmp"); +                \
KisMetaData::IOBackend *xmpIO = KisMetadataBackendRegistry::instance()->value("xmp"); \
Q_ASSERT(xmpIO);  
                 QBuffer buffer;
diff --git a/plugins/impex/jpeg/tests/CMakeLists.txt \
b/plugins/impex/jpeg/tests/CMakeLists.txt index bbe98e1d57..d7f067cb73 100644
--- a/plugins/impex/jpeg/tests/CMakeLists.txt
+++ b/plugins/impex/jpeg/tests/CMakeLists.txt
@@ -7,7 +7,7 @@ if (APPLE)
 
     krita_add_broken_unit_tests(
         kis_jpeg_test.cpp
-        LINK_LIBRARIES kritaui Qt5::Test
+        LINK_LIBRARIES kritametadata kritaui Qt5::Test
         NAME_PREFIX "plugins-impex-"
         TARGET_NAMES_VAR BROKEN_TESTS
         ${MACOS_GUI_TEST}
@@ -19,7 +19,7 @@ else (APPLE)
     ecm_add_test(
         kis_jpeg_test.cpp
         TEST_NAME kis_jpeg_test
-        LINK_LIBRARIES kritaui Qt5::Test
+        LINK_LIBRARIES kritametadata kritaui Qt5::Test
         NAME_PREFIX "plugins-impex-"
         )
 
diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp \
b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp index de2c82af88..e9eef81664 100644
--- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
+++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
@@ -9,10 +9,10 @@
 #include <simpletest.h>
 #include <QCoreApplication>
 
-#include "kisexiv2/kis_exiv2.h"
 #include "filestest.h"
 #include "jpeglib.h"
-#include  <sdk/tests/testui.h>
+#include <kis_meta_data_backend_registry.h>
+#include <sdk/tests/testui.h>
 
 #ifndef FILES_DATA_DIR
 #error "FILES_DATA_DIR not set. A directory with the data used for testing the \
importing of files in krita" @@ -26,7 +26,7 @@ const QString JpegMimetype = \
"image/jpeg";  
 void KisJpegTest::testFiles()
 {
-    KisExiv2::initialize();
+    KisMetadataBackendRegistry::instance();
     /**
      * Different versions of JPEG library may produce a bit different
      * result, so just compare in a weak way, i.e, only the size for real
diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp \
b/plugins/impex/libkra/kis_kra_load_visitor.cpp index fe30ebfd03..873a83cb71 100644
--- a/plugins/impex/libkra/kis_kra_load_visitor.cpp
+++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp
@@ -28,37 +28,36 @@
 #include <KisGlobalResourcesInterface.h>
 
 // kritaimage
-#include <kis_meta_data_io_backend.h>
-#include <kis_meta_data_store.h>
-#include <kis_types.h>
-#include <kis_node_visitor.h>
-#include <kis_image.h>
-#include <kis_selection.h>
-#include <kis_layer.h>
-#include <kis_paint_layer.h>
-#include <kis_group_layer.h>
-#include <kis_adjustment_layer.h>
+#include "kis_colorize_dom_utils.h"
+#include "kis_dom_utils.h"
+#include "kis_filter_registry.h"
+#include "kis_generator_registry.h"
+#include "kis_paint_device_frames_interface.h"
+#include "kis_raster_keyframe_channel.h"
+#include "kis_shape_selection.h"
+#include "kis_transform_mask_params_factory_registry.h"
 #include <filter/kis_filter_configuration.h>
-#include <kis_datamanager.h>
 #include <generator/kis_generator_layer.h>
-#include <kis_pixel_selection.h>
+#include <kis_adjustment_layer.h>
 #include <kis_clone_layer.h>
+#include <kis_datamanager.h>
 #include <kis_filter_mask.h>
+#include <kis_group_layer.h>
+#include <kis_image.h>
+#include <kis_layer.h>
+#include <kis_meta_data_backend_registry.h>
+#include <kis_meta_data_store.h>
+#include <kis_node_visitor.h>
+#include <kis_paint_layer.h>
+#include <kis_pixel_selection.h>
+#include <kis_selection.h>
+#include <kis_selection_mask.h>
 #include <kis_transform_mask.h>
 #include <kis_transform_mask_params_interface.h>
-#include "kis_transform_mask_params_factory_registry.h"
 #include <kis_transparency_mask.h>
-#include <kis_selection_mask.h>
+#include <kis_types.h>
 #include <lazybrush/kis_colorize_mask.h>
 #include <lazybrush/kis_lazy_fill_tools.h>
-#include "kis_shape_selection.h"
-#include "kis_colorize_dom_utils.h"
-#include "kis_dom_utils.h"
-#include "kis_raster_keyframe_channel.h"
-#include "kis_paint_device_frames_interface.h"
-#include "kis_filter_registry.h"
-#include "kis_generator_registry.h"
-
 
 using namespace KRA;
 
@@ -693,7 +692,7 @@ bool KisKraLoadVisitor::loadMetaData(KisNode* node)
     KisLayer* layer = qobject_cast<KisLayer*>(node);
     if (!layer) return true;
 
-    KisMetaData::IOBackend* backend = \
KisMetaData::IOBackendRegistry::instance()->get("xmp"); +    KisMetaData::IOBackend \
*backend = KisMetadataBackendRegistry::instance()->get("xmp");  
     if (!backend || !backend->supportLoading()) {
         if (backend)
diff --git a/plugins/impex/libkra/kis_kra_save_visitor.cpp \
b/plugins/impex/libkra/kis_kra_save_visitor.cpp index b5b1ecf41b..a8c8935f51 100644
--- a/plugins/impex/libkra/kis_kra_save_visitor.cpp
+++ b/plugins/impex/libkra/kis_kra_save_visitor.cpp
@@ -16,31 +16,31 @@
 #include <KoStore.h>
 #include <KoColorSpace.h>
 
+#include "lazybrush/kis_colorize_mask.h"
+#include <KisReferenceImage.h>
+#include <KisReferenceImagesLayer.h>
 #include <filter/kis_filter_configuration.h>
 #include <generator/kis_generator_layer.h>
 #include <kis_adjustment_layer.h>
 #include <kis_annotation.h>
+#include <kis_clone_layer.h>
+#include <kis_file_layer.h>
+#include <kis_filter_mask.h>
 #include <kis_group_layer.h>
 #include <kis_image.h>
 #include <kis_layer.h>
+#include <kis_mask.h>
+#include <kis_meta_data_backend_registry.h>
+#include <kis_meta_data_store.h>
 #include <kis_paint_layer.h>
+#include <kis_pixel_selection.h>
 #include <kis_selection.h>
+#include <kis_selection_component.h>
+#include <kis_selection_mask.h>
 #include <kis_shape_layer.h>
-#include <KisReferenceImagesLayer.h>
-#include <KisReferenceImage.h>
-#include <kis_file_layer.h>
-#include <kis_clone_layer.h>
-#include <kis_mask.h>
-#include <kis_filter_mask.h>
 #include <kis_transform_mask.h>
 #include <kis_transform_mask_params_interface.h>
 #include <kis_transparency_mask.h>
-#include <kis_selection_mask.h>
-#include "lazybrush/kis_colorize_mask.h"
-#include <kis_selection_component.h>
-#include <kis_pixel_selection.h>
-#include <kis_meta_data_store.h>
-#include <kis_meta_data_io_backend.h>
 
 #include "kis_config.h"
 #include "kis_store_paintdevice_writer.h"
@@ -502,7 +502,7 @@ bool KisKraSaveVisitor::saveMetaData(KisNode* node)
     if (metadata->isEmpty()) return true;
 
     // Serialize all the types of metadata there are
-    KisMetaData::IOBackend* backend = \
KisMetaData::IOBackendRegistry::instance()->get("xmp"); +    KisMetaData::IOBackend \
*backend = KisMetadataBackendRegistry::instance()->get("xmp");  if \
                (!backend->supportSaving()) {
         dbgFile << "Backend " << backend->id() << " does not support saving.";
         return false;
diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp \
b/plugins/impex/tiff/tests/kis_tiff_test.cpp index 2b2f081ca5..5cc215f309 100644
--- a/plugins/impex/tiff/tests/kis_tiff_test.cpp
+++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp
@@ -15,9 +15,9 @@
 #include <KoColorModelStandardIds.h>
 #include <KoColor.h>
 
-#include "kisexiv2/kis_exiv2.h"
-#include  <sdk/tests/testui.h>
 #include <KoColorModelStandardIdsUtils.h>
+#include <kis_meta_data_backend_registry.h>
+#include <sdk/tests/testui.h>
 
 #ifndef FILES_DATA_DIR
 #error "FILES_DATA_DIR not set. A directory with the data used for testing the \
importing of files in krita" @@ -28,8 +28,7 @@ const QString TiffMimetype = \
"image/tiff";  
 void KisTiffTest::testFiles()
 {
-    // XXX: make the exiv io backends real plugins
-    KisExiv2::initialize();
+    KisMetadataBackendRegistry::instance();
 
     QStringList excludes;
 
diff --git a/plugins/metadata/CMakeLists.txt b/plugins/metadata/CMakeLists.txt
new file mode 100644
index 0000000000..9c7a9e3f75
--- /dev/null
+++ b/plugins/metadata/CMakeLists.txt
@@ -0,0 +1,7 @@
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common)
+
+add_subdirectory(exif)
+add_subdirectory(iptc)
+add_subdirectory(xmp)
+
+add_subdirectory(tests)
diff --git a/libs/ui/kisexiv2/kis_exiv2.cpp \
b/plugins/metadata/common/kis_exiv2_common.h similarity index 81%
rename from libs/ui/kisexiv2/kis_exiv2.cpp
rename to plugins/metadata/common/kis_exiv2_common.h
index 211afca4ee..3c3299e3b7 100644
--- a/libs/ui/kisexiv2/kis_exiv2.cpp
+++ b/plugins/metadata/common/kis_exiv2_common.h
@@ -5,21 +5,23 @@
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
 
-#include "kis_exiv2.h"
+#ifndef _KIS_EXIV2_COMMON_H_
+#define _KIS_EXIV2_COMMON_H_
 
 #include <QDateTime>
 
-#include "kis_iptc_io.h"
-#include "kis_exif_io.h"
-#include "kis_xmp_io.h"
+#include <exiv2/exiv2.hpp>
 
-#include <kis_meta_data_value.h>
 #include <kis_debug.h>
+#include <kis_meta_data_value.h>
 
 // ---- Generic conversion functions ---- //
 
 // Convert an exiv value to a KisMetaData value
-KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr value, bool \
forceSeq, KisMetaData::Value::ValueType arrayType) +inline KisMetaData::Value
+exivValueToKMDValue(const Exiv2::Value::AutoPtr value,
+                    bool forceSeq,
+                    KisMetaData::Value::ValueType arrayType = \
KisMetaData::Value::UnorderedArray)  {
     switch (value->typeId()) {
     case Exiv2::signedByte:
@@ -30,8 +32,8 @@ KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr \
value, bool f  return KisMetaData::Value();
     case Exiv2::undefined: {
         dbgMetaData << "Undefined value :" << value->typeId() << " value =" << \
                value->toString().c_str();
-        QByteArray array(value->count() , 0);
-        value->copy((Exiv2::byte*)array.data(), Exiv2::invalidByteOrder);
+        QByteArray array(value->count(), 0);
+        value->copy((Exiv2::byte *)array.data(), Exiv2::invalidByteOrder);
         return KisMetaData::Value(QString(array.toBase64()));
     }
     case Exiv2::unsignedByte:
@@ -90,23 +92,22 @@ KisMetaData::Value exivValueToKMDValue(const \
Exiv2::Value::AutoPtr value, bool f  case Exiv2::langAlt:
     default: {
         dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << \
                value->toString().c_str();
-        //Q_ASSERT(false); // This point must never be reached !
+        // Q_ASSERT(false); // This point must never be reached !
         return KisMetaData::Value();
     }
     }
     dbgMetaData << "Unknown type id :" << value->typeId() << " value =" << \
                value->toString().c_str();
-    //Q_ASSERT(false); // This point must never be reached !
+    // Q_ASSERT(false); // This point must never be reached !
     return KisMetaData::Value();
 }
 
-
 // Convert a QtVariant to an Exiv value
-Exiv2::Value* variantToExivValue(const QVariant& variant, Exiv2::TypeId type)
+inline Exiv2::Value *variantToExivValue(const QVariant &variant, Exiv2::TypeId type)
 {
     switch (type) {
     case Exiv2::undefined: {
         QByteArray arr = QByteArray::fromBase64(variant.toString().toLatin1());
-        return new Exiv2::DataValue((Exiv2::byte*)arr.data(), arr.size());
+        return new Exiv2::DataValue((Exiv2::byte *)arr.data(), arr.size());
     }
     case Exiv2::unsignedByte:
         return new Exiv2::ValueType<uint16_t>((uint16_t)variant.toUInt());
@@ -124,12 +125,14 @@ Exiv2::Value* variantToExivValue(const QVariant& variant, \
Exiv2::TypeId type)  }
     case Exiv2::asciiString:
         if (variant.type() == QVariant::DateTime) {
-            return new \
Exiv2::AsciiValue(qPrintable(QLocale::c().toString(variant.toDateTime(), \
QStringLiteral("yyyy:MM:dd hh:mm:ss")))); +            return new Exiv2::AsciiValue(
+                qPrintable(QLocale::c().toString(variant.toDateTime(), \
QStringLiteral("yyyy:MM:dd hh:mm:ss"))));  } else
             return new Exiv2::AsciiValue(qPrintable(variant.toString()));
     case Exiv2::string: {
         if (variant.type() == QVariant::DateTime) {
-            return new \
Exiv2::StringValue(qPrintable(QLocale::c().toString(variant.toDateTime(), \
QStringLiteral("yyyy:MM:dd hh:mm:ss")))); +            return new Exiv2::StringValue(
+                qPrintable(QLocale::c().toString(variant.toDateTime(), \
QStringLiteral("yyyy:MM:dd hh:mm:ss"))));  } else
             return new Exiv2::StringValue(qPrintable(variant.toString()));
     }
@@ -137,23 +140,23 @@ Exiv2::Value* variantToExivValue(const QVariant& variant, \
Exiv2::TypeId type)  return new Exiv2::CommentValue(qPrintable(variant.toString()));
     default:
         dbgMetaData << "Unhandled type:" << type;
-        //Q_ASSERT(false);
+        // Q_ASSERT(false);
         return 0;
     }
 }
 
 template<typename _TYPE_>
-Exiv2::Value* arrayToExivValue(const KisMetaData::Value& value)
+Exiv2::Value *arrayToExivValue(const KisMetaData::Value &value)
 {
-    Exiv2::ValueType<_TYPE_>* ev = new Exiv2::ValueType<_TYPE_>();
+    Exiv2::ValueType<_TYPE_> *ev = new Exiv2::ValueType<_TYPE_>();
     for (int i = 0; i < value.asArray().size(); ++i) {
         ev->value_.push_back(qvariant_cast<_TYPE_>(value.asArray()[i].asVariant()));
     }
     return ev;
 }
 
-// Convert a KisMetaData to an Exiv value
-Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& value, Exiv2::TypeId \
type) +/// Convert a KisMetaData to an Exiv value
+inline Exiv2::Value *kmdValueToExivValue(const KisMetaData::Value &value, \
Exiv2::TypeId type)  {
     switch (value.type()) {
     case KisMetaData::Value::Invalid:
@@ -162,7 +165,7 @@ Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& \
value, Exiv2::TypeId  return variantToExivValue(value.asVariant(), type);
     }
     case KisMetaData::Value::Rational:
-        //Q_ASSERT(type == Exiv2::signedRational || type == \
Exiv2::unsignedRational); +        // Q_ASSERT(type == Exiv2::signedRational || type \
== Exiv2::unsignedRational);  if (type == Exiv2::signedRational) {
             return new Exiv2::RationalValue({value.asRational().numerator, \
value.asRational().denominator});  } else {
@@ -185,10 +188,11 @@ Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& \
value, Exiv2::TypeId  case Exiv2::signedLong:
             return arrayToExivValue<int32_t>(value);
         case Exiv2::string: {
-            Exiv2::StringValue* ev = new Exiv2::StringValue();
+            Exiv2::StringValue *ev = new Exiv2::StringValue();
             for (int i = 0; i < value.asArray().size(); ++i) {
                 ev->value_ += \
                qvariant_cast<QString>(value.asArray()[i].asVariant()).toLatin1().constData();
                
-                if (i != value.asArray().size() - 1) ev->value_ += ',';
+                if (i != value.asArray().size() - 1)
+                    ev->value_ += ',';
             }
             return ev;
         }
@@ -223,9 +227,11 @@ Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& \
value, Exiv2::TypeId  return 0;
 }
 
-Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& value)
+/// Convert a KisMetaData to an Exiv value, without knowing the targeted \
Exiv2::TypeId +/// This function should be used for saving to XMP.
+inline Exiv2::Value *kmdValueToExivXmpValue(const KisMetaData::Value &value)
 {
-    //Q_ASSERT(value.type() != KisMetaData::Value::Structure);
+    // Q_ASSERT(value.type() != KisMetaData::Value::Structure);
     switch (value.type()) {
     case KisMetaData::Value::Invalid:
         return new Exiv2::DataValue(Exiv2::invalidTypeId);
@@ -238,7 +244,7 @@ Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& \
value)  return new Exiv2::XmpTextValue("False");
             }
         } else {
-            //Q_ASSERT(var.canConvert(QVariant::String));
+            // Q_ASSERT(var.canConvert(QVariant::String));
             return new Exiv2::XmpTextValue(var.toString().toLatin1().constData());
         }
     }
@@ -251,7 +257,7 @@ Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& \
value)  case KisMetaData::Value::AlternativeArray:
     case KisMetaData::Value::OrderedArray:
     case KisMetaData::Value::UnorderedArray: {
-        Exiv2::XmpArrayValue* arrV = new Exiv2::XmpArrayValue;
+        Exiv2::XmpArrayValue *arrV = new Exiv2::XmpArrayValue;
         switch (value.type()) {
         case KisMetaData::Value::OrderedArray:
             arrV->setXmpArrayType(Exiv2::XmpValue::xaSeq);
@@ -266,8 +272,8 @@ Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& \
value)  // Cannot happen
             ;
         }
-        Q_FOREACH (const KisMetaData::Value& v, value.asArray()) {
-            Exiv2::Value* ev = kmdValueToExivXmpValue(v);
+        Q_FOREACH (const KisMetaData::Value &v, value.asArray()) {
+            Exiv2::Value *ev = kmdValueToExivXmpValue(v);
             if (ev) {
                 arrV->read(ev->toString());
                 delete ev;
@@ -276,17 +282,16 @@ Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& \
value)  return arrV;
     }
     case KisMetaData::Value::LangArray: {
-        Exiv2::Value* arrV = new Exiv2::LangAltValue;
+        Exiv2::Value *arrV = new Exiv2::LangAltValue;
         QMap<QString, KisMetaData::Value> langArray = value.asLangArray();
-        for (QMap<QString, KisMetaData::Value>::iterator it = langArray.begin();
-             it != langArray.end(); ++it) {
+        for (QMap<QString, KisMetaData::Value>::iterator it = langArray.begin(); it \
!= langArray.end(); ++it) {  QString exivVal;
             if (it.key() != "x-default") {
                 exivVal = "lang=" + it.key() + ' ';
             }
-            //Q_ASSERT(it.value().type() == KisMetaData::Value::Variant);
+            // Q_ASSERT(it.value().type() == KisMetaData::Value::Variant);
             QVariant var = it.value().asVariant();
-            //Q_ASSERT(var.type() == QVariant::String);
+            // Q_ASSERT(var.type() == QVariant::String);
             exivVal += var.toString();
             arrV->read(exivVal.toLatin1().constData());
         }
@@ -297,18 +302,8 @@ Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& \
value)  warnKrita << "KisExiv2: Unhandled value type";
         return 0;
     }
-
     }
     warnKrita << "KisExiv2: Unhandled value type";
     return 0;
 }
-
-void KisExiv2::initialize()
-{
-    // XXX_EXIV: make the exiv io backends real plugins
-
-    KisMetaData::IOBackendRegistry* ioreg = \
                KisMetaData::IOBackendRegistry::instance();
-    ioreg->add(new KisIptcIO);
-    ioreg->add(new KisExifIO);
-    ioreg->add(new KisXMPIO);
-}
+#endif
diff --git a/libs/ui/kisexiv2/kis_exiv2_constants.h \
b/plugins/metadata/common/kis_exiv2_constants.h similarity index 100%
rename from libs/ui/kisexiv2/kis_exiv2_constants.h
rename to plugins/metadata/common/kis_exiv2_constants.h
diff --git a/plugins/metadata/exif/CMakeLists.txt \
b/plugins/metadata/exif/CMakeLists.txt new file mode 100644
index 0000000000..a6cf68541a
--- /dev/null
+++ b/plugins/metadata/exif/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(kritaexif_SOURCES
+    kis_exif_io.cpp
+    kis_exif_plugin.cpp
+)
+
+add_library(kritaexif MODULE ${kritaexif_SOURCES})
+
+generate_export_header(kritaexif)
+
+target_link_libraries(kritaexif
+    PRIVATE
+        kritametadata
+        KF5::CoreAddons
+        LibExiv2::LibExiv2
+)
+
+install(TARGETS kritaexif DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/libs/ui/kisexiv2/kis_exif_io.cpp b/plugins/metadata/exif/kis_exif_io.cpp
similarity index 76%
rename from libs/ui/kisexiv2/kis_exif_io.cpp
rename to plugins/metadata/exif/kis_exif_io.cpp
index 8477ba3459..e12107e14c 100644
--- a/libs/ui/kisexiv2/kis_exif_io.cpp
+++ b/plugins/metadata/exif/kis_exif_io.cpp
@@ -7,8 +7,8 @@
 
 #include "kis_exif_io.h"
 
-#include <exiv2/exif.hpp>
 #include <exiv2/error.hpp>
+#include <exiv2/exif.hpp>
 
 #include <QByteArray>
 #include <QDate>
@@ -20,42 +20,37 @@
 #include <QtEndian>
 
 #include <kis_debug.h>
-#include <kis_meta_data_store.h>
+#include <kis_exiv2_common.h>
+#include <kis_exiv2_constants.h>
 #include <kis_meta_data_entry.h>
-#include <kis_meta_data_value.h>
 #include <kis_meta_data_schema.h>
 #include <kis_meta_data_schema_registry.h>
-
-#include "kis_exiv2.h"
-#include "kis_exiv2_constants.h"
-
-struct KisExifIO::Private {
-};
+#include <kis_meta_data_store.h>
+#include <kis_meta_data_value.h>
 
 // ---- Exception conversion functions ---- //
 
 // convert ExifVersion and FlashpixVersion to a KisMetaData value
 KisMetaData::Value exifVersionToKMDValue(const Exiv2::Value::AutoPtr value)
 {
-    const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
+    const Exiv2::DataValue *dvalue = dynamic_cast<const Exiv2::DataValue \
*>(&*value);  if (dvalue) {
         Q_ASSERT(dvalue);
         QByteArray array(dvalue->count(), 0);
-        dvalue->copy((Exiv2::byte*)array.data());
+        dvalue->copy((Exiv2::byte *)array.data());
         return KisMetaData::Value(QString(array));
-    }
-    else {
+    } else {
         Q_ASSERT(value->typeId() == Exiv2::asciiString);
         return KisMetaData::Value(QString::fromLatin1(value->toString().c_str()));
     }
 }
 
 // convert from KisMetaData value to ExifVersion and FlashpixVersion
-Exiv2::Value* kmdValueToExifVersion(const KisMetaData::Value& value)
+Exiv2::Value *kmdValueToExifVersion(const KisMetaData::Value &value)
 {
-    Exiv2::DataValue* dvalue = new Exiv2::DataValue;
+    Exiv2::DataValue *dvalue = new Exiv2::DataValue;
     QString ver = value.asVariant().toString();
-    dvalue->read((const Exiv2::byte*)ver.toLatin1().constData(), ver.size());
+    dvalue->read((const Exiv2::byte *)ver.toLatin1().constData(), ver.size());
     return dvalue;
 }
 
@@ -63,7 +58,7 @@ Exiv2::Value* kmdValueToExifVersion(const KisMetaData::Value& \
value)  KisMetaData::Value exifArrayToKMDIntOrderedArray(const Exiv2::Value::AutoPtr \
value)  {
     QList<KisMetaData::Value> v;
-    const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
+    const Exiv2::DataValue *dvalue = dynamic_cast<const Exiv2::DataValue \
*>(&*value);  if (dvalue) {
         for (long i = 0; i < dvalue->count(); i++) {
             v.push_back({(int)dvalue->toLong(i)});
@@ -77,7 +72,7 @@ KisMetaData::Value exifArrayToKMDIntOrderedArray(const \
Exiv2::Value::AutoPtr val  }
 
 // Convert a KisMetaData array of integer to an exif array of integer string
-Exiv2::Value* kmdIntOrderedArrayToExifArray(const KisMetaData::Value& value)
+Exiv2::Value *kmdIntOrderedArrayToExifArray(const KisMetaData::Value &value)
 {
     std::vector<Exiv2::byte> v;
     for (const KisMetaData::Value &it : value.asArray()) {
@@ -120,22 +115,23 @@ Exiv2::ByteOrder invertByteOrder(Exiv2::ByteOrder order)
     return Exiv2::invalidByteOrder;
 }
 
-
 KisMetaData::Value exifOECFToKMDOECFStructure(const Exiv2::Value::AutoPtr value, \
Exiv2::ByteOrder order)  {
     QMap<QString, KisMetaData::Value> oecfStructure;
-    const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
+    const Exiv2::DataValue *dvalue = dynamic_cast<const Exiv2::DataValue \
*>(&*value);  Q_ASSERT(dvalue);
     QByteArray array(dvalue->count(), 0);
 
-    dvalue->copy((Exiv2::byte*)array.data());
-    int columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], \
                order);
-    int rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], \
order); +    dvalue->copy((Exiv2::byte *)array.data());
+    int columns = fixEndianess<quint16>((reinterpret_cast<quint16 \
*>(array.data()))[0], order); +    int rows = \
fixEndianess<quint16>((reinterpret_cast<quint16 *>(array.data()))[1], order);  
-    if ((columns * rows + 4) > dvalue->count()) { // Sometime byteOrder get messed \
up (especially if metadata got saved with kexiv2 library, or any library that doesn't \
save back with the same byte order as the camera) +    if ((columns * rows + 4)
+        > dvalue->count()) { // Sometime byteOrder get messed up (especially if \
metadata got saved with kexiv2 library, +                             // or any \
library that doesn't save back with the same byte order as the camera)  order = \
                invertByteOrder(order);
-        columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], \
                order);
-        rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], \
order); +        columns = fixEndianess<quint16>((reinterpret_cast<quint16 \
*>(array.data()))[0], order); +        rows = \
fixEndianess<quint16>((reinterpret_cast<quint16 *>(array.data()))[1], order);  \
Q_ASSERT((columns * rows + 4) > dvalue->count());  }
     oecfStructure["Columns"] = KisMetaData::Value(columns);
@@ -156,10 +152,11 @@ KisMetaData::Value exifOECFToKMDOECFStructure(const \
Exiv2::Value::AutoPtr value,  
     oecfStructure["Names"] = KisMetaData::Value(names, \
KisMetaData::Value::OrderedArray);  QList<KisMetaData::Value> values;
-    qint32* dataIt = reinterpret_cast<qint32*>(array.data() + index);
+    qint32 *dataIt = reinterpret_cast<qint32 *>(array.data() + index);
     for (int i = 0; i < columns; i++) {
         for (int j = 0; j < rows; j++) {
-            values.append(KisMetaData::Value(KisMetaData::Rational(fixEndianess<qint32>(dataIt[0], \
order), fixEndianess<qint32>(dataIt[1], order)))); +            \
values.append(KisMetaData::Value( +                \
KisMetaData::Rational(fixEndianess<qint32>(dataIt[0], order), \
fixEndianess<qint32>(dataIt[1], order))));  dataIt += 2;
         }
     }
@@ -168,7 +165,7 @@ KisMetaData::Value exifOECFToKMDOECFStructure(const \
Exiv2::Value::AutoPtr value,  return KisMetaData::Value(oecfStructure);
 }
 
-Exiv2::Value* kmdOECFStructureToExifOECF(const KisMetaData::Value& value)
+Exiv2::Value *kmdOECFStructureToExifOECF(const KisMetaData::Value &value)
 {
     QMap<QString, KisMetaData::Value> oecfStructure = value.asStructure();
     const quint16 columns = \
static_cast<quint16>(oecfStructure["Columns"].asVariant().toUInt()); @@ -176,7 +173,7 \
@@ Exiv2::Value* kmdOECFStructureToExifOECF(const KisMetaData::Value& value)  
     QList<KisMetaData::Value> names = oecfStructure["Names"].asArray();
     QList<KisMetaData::Value> values = oecfStructure["Values"].asArray();
-    Q_ASSERT(columns*rows == values.size());
+    Q_ASSERT(columns * rows == values.size());
     int length = 4 + rows * columns * 8; // The 4 byte for storing rows/columns and \
                the rows*columns*sizeof(rational)
     bool saveNames = (!names.empty() && names[0].asVariant().toString().size() > 0);
     if (saveNames) {
@@ -185,8 +182,8 @@ Exiv2::Value* kmdOECFStructureToExifOECF(const \
KisMetaData::Value& value)  }
     }
     QByteArray array(length, 0);
-    (reinterpret_cast<quint16*>(array.data()))[0] = columns;
-    (reinterpret_cast<quint16*>(array.data()))[1] = rows;
+    (reinterpret_cast<quint16 *>(array.data()))[0] = columns;
+    (reinterpret_cast<quint16 *>(array.data()))[1] = rows;
     int index = 4;
     if (saveNames) {
         for (int i = 0; i < columns; i++) {
@@ -196,13 +193,13 @@ Exiv2::Value* kmdOECFStructureToExifOECF(const \
KisMetaData::Value& value)  index += name.size();
         }
     }
-    qint32* dataIt = reinterpret_cast<qint32*>(array.data() + index);
+    qint32 *dataIt = reinterpret_cast<qint32 *>(array.data() + index);
     for (const KisMetaData::Value &it : values) {
         dataIt[0] = it.asRational().numerator;
         dataIt[1] = it.asRational().denominator;
         dataIt += 2;
     }
-    return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size());
+    return new Exiv2::DataValue((const Exiv2::byte *)array.data(), array.size());
 }
 
 KisMetaData::Value deviceSettingDescriptionExifToKMD(const Exiv2::Value::AutoPtr \
value) @@ -210,29 +207,29 @@ KisMetaData::Value \
deviceSettingDescriptionExifToKMD(const Exiv2::Value::AutoPtr  QMap<QString, \
KisMetaData::Value> deviceSettingStructure;  QByteArray array;
 
-    const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
+    const Exiv2::DataValue *dvalue = dynamic_cast<const Exiv2::DataValue \
*>(&*value);  if (dvalue) {
         array.resize(dvalue->count());
-        dvalue->copy((Exiv2::byte*)array.data());
+        dvalue->copy((Exiv2::byte *)array.data());
     } else {
         Q_ASSERT(value->typeId() == Exiv2::unsignedShort);
         array.resize(2 * value->count());
-        value->copy((Exiv2::byte*)array.data(), Exiv2::littleEndian);
+        value->copy((Exiv2::byte *)array.data(), Exiv2::littleEndian);
     }
-    int columns = (reinterpret_cast<quint16*>(array.data()))[0];
-    int rows = (reinterpret_cast<quint16*>(array.data()))[1];
+    int columns = (reinterpret_cast<quint16 *>(array.data()))[0];
+    int rows = (reinterpret_cast<quint16 *>(array.data()))[1];
     deviceSettingStructure["Columns"] = KisMetaData::Value(columns);
     deviceSettingStructure["Rows"] = KisMetaData::Value(rows);
     QList<KisMetaData::Value> settings;
     QByteArray null(2, 0);
 
-    for (int index = 4; index < array.size(); )
-    {
+    for (int index = 4; index < array.size();) {
         const int lastIndex = array.indexOf(null, index);
-        if (lastIndex < 0) break; // Data is not a String, ignore
+        if (lastIndex < 0)
+            break; // Data is not a String, ignore
         const int numChars = (lastIndex - index) / 2; // including trailing zero
 
-        QString setting = QString::fromUtf16((ushort*)(void*)( array.data() + \
index), numChars); +        QString setting = QString::fromUtf16((ushort *)(void \
*)(array.data() + index), numChars);  index = lastIndex + 2;
         dbgMetaData << "Setting << " << setting;
         settings.append(KisMetaData::Value(setting));
@@ -241,39 +238,41 @@ KisMetaData::Value deviceSettingDescriptionExifToKMD(const \
Exiv2::Value::AutoPtr  return KisMetaData::Value(deviceSettingStructure);
 }
 
-Exiv2::Value* deviceSettingDescriptionKMDToExif(const KisMetaData::Value& value)
+Exiv2::Value *deviceSettingDescriptionKMDToExif(const KisMetaData::Value &value)
 {
     QMap<QString, KisMetaData::Value> deviceSettingStructure = value.asStructure();
     const quint16 columns = \
                static_cast<quint16>(deviceSettingStructure["Columns"].asVariant().toUInt());
                
     quint16 rows = static_cast<quint16>(deviceSettingStructure["Rows"].asVariant().toUInt());
  
-    QTextCodec* codec = QTextCodec::codecForName("UTF-16");
+    QTextCodec *codec = QTextCodec::codecForName("UTF-16");
 
     QList<KisMetaData::Value> settings = \
deviceSettingStructure["Settings"].asArray();  QByteArray array(4, 0);
-    (reinterpret_cast<quint16*>(array.data()))[0] = columns;
-    (reinterpret_cast<quint16*>(array.data()))[1] = rows;
+    (reinterpret_cast<quint16 *>(array.data()))[0] = columns;
+    (reinterpret_cast<quint16 *>(array.data()))[1] = rows;
     for (const KisMetaData::Value &v : settings) {
         const QString str = v.asVariant().toString();
         QByteArray setting = codec->fromUnicode(str);
         array.append(setting);
     }
-    return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size());
+    return new Exiv2::DataValue((const Exiv2::byte *)array.data(), array.size());
 }
 
 KisMetaData::Value cfaPatternExifToKMD(const Exiv2::Value::AutoPtr value, \
Exiv2::ByteOrder order)  {
     QMap<QString, KisMetaData::Value> cfaPatternStructure;
-    const Exiv2::DataValue* dvalue = dynamic_cast<const Exiv2::DataValue*>(&*value);
+    const Exiv2::DataValue *dvalue = dynamic_cast<const Exiv2::DataValue \
*>(&*value);  Q_ASSERT(dvalue);
     QByteArray array(dvalue->count(), 0);
-    dvalue->copy((Exiv2::byte*)array.data());
-    int columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], \
                order);
-    int rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], \
                order);
-    if ((columns * rows + 4) != dvalue->count()) { // Sometime byteOrder get messed \
up (especially if metadata got saved with kexiv2 library, or any library that doesn't \
save back with the same byte order as the camera) +    dvalue->copy((Exiv2::byte \
*)array.data()); +    int columns = fixEndianess<quint16>((reinterpret_cast<quint16 \
*>(array.data()))[0], order); +    int rows = \
fixEndianess<quint16>((reinterpret_cast<quint16 *>(array.data()))[1], order); +    if \
((columns * rows + 4) +        != dvalue->count()) { // Sometime byteOrder get messed \
up (especially if metadata got saved with kexiv2 library, +                           \
// or any library that doesn't save back with the same byte order as the camera)  \
                order = invertByteOrder(order);
-        columns = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[0], \
                order);
-        rows = fixEndianess<quint16>((reinterpret_cast<quint16*>(array.data()))[1], \
order); +        columns = fixEndianess<quint16>((reinterpret_cast<quint16 \
*>(array.data()))[0], order); +        rows = \
fixEndianess<quint16>((reinterpret_cast<quint16 *>(array.data()))[1], order);  \
Q_ASSERT((columns * rows + 4) == dvalue->count());  }
     cfaPatternStructure["Columns"] = KisMetaData::Value(columns);
@@ -285,27 +284,28 @@ KisMetaData::Value cfaPatternExifToKMD(const \
Exiv2::Value::AutoPtr value, Exiv2:  index++;
     }
     cfaPatternStructure["Values"] = KisMetaData::Value(values, \
                KisMetaData::Value::OrderedArray);
-    dbgMetaData << "CFAPattern " << ppVar(columns) << " " << ppVar(rows) << \
ppVar(values.size()) << ppVar(dvalue->count()); +    dbgMetaData << "CFAPattern " << \
ppVar(columns) << " " << ppVar(rows) << ppVar(values.size()) +                << \
ppVar(dvalue->count());  return KisMetaData::Value(cfaPatternStructure);
 }
 
-Exiv2::Value* cfaPatternKMDToExif(const KisMetaData::Value& value)
+Exiv2::Value *cfaPatternKMDToExif(const KisMetaData::Value &value)
 {
     QMap<QString, KisMetaData::Value> cfaStructure = value.asStructure();
     const quint16 columns = \
                static_cast<quint16>(cfaStructure["Columns"].asVariant().toUInt());
     const quint16 rows = \
static_cast<quint16>(cfaStructure["Rows"].asVariant().toUInt());  
     QList<KisMetaData::Value> values = cfaStructure["Values"].asArray();
-    Q_ASSERT(columns*rows == values.size());
-    QByteArray array(4 + columns*rows, 0);
-    (reinterpret_cast<quint16*>(array.data()))[0] = columns;
-    (reinterpret_cast<quint16*>(array.data()))[1] = rows;
+    Q_ASSERT(columns * rows == values.size());
+    QByteArray array(4 + columns * rows, 0);
+    (reinterpret_cast<quint16 *>(array.data()))[0] = columns;
+    (reinterpret_cast<quint16 *>(array.data()))[1] = rows;
     for (int i = 0; i < columns * rows; i++) {
         const quint8 val = (quint8)values[i].asVariant().toUInt();
         *(array.data() + 4 + i) = (char)val;
     }
     dbgMetaData << "Cfa Array " << ppVar(columns) << ppVar(rows) << \
                ppVar(array.size());
-    return new Exiv2::DataValue((const Exiv2::byte*)array.data(), array.size());
+    return new Exiv2::DataValue((const Exiv2::byte *)array.data(), array.size());
 }
 
 // Read and write Flash //
@@ -314,20 +314,20 @@ KisMetaData::Value flashExifToKMD(const Exiv2::Value::AutoPtr \
value)  {
     const uint16_t v = static_cast<uint16_t>(value->toLong());
     QMap<QString, KisMetaData::Value> flashStructure;
-    bool fired = (v & 0x01);  // bit 1 is whether flash was fired or not
+    bool fired = (v & 0x01); // bit 1 is whether flash was fired or not
     flashStructure["Fired"] = QVariant(fired);
-    int ret = ((v >> 1) & 0x03);  // bit 2 and 3 are Return
+    int ret = ((v >> 1) & 0x03); // bit 2 and 3 are Return
     flashStructure["Return"] = QVariant(ret);
-    int mode = ((v >> 3) & 0x03);  // bit 4 and 5 are Mode
+    int mode = ((v >> 3) & 0x03); // bit 4 and 5 are Mode
     flashStructure["Mode"] = QVariant(mode);
-    bool function = ((v >> 5) & 0x01);  // bit 6 if function
+    bool function = ((v >> 5) & 0x01); // bit 6 if function
     flashStructure["Function"] = QVariant(function);
-    bool redEye = ((v >> 6) & 0x01);  // bit 7 if function
+    bool redEye = ((v >> 6) & 0x01); // bit 7 if function
     flashStructure["RedEyeMode"] = QVariant(redEye);
     return KisMetaData::Value(flashStructure);
 }
 
-Exiv2::Value* flashKMDToExif(const KisMetaData::Value& value)
+Exiv2::Value *flashKMDToExif(const KisMetaData::Value &value)
 {
     uint16_t v = 0;
     QMap<QString, KisMetaData::Value> flashStructure = value.asStructure();
@@ -339,20 +339,17 @@ Exiv2::Value* flashKMDToExif(const KisMetaData::Value& value)
     return new Exiv2::ValueType<uint16_t>(v);
 }
 
-
-
 // ---- Implementation of KisExifIO ----//
-KisExifIO::KisExifIO() : d(new Private)
+KisExifIO::KisExifIO()
+    : KisMetaData::IOBackend()
 {
 }
 
 KisExifIO::~KisExifIO()
 {
-    delete d;
 }
 
-
-bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType \
headerType) const +bool KisExifIO::saveTo(KisMetaData::Store *store, QIODevice \
*ioDevice, HeaderType headerType) const  {
     ioDevice->open(QIODevice::WriteOnly);
     Exiv2::ExifData exifData;
@@ -369,11 +366,13 @@ bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderTyp  
     for (const KisMetaData::Entry &entry : *store) {
         try {
-            dbgMetaData << "Trying to save: " << entry.name() << " of " << \
entry.schema()->prefix() << ":" << entry.schema()->uri(); +            dbgMetaData << \
"Trying to save: " << entry.name() << " of " << entry.schema()->prefix() << ":" +     \
<< entry.schema()->uri();  QString exivKey;
             if (entry.schema()->uri() == KisMetaData::Schema::TIFFSchemaUri) {
                 exivKey = "Exif.Image." + entry.name();
-            } else if (entry.schema()->uri() == KisMetaData::Schema::EXIFSchemaUri) \
{ // Distinguish between exif and gps +            } else if (entry.schema()->uri()
+                       == KisMetaData::Schema::EXIFSchemaUri) { // Distinguish \
between exif and gps  if (entry.name().left(3) == "GPS") {
                     exivKey = "Exif.GPSInfo." + entry.name();
                 } else {
@@ -403,15 +402,15 @@ bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* \
                ioDevice, HeaderTyp
                 dbgMetaData << entry.qualifiedName() << " is unsavable to EXIF";
             } else {
                 Exiv2::ExifKey exifKey(qPrintable(exivKey));
-                Exiv2::Value* v = 0;
+                Exiv2::Value *v = 0;
                 if (exivKey == "Exif.Photo.ExifVersion" || exivKey == \
"Exif.Photo.FlashpixVersion") {  v = kmdValueToExifVersion(entry.value());
                 } else if (exivKey == "Exif.Photo.FileSource") {
-                    char s[] = { 0x03 };
-                    v = new Exiv2::DataValue((const Exiv2::byte*)s, 1);
+                    char s[] = {0x03};
+                    v = new Exiv2::DataValue((const Exiv2::byte *)s, 1);
                 } else if (exivKey == "Exif.Photo.SceneType") {
-                    char s[] = { 0x01 };
-                    v = new Exiv2::DataValue((const Exiv2::byte*)s, 1);
+                    char s[] = {0x01};
+                    v = new Exiv2::DataValue((const Exiv2::byte *)s, 1);
                 } else if (exivKey == "Exif.Photo.ComponentsConfiguration") {
                     v = kmdIntOrderedArrayToExifArray(entry.value());
                 } else if (exivKey == "Exif.Image.Artist") { // load as dc:creator
@@ -419,7 +418,7 @@ bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderTyp  if (entry.value().asArray().size() > 0) {
                         creator = entry.value().asArray()[0];
                     }
-#if !EXIV2_TEST_VERSION(0,21,0)
+#if !EXIV2_TEST_VERSION(0, 21, 0)
                     v = kmdValueToExivValue(creator, \
Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));  #else
                     v = kmdValueToExivValue(creator, exifKey.defaultTypeId());
@@ -436,21 +435,23 @@ bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* \
                ioDevice, HeaderTyp
                     Q_ASSERT(entry.value().type() == KisMetaData::Value::LangArray);
                     QMap<QString, KisMetaData::Value> langArr = \
entry.value().asLangArray();  if (langArr.contains("x-default")) {
-#if !EXIV2_TEST_VERSION(0,21,0)
-                        v = kmdValueToExivValue(langArr.value("x-default"), \
Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId())); +#if \
!EXIV2_TEST_VERSION(0, 21, 0) +                        v = \
kmdValueToExivValue(langArr.value("x-default"), +                                     \
Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));  #else
                         v = kmdValueToExivValue(langArr.value("x-default"), \
exifKey.defaultTypeId());  #endif
                     } else if (langArr.size() > 0) {
-#if !EXIV2_TEST_VERSION(0,21,0)
-                        v = kmdValueToExivValue(langArr.begin().value(), \
Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId())); +#if \
!EXIV2_TEST_VERSION(0, 21, 0) +                        v = \
kmdValueToExivValue(langArr.begin().value(), +                                        \
Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));  #else
                         v = kmdValueToExivValue(langArr.begin().value(), \
exifKey.defaultTypeId());  #endif
                     }
                 } else {
                     dbgMetaData << exifKey.tag();
-#if !EXIV2_TEST_VERSION(0,21,0)
+#if !EXIV2_TEST_VERSION(0, 21, 0)
                     v = kmdValueToExivValue(entry.value(), \
Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId()));  #else
                     v = kmdValueToExivValue(entry.value(), exifKey.defaultTypeId());
@@ -460,16 +461,17 @@ bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* \
                ioDevice, HeaderTyp
                     dbgMetaData << "Saving key" << exivKey << " of KMD value" << \
entry.value();  exifData.add(exifKey, v);
                 } else {
-                    dbgMetaData << "No exif value was created for" << \
entry.qualifiedName() << " as" << exivKey;// << " of KMD value" << entry.value(); +   \
dbgMetaData << "No exif value was created for" << entry.qualifiedName() << " as" +    \
<< exivKey; // << " of KMD value" << entry.value();  }
             }
-        } catch (Exiv2::AnyError& e) {
+        } catch (Exiv2::AnyError &e) {
             dbgMetaData << "exiv error " << e.what();
         }
     }
-#if !EXIV2_TEST_VERSION(0,18,0)
+#if !EXIV2_TEST_VERSION(0, 18, 0)
     Exiv2::DataBuf rawData = exifData.copy();
-    ioDevice->write((const char*) rawData.pData_, rawData.size_);
+    ioDevice->write((const char *)rawData.pData_, rawData.size_);
 #else
     Exiv2::Blob rawData;
     Exiv2::ExifParser::encode(rawData, Exiv2::littleEndian, exifData);
@@ -479,13 +481,12 @@ bool KisExifIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderTyp  return true;
 }
 
-bool KisExifIO::canSaveAllEntries(KisMetaData::Store* /*store*/) const
+bool KisExifIO::canSaveAllEntries(KisMetaData::Store * /*store*/) const
 {
     return false; // It's a known fact that exif can't save all information, but \
TODO: write the check  }
 
-
-bool KisExifIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const
+bool KisExifIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const
 {
     if (!ioDevice->open(QIODevice::ReadOnly)) {
         return false;
@@ -493,40 +494,45 @@ bool KisExifIO::loadFrom(KisMetaData::Store* store, QIODevice* \
ioDevice) const  QByteArray arr(ioDevice->readAll());
     Exiv2::ExifData exifData;
     Exiv2::ByteOrder byteOrder;
-#if !EXIV2_TEST_VERSION(0,18,0)
-    exifData.load((const Exiv2::byte*)arr.data(), arr.size());
+#if !EXIV2_TEST_VERSION(0, 18, 0)
+    exifData.load((const Exiv2::byte *)arr.data(), arr.size());
     byteOrder = exifData.byteOrder();
 #else
     try {
-        byteOrder = Exiv2::ExifParser::decode(exifData, (const Exiv2::byte \
                *)arr.data(), static_cast<uint32_t>(arr.size()));
-    }
-    catch (const std::exception& ex) {
+        byteOrder =
+            Exiv2::ExifParser::decode(exifData, (const Exiv2::byte *)arr.data(), \
static_cast<uint32_t>(arr.size())); +    } catch (const std::exception &ex) {
         warnKrita << "Received exception trying to parse exiv data" << ex.what();
         return false;
-    }
-    catch (...) {
+    } catch (...) {
         dbgKrita << "Received unknown exception trying to parse exiv data";
         return false;
     }
 #endif
     dbgMetaData << "Byte order = " << byteOrder << ppVar(Exiv2::bigEndian) << \
                ppVar(Exiv2::littleEndian);
     dbgMetaData << "There are" << exifData.count() << " entries in the exif \
                section";
-    const KisMetaData::Schema* tiffSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri);
 +    const KisMetaData::Schema *tiffSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri);
  Q_ASSERT(tiffSchema);
-    const KisMetaData::Schema* exifSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri);
 +    const KisMetaData::Schema *exifSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri);
  Q_ASSERT(exifSchema);
-    const KisMetaData::Schema* dcSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
 +    const KisMetaData::Schema *dcSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
  Q_ASSERT(dcSchema);
-    const KisMetaData::Schema* xmpSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri);
 +    const KisMetaData::Schema *xmpSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri);
  Q_ASSERT(xmpSchema);
-    const KisMetaData::Schema *makerNoteSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri);
 +    const KisMetaData::Schema *makerNoteSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri);
  Q_ASSERT(makerNoteSchema);
 
     for (const Exiv2::Exifdatum &it : exifData) {
         const uint16_t tag = it.tag();
 
         if (tag == Exif::Image::StripOffsets || tag == Exif::Image::RowsPerStrip || \
                tag == Exif::Image::StripByteCounts
-            || tag == Exif::Image::JPEGInterchangeFormat || tag == \
Exif::Image::JPEGInterchangeFormatLength || it.tagName() == "0x0000") { +            \
|| tag == Exif::Image::JPEGInterchangeFormat || tag == \
Exif::Image::JPEGInterchangeFormatLength +            || it.tagName() == "0x0000") {
             dbgMetaData << it.key().c_str() << " is ignored";
         } else if (tag == Exif::Photo::MakerNote) {
             store->addEntry({makerNoteSchema, "RawData", \
exivValueToKMDValue(it.getValue(), false)}); @@ -602,7 +608,8 @@ bool \
                KisExifIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) \
                const
                 metaDataValue = exivValueToKMDValue(it.getValue(), forceSeq, \
arrayType);  }
             if (tag == Exif::Photo::InteroperabilityTag
-                || metaDataValue.type() == KisMetaData::Value::Invalid) { // \
InteroperabilityTag isn't useful for XMP, 0xea1d isn't a valid Exif tag +             \
|| metaDataValue.type() == KisMetaData::Value::Invalid) { // InteroperabilityTag \
isn't useful for XMP, +                                                               \
// 0xea1d isn't a valid Exif tag  warnMetaData << "Ignoring " << it.key().c_str();
             } else {
                 store->addEntry({exifSchema, it.tagName().c_str(), metaDataValue});
diff --git a/libs/ui/kisexiv2/kis_exif_io.h b/plugins/metadata/exif/kis_exif_io.h
similarity index 54%
rename from libs/ui/kisexiv2/kis_exif_io.h
rename to plugins/metadata/exif/kis_exif_io.h
index af9effd975..b2b3bd9547 100644
--- a/libs/ui/kisexiv2/kis_exif_io.h
+++ b/plugins/metadata/exif/kis_exif_io.h
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.0-or-later
  */
@@ -7,36 +8,40 @@
 #ifndef _KIS_EXIF_IO_H_
 #define _KIS_EXIF_IO_H_
 
-#include <kis_meta_data_io_backend.h>
+#include <QObject>
 
 #include <klocalizedstring.h>
 
+#include <kis_meta_data_io_backend.h>
+
 class KisExifIO : public KisMetaData::IOBackend
 {
-    struct Private;
 public:
     KisExifIO();
     ~KisExifIO() override;
-    QString id() const override {
+    QString id() const override
+    {
         return "exif";
     }
-    QString name() const override {
+    QString name() const override
+    {
         return i18n("Exif");
     }
-    BackendType type() const override {
+    BackendType type() const override
+    {
         return Binary;
     }
-    bool supportSaving() const override {
+    bool supportSaving() const override
+    {
         return true;
     }
-    bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType \
                headerType = NoHeader) const override;
-    bool canSaveAllEntries(KisMetaData::Store* store) const override;
-    bool supportLoading() const override {
+    bool saveTo(KisMetaData::Store *store, QIODevice *ioDevice, HeaderType \
headerType = NoHeader) const override; +    bool canSaveAllEntries(KisMetaData::Store \
*store) const override; +    bool supportLoading() const override
+    {
         return true;
     }
-    bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override;
-private:
-    Private* const d;
+    bool loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const override;
 };
 
 #endif
diff --git a/plugins/metadata/exif/kis_exif_plugin.cpp \
b/plugins/metadata/exif/kis_exif_plugin.cpp new file mode 100644
index 0000000000..3ea4b6882e
--- /dev/null
+++ b/plugins/metadata/exif/kis_exif_plugin.cpp
@@ -0,0 +1,29 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kis_exif_plugin.h"
+
+#include <kpluginfactory.h>
+
+#include <kis_meta_data_backend_registry.h>
+
+#include "kis_exif_io.h"
+
+K_PLUGIN_FACTORY_WITH_JSON(KisExifIOPluginFactory, "kritaexif.json", \
registerPlugin<KisExifPlugin>();) +
+KisExifPlugin::KisExifPlugin(QObject *parent, const QVariantList &)
+    : QObject(parent)
+{
+    KisMetadataBackendRegistry::instance()->add(new KisExifIO());
+}
+
+KisExifPlugin::~KisExifPlugin()
+{
+}
+
+#include "kis_exif_plugin.moc"
diff --git a/plugins/metadata/exif/kis_exif_plugin.h \
b/plugins/metadata/exif/kis_exif_plugin.h new file mode 100644
index 0000000000..4cbc6e56e3
--- /dev/null
+++ b/plugins/metadata/exif/kis_exif_plugin.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef _KIS_EXIF_PLUGIN_H_
+#define _KIS_EXIF_PLUGIN_H_
+
+#include <QObject>
+
+class KisExifPlugin : public QObject
+{
+public:
+    KisExifPlugin(QObject *parent, const QVariantList &);
+    ~KisExifPlugin() override;
+};
+
+#endif // _KIS_EXIF_PLUGIN_H_
diff --git a/plugins/metadata/exif/kritaexif.json \
b/plugins/metadata/exif/kritaexif.json new file mode 100644
index 0000000000..56f1314ccb
--- /dev/null
+++ b/plugins/metadata/exif/kritaexif.json
@@ -0,0 +1,9 @@
+{
+    "Id": "EXIF",
+    "Type": "Service",
+    "X-KDE-Library": "kritaexif",
+    "X-KDE-ServiceTypes": [
+        "Krita/Metadata"
+    ],
+    "X-Krita-Version": "28"
+}
diff --git a/plugins/metadata/iptc/CMakeLists.txt \
b/plugins/metadata/iptc/CMakeLists.txt new file mode 100644
index 0000000000..813a1d6a8d
--- /dev/null
+++ b/plugins/metadata/iptc/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(kritaiptc_SOURCES
+    kis_iptc_io.cpp
+    kis_iptc_plugin.cpp
+)
+
+add_library(kritaiptc MODULE ${kritaiptc_SOURCES})
+
+generate_export_header(kritaiptc)
+
+target_link_libraries(kritaiptc
+    PRIVATE
+        kritametadata
+        KF5::CoreAddons
+        LibExiv2::LibExiv2
+)
+
+install(TARGETS kritaiptc DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/libs/ui/kisexiv2/kis_iptc_io.cpp b/plugins/metadata/iptc/kis_iptc_io.cpp
similarity index 51%
rename from libs/ui/kisexiv2/kis_iptc_io.cpp
rename to plugins/metadata/iptc/kis_iptc_io.cpp
index cc43918d32..05f66eef29 100644
--- a/libs/ui/kisexiv2/kis_iptc_io.cpp
+++ b/plugins/metadata/iptc/kis_iptc_io.cpp
@@ -1,26 +1,25 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
 #include "kis_iptc_io.h"
 
-#include <kis_debug.h>
-
 #include <exiv2/iptc.hpp>
 
-#include "kis_exiv2.h"
-
-#include <kis_meta_data_store.h>
+#include <kis_debug.h>
+#include <kis_exiv2_common.h>
 #include <kis_meta_data_entry.h>
-#include <kis_meta_data_value.h>
 #include <kis_meta_data_schema.h>
 #include <kis_meta_data_schema_registry.h>
+#include <kis_meta_data_store.h>
+#include <kis_meta_data_value.h>
 
 const char photoshopMarker[] = "Photoshop 3.0\0";
 const char photoshopBimId_[] = "8BIM";
 const uint16_t photoshopIptc = 0x0404;
-const QByteArray photoshopIptc_((char*)&photoshopIptc, 2);
+const QByteArray photoshopIptc_((char *)&photoshopIptc, 2);
 
 struct IPTCToKMD {
     QString exivTag;
@@ -29,30 +28,30 @@ struct IPTCToKMD {
 };
 
 static const IPTCToKMD mappings[] = {
-    { "Iptc.Application2.City", KisMetaData::Schema::PhotoshopSchemaUri, "City" },
-    { "Iptc.Application2.Copyright", KisMetaData::Schema::DublinCoreSchemaUri, \
                "rights" },
-    { "Iptc.Application2.CountryName", KisMetaData::Schema::PhotoshopSchemaUri, \
                "Country" },
-    { "Iptc.Application2.CountryCode", KisMetaData::Schema::IPTCSchemaUri, \
                "CountryCode" },
-    { "Iptc.Application2.Byline", KisMetaData::Schema::DublinCoreSchemaUri, \
                "creator" },
-    { "Iptc.Application2.BylineTitle", KisMetaData::Schema::PhotoshopSchemaUri, \
                "AuthorsPosition" },
-    { "Iptc.Application2.DateCreated", KisMetaData::Schema::PhotoshopSchemaUri, \
                "DateCreated" },
-    { "Iptc.Application2.Caption", KisMetaData::Schema::DublinCoreSchemaUri, \
                "description" },
-    { "Iptc.Application2.Writer", KisMetaData::Schema::PhotoshopSchemaUri, \
                "CaptionWriter" },
-    { "Iptc.Application2.Headline", KisMetaData::Schema::PhotoshopSchemaUri, \
                "Headline" },
-    { "Iptc.Application2.SpecialInstructions", \
                KisMetaData::Schema::PhotoshopSchemaUri, "Instructions" },
-    { "Iptc.Application2.ObjectAttribute", KisMetaData::Schema::IPTCSchemaUri, \
                "IntellectualGenre" },
-    { "Iptc.Application2.TransmissionReference", \
                KisMetaData::Schema::PhotoshopSchemaUri, "JobID" },
-    { "Iptc.Application2.Keywords", KisMetaData::Schema::DublinCoreSchemaUri, \
                "subject" },
-    { "Iptc.Application2.SubLocation", KisMetaData::Schema::IPTCSchemaUri, \
                "Location" },
-    { "Iptc.Application2.Credit", KisMetaData::Schema::PhotoshopSchemaUri, "Credit" \
                },
-    { "Iptc.Application2.ProvinceState", KisMetaData::Schema::PhotoshopSchemaUri, \
                "State" },
-    { "Iptc.Application2.Source", KisMetaData::Schema::PhotoshopSchemaUri, "Source" \
                },
-    { "Iptc.Application2.Subject", KisMetaData::Schema::IPTCSchemaUri, "SubjectCode" \
                },
-    { "Iptc.Application2.ObjectName", KisMetaData::Schema::DublinCoreSchemaUri, \
                "title" },
-    { "Iptc.Application2.Urgency", KisMetaData::Schema::PhotoshopSchemaUri, \
                "Urgency" },
-    { "Iptc.Application2.Category", KisMetaData::Schema::PhotoshopSchemaUri, \
                "Category" },
-    { "Iptc.Application2.SuppCategory", KisMetaData::Schema::PhotoshopSchemaUri, \
                "SupplementalCategory" },
-    { "", "", "" } // indicates the end of the array
+    {"Iptc.Application2.City", KisMetaData::Schema::PhotoshopSchemaUri, "City"},
+    {"Iptc.Application2.Copyright", KisMetaData::Schema::DublinCoreSchemaUri, \
"rights"}, +    {"Iptc.Application2.CountryName", \
KisMetaData::Schema::PhotoshopSchemaUri, "Country"}, +    \
{"Iptc.Application2.CountryCode", KisMetaData::Schema::IPTCSchemaUri, "CountryCode"}, \
+    {"Iptc.Application2.Byline", KisMetaData::Schema::DublinCoreSchemaUri, \
"creator"}, +    {"Iptc.Application2.BylineTitle", \
KisMetaData::Schema::PhotoshopSchemaUri, "AuthorsPosition"}, +    \
{"Iptc.Application2.DateCreated", KisMetaData::Schema::PhotoshopSchemaUri, \
"DateCreated"}, +    {"Iptc.Application2.Caption", \
KisMetaData::Schema::DublinCoreSchemaUri, "description"}, +    \
{"Iptc.Application2.Writer", KisMetaData::Schema::PhotoshopSchemaUri, \
"CaptionWriter"}, +    {"Iptc.Application2.Headline", \
KisMetaData::Schema::PhotoshopSchemaUri, "Headline"}, +    \
{"Iptc.Application2.SpecialInstructions", KisMetaData::Schema::PhotoshopSchemaUri, \
"Instructions"}, +    {"Iptc.Application2.ObjectAttribute", \
KisMetaData::Schema::IPTCSchemaUri, "IntellectualGenre"}, +    \
{"Iptc.Application2.TransmissionReference", KisMetaData::Schema::PhotoshopSchemaUri, \
"JobID"}, +    {"Iptc.Application2.Keywords", \
KisMetaData::Schema::DublinCoreSchemaUri, "subject"}, +    \
{"Iptc.Application2.SubLocation", KisMetaData::Schema::IPTCSchemaUri, "Location"}, +  \
{"Iptc.Application2.Credit", KisMetaData::Schema::PhotoshopSchemaUri, "Credit"}, +    \
{"Iptc.Application2.ProvinceState", KisMetaData::Schema::PhotoshopSchemaUri, \
"State"}, +    {"Iptc.Application2.Source", KisMetaData::Schema::PhotoshopSchemaUri, \
"Source"}, +    {"Iptc.Application2.Subject", KisMetaData::Schema::IPTCSchemaUri, \
"SubjectCode"}, +    {"Iptc.Application2.ObjectName", \
KisMetaData::Schema::DublinCoreSchemaUri, "title"}, +    \
{"Iptc.Application2.Urgency", KisMetaData::Schema::PhotoshopSchemaUri, "Urgency"}, +  \
{"Iptc.Application2.Category", KisMetaData::Schema::PhotoshopSchemaUri, "Category"}, \
+    {"Iptc.Application2.SuppCategory", KisMetaData::Schema::PhotoshopSchemaUri, \
"SupplementalCategory"}, +    {"", "", ""} // indicates the end of the array
 };
 
 struct KisIptcIO::Private {
@@ -60,8 +59,10 @@ struct KisIptcIO::Private {
     QHash<QString, IPTCToKMD> kmdToIPTC;
 };
 
-// ---- Implementation of KisExifIO ----//
-KisIptcIO::KisIptcIO() : d(new Private)
+// ---- Implementation of KisIptcIO ----//
+KisIptcIO::KisIptcIO()
+    : KisMetaData::IOBackend()
+    , d(new Private)
 {
 }
 
@@ -75,46 +76,46 @@ void KisIptcIO::initMappingsTable() const
     // For some reason, initializing the tables in the constructor makes the it \
crash  if (d->iptcToKMD.size() == 0) {
         for (int i = 0; !mappings[i].exivTag.isEmpty(); i++) {
-            dbgKrita << "mapping[i] = " << mappings[i].exivTag << " " << \
mappings[i].namespaceUri << " " << mappings[i].name; +            dbgKrita << \
"mapping[i] = " << mappings[i].exivTag << " " << mappings[i].namespaceUri << " " +    \
<< mappings[i].name;  d->iptcToKMD[mappings[i].exivTag] = mappings[i];
-            d->kmdToIPTC[
-                KisMetaData::SchemaRegistry::instance()
-                ->schemaFromUri(mappings[i].namespaceUri)
-                ->generateQualifiedName(mappings[i].name)] = mappings[i];
+            d->kmdToIPTC[KisMetaData::SchemaRegistry::instance()
+                             ->schemaFromUri(mappings[i].namespaceUri)
+                             ->generateQualifiedName(mappings[i].name)] = \
mappings[i];  }
     }
 }
 
-bool KisIptcIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType \
headerType) const +bool KisIptcIO::saveTo(KisMetaData::Store *store, QIODevice \
*ioDevice, HeaderType headerType) const  {
     QStringList blockedEntries = QStringList() << "photoshop:DateCreated";
 
     initMappingsTable();
     ioDevice->open(QIODevice::WriteOnly);
     Exiv2::IptcData iptcData;
-    for (QHash<QString, KisMetaData::Entry>::const_iterator it = store->begin();
-            it != store->end(); ++it) {
-        const KisMetaData::Entry& entry = *it;
+    for (QHash<QString, KisMetaData::Entry>::const_iterator it = store->begin(); it \
!= store->end(); ++it) { +        const KisMetaData::Entry &entry = *it;
         if (d->kmdToIPTC.contains(entry.qualifiedName())) {
             if (blockedEntries.contains(entry.qualifiedName())) {
                 warnKrita << "skipping" << entry.qualifiedName() << entry.value();
                 continue;
             }
             try {
-                QString iptcKeyStr = d->kmdToIPTC[ entry.qualifiedName()].exivTag;
+                QString iptcKeyStr = d->kmdToIPTC[entry.qualifiedName()].exivTag;
                 Exiv2::IptcKey iptcKey(qPrintable(iptcKeyStr));
-                Exiv2::Value *v = kmdValueToExivValue(entry.value(),
-                                                      \
Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record())); +                \
Exiv2::Value *v = +                    kmdValueToExivValue(entry.value(),
+                                        \
Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record()));  
                 if (v && v->typeId() != Exiv2::invalidTypeId) {
                     iptcData.add(iptcKey, v);
                 }
-            } catch (Exiv2::AnyError& e) {
+            } catch (Exiv2::AnyError &e) {
                 dbgMetaData << "exiv error " << e.what();
             }
         }
     }
-#if !EXIV2_TEST_VERSION(0,18,0)
+#if !EXIV2_TEST_VERSION(0, 18, 0)
     Exiv2::DataBuf rawData = iptcData.copy();
 #else
     Exiv2::DataBuf rawData = Exiv2::IptcParser::encode(iptcData);
@@ -123,7 +124,7 @@ bool KisIptcIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderTyp  if (headerType == KisMetaData::IOBackend::JpegHeader) {
         QByteArray header;
         header.append(photoshopMarker);
-        header.append(QByteArray(1, 0));   // Null terminated string
+        header.append(QByteArray(1, 0)); // Null terminated string
         header.append(photoshopBimId_);
         header.append(photoshopIptc_);
         header.append(QByteArray(2, 0));
@@ -137,36 +138,36 @@ bool KisIptcIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderTyp  ioDevice->write(header);
     }
 
-    ioDevice->write((const char*) rawData.pData_, rawData.size_);
+    ioDevice->write((const char *)rawData.pData_, rawData.size_);
     ioDevice->close();
     return true;
 }
 
-bool KisIptcIO::canSaveAllEntries(KisMetaData::Store* store) const
+bool KisIptcIO::canSaveAllEntries(KisMetaData::Store *store) const
 {
     Q_UNUSED(store);
     return false;
 }
 
-bool KisIptcIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const
+bool KisIptcIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const
 {
     initMappingsTable();
     dbgMetaData << "Loading IPTC Tags";
     ioDevice->open(QIODevice::ReadOnly);
     QByteArray arr = ioDevice->readAll();
     Exiv2::IptcData iptcData;
-#if !EXIV2_TEST_VERSION(0,18,0)
-    iptcData.load((const Exiv2::byte*)arr.data(), arr.size());
+#if !EXIV2_TEST_VERSION(0, 18, 0)
+    iptcData.load((const Exiv2::byte *)arr.data(), arr.size());
 #else
-    Exiv2::IptcParser::decode(iptcData, (const Exiv2::byte*)arr.data(), arr.size());
+    Exiv2::IptcParser::decode(iptcData, (const Exiv2::byte *)arr.data(), \
arr.size());  #endif
     dbgMetaData << "There are" << iptcData.count() << " entries in the IPTC \
                section";
-    for (Exiv2::IptcMetadata::const_iterator it = iptcData.begin();
-            it != iptcData.end(); ++it) {
+    for (Exiv2::IptcMetadata::const_iterator it = iptcData.begin(); it != \
iptcData.end(); ++it) {  dbgMetaData << "Reading info for key" << it->key().c_str();
         if (d->iptcToKMD.contains(it->key().c_str())) {
-            const IPTCToKMD& iptcToKMd = d->iptcToKMD[it->key().c_str()];
-            const KisMetaData::Schema* schema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(iptcToKMd.namespaceUri); +     \
const IPTCToKMD &iptcToKMd = d->iptcToKMD[it->key().c_str()]; +            const \
KisMetaData::Schema *schema = +                \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(iptcToKMd.namespaceUri);  \
KisMetaData::Value value;  if (iptcToKMd.exivTag == "Iptc.Application2.Keywords") {
                 Q_ASSERT(it->getValue()->typeId() == Exiv2::string);
diff --git a/libs/ui/kisexiv2/kis_iptc_io.h b/plugins/metadata/iptc/kis_iptc_io.h
similarity index 56%
rename from libs/ui/kisexiv2/kis_iptc_io.h
rename to plugins/metadata/iptc/kis_iptc_io.h
index 3b4ea2fa92..a7092f78e8 100644
--- a/libs/ui/kisexiv2/kis_iptc_io.h
+++ b/plugins/metadata/iptc/kis_iptc_io.h
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
@@ -7,38 +8,45 @@
 #ifndef _KIS_IPTC_IO_H_
 #define _KIS_IPTC_IO_H_
 
-#include <kis_meta_data_io_backend.h>
-
 #include <klocalizedstring.h>
 
+#include <kis_meta_data_io_backend.h>
+
 class KisIptcIO : public KisMetaData::IOBackend
 {
-    struct Private;
 public:
     KisIptcIO();
     ~KisIptcIO() override;
-    QString id() const override {
+    QString id() const override
+    {
         return "iptc";
     }
-    QString name() const override {
+    QString name() const override
+    {
         return i18n("Iptc");
     }
-    BackendType type() const override {
+    BackendType type() const override
+    {
         return Binary;
     }
-    bool supportSaving() const override {
+    bool supportSaving() const override
+    {
         return true;
     }
-    bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType \
                headerType = NoHeader) const override;
-    bool canSaveAllEntries(KisMetaData::Store* store) const override;
-    bool supportLoading() const override {
+    bool saveTo(KisMetaData::Store *store, QIODevice *ioDevice, HeaderType \
headerType = NoHeader) const override; +    bool canSaveAllEntries(KisMetaData::Store \
*store) const override; +    bool supportLoading() const override
+    {
         return true;
     }
-    bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override;
+    bool loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const override;
+
 private:
     void initMappingsTable() const;
+
 private:
-    Private* const d;
+    struct Private;
+    Private *const d;
 };
 
 #endif
diff --git a/plugins/metadata/iptc/kis_iptc_plugin.cpp \
b/plugins/metadata/iptc/kis_iptc_plugin.cpp new file mode 100644
index 0000000000..dea647cf2f
--- /dev/null
+++ b/plugins/metadata/iptc/kis_iptc_plugin.cpp
@@ -0,0 +1,29 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kis_iptc_plugin.h"
+
+#include <kpluginfactory.h>
+
+#include <kis_meta_data_backend_registry.h>
+
+#include "kis_iptc_io.h"
+
+K_PLUGIN_FACTORY_WITH_JSON(KisIptcIOPluginFactory, "kritaiptc.json", \
registerPlugin<KisIptcPlugin>();) +
+KisIptcPlugin::KisIptcPlugin(QObject *parent, const QVariantList &)
+    : QObject(parent)
+{
+    KisMetadataBackendRegistry::instance()->add(new KisIptcIO());
+}
+
+KisIptcPlugin::~KisIptcPlugin()
+{
+}
+
+#include "kis_iptc_plugin.moc"
diff --git a/plugins/metadata/iptc/kis_iptc_plugin.h \
b/plugins/metadata/iptc/kis_iptc_plugin.h new file mode 100644
index 0000000000..2d3d813173
--- /dev/null
+++ b/plugins/metadata/iptc/kis_iptc_plugin.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef _KIS_IPTC_PLUGIN_H_
+#define _KIS_IPTC_PLUGIN_H_
+
+#include <QObject>
+
+class KisIptcPlugin : public QObject
+{
+public:
+    KisIptcPlugin(QObject *parent, const QVariantList &);
+    ~KisIptcPlugin() override;
+};
+
+#endif // _KIS_IPTC_PLUGIN_H_
diff --git a/plugins/metadata/iptc/kritaiptc.json \
b/plugins/metadata/iptc/kritaiptc.json new file mode 100644
index 0000000000..6af4fac9fc
--- /dev/null
+++ b/plugins/metadata/iptc/kritaiptc.json
@@ -0,0 +1,7 @@
+{
+    "Id" : "IPTC",
+    "Type" : "Service",
+    "X-KDE-Library" : "kritaiptc",
+    "X-KDE-ServiceTypes" : ["Krita/Metadata"],
+    "X-Krita-Version" : "28"
+}
diff --git a/plugins/metadata/tests/CMakeLists.txt \
b/plugins/metadata/tests/CMakeLists.txt new file mode 100644
index 0000000000..c50efe5f08
--- /dev/null
+++ b/plugins/metadata/tests/CMakeLists.txt
@@ -0,0 +1,21 @@
+include_directories(${CMAKE_SOURCE_DIR}/sdk/tests )
+
+include(ECMAddTests)
+
+macro_add_unittest_definitions()
+
+include(KritaAddBrokenUnitTest)
+
+##### Tests that currently fail and should be fixed #####
+
+# Works under Linux but does not work under Windows
+krita_add_broken_unit_test( kis_exif_test.cpp
+    TEST_NAME KisExifTest
+    LINK_LIBRARIES kritametadata kritaui Qt5::Test
+    NAME_PREFIX "plugins-metadata-"
+    ${MACOS_GUI_TEST})
+
+macos_test_fixrpath(
+    ${BROKEN_TESTS}
+    KisExifTest
+    )
diff --git a/libs/ui/tests/data/metadata/hpim3238.exv \
b/plugins/metadata/tests/data/metadata/hpim3238.exv similarity index 100%
rename from libs/ui/tests/data/metadata/hpim3238.exv
rename to plugins/metadata/tests/data/metadata/hpim3238.exv
diff --git a/libs/ui/tests/kis_exiv2_test.cpp \
b/plugins/metadata/tests/kis_exif_test.cpp similarity index 65%
rename from libs/ui/tests/kis_exiv2_test.cpp
rename to plugins/metadata/tests/kis_exif_test.cpp
index c515c259dc..86a9aca026 100644
--- a/libs/ui/tests/kis_exiv2_test.cpp
+++ b/plugins/metadata/tests/kis_exif_test.cpp
@@ -1,27 +1,28 @@
 /*
  * SPDX-FileCopyrightText: 2009 Cyrille Berger <cberger@cberger.net>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
 
-#include "kis_exiv2_test.h"
+#include "kis_exif_test.h"
 
+#include <filestest.h>
+#include <sdk/tests/testui.h>
 #include <simpletest.h>
-#include <QCoreApplication>
 
 #include <QBuffer>
+#include <QCoreApplication>
 
-#include "kis_debug.h"
-#include "kis_meta_data_entry.h"
-#include "kis_meta_data_io_backend.h"
-#include "kis_meta_data_schema.h"
-#include "kis_meta_data_schema_registry.h"
-#include "kis_meta_data_store.h"
-#include "kis_meta_data_validator.h"
+#include <kis_debug.h>
+#include <kis_meta_data_backend_registry.h>
+#include <kis_meta_data_entry.h>
+#include <kis_meta_data_io_backend.h>
+#include <kis_meta_data_schema.h>
+#include <kis_meta_data_schema_registry.h>
+#include <kis_meta_data_store.h>
+#include <kis_meta_data_validator.h>
 #include <kis_meta_data_value.h>
-#include "kisexiv2/kis_exiv2.h"
-#include "filestest.h"
-#include "sdk/tests/testui.h"
 
 #ifndef FILES_DATA_DIR
 #error "FILES_DATA_DIR not set. A directory with the data used for testing the \
metadata parser in krita" @@ -29,11 +30,9 @@
 
 using namespace KisMetaData;
 
-void KisExiv2Test::testExifLoader()
+void KisExifTest::testExifLoader()
 {
-    KisExiv2::initialize();
-
-    IOBackend* exifIO = IOBackendRegistry::instance()->get("exif");
+    IOBackend *exifIO = KisMetadataBackendRegistry::instance()->get("exif");
     QVERIFY(exifIO);
     QFile exifFile(QString(FILES_DATA_DIR) + "/metadata/hpim3238.exv");
     exifFile.open(QIODevice::ReadOnly);
@@ -41,20 +40,22 @@ void KisExiv2Test::testExifLoader()
     QByteArray exifBytes = exifFile.readAll();
     QBuffer exifBuffer(&exifBytes);
 
-    Store* store = new Store;
+    Store *store = new Store;
     bool loadSuccess = exifIO->loadFrom(store, &exifBuffer);
     QVERIFY(loadSuccess);
     Validator validator(store);
 
     for (QMap<QString, Validator::Reason>::const_iterator it = \
                validator.invalidEntries().begin();
-            it != validator.invalidEntries().end(); ++it) {
+         it != validator.invalidEntries().end();
+         ++it) {
         dbgKrita << it.key() << " = " << it.value().type() << " entry = " << \
store->getEntry(it.key());  }
 
     QCOMPARE(validator.countInvalidEntries(), 0);
     QCOMPARE(validator.countValidEntries(), 51);
 
-    const KisMetaData::Schema* tiffSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri);
 +    const KisMetaData::Schema *tiffSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::TIFFSchemaUri);
  
     QCOMPARE(store->getEntry(tiffSchema, "Make").value(), Value("Hewlett-Packard"));
     QCOMPARE(store->getEntry(tiffSchema, "Model").value(), Value("HP PhotoSmart R707 \
(V01.00) ")); @@ -64,16 +65,19 @@ void KisExiv2Test::testExifLoader()
     QCOMPARE(store->getEntry(tiffSchema, "ResolutionUnit").value(), Value(2));
     QCOMPARE(store->getEntry(tiffSchema, "YCbCrPositioning").value(), Value(1));
 
-    const KisMetaData::Schema* exifSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri);
 +    const KisMetaData::Schema *exifSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::EXIFSchemaUri);
  
     QCOMPARE(store->getEntry(exifSchema, "ExposureTime").value(), \
                Value(Rational(35355, 100000)));
     QCOMPARE(store->getEntry(exifSchema, "FNumber").value(), Value(Rational(280, \
                100)));
     QCOMPARE(store->getEntry(exifSchema, "ExposureProgram").value(), Value(2));
-//     QCOMPARE(store->getEntry(exifSchema, "ISOSpeedRatings").value(), Value(100)); \
// TODO it's a list +    //     QCOMPARE(store->getEntry(exifSchema, \
"ISOSpeedRatings").value(), Value(100)); // TODO it's a list  // TODO test OECF
     QCOMPARE(store->getEntry(exifSchema, "ExifVersion").value(), Value("0220"));
-    QCOMPARE(store->getEntry(exifSchema, "DateTimeOriginal").value(), \
                Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18))));
-    QCOMPARE(store->getEntry(exifSchema, "DateTimeDigitized").value(), \
Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18)))); +    \
QCOMPARE(store->getEntry(exifSchema, "DateTimeOriginal").value(), +             \
Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18)))); +    \
QCOMPARE(store->getEntry(exifSchema, "DateTimeDigitized").value(), +             \
Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18))));  // TODO \
                ComponentsConfiguration
     QCOMPARE(store->getEntry(exifSchema, "ShutterSpeedValue").value(), \
                Value(Rational(384, 256)));
     QCOMPARE(store->getEntry(exifSchema, "ApertureValue").value(), \
Value(Rational(780, 256))); @@ -82,17 +86,18 @@ void KisExiv2Test::testExifLoader()
     QCOMPARE(store->getEntry(exifSchema, "MaxApertureValue").value(), \
                Value(Rational(280, 100)));
     QCOMPARE(store->getEntry(exifSchema, "SubjectDistance").value(), \
Value(Rational(65535, 1000)));  
-
-    const KisMetaData::Schema* dcSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
 +    const KisMetaData::Schema *dcSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri);
  Q_UNUSED(dcSchema);
 
-    const KisMetaData::Schema* xmpSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri);
 +    const KisMetaData::Schema *xmpSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::XMPSchemaUri);
                
     QCOMPARE(store->getEntry(xmpSchema, "CreatorTool").value(), \
                Value("digiKam-0.9.1"));
     QCOMPARE(store->getEntry(xmpSchema, "ModifyDate").value(), \
Value(QDateTime(QDate(2007, 5, 8), QTime(0, 19, 18))));  
-    const KisMetaData::Schema* mknSchema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri);
 +    const KisMetaData::Schema *mknSchema =
+        KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::MakerNoteSchemaUri);
                
     QCOMPARE(store->getEntry(mknSchema, "RawData").value(), Value("SFBNZXQ="));
 }
 
-KISTEST_MAIN(KisExiv2Test)
-
+KISTEST_MAIN(KisExifTest)
diff --git a/libs/ui/tests/kis_exiv2_test.h b/plugins/metadata/tests/kis_exif_test.h
similarity index 73%
rename from libs/ui/tests/kis_exiv2_test.h
rename to plugins/metadata/tests/kis_exif_test.h
index 11c9f11c0f..d45d8708ea 100644
--- a/libs/ui/tests/kis_exiv2_test.h
+++ b/plugins/metadata/tests/kis_exif_test.h
@@ -1,5 +1,6 @@
 /*
  * SPDX-FileCopyrightText: 2009 Cyrille Berger <cberger@cberger.net>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -9,7 +10,7 @@
 
 #include <QObject>
 
-class KisExiv2Test : public QObject
+class KisExifTest : public QObject
 {
     Q_OBJECT
 private Q_SLOTS:
diff --git a/plugins/metadata/xmp/CMakeLists.txt \
b/plugins/metadata/xmp/CMakeLists.txt new file mode 100644
index 0000000000..052452febc
--- /dev/null
+++ b/plugins/metadata/xmp/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(kritaxmp_SOURCES
+    kis_xmp_io.cpp
+    kis_xmp_plugin.cpp
+)
+
+add_library(kritaxmp MODULE ${kritaxmp_SOURCES})
+
+generate_export_header(kritaxmp)
+
+target_link_libraries(kritaxmp
+    PRIVATE
+        kritametadata
+        KF5::CoreAddons
+        LibExiv2::LibExiv2
+)
+
+install(TARGETS kritaxmp DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/libs/ui/kisexiv2/kis_xmp_io.cpp b/plugins/metadata/xmp/kis_xmp_io.cpp
similarity index 75%
rename from libs/ui/kisexiv2/kis_xmp_io.cpp
rename to plugins/metadata/xmp/kis_xmp_io.cpp
index af36fcd6b1..7a1f4e7553 100644
--- a/libs/ui/kisexiv2/kis_xmp_io.cpp
+++ b/plugins/metadata/xmp/kis_xmp_io.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2008-2010 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.0-or-later
  */
@@ -7,19 +8,19 @@
 
 #include <string>
 
-#include "kis_exiv2.h"
-
-#include <kis_meta_data_store.h>
+#include <kis_exiv2_common.h>
 #include <kis_meta_data_entry.h>
 #include <kis_meta_data_parser.h>
-#include <kis_meta_data_value.h>
 #include <kis_meta_data_schema.h>
 #include <kis_meta_data_schema_registry.h>
+#include <kis_meta_data_store.h>
 #include <kis_meta_data_type_info.h>
+#include <kis_meta_data_value.h>
 
 #include <kis_debug.h>
 
 KisXMPIO::KisXMPIO()
+    : KisMetaData::IOBackend()
 {
 }
 
@@ -27,7 +28,7 @@ KisXMPIO::~KisXMPIO()
 {
 }
 
-inline std::string exiv2Prefix(const KisMetaData::Schema* _schema)
+inline std::string exiv2Prefix(const KisMetaData::Schema *_schema)
 {
     const QByteArray latin1SchemaUri = _schema->uri().toLatin1();
     std::string prefix = Exiv2::XmpProperties::prefix(latin1SchemaUri.constData());
@@ -41,12 +42,15 @@ inline std::string exiv2Prefix(const KisMetaData::Schema* \
_schema)  
 namespace
 {
-void saveStructure(Exiv2::XmpData& xmpData_, const QString& name, const std::string& \
prefix, const QMap<QString, KisMetaData::Value>& structure, const \
KisMetaData::Schema* structureSchema) +void saveStructure(Exiv2::XmpData &xmpData_,
+                   const QString &name,
+                   const std::string &prefix,
+                   const QMap<QString, KisMetaData::Value> &structure,
+                   const KisMetaData::Schema *structureSchema)
 {
     std::string structPrefix = exiv2Prefix(structureSchema);
-    for (QMap<QString, KisMetaData::Value>::const_iterator it = structure.begin();
-            it != structure.end(); ++it) {
-        Q_ASSERT(it.value().type() != KisMetaData::Value::Structure);   // Can't \
nest structure +    for (QMap<QString, KisMetaData::Value>::const_iterator it = \
structure.begin(); it != structure.end(); ++it) { +        Q_ASSERT(it.value().type() \
                != KisMetaData::Value::Structure); // Can't nest structure
         QString key = \
QString("%1/%2:%3").arg(name).arg(structPrefix.c_str()).arg(it.key());  Exiv2::XmpKey \
ekey(prefix, key.toLatin1().constData());  dbgMetaData << ppVar(key) << \
ppVar(ekey.key().c_str()); @@ -58,25 +62,24 @@ void saveStructure(Exiv2::XmpData& \
xmpData_, const QString& name, const std::str  }
 }
 
-bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType \
headerType) const +bool KisXMPIO::saveTo(KisMetaData::Store *store, QIODevice \
*ioDevice, HeaderType headerType) const  {
     dbgMetaData << "Save XMP Data";
     Exiv2::XmpData xmpData_;
 
-    for (QHash<QString, KisMetaData::Entry>::const_iterator it = store->begin();
-            it != store->end(); ++it) {
-        const KisMetaData::Entry& entry = *it;
+    for (QHash<QString, KisMetaData::Entry>::const_iterator it = store->begin(); it \
!= store->end(); ++it) { +        const KisMetaData::Entry &entry = *it;
 
         // Check whether the prefix and namespace are know to exiv2
         std::string prefix = exiv2Prefix(entry.schema());
         dbgMetaData << "Saving " << entry.name();
 
-        const KisMetaData::Value& value = entry.value();
+        const KisMetaData::Value &value = entry.value();
 
-        const KisMetaData::TypeInfo* typeInfo = \
entry.schema()->propertyType(entry.name()); +        const KisMetaData::TypeInfo \
*typeInfo = entry.schema()->propertyType(entry.name());  if (value.type() == \
                KisMetaData::Value::Structure) {
             QMap<QString, KisMetaData::Value> structure = value.asStructure();
-            const KisMetaData::Schema* structureSchema = 0;
+            const KisMetaData::Schema *structureSchema = 0;
             if (typeInfo) {
                 structureSchema = typeInfo->structureSchema();
             }
@@ -88,10 +91,11 @@ bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* \
                ioDevice, HeaderType
             saveStructure(xmpData_, entry.name(), prefix, structure, \
structureSchema);  } else {
             Exiv2::XmpKey key(prefix, entry.name().toLatin1().constData());
-            if (typeInfo && (typeInfo->propertyType() == \
                KisMetaData::TypeInfo::OrderedArrayType
-                             || typeInfo->propertyType() == \
                KisMetaData::TypeInfo::UnorderedArrayType
-                             || typeInfo->propertyType() == \
                KisMetaData::TypeInfo::AlternativeArrayType)
-                    && typeInfo->embeddedPropertyType()->propertyType() == \
KisMetaData::TypeInfo::StructureType) { +            if (typeInfo
+                && (typeInfo->propertyType() == \
KisMetaData::TypeInfo::OrderedArrayType +                    || \
typeInfo->propertyType() == KisMetaData::TypeInfo::UnorderedArrayType +               \
|| typeInfo->propertyType() == KisMetaData::TypeInfo::AlternativeArrayType) +         \
&& typeInfo->embeddedPropertyType()->propertyType() == \
KisMetaData::TypeInfo::StructureType) {  // Here is the bad part, again we need to do \
it by hand  Exiv2::XmpTextValue tv;
                 switch (typeInfo->propertyType()) {
@@ -109,8 +113,8 @@ bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderType  ;
                 }
                 xmpData_.add(key, &tv); // set the arrya type
-                const KisMetaData::TypeInfo* stuctureTypeInfo = \
                typeInfo->embeddedPropertyType();
-                const KisMetaData::Schema* structureSchema = 0;
+                const KisMetaData::TypeInfo *stuctureTypeInfo = \
typeInfo->embeddedPropertyType(); +                const KisMetaData::Schema \
*structureSchema = 0;  if (stuctureTypeInfo) {
                     structureSchema = stuctureTypeInfo->structureSchema();
                 }
@@ -121,7 +125,11 @@ bool KisXMPIO::saveTo(KisMetaData::Store* store, QIODevice* \
ioDevice, HeaderType  Q_ASSERT(structureSchema);
                 QList<KisMetaData::Value> array = value.asArray();
                 for (int idx = 0; idx < array.size(); ++idx) {
-                    saveStructure(xmpData_, \
QString("%1[%2]").arg(entry.name()).arg(idx + 1), prefix, array[idx].asStructure(), \
structureSchema); +                    saveStructure(xmpData_,
+                                  QString("%1[%2]").arg(entry.name()).arg(idx + 1),
+                                  prefix,
+                                  array[idx].asStructure(),
+                                  structureSchema);
                 }
             } else {
                 dbgMetaData << ppVar(key.key().c_str());
@@ -149,7 +157,7 @@ bool parseTagName(const QString &tagString,
                   QString &structName,
                   int &arrayIndex,
                   QString &tagName,
-                  const KisMetaData::TypeInfo** typeInfo,
+                  const KisMetaData::TypeInfo **typeInfo,
                   const KisMetaData::Schema *schema)
 {
     arrayIndex = -1;
@@ -164,12 +172,11 @@ bool parseTagName(const QString &tagString,
         return true;
     }
 
-
     if (numSubNames == 2) {
         QRegExp regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)");
         if (regexp.indexIn(tagString) != -1) {
             structName = regexp.capturedTexts()[1];
-            tagName =  regexp.capturedTexts()[3];
+            tagName = regexp.capturedTexts()[3];
             *typeInfo = schema->propertyType(structName);
 
             if (*typeInfo && (*typeInfo)->propertyType() == \
KisMetaData::TypeInfo::StructureType) { @@ -203,7 +210,7 @@ bool parseTagName(const \
QString &tagString,  return false;
 }
 
-bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const
+bool KisXMPIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const
 {
     ioDevice->open(QIODevice::ReadOnly);
     dbgMetaData << "Load XMP Data";
@@ -211,20 +218,24 @@ bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* \
ioDevice) const  QByteArray arr = ioDevice->readAll();
     xmpPacket_.assign(arr.data(), arr.length());
     dbgMetaData << xmpPacket_.length();
-//     dbgMetaData << xmpPacket_.c_str();
+    //     dbgMetaData << xmpPacket_.c_str();
     Exiv2::XmpData xmpData_;
     Exiv2::XmpParser::decode(xmpData_, xmpPacket_);
-    QMap< const KisMetaData::Schema*, QMap<QString, QMap<QString, \
                KisMetaData::Value> > > structures;
-    QMap< const KisMetaData::Schema*, QMap<QString, QVector< QMap<QString, \
KisMetaData::Value> > > > arraysOfStructures; +    QMap<const KisMetaData::Schema *, \
QMap<QString, QMap<QString, KisMetaData::Value>>> structures; +    QMap<const \
KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>> \
                arraysOfStructures;
     for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) \
{  dbgMetaData << "Start iteration" << it->key().c_str();
 
         Exiv2::XmpKey key(it->key());
         dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " \
                " << key.ns().c_str();
-        if ((key.groupName() == "exif" || key.groupName() == "tiff") && \
key.tagName() == "NativeDigest") {  // TODO: someone who has time to lose can look in \
adding support for NativeDigest, it's undocumented use by the XMP SDK to check if \
exif data has been changed while XMP hasn't been updated +        if \
((key.groupName() == "exif" || key.groupName() == "tiff") +            && \
key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in \
adding support for +                                                  // \
NativeDigest, it's undocumented use by the XMP SDK to check if exif +                 \
// data has been changed while XMP hasn't been updated  dbgMetaData << "dropped";
         } else {
-            const KisMetaData::Schema* schema = \
KisMetaData::SchemaRegistry::instance()->schemaFromPrefix(key.groupName().c_str()); + \
const KisMetaData::Schema *schema = +                \
KisMetaData::SchemaRegistry::instance()->schemaFromPrefix(key.groupName().c_str());  \
                if (!schema) {
                 schema = \
KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str());  if \
(!schema) { @@ -237,32 +248,29 @@ bool KisXMPIO::loadFrom(KisMetaData::Store* store, \
QIODevice* ioDevice) const  QString structName;
             int arrayIndex = -1;
             QString tagName;
-            const KisMetaData::TypeInfo* typeInfo = 0;
+            const KisMetaData::TypeInfo *typeInfo = 0;
 
-            if (!parseTagName(key.tagName().c_str(),
-                              structName, arrayIndex, tagName,
-                              &typeInfo, schema)) continue;
+            if (!parseTagName(key.tagName().c_str(), structName, arrayIndex, \
tagName, &typeInfo, schema)) +                continue;
 
             bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1;
             bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != \
                -1;
             Q_ASSERT(isStructureEntry != isStructureInArrayEntry || \
!isStructureEntry);  
-
             KisMetaData::Value v;
             bool ignoreValue = false;
             // Compute the value
-            if (value->typeId() == Exiv2::xmpBag
-                    || value->typeId() == Exiv2::xmpSeq
-                    || value->typeId() == Exiv2::xmpAlt) {
-                const KisMetaData::TypeInfo* embeddedTypeInfo = 0;
+            if (value->typeId() == Exiv2::xmpBag || value->typeId() == Exiv2::xmpSeq
+                || value->typeId() == Exiv2::xmpAlt) {
+                const KisMetaData::TypeInfo *embeddedTypeInfo = 0;
                 if (typeInfo) {
                     embeddedTypeInfo = typeInfo->embeddedPropertyType();
                 }
-                const KisMetaData::Parser* parser = 0;
+                const KisMetaData::Parser *parser = 0;
                 if (embeddedTypeInfo) {
                     parser = embeddedTypeInfo->parser();
                 }
-                const Exiv2::XmpArrayValue* xav = dynamic_cast<const \
Exiv2::XmpArrayValue*>(value.get()); +                const Exiv2::XmpArrayValue *xav \
= dynamic_cast<const Exiv2::XmpArrayValue *>(value.get());  Q_ASSERT(xav);
                 QList<KisMetaData::Value> array;
                 for (int i = 0; i < xav->count(); ++i) {
@@ -291,10 +299,11 @@ bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* \
ioDevice) const  }
                 v = KisMetaData::Value(array, vt);
             } else if (value->typeId() == Exiv2::langAlt) {
-                const Exiv2::LangAltValue* xav = dynamic_cast<const \
Exiv2::LangAltValue*>(value.get()); +                const Exiv2::LangAltValue *xav = \
dynamic_cast<const Exiv2::LangAltValue *>(value.get());  QList<KisMetaData::Value> \
                alt;
-                for (std::map< std::string, std::string>::const_iterator it = \
                xav->value_.begin();
-                        it != xav->value_.end(); ++it) {
+                for (std::map<std::string, std::string>::const_iterator it = \
xav->value_.begin(); +                     it != xav->value_.end();
+                     ++it) {
                     KisMetaData::Value valt(it->second.c_str());
                     valt.addPropertyQualifier("xml:lang", \
KisMetaData::Value(it->first.c_str()));  alt.push_back(valt);
@@ -338,18 +347,25 @@ bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* \
ioDevice) const  }
     }
 
-    for (QMap< const KisMetaData::Schema*, QMap<QString, QMap<QString, \
                KisMetaData::Value>  > >::iterator it = structures.begin();
-            it != structures.end(); ++it) {
-        const KisMetaData::Schema* schema = it.key();
-        for (QMap<QString, QMap<QString, KisMetaData::Value> >::iterator it2 = \
                it.value().begin();
-                it2 != it.value().end(); ++it2) {
+    for (QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, \
KisMetaData::Value>>>::iterator it = +             structures.begin();
+         it != structures.end();
+         ++it) {
+        const KisMetaData::Schema *schema = it.key();
+        for (QMap<QString, QMap<QString, KisMetaData::Value>>::iterator it2 = \
it.value().begin(); +             it2 != it.value().end();
+             ++it2) {
             store->addEntry(KisMetaData::Entry(schema, it2.key(), \
KisMetaData::Value(it2.value())));  }
     }
-    for (QMap< const KisMetaData::Schema*, QMap<QString, QVector< QMap<QString, \
KisMetaData::Value> > > >::iterator it = arraysOfStructures.begin(); it != \
                arraysOfStructures.end(); ++it) {
-        const KisMetaData::Schema* schema = it.key();
-        for (QMap<QString, QVector<QMap<QString, KisMetaData::Value> > >::iterator \
                it2 = it.value().begin();
-                it2 != it.value().end(); ++it2) {
+    for (QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, \
KisMetaData::Value>>>>::iterator it = +             arraysOfStructures.begin();
+         it != arraysOfStructures.end();
+         ++it) {
+        const KisMetaData::Schema *schema = it.key();
+        for (QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>::iterator it2 \
= it.value().begin(); +             it2 != it.value().end();
+             ++it2) {
             KisMetaData::Value::ValueType type = KisMetaData::Value::OrderedArray;
             QString entryName = it2.key();
             if (schema->propertyType(entryName)) {
@@ -375,7 +391,7 @@ bool KisXMPIO::loadFrom(KisMetaData::Store* store, QIODevice* \
ioDevice) const  }
             store->removeEntry(schema, entryName);
             if (type != KisMetaData::Value::Invalid) {
-                QList< KisMetaData::Value > valueList;
+                QList<KisMetaData::Value> valueList;
                 for (int i = 0; i < it2.value().size(); ++i) {
                     valueList.append(it2.value()[i]);
                 }
diff --git a/libs/ui/kisexiv2/kis_xmp_io.h b/plugins/metadata/xmp/kis_xmp_io.h
similarity index 55%
rename from libs/ui/kisexiv2/kis_xmp_io.h
rename to plugins/metadata/xmp/kis_xmp_io.h
index 9160933212..6cfd0fa75e 100644
--- a/libs/ui/kisexiv2/kis_xmp_io.h
+++ b/plugins/metadata/xmp/kis_xmp_io.h
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.0-or-later
  */
@@ -7,36 +8,41 @@
 #ifndef _KIS_XMP_IO_H_
 #define _KIS_XMP_IO_H_
 
-#include <kis_meta_data_io_backend.h>
-
 #include <klocalizedstring.h>
 
+#include <kis_meta_data_io_backend.h>
+
 class KisXMPIO : public KisMetaData::IOBackend
 {
-    struct Private;
 public:
     KisXMPIO();
     ~KisXMPIO() override;
-    QString id() const override {
+    QString id() const override
+    {
         return "xmp";
     }
-    QString name() const override {
+    QString name() const override
+    {
         return i18n("XMP");
     }
-    BackendType type() const override {
+    BackendType type() const override
+    {
         return Text;
     }
-    bool supportSaving() const override {
+    bool supportSaving() const override
+    {
         return true;
     }
-    bool saveTo(KisMetaData::Store* store, QIODevice* ioDevice, HeaderType \
                headerType = NoHeader) const override;
-    bool canSaveAllEntries(KisMetaData::Store*) const override {
+    bool saveTo(KisMetaData::Store *store, QIODevice *ioDevice, HeaderType \
headerType = NoHeader) const override; +    bool canSaveAllEntries(KisMetaData::Store \
*) const override +    {
         return true;
     }
-    bool supportLoading() const override {
+    bool supportLoading() const override
+    {
         return true;
     }
-    bool loadFrom(KisMetaData::Store* store, QIODevice* ioDevice) const override;
+    bool loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const override;
 };
 
 #endif
diff --git a/plugins/metadata/xmp/kis_xmp_plugin.cpp \
b/plugins/metadata/xmp/kis_xmp_plugin.cpp new file mode 100644
index 0000000000..bb85466fbb
--- /dev/null
+++ b/plugins/metadata/xmp/kis_xmp_plugin.cpp
@@ -0,0 +1,29 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kis_xmp_plugin.h"
+
+#include <kpluginfactory.h>
+
+#include <kis_meta_data_backend_registry.h>
+
+#include "kis_xmp_io.h"
+
+K_PLUGIN_FACTORY_WITH_JSON(KisIptcIOPluginFactory, "kritaxmp.json", \
registerPlugin<KisXmpPlugin>();) +
+KisXmpPlugin::KisXmpPlugin(QObject *parent, const QVariantList &)
+    : QObject(parent)
+{
+    KisMetadataBackendRegistry::instance()->add(new KisXMPIO());
+}
+
+KisXmpPlugin::~KisXmpPlugin()
+{
+}
+
+#include "kis_xmp_plugin.moc"
diff --git a/plugins/metadata/xmp/kis_xmp_plugin.h \
b/plugins/metadata/xmp/kis_xmp_plugin.h new file mode 100644
index 0000000000..fa670de9ae
--- /dev/null
+++ b/plugins/metadata/xmp/kis_xmp_plugin.h
@@ -0,0 +1,21 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef _KIS_XMP_PLUGIN_H_
+#define _KIS_XMP_PLUGIN_H_
+
+#include <QObject>
+
+class KisXmpPlugin : public QObject
+{
+public:
+    KisXmpPlugin(QObject *parent, const QVariantList &);
+    ~KisXmpPlugin() override;
+};
+
+#endif // _KIS_IPTC_PLUGIN_H_
diff --git a/plugins/metadata/xmp/kritaxmp.json b/plugins/metadata/xmp/kritaxmp.json
new file mode 100644
index 0000000000..b6128ad7c5
--- /dev/null
+++ b/plugins/metadata/xmp/kritaxmp.json
@@ -0,0 +1,9 @@
+{
+    "Id": "XMP",
+    "Type": "Service",
+    "X-KDE-Library": "kritaxmp",
+    "X-KDE-ServiceTypes": [
+        "Krita/Metadata"
+    ],
+    "X-Krita-Version": "28"
+}


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic