[graphics/krita] /: Refactor metadata backends into their own plugins

Dmitry Kazakov null at kde.org
Sat Sep 4 14:35:59 BST 2021


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 at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at 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 at cberger.net>
-*
-*  SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at 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 at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at 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 at 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 at cberger.net>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at cberger.net>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at 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 at 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 at 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"
+}



More information about the kimageshop mailing list