diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0b90ba619e018d0102484656f92623c26c04e6ac..edb6c4f9740ebf5f5958aaf16fd6b525f8bfea6c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,24 +67,9 @@ target_link_libraries(Vivy PRIVATE ${MPV_LIBRARY})
 target_link_libraries(Vivy PRIVATE lua)
 
 # Headers related things
+include("${CMAKE_CURRENT_SOURCE_DIR}/PreCompiledHeaders.cmake")
 target_include_directories(Vivy PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc)
-target_precompile_headers(Vivy PRIVATE
-    # Private Vivy headers
-    ${Vivy_INC}
-
-    # STL headers
-    <memory>
-    <vector>
-    <atomic>
-
-    # Qt headers
-    <QString>
-    <QList>
-    <QVector>
-    <QMap>
-    <QWidget>
-    <QIcon>
-)
+target_precompile_headers(Vivy PRIVATE ${Vivy_PRECOMPILED_INC})
 
 # Set Vivy's needed C++ features
 target_compile_features(Vivy PRIVATE
@@ -121,7 +106,7 @@ target_link_libraries(Vivy PRIVATE -fopenmp)
 target_compile_definitions(Vivy PRIVATE
     QT_DISABLE_DEPRECATED_BEFORE=0x050F00
     QT_NO_CAST_TO_ASCII
-    #    QT_RESTRICTED_CAST_FROM_ASCII
+    QT_RESTRICTED_CAST_FROM_ASCII
     QTCREATOR_UTILS_STATIC_LIB
 )
 
@@ -130,9 +115,10 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
     target_compile_options(Vivy PRIVATE
         -Weverything
 
-        # Disable some things because we want C++20 and don't constrol some Qt
-        # generated files... Also permit VLA
+        # Disable some things because we want C++20 and don't control some Qt
+        # generated files...
         -Wno-c++98-compat -Wno-c++98-c++11-c++14-c++17-compat-pedantic
+        -Wno-c++98-compat-pedantic
         -Wno-extra-semi-stmt
         -Wno-redundant-parens
         -Wno-padded
@@ -143,7 +129,13 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
         -fopenmp
     )
 elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
-    target_compile_options(Vivy PRIVATE -fopenmp)
+    target_compile_options(Vivy PRIVATE
+        -fopenmp
+        -Wno-subobject-linkage  # Kubat: Some proglems here, it seems they
+                                # occure because of the usage of "using" for
+                                # type aliasing... As I won't get ride of the
+                                # "using"s the warning is disabled
+    )
 endif()
 
 set_target_properties(Vivy PROPERTIES
diff --git a/PreCompiledHeaders.cmake b/PreCompiledHeaders.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..4509f913c7712f48d801e1a550e829929b81da57
--- /dev/null
+++ b/PreCompiledHeaders.cmake
@@ -0,0 +1,143 @@
+# System headers
+set(STL_INC
+    PRIVATE
+    <memory>
+    <vector>
+    <atomic>
+    <algorithm>
+    <stdexcept>
+    <sstream>
+    <map>
+    <locale>
+    <functional>
+    <initializer_list>
+    <cstring>
+    <string>
+    <string_view>
+    <cstdio>
+    <iostream>
+    <optional>
+    <type_traits>
+    <chrono>
+    <climits>
+    <ctype.h>
+    <mutex>
+)
+
+set(EXT_INC PRIVATE
+    <mpv/client.h>
+    "${CMAKE_CURRENT_SOURCE_DIR}/vendor/lua-5.4.3/src/lua.hpp"
+)
+
+set(QT_STRUCT_INC
+    PRIVATE
+    <QtGlobal>
+    <QObject>
+    <QRegularExpression>
+    <QRegExp>
+    <QString>
+    <QVariant>
+    <QFile>
+    <QDebug>
+    <QHash>
+    <QDir>
+    <QTextStream>
+    <QFileInfo>
+    <QMessageLogger>
+    <QUuid>
+    <QPointer>
+    <QProcess>
+    <QTimer>
+    <QStack>
+    <QTemporaryFile>
+    <QStandardPaths>
+    <QList>
+    <QVector>
+    <QMap>
+    <QStringList>
+    <QJsonObject>
+    <QJsonArray>
+    <QJsonDocument>
+    <QJsonValue>
+)
+
+set(QT_WIDGET_INC
+    PRIVATE
+    <QCoreApplication>
+    <QTextDocument>
+    <QColor>
+    <QWidget>
+    <QIcon>
+    <QFont>
+    <QPixmap>
+    <QApplication>
+    <QCloseEvent>
+    <QLabel>
+    <QPushButton>
+    <QVBoxLayout>
+    <QHBoxLayout>
+    <QEvent>
+    <QMouseEvent>
+    <QTextEdit>
+    <QDockWidget>
+    <QMenu>
+    <QFileDialog>
+    <QMutex>
+    <QMainWindow>
+    <QPainter>
+    <QPlainTextEdit>
+    <QSlider>
+    <QAbstractItemModel>
+    <QHeaderView>
+    <QPaintEvent>
+    <QTableView>
+    <QStyledItemDelegate>
+    <QMessageBox>
+    <QScrollBar>
+    <QScrollArea>
+    <QGraphicsPixmapItem>
+    <QGraphicsSceneMouseEvent>
+    <QPen>
+    <QAction>
+    <QGraphicsItem>
+    <QGraphicsLineItem>
+    <QTreeView>
+    <QGraphicsView>
+    <QGraphicsScene>
+    <QWindow>
+    <QAbstractScrollArea>
+    <QGraphicsLineItem>
+    <QGraphicsPixmapItem>
+    <QClipboard>
+    <QInputMethodEvent>
+    <QKeyEvent>
+    <QLineEdit>
+    <QTextBlock>
+    <QTextCursor>
+    <QTextDocumentFragment>
+    <QMimeData>
+    <QSharedPointer>
+    <QStatusBar>
+    <QBrush>
+    <QFrame>
+    <QSyntaxHighlighter>
+    <QTextCharFormat>
+    <QTabWidget>
+    <QLayoutItem>
+    <QLayout>
+    <QEventLoop>
+    <QImage>
+    <QToolBar>
+    <QMenuBar>
+    <QDialogButtonBox>
+    <QFileIconProvider>
+    <QFontDatabase>
+    <QTextCodec>
+)
+
+set(Vivy_PRECOMPILED_INC PRIVATE
+    ${STL_INC}
+    ${QT_STRUCT_INC}
+    ${QT_WIDGET_INC}
+    ${EXT_INC}
+)
diff --git a/rsc/VivyRessources.qrc b/rsc/VivyRessources.qrc
index 9078302c70ef82f2cf1a5eda9c0d82fc0b3ec51a..f86514d871aa19755d64bb9729a9771dad6b6240 100644
--- a/rsc/VivyRessources.qrc
+++ b/rsc/VivyRessources.qrc
@@ -33,6 +33,7 @@
     <file alias="document-open.svg">icons/breeze-dark/document-open.svg</file>
     <file alias="document-save.svg">icons/breeze-dark/document-save.svg</file>
     <file alias="document-save-as.svg">icons/breeze-dark/document-save-as.svg</file>
+    <file alias="document-rename.svg">icons/breeze-dark/edit-rename.svg</file>
     <file alias="folder.svg">icons/breeze-dark/folder.svg</file>
     <file alias="text-x-generic.svg">icons/breeze-dark/text-x-generic.svg</file>
     <file alias="help-about.svg">icons/breeze-dark/help-about.svg</file>
@@ -45,6 +46,7 @@
     <file alias="document-open.svg">icons/breeze-light/document-open.svg</file>
     <file alias="document-save.svg">icons/breeze-light/document-save.svg</file>
     <file alias="document-save-as.svg">icons/breeze-light/document-save-as.svg</file>
+    <file alias="document-rename.svg">icons/breeze-light/edit-rename.svg</file>
     <file alias="folder.svg">icons/breeze-light/folder.svg</file>
     <file alias="text-x-generic.svg">icons/breeze-light/text-x-generic.svg</file>
     <file alias="help-about.svg">icons/breeze-light/help-about.svg</file>
diff --git a/rsc/icons/breeze-dark/edit-rename.svg b/rsc/icons/breeze-dark/edit-rename.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6a844965eafca88604c3705f6f364bfc2ad28b52
--- /dev/null
+++ b/rsc/icons/breeze-dark/edit-rename.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/rsc/icons/breeze-light/edit-rename.svg b/rsc/icons/breeze-light/edit-rename.svg
new file mode 100644
index 0000000000000000000000000000000000000000..18ccc58a83cd18f21997fafe18029787255f6117
--- /dev/null
+++ b/rsc/icons/breeze-light/edit-rename.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/src/Lib/AbstractDocument.cc b/src/Lib/AbstractDocument.cc
index 4cf89ed4bb61bd50ba7149d0d6bb81bf2269a647..def32ead006169ef84f7ac23979d74f1f77598c3 100644
--- a/src/Lib/AbstractDocument.cc
+++ b/src/Lib/AbstractDocument.cc
@@ -1,7 +1,7 @@
 #include "AbstractDocument.hh"
 
 bool
-Vivy::operator==(const AbstractDocument &a, const AbstractDocument &b) noexcept
+operator==(const Vivy::AbstractDocument &a, const Vivy::AbstractDocument &b) noexcept
 {
     return a.getUuid() == b.getUuid();
 }
diff --git a/src/Lib/AbstractDocument.hh b/src/Lib/AbstractDocument.hh
index 5a22f96aa7eb51b416dafc004d1b34b2d006e5be..51ed90b0f6c1b4aae910e30d4554d42b32d5442d 100644
--- a/src/Lib/AbstractDocument.hh
+++ b/src/Lib/AbstractDocument.hh
@@ -7,8 +7,6 @@
 
 #include "Utils.hh"
 #include "Uuid.hh"
-#include <QFileInfo>
-#include <QDir>
 
 namespace Vivy
 {
@@ -16,6 +14,41 @@ class AbstractDocument : public QObject {
     Q_OBJECT
     VIVY_UNMOVABLE_OBJECT(AbstractDocument)
 
+    void copyOrRenameWith(const QFileInfo &newFile, auto action, auto success)
+    {
+        const QFileInfo oldFile(getName());
+
+        // Create folder if needed
+        QDir dirOp;
+        const QString newAbsDirPath = newFile.dir().absolutePath();
+        if (!dirOp.exists(newAbsDirPath)) {
+            qInfo() << "Create folder " << newAbsDirPath;
+            dirOp.mkpath(newAbsDirPath);
+        }
+
+        if (newFile.absoluteFilePath() == oldFile.absoluteFilePath()) {
+            throw std::runtime_error("Can't rename or copy a file to itself!");
+        }
+
+        if (newFile.exists()) {
+            qWarning() << "Deleting the already existing" << newFile;
+            if (!dirOp.remove(newFile.absoluteFilePath()))
+                throw std::runtime_error("Failed to remove " +
+                                         newFile.absoluteFilePath().toStdString());
+        }
+
+        if (action(oldFile.absoluteFilePath(), newFile.absoluteFilePath())) {
+            success();
+            save();
+        }
+
+        else {
+            throw std::runtime_error("Failed to copy or rename " +
+                                     oldFile.absoluteFilePath().toStdString() + " to " +
+                                     newFile.absoluteFilePath().toStdString());
+        }
+    }
+
 public:
     enum class Type : quint64 {
         Vivy   = Utils::toUnderlying(Utils::DocumentType::Vivy),
@@ -31,25 +64,22 @@ protected:
 
     // Automate a part of the rename process, just need to provide a "success"
     // callback and the new file info.
-    bool renameWith(const QFileInfo &newFile, auto success) noexcept
+    void copyWith(const QFileInfo &newFile, auto success)
     {
-        const QFileInfo oldFile(getName());
-
-        // Create folder if needed
-        QDir dirOp;
-        const QString newAbsDirPath = newFile.dir().absolutePath();
-        if (!dirOp.exists(newAbsDirPath)) {
-            qInfo() << "Create folder " << newAbsDirPath;
-            dirOp.mkpath(newAbsDirPath);
-        }
-
-        if (dirOp.rename(oldFile.absoluteFilePath(), newFile.absoluteFilePath())) {
-            success();
-            return true;
-        }
+        auto action = [](const QString &oldFile, const QString &newFileName) noexcept -> bool {
+            return QFile::copy(oldFile, newFileName);
+        };
+        copyOrRenameWith(newFile, action, success);
+    }
 
-        qCritical() << "Failed to rename" << oldFile << "to" << newFile;
-        return false;
+    // Automate a part of the rename process, just need to provide a "success"
+    // callback and the new file info.
+    void renameWith(const QFileInfo &newFile, auto success)
+    {
+        auto action = [](const QString &oldFile, const QString &newFileName) noexcept -> bool {
+            return QFile::rename(oldFile, newFileName);
+        };
+        copyOrRenameWith(newFile, action, success);
     }
 
     Type type;
@@ -57,7 +87,9 @@ protected:
     const Uuid uuid{};
 
 public:
-    virtual bool rename(const QString &) noexcept = 0;
+    virtual void copy(const QString &)   = 0;
+    virtual void rename(const QString &) = 0;
+    virtual void save()                  = 0;
 
     QString getName() const noexcept { return name; }
     Type getType() const noexcept { return type; }
@@ -66,8 +98,8 @@ public:
 signals:
     void documentChanged();
 };
-
-bool operator==(const AbstractDocument &a, const AbstractDocument &b) noexcept;
 }
 
+bool operator==(const Vivy::AbstractDocument &a, const Vivy::AbstractDocument &b) noexcept;
+
 #endif // VIVY_ABSTRACT_DOCUMENT_H
diff --git a/src/Lib/AbstractMediaContext.hh b/src/Lib/AbstractMediaContext.hh
new file mode 100644
index 0000000000000000000000000000000000000000..f4a09ed6c040a96dcbea04d0b71fe2491430f727
--- /dev/null
+++ b/src/Lib/AbstractMediaContext.hh
@@ -0,0 +1,225 @@
+#pragma once
+
+#ifndef __cplusplus
+#error "This is a C++ header"
+#endif
+
+extern "C" {
+#include <libavutil/opt.h>
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libswresample/swresample.h>
+#include <libavcodec/avfft.h>
+#include <memory.h>
+}
+
+#include "Utils.hh"
+
+namespace Vivy
+{
+static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void {
+    if (ptr)
+        avcodec_free_context(&ptr);
+};
+static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void {
+    if (ptr)
+        av_free(ptr);
+};
+static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void {
+    if (ptr)
+        av_frame_free(&ptr);
+};
+
+static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void {
+    if (swr)
+        swr_free(&swr);
+};
+
+static inline constexpr auto avFormatContextDeleter = [](AVFormatContext *ptr) noexcept -> void {
+    if (ptr)
+        avformat_free_context(ptr);
+};
+
+using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
+using AVCodecContextPtr  = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
+using AVFramePtr         = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
+using SwrContextPtr      = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;
+
+// Simple abstract class to be inherited by video streams and audio streams.
+template <size_t AVMEDIA_TYPE> class AbstractMediaStream {
+    VIVY_UNMOVABLE_OBJECT(AbstractMediaStream)
+
+public:
+    static inline constexpr AVMediaType avMediaType = static_cast<AVMediaType>(AVMEDIA_TYPE);
+
+protected:
+    using Super = AbstractMediaStream<AVMEDIA_TYPE>;
+
+protected:
+    AbstractMediaStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg,
+                        int index)
+        : codecId(streamArg->codecpar->codec_id)
+        , codec(streamCodec)
+        , codecParams(streamArg->codecpar)
+        , dataFormat(formatArg)
+        , stream(streamArg)
+        , streamIndexInContext(index)
+    {
+        if (codec == nullptr)
+            throw std::runtime_error("failed to find a decoder for stream");
+
+        codecContext.reset(avcodec_alloc_context3(codec));
+        if (!codecContext)
+            throw std::runtime_error("failed to allocate codec context");
+
+        if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
+            throw std::runtime_error("failed to copy parameters to codec context");
+
+        if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
+            throw std::runtime_error("failed to open audio decoder for a stream");
+
+        qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
+        qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
+        qDebug() << "[Stream] bit rate:   " << codecParams->bit_rate;
+        qDebug() << "[Stream] channels:   " << codecParams->channels;
+    }
+
+public:
+    virtual ~AbstractMediaStream() noexcept {}
+    int getStreamIndex() const noexcept { return streamIndexInContext; }
+    QString getName() const noexcept { return QString::fromUtf8(codec->name); }
+    AVCodecID getCodecId() const noexcept { return codecId; }
+
+    virtual QJsonObject getProperties() const noexcept
+    {
+        QJsonObject ret;
+        ret.insert("Codec name", codec->name);
+        return ret;
+    }
+
+protected:
+    // Codec related informations
+    AVCodecID codecId{ AV_CODEC_ID_NONE };
+    AVCodec *codec{ nullptr };
+    AVCodecParameters *codecParams{ nullptr };
+    AVCodecContextPtr codecContext{ nullptr };
+    AVFormatContext *dataFormat{ nullptr };
+
+    // Stream is held by AudioContext
+    AVStream *stream{ nullptr };
+
+    // Store the index of this stream
+    int streamIndexInContext;
+};
+
+template <typename Stream> class AbstractMediaContext {
+    VIVY_UNMOVABLE_OBJECT(AbstractMediaContext)
+
+public:
+    static inline constexpr AVMediaType avMediaType = Stream::avMediaType;
+    using StreamPtr                                 = std::shared_ptr<Stream>;
+    using StreamWeakPtr                             = std::weak_ptr<Stream>;
+
+protected:
+    using Super = AbstractMediaContext<Stream>;
+
+public:
+    AbstractMediaContext(const QString &path)
+        : filePath(path)
+    {
+        if (!format)
+            throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
+
+        const std::string filePathStdHolder = filePath.toStdString();
+        const char *filename                = filePathStdHolder.c_str();
+        AVFormatContext *formatPtr          = format.get();
+
+        // Get the format from the file
+        if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) {
+            [[maybe_unused]] void *relatedOnFailure =
+                format.release(); // freed by avformat_open_input
+            throw std::runtime_error("failed to open file");
+        }
+
+        if (avformat_find_stream_info(formatPtr, nullptr) < 0) {
+            throw std::runtime_error("failed to get audio stream info");
+        }
+
+        // Populate all the stream indexes
+        for (uint i = 0; i < format->nb_streams; ++i) {
+            AVStream *itFmt           = format->streams[i];
+            AVCodecParameters *params = itFmt->codecpar;
+            AVCodec *streamCodec      = avcodec_find_decoder(params->codec_id);
+            if (streamCodec && streamCodec->type == avMediaType)
+                audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFmt, i));
+        }
+
+        // Get the default stream
+        defaultStreamIndex = av_find_best_stream(formatPtr, avMediaType,
+                                                 -1, // Let AV find one stream
+                                                 -1, // We don't want related streams
+                                                 nullptr, 0);
+        if (defaultStreamIndex < 0)
+            qCritical() << "Could not find the best stream";
+
+        qDebug() << "Opened context for" << path << "with duration" << formatPtr->duration
+                 << "and default stream index" << defaultStreamIndex;
+    }
+
+    virtual ~AbstractMediaContext() {}
+
+    StreamWeakPtr getStream(int index) const noexcept
+    {
+        if (index < 0)
+            return StreamWeakPtr{ spareNullSreamPtr };
+
+        uint unsignedIndex = static_cast<uint>(index);
+        const auto found   = audioStreams.find(unsignedIndex);
+
+        if (found != audioStreams.end())
+            return StreamWeakPtr{ *found };
+
+        return StreamWeakPtr{ spareNullSreamPtr };
+    }
+
+    StreamWeakPtr getDefaultStream() const noexcept
+    {
+        return (defaultStreamIndex < 0) ? StreamWeakPtr{ spareNullSreamPtr }
+                                        : getStream(defaultStreamIndex);
+    }
+
+    virtual QString getElementName() const noexcept
+    {
+        return "Context: " + QFileInfo(filePath).baseName();
+    }
+
+    virtual QJsonDocument getProperties() const noexcept
+    {
+        QJsonDocument ret;
+        QJsonArray streams;
+
+        QFileInfo file(filePath);
+
+        for (const auto &audioStreamPtr : audioStreams) {
+            streams.append(audioStreamPtr->getProperties());
+        }
+
+        QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } };
+
+        ret.setObject(self);
+        return ret;
+    }
+
+private:
+    AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };
+
+    const QString filePath;               // Usefull information
+    QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file
+
+    int defaultStreamIndex{ -1 };
+
+    // Spare always null shared pointer, to be used when the audioStream[i] was
+    // not found.
+    StreamPtr spareNullSreamPtr{ nullptr };
+};
+}
diff --git a/src/Lib/Ass/Ass.hh b/src/Lib/Ass/Ass.hh
index acb14ff2f5c568a0db4277cb455a278526f210d0..d7081d6a3d9afc747c36a78539c847d1a274ecd0 100644
--- a/src/Lib/Ass/Ass.hh
+++ b/src/Lib/Ass/Ass.hh
@@ -7,6 +7,5 @@
 #include "AssPrivate.hh"
 #include "AssFactory.hh"
 #include "StyleProperties.hh"
-#include <memory.h>
 
 #endif // VIVY_ASS_ASS_H
diff --git a/src/Lib/Ass/AssFactory.cc b/src/Lib/Ass/AssFactory.cc
index a8768767f6b4e9e3cb5b0a68e354be62ffdc0131..08ca4b7e08e93d57f0ca273adb463fb27b2032cf 100644
--- a/src/Lib/Ass/AssFactory.cc
+++ b/src/Lib/Ass/AssFactory.cc
@@ -1,8 +1,5 @@
 #include "AssFactory.hh"
 
-#include <algorithm>
-#include <stdexcept>
-
 using namespace Vivy::Ass;
 
 bool
diff --git a/src/Lib/Ass/AssFactory.hh b/src/Lib/Ass/AssFactory.hh
index f21fddb15a6b33a60917b0eb0371b4cd33592599..b220c0f1995a4c633664cb42554505080b494452 100644
--- a/src/Lib/Ass/AssFactory.hh
+++ b/src/Lib/Ass/AssFactory.hh
@@ -6,12 +6,6 @@
 #include "Line.hh"
 #include "AssPrivate.hh"
 
-#include <memory>
-#include <QFile>
-#include <QMap>
-#include <QVariant>
-#include <QString>
-
 // An ASS file is basically an INI file, but some keys are present multiple
 // times (V4+ Styles/Style, Events/Dialogue, Events/Comment).
 
@@ -21,11 +15,7 @@ class AssFactory final {
     VIVY_UNMOVABLE_OBJECT(AssFactory)
 
 public:
-    enum class Section {
-        ScriptInfo = 1,
-        Styles     = 2,
-        Events     = 3,
-    };
+    enum class Section { ScriptInfo = 1, Styles = 2, Events = 3 };
 
     using SectionContent = QMap<QString, QVariant>;
 
diff --git a/src/Lib/Ass/AssPrivate.hh b/src/Lib/Ass/AssPrivate.hh
index 71f9c7ee1b3fcfddb33e7961a01d7a96ab39bc1d..39d931a99d8c92c1d0d83d0d44de97cdaed20e38 100644
--- a/src/Lib/Ass/AssPrivate.hh
+++ b/src/Lib/Ass/AssPrivate.hh
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <memory>
-
 namespace Vivy::Ass
 {
 class Style;
diff --git a/src/Lib/Ass/Line.cc b/src/Lib/Ass/Line.cc
index c509af7449227b27421f446ed348c3c23121474d..fe0b257ab4585b7d0d962f2577177238cd328db9 100644
--- a/src/Lib/Ass/Line.cc
+++ b/src/Lib/Ass/Line.cc
@@ -1,8 +1,6 @@
 #include "Line.hh"
 #include "AssFactory.hh"
 
-#include <QRegularExpression>
-
 using namespace Vivy::Ass;
 
 Line::Line(AssFactory *const factory, const QString &lineString)
diff --git a/src/Lib/Ass/Line.hh b/src/Lib/Ass/Line.hh
index 7463dad6692230fe9627ff83f21d4f32290f1862..15984da129990d512c3cdc388b67143a4d808d08 100644
--- a/src/Lib/Ass/Line.hh
+++ b/src/Lib/Ass/Line.hh
@@ -1,8 +1,5 @@
-#ifndef VIVY_ASS_LINE_H
-#define VIVY_ASS_LINE_H
+#pragma once
 
-#include <QString>
-#include <QtGlobal>
 #include "Syl.hh"
 #include "StyleProperties.hh"
 #include "Style.hh"
@@ -47,7 +44,4 @@ public:
 private:
     void initSylFromString(const QString &) noexcept;
 };
-
 }
-
-#endif // VIVY_ASS_LINE_H
diff --git a/src/Lib/Ass/Style.cc b/src/Lib/Ass/Style.cc
index 4c2fd133bbfbb691d292047865a68dd0d9044eb4..1a6ce05cc1658ca8169c81a338917cb37f535ba8 100644
--- a/src/Lib/Ass/Style.cc
+++ b/src/Lib/Ass/Style.cc
@@ -1,8 +1,5 @@
 #include "Style.hh"
-
-#include <stdexcept>
-#include <QJsonDocument>
-#include <QJsonObject>
+#include "../Utils.hh"
 
 using namespace Vivy::Ass;
 
@@ -179,13 +176,13 @@ Style::getProperties() const noexcept
     };
 
     QJsonObject styleFontProperties{
-        { "Scale X", static_cast<const double>(scaleX) },
-        { "Scale Y", static_cast<const double>(scaleY) },
-        { "Spacing", static_cast<const double>(spacing) },
-        { "Angle", static_cast<const double>(angle) },
-        { "Border Style", static_cast<const double>(borderStyle) },
-        { "Outline", static_cast<const double>(outline) },
-        { "Shadow", static_cast<const double>(shadow) },
+        { "Scale X", static_cast<double>(scaleX) },
+        { "Scale Y", static_cast<double>(scaleY) },
+        { "Spacing", static_cast<double>(spacing) },
+        { "Angle", static_cast<double>(angle) },
+        { "Border Style", static_cast<double>(borderStyle) },
+        { "Outline", static_cast<double>(outline) },
+        { "Shadow", static_cast<double>(shadow) },
     };
 
     QJsonObject styleMargins{
diff --git a/src/Lib/Ass/Style.hh b/src/Lib/Ass/Style.hh
index 9b9bba686f6e61e10b8227a791c73ef288f6e2fc..696cfc79e4b082a14e8b3ad295e31455611f283c 100644
--- a/src/Lib/Ass/Style.hh
+++ b/src/Lib/Ass/Style.hh
@@ -1,20 +1,17 @@
-#ifndef VIVY_STYLE_H
-#define VIVY_STYLE_H
-
-#include <QString>
-#include <QVector>
-#include <QtGlobal>
-#include <QColor>
-#include <QObject>
+#pragma once
+
 #include "AssPrivate.hh"
 
 namespace Vivy::Ass
 {
-namespace Color
-{
-QColor fromString(const QString &) noexcept;
-QString toString(const QColor &) noexcept;
-static inline const QColor defaultValue = QColor(0, 0, 0, 0);
+struct Color {
+    static QColor fromString(const QString &) noexcept;
+    static QString toString(const QColor &) noexcept;
+
+    static inline const QColor defaultValue = QColor(0, 0, 0, 0);
+
+private:
+    Color() = default;
 };
 
 class Style final {
@@ -47,7 +44,4 @@ public:
     QString getElementName() const noexcept;
     QJsonDocument getProperties() const noexcept;
 };
-
 }
-
-#endif
diff --git a/src/Lib/Ass/StyleProperties.hh b/src/Lib/Ass/StyleProperties.hh
index 374d9e2afb3a42f4c5d195c33fafe5a1494f09c8..a72acf98782c66af25a4c8f960e81e1fd6022b05 100644
--- a/src/Lib/Ass/StyleProperties.hh
+++ b/src/Lib/Ass/StyleProperties.hh
@@ -1,8 +1,4 @@
-#ifndef VIVY_ASS_STYLE_PROPERTIES_H
-#define VIVY_ASS_STYLE_PROPERTIES_H
-
-#include <QColor>
-#include <QString>
+#pragma once
 #include "Style.hh"
 
 namespace Vivy::Ass
@@ -31,7 +27,4 @@ struct StyleProperties final {
     // Alignement is bottom center
     int alignment{ 2 };
 };
-
 }
-
-#endif // VIVY_ASS_STYLE_PROPERTIES_H
diff --git a/src/Lib/Ass/Syl.hh b/src/Lib/Ass/Syl.hh
index 79c7a9bc376c028ff9b283d7d12aca3ece0414c0..084e14e5f93ea11e001260bae8543aeef48c96d9 100644
--- a/src/Lib/Ass/Syl.hh
+++ b/src/Lib/Ass/Syl.hh
@@ -21,8 +21,8 @@ public:
 
 public:
     enum class ConstructMode {
-        Raw,         // Don't read ASS tags
-        ReadAssTags, // Read ass tags
+        Raw,        // Don't read ASS tags
+        ReadAssTags // Read ass tags
     };
 
     explicit Syl(const Syl &) noexcept = default;
diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc
index 8c492175f4849f7381af719a860cca4d73f1e706..218763c297441fe96745decac1d076922f209f15 100644
--- a/src/Lib/Audio.cc
+++ b/src/Lib/Audio.cc
@@ -1,133 +1,34 @@
 #include "Audio.hh"
 
-#include <QJsonObject>
-#include <QJsonArray>
-
 using namespace Vivy;
 
 // AudioContext class implementation
 
 // Create an audio contecxt from a file
 AudioContext::AudioContext(const QString &path)
-    : filePath(path)
-{
-    if (!format)
-        throw std::runtime_error("out of memory, can't create allocate the AVFormatContext");
-
-    const std::string filePathStdHolder = filePath.toStdString();
-    const char *filename                = filePathStdHolder.c_str();
-    AVFormatContext *formatPtr          = format.get();
-
-    // Get the format from the audio file
-    if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) {
-        [[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input
-        throw std::runtime_error("failed to open file");
-    }
-
-    if (avformat_find_stream_info(formatPtr, nullptr) < 0) {
-        throw std::runtime_error("failed to get audio stream info");
-    }
-
-    // Populate all the stream indexes
-    for (uint i = 0; i < format->nb_streams; ++i) {
-        AVStream *itFormat        = format->streams[i];
-        AVCodecParameters *params = itFormat->codecpar;
-        AVCodec *streamCodec      = avcodec_find_decoder(params->codec_id);
-        if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) {
-            audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i));
-        }
-    }
-
-    // Get the default stream
-    defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO,
-                                             -1, // Let AV find one stream
-                                             -1, // We don't want related streams
-                                             nullptr, 0);
-    if (defaultStreamIndex < 0) {
-        qCritical() << "Could not find the best audio stream";
-    }
-
-    qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration
-             << "and default stream index" << defaultStreamIndex;
-}
-
-// Get a specific audio stream, try to lock the weak pointer. if the index was
-// not present the used_count will be 0 and you won't be able to lock it.
-AudioContext::StreamWeakPtr
-AudioContext::getStream(int index) const noexcept
+    : Super(path)
 {
-    if (index < 0)
-        return StreamWeakPtr{ spareNullSreamPtr };
-
-    uint unsignedIndex = static_cast<uint>(index);
-    const auto found   = audioStreams.find(unsignedIndex);
-
-    if (found != audioStreams.end())
-        return StreamWeakPtr{ *found };
-
-    return StreamWeakPtr{ spareNullSreamPtr };
-}
-
-// Get the default stream of this audio context. If no default audio stream is
-// present you won't be able to lock the weak pointer.
-AudioContext::StreamWeakPtr
-AudioContext::getDefaultStream() const noexcept
-{
-    if (defaultStreamIndex < 0)
-        return StreamWeakPtr{ spareNullSreamPtr };
-    return getStream(defaultStreamIndex);
 }
 
 QString
 AudioContext::getElementName() const noexcept
 {
-    QFileInfo file(filePath);
-    return "AudioContext: " + file.baseName();
+    return "Audio" + Super::getElementName();
 }
 
 QJsonDocument
 AudioContext::getProperties() const noexcept
 {
-    QJsonDocument ret;
-    QJsonArray streams;
-
-    QFileInfo file(filePath);
-
-    for (const auto &audioStreamPtr : audioStreams) {
-        streams.append(audioStreamPtr->getProperties());
-    }
-
-    QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } };
-
-    ret.setObject(self);
-    return ret;
+    return Super::getProperties();
 }
 
-// AudioContext::Stream class implementation
+// AudioStream class implementation
 
 // Constructor, need an AVFormat and an AVStream
-AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *stream,
-                             int index)
-    : codecId(stream->codecpar->codec_id)
-    , codec(streamCodec)
-    , codecParams(stream->codecpar)
-    , audioStream(stream)
-    , streamIndexInAudioContext(index)
-    , dataFormat(formatPtr)
+AudioStream::AudioStream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *streamArg,
+                         int index)
+    : Super(streamCodec, formatPtr, streamArg, index)
 {
-    if (codec == nullptr)
-        throw std::runtime_error("failed to find a decoder for stream");
-
-    codecContext.reset(avcodec_alloc_context3(codec));
-    if (!codecContext)
-        throw std::runtime_error("failed to allocate codec context");
-
-    if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0)
-        throw std::runtime_error("failed to copy parameters to codec context");
-
-    if (avcodec_open2(codecContext.get(), codec, nullptr) < 0)
-        throw std::runtime_error("failed to open audio decoder for a stream");
-
     SwrContext *s = dataSwrContext.get();
     av_opt_set_int(s, "in_channel_count", codecContext->channels, 0);
     av_opt_set_int(s, "out_channel_count", 1, 0);
@@ -140,14 +41,9 @@ AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, A
     swr_init(s);
     if (!swr_is_initialized(s))
         throw std::runtime_error("failed to initialize SwrContext resampler");
-
-    qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId;
-    qDebug() << "[Stream] sample rate:" << codecParams->sample_rate;
-    qDebug() << "[Stream] bit rate:   " << codecParams->bit_rate;
-    qDebug() << "[Stream] channels:   " << codecParams->channels;
 }
 
-AudioContext::Stream::~Stream() noexcept
+AudioStream::~AudioStream() noexcept
 {
     if (dataPtr) {
         qDebug() << "Free data ptr";
@@ -157,10 +53,9 @@ AudioContext::Stream::~Stream() noexcept
 }
 
 QJsonObject
-AudioContext::Stream::getProperties() const noexcept
+AudioStream::getProperties() const noexcept
 {
-    QJsonObject ret;
-    ret.insert("Codec name", codec->name);
+    QJsonObject ret = Super::getProperties();
     ret.insert("Sample rate", codecParams->sample_rate);
     ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate));
     ret.insert("Channels", codecParams->channels);
@@ -169,11 +64,11 @@ AudioContext::Stream::getProperties() const noexcept
 
 // Decode the data, don't call it if data already decoded
 void
-AudioContext::Stream::decodeData()
+AudioStream::decodeData()
 {
     if (isDecoded())
         throw std::logic_error("audio stream is already resampled");
-    qDebug() << "Launch decoding of stream" << streamIndexInAudioContext;
+    qDebug() << "Launch decoding of stream" << streamIndexInContext;
 
     AVPacket packet;
     av_init_packet(&packet);
@@ -181,7 +76,7 @@ AudioContext::Stream::decodeData()
     // Iterate through frames
     while (av_read_frame(dataFormat, &packet) >= 0) {
         // Only decode audio
-        if (packet.stream_index != streamIndexInAudioContext) {
+        if (packet.stream_index != streamIndexInContext) {
             av_packet_unref(&packet);
             continue;
         }
@@ -242,13 +137,13 @@ AudioContext::Stream::decodeData()
         av_packet_unref(&packet);
     }
 
-    qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext
+    qDebug() << "Decoding data finished for stream" << streamIndexInContext
              << "dataPtr =" << dataPtr << "with dataSize =" << dataSize;
 }
 
 // Delete decoded data, clean up thing
 void
-AudioContext::Stream::cleanUpData() noexcept
+AudioStream::cleanUpData() noexcept
 {
     free(dataPtr);
     dataPtr  = nullptr;
@@ -257,14 +152,14 @@ AudioContext::Stream::cleanUpData() noexcept
 
 // Get the number of channels in the audio context stream
 int
-AudioContext::Stream::getChannels() const noexcept
+AudioStream::getChannels() const noexcept
 {
     return codecContext->channels;
 }
 
 // Get the sample rate
 int
-AudioContext::Stream::getSampleRate() const noexcept
+AudioStream::getSampleRate() const noexcept
 {
     return codecContext->sample_rate;
 }
@@ -272,35 +167,21 @@ AudioContext::Stream::getSampleRate() const noexcept
 // Get the sample rate of the decoded data (need to call decodeData for that
 // value to have a meaning).
 int
-AudioContext::Stream::getDataSampleRate() const noexcept
+AudioStream::getDataSampleRate() const noexcept
 {
     return resamplerSampleRate;
 }
 
-// Get the name of the codec used to decode the stream
-QString
-AudioContext::Stream::getName() const noexcept
-{
-    return QString(codec->name);
-}
-
-// Get the codec's ID used to decode the stream
-AVCodecID
-AudioContext::Stream::getCodecId() const noexcept
-{
-    return codecId;
-}
-
 // Get the bit rate of the stream
 qint64
-AudioContext::Stream::getBitRate() const noexcept
+AudioStream::getBitRate() const noexcept
 {
     return codecContext->bit_rate;
 }
 
 // Get the information about the decoded state of this stream
 bool
-AudioContext::Stream::isDecoded() const noexcept
+AudioStream::isDecoded() const noexcept
 {
     return dataPtr != nullptr;
 }
@@ -308,7 +189,7 @@ AudioContext::Stream::isDecoded() const noexcept
 // Get the decoded data's size, will return 0 if the data is not decoded. Don't
 // rely on this behaviour to get the decoded state of the stream!
 size_t
-AudioContext::Stream::getDecodedDataSize() const noexcept
+AudioStream::getDecodedDataSize() const noexcept
 {
     return dataSize;
 }
@@ -316,28 +197,22 @@ AudioContext::Stream::getDecodedDataSize() const noexcept
 // Get the decoded data, safe to call even if the data is not decoded has the
 // pointer will be null. You must check it with an `if` statement.
 double *
-AudioContext::Stream::getDecodedData() const noexcept
+AudioStream::getDecodedData() const noexcept
 {
     return dataPtr;
 }
 
 // Get the chunk size of the decoded data
 size_t
-AudioContext::Stream::getDecodedChunkSize() const noexcept
+AudioStream::getDecodedChunkSize() const noexcept
 {
     return 512;
 }
 
 // Get the decalage of the decoded data
 size_t
-AudioContext::Stream::getDecodedDecalage() const noexcept
+AudioStream::getDecodedDecalage() const noexcept
 {
     constexpr size_t overlap = 128; // The overlap
     return getDecodedChunkSize() - overlap;
 }
-
-int
-AudioContext::Stream::getStreamIndex() const noexcept
-{
-    return streamIndexInAudioContext;
-}
diff --git a/src/Lib/Audio.hh b/src/Lib/Audio.hh
index 3a7ad817e1a6c65d76b36c7084b8098d1e6f63eb..920b1033228943d2ffc2a63c435682e06a7883fe 100644
--- a/src/Lib/Audio.hh
+++ b/src/Lib/Audio.hh
@@ -1,157 +1,69 @@
-#ifndef VIVY_AUDIO_H
-#define VIVY_AUDIO_H
+#pragma once
 
 #ifndef __cplusplus
 #error "This is a C++ header"
 #endif
 
-extern "C" {
-#include <libavutil/opt.h>
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libswresample/swresample.h>
-#include <libavcodec/avfft.h>
-}
-
-#include "Utils.hh"
-#include <QtGlobal>
-#include <QMap>
-#include <QVector>
-#include <QString>
-#include <memory.h>
+#include "AbstractMediaContext.hh"
 
 namespace Vivy
 {
 class AudioContext;
 
+// Hold all the data for a stream in an audio file. Should only be owned by
+// the AudioContext class.
+class AudioStream final : public AbstractMediaStream<AVMEDIA_TYPE_AUDIO> {
+    VIVY_UNMOVABLE_OBJECT(AudioStream)
+
+public:
+    AudioStream(AVCodec *, AVFormatContext *, AVStream *, int index);
+    ~AudioStream() noexcept override;
+
+    // Decode the stream
+    void decodeData();
+    void cleanUpData() noexcept;
+    bool isDecoded() const noexcept;
+    int getDataSampleRate() const noexcept;
+    size_t getDecodedDataSize() const noexcept;
+    size_t getDecodedChunkSize() const noexcept;
+    size_t getDecodedDecalage() const noexcept;
+    double *getDecodedData() const noexcept;
+
+    // Some getters
+    int getChannels() const noexcept;
+    int getSampleRate() const noexcept;
+    qint64 getBitRate() const noexcept;
+    QJsonObject getProperties() const noexcept override;
+
+private:
+    // Resampled frame data
+    SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
+    AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
+    double *dataPtr{ nullptr };
+    size_t dataSize{ 0 };
+
+    // Constants for the resampler
+    static constexpr uint resamplerChunkSize  = 512;
+    static constexpr uint resamplerOverlap    = 128;
+    static constexpr uint resamplerDecalage   = resamplerChunkSize - resamplerOverlap;
+    static constexpr uint resamplerSampleRate = 44100;
+};
+
 // The memory representation of an audio file. It is created inplace and should
 // not be moved around. It should be used to get the properties of an audio
 // file and its streams. The user can use a stream from the file and get data
 // from it.
-class AudioContext final {
+class AudioContext final : public AbstractMediaContext<AudioStream> {
     VIVY_UNMOVABLE_OBJECT(AudioContext)
 
 public:
-    // Hold all the data for a stream in an audio file. Should only be owned by
-    // the AudioContext class.
-    class Stream final {
-        VIVY_UNMOVABLE_OBJECT(Stream)
-
-        // All the needed deleters
-        static inline constexpr auto codecContexteleter = [](AVCodecContext *ptr) noexcept -> void {
-            if (ptr)
-                avcodec_free_context(&ptr);
-        };
-        static constexpr inline auto dataDeleter = [](double *ptr) noexcept -> void {
-            if (ptr)
-                av_free(ptr);
-        };
-        static constexpr inline auto avFrameDeleter = [](AVFrame *ptr) noexcept -> void {
-            if (ptr)
-                av_frame_free(&ptr);
-        };
-
-        static constexpr inline auto swrContenxtDeleter = [](SwrContext *swr) noexcept -> void {
-            if (swr)
-                swr_free(&swr);
-        };
-
-        // All the used types
-        using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
-        using DataPtr           = std::shared_ptr<double[]>;
-        using AVFramePtr        = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
-        using SwrContextPtr     = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;
-
-    public:
-        Stream(AVCodec *, AVFormatContext *, AVStream *, int index);
-        ~Stream() noexcept;
-
-        // The non-owning view of the stream's data
-        using DataWeakPtr = std::weak_ptr<double[]>;
-
-        // Decode the stream
-        void decodeData();
-        void cleanUpData() noexcept;
-        bool isDecoded() const noexcept;
-        int getDataSampleRate() const noexcept;
-        size_t getDecodedDataSize() const noexcept;
-        size_t getDecodedChunkSize() const noexcept;
-        size_t getDecodedDecalage() const noexcept;
-        double *getDecodedData() const noexcept;
-
-        // Some getters
-        int getChannels() const noexcept;
-        int getSampleRate() const noexcept;
-        QString getName() const noexcept;
-        AVCodecID getCodecId() const noexcept;
-        qint64 getBitRate() const noexcept;
-
-        // Get the index from the audio context
-        int getStreamIndex() const noexcept;
-
-        QJsonObject getProperties() const noexcept;
-
-    private:
-        // Codec related informations
-        AVCodecID codecId{ AV_CODEC_ID_NONE };
-        AVCodec *codec{ nullptr };
-        AVCodecParameters *codecParams{ nullptr };
-        AVCodecContextPtr codecContext{ nullptr };
-
-        // Stream is held by AudioContext
-        AVStream *audioStream{ nullptr };
-
-        // Store the index of this stream
-        int streamIndexInAudioContext;
-
-        // Resampled frame data
-        AVFormatContext *dataFormat;
-        SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
-        AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
-        double *dataPtr{ nullptr };
-        size_t dataSize{ 0 };
-
-        // Constants for the resampler
-        static constexpr uint resamplerChunkSize  = 512;
-        static constexpr uint resamplerOverlap    = 128;
-        static constexpr uint resamplerDecalage   = resamplerChunkSize - resamplerOverlap;
-        static constexpr uint resamplerSampleRate = 44100;
-    };
-
-    using StreamPtr = std::shared_ptr<Stream>;
+    using StreamPtr     = std::shared_ptr<AudioStream>;
+    using StreamWeakPtr = std::weak_ptr<AudioStream>;
 
 public:
     AudioContext(const QString &path);
-    ~AudioContext() noexcept = default;
-
-    // The stream non-owning view pointer
-    using StreamWeakPtr = std::weak_ptr<Stream>;
 
-    StreamWeakPtr getStream(int) const noexcept;
-    StreamWeakPtr getDefaultStream() const noexcept;
-
-    QString getElementName() const noexcept;
-    QJsonDocument getProperties() const noexcept;
-
-private:
-    // Regarding the format
-    static inline constexpr auto avFormatContextDeleter =
-        [](AVFormatContext *ptr) noexcept -> void {
-        if (ptr)
-            avformat_free_context(ptr);
-    };
-    using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
-    AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };
-
-    QString filePath;                     // Usefull information
-    QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file
-
-    int defaultStreamIndex{ -1 };
-
-    // Spare always null shared pointer, to be used when the audioStream[i] was
-    // not found.
-    StreamPtr spareNullSreamPtr{ nullptr };
+    QString getElementName() const noexcept override;
+    QJsonDocument getProperties() const noexcept override;
 };
 }
-
-#endif // VIVY_AUDIO_H
diff --git a/src/Lib/CRTPStore.hh b/src/Lib/CRTPStore.hh
index 94b03353c2fd1c9ed525109d4981374ad2a910ae..8324d9c606635812731708f754c7997bbe6c49e2 100644
--- a/src/Lib/CRTPStore.hh
+++ b/src/Lib/CRTPStore.hh
@@ -6,9 +6,6 @@
 
 #include "Utils.hh"
 #include "Uuid.hh"
-#include <QString>
-#include <QMap>
-#include <memory>
 
 #define VIVY_STORAGE_CLASS(theClassName, theDocumentName) \
     VIVY_UNMOVABLE_OBJECT(theClassName)                   \
diff --git a/src/Lib/Document/CRTPSubDocument.cc b/src/Lib/Document/CRTPSubDocument.cc
deleted file mode 100644
index ed788183a5f1acc939f1c28479d9cfeec9951e42..0000000000000000000000000000000000000000
--- a/src/Lib/Document/CRTPSubDocument.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-#include "CRTPSubDocument.hh"
-
-#include <QJsonObject>
-#include <QJsonDocument>
-
-using namespace Vivy;
-
-// AudioSubDocument implementation
-
-// Get the default stream index or -1 if not possible
-int
-AudioSubDocument::getDefaultStreamIndex() const noexcept
-{
-    if (auto ptr = getDefaultStream()) {
-        return ptr->getStreamIndex();
-    } else {
-        return -1;
-    }
-}
-
-// Get a pointer to the default stream, nullptr if not possible
-AudioContext::StreamPtr
-AudioSubDocument::getDefaultStream() const noexcept
-{
-    if (auto ptr = contextPtr->getDefaultStream().lock()) {
-        return ptr;
-    } else {
-        qCritical() << "Document deleted!";
-        return nullptr;
-    }
-}
-
-// Get the stream asked for, nullptr if no stream or if the index is invalid
-AudioContext::StreamPtr
-AudioSubDocument::getStream(int index) const noexcept
-{
-    if (auto ptr = contextPtr->getStream(index).lock()) {
-        return ptr;
-    } else {
-        return nullptr;
-    }
-}
-
-// Init an audio sub-document from a file
-void
-AudioSubDocument::initFromPath(const QString &path)
-{
-    if (contextPtr)
-        qDebug() << "Replacing the audio contetx by a new one for file" << path;
-    contextPtr.reset(new AudioContext(path)); // May throw
-
-    qDebug() << "Audio OK for" << path;
-}
-
-QString
-AudioSubDocument::getElementName() const noexcept
-{
-    return "AudioSubDocument";
-}
-
-QJsonDocument
-AudioSubDocument::getProperties() const noexcept
-{
-    QJsonDocument ret;
-    QJsonObject object{
-        { "Audio context", contextPtr->getProperties().object() },
-        { "File", filePath },
-    };
-    ret.setObject(object);
-    return ret;
-}
-
-// VideoSubDocument implementation
-
-// Init a video sub-document from a file
-void
-VideoSubDocument::initFromPath(const QString &)
-{
-}
-
-QString
-VideoSubDocument::getElementName() const noexcept
-{
-    return "VideoSubDocument";
-}
-
-QJsonDocument
-VideoSubDocument::getProperties() const noexcept
-{
-    QJsonDocument ret;
-    QJsonObject object;
-    ret.setObject(object);
-    return ret;
-}
-
-// AssSubDocument implementation
-
-// Init a ASS sub-document from a file
-void
-AssSubDocument::initFromPath(const QString &path)
-{
-    Ass::AssFactory factory(path);
-    factory.getStyles(styles);
-    factory.getLines(lines);
-}
-
-QString
-AssSubDocument::getElementName() const noexcept
-{
-    return "AssSubDocument";
-}
-
-const QVector<Ass::LinePtr> &
-AssSubDocument::getLines() const noexcept
-{
-    return lines;
-}
-
-const QVector<Ass::StylePtr> &
-AssSubDocument::getStyles() const noexcept
-{
-    return styles;
-}
-
-QJsonDocument
-AssSubDocument::getProperties() const noexcept
-{
-    QJsonDocument ret;
-
-    QJsonObject styleObject;
-    for (const Ass::StylePtr &style : styles) {
-        styleObject.insert(style->getElementName(), style->getProperties().object());
-    }
-
-    QJsonObject object{
-        { "Styles", styleObject },
-        { "File", filePath },
-    };
-    ret.setObject(object);
-    return ret;
-}
diff --git a/src/Lib/Document/CRTPSubDocument.hh b/src/Lib/Document/CRTPSubDocument.hh
index eb6683d58a6b3eeddf3ed47bc1265b67791beee8..c89f25c587539613cc5ad1f0c00305629f2eb984 100644
--- a/src/Lib/Document/CRTPSubDocument.hh
+++ b/src/Lib/Document/CRTPSubDocument.hh
@@ -1,5 +1,4 @@
-#ifndef VIVY_CRTP_DOCUMENT_H
-#define VIVY_CRTP_DOCUMENT_H
+#pragma once
 
 #ifndef __cplusplus
 #error "This is a C++ header"
@@ -7,14 +6,16 @@
 
 #include "../Utils.hh"
 #include "../Audio.hh"
+#include "../Video.hh"
 #include "../Ass/Ass.hh"
-#include <QString>
-#include <memory>
+
+#define VIVY_ENABLE_IF_TYPE(Type) \
+    template <typename = typename std::enable_if<!std::is_same<Type, void>::value>>
 
 namespace Vivy
 {
 // The Big CRTP class for all common things to all the subdocuments
-template <class CRTPSubDocumentType, class Document> class CRTPSubDocument {
+template <class CRTPSubDocumentType, class Document, class Context> class CRTPSubDocument {
 public:
     using Type = CRTPSubDocumentType;
     friend std::unique_ptr<Document>;
@@ -48,41 +49,68 @@ public:
         return ret;
     }
 
+    // Get the default stream index or -1 if not possible.
+    VIVY_ENABLE_IF_TYPE(Context)
+    int getDefaultStreamIndex() const noexcept
+    {
+        if (auto ptr = getDefaultStream())
+            return ptr->getStreamIndex();
+        return -1;
+    }
+
+    // Get a pointer to the default stream, nullptr if not possible
+    VIVY_ENABLE_IF_TYPE(Context)
+    auto getDefaultStream() const
+    {
+        const Document *const self = static_cast<const Document *const>(this);
+        auto weakPtr               = self->contextPtr->getDefaultStream();
+        if (auto ptr = weakPtr.lock())
+            return ptr;
+        throw std::runtime_error("SubDocument has been deleted");
+    }
+
+    // Get the stream asked for, nullptr if no stream or if the index is invalid
+    VIVY_ENABLE_IF_TYPE(Context)
+    auto getStream(int index) const
+    {
+        const Document *const self = static_cast<const Document *const>(this);
+        auto weakPtr               = self->contextPtr->getStream(index);
+        if (auto ptr = weakPtr.lock())
+            return ptr;
+        throw std::runtime_error("SubDocument has been deleted");
+    }
+
     inline Type getType() const noexcept { return fileType; }
     inline QString getFilePath() const noexcept { return filePath; }
 };
 
 // Audio document
-class AudioSubDocument final : public CRTPSubDocument<AudioDocumentType, AudioSubDocument> {
-private:
+class AudioSubDocument final
+    : public CRTPSubDocument<AudioDocumentType, AudioSubDocument, AudioContext> {
     const QStringList &suffixList = Vivy::Utils::audioFileSuffix;
+    std::unique_ptr<AudioContext> contextPtr{ nullptr };
 
     void initFromPath(const QString &);
 
     explicit AudioSubDocument() = default;
-    friend CRTPSubDocument<AudioDocumentType, AudioSubDocument>;
-
-private:
-    std::unique_ptr<AudioContext> contextPtr;
+    friend CRTPSubDocument<AudioDocumentType, AudioSubDocument, AudioContext>;
 
 public:
-    int getDefaultStreamIndex() const noexcept;
-    AudioContext::StreamPtr getDefaultStream() const noexcept;
-    AudioContext::StreamPtr getStream(int index) const noexcept;
-
     QString getElementName() const noexcept;
     QJsonDocument getProperties() const noexcept;
 };
 
 // Video document
-class VideoSubDocument final : public CRTPSubDocument<VideoDocumentType, VideoSubDocument> {
-private:
-    const QStringList &suffixList = Vivy::Utils::videoFileSuffix;
+class VideoSubDocument final
+    : public CRTPSubDocument<VideoDocumentType, VideoSubDocument, VideoContext> {
+    static constexpr inline bool hasContext = true;
+    const QStringList &suffixList           = Vivy::Utils::videoFileSuffix;
+    std::unique_ptr<VideoContext> contextPtr{ nullptr };
 
     void initFromPath(const QString &);
 
     explicit VideoSubDocument() noexcept = default;
-    friend CRTPSubDocument<VideoDocumentType, VideoSubDocument>;
+    friend CRTPSubDocument<VideoDocumentType, VideoSubDocument, VideoContext>;
 
 public:
     QString getElementName() const noexcept;
@@ -90,13 +118,14 @@ public:
 };
 
 // ASS document
-class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocument> {
-    const QStringList &suffixList = Vivy::Utils::assFileSuffix;
+class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocument, void> {
+    static constexpr inline bool hasContext = false;
+    const QStringList &suffixList           = Vivy::Utils::assFileSuffix;
 
     void initFromPath(const QString &);
 
     explicit AssSubDocument() noexcept = default;
-    friend CRTPSubDocument<AssDocumentType, AssSubDocument>;
+    friend CRTPSubDocument<AssDocumentType, AssSubDocument, void>;
 
 public:
     QString getElementName() const noexcept;
@@ -109,7 +138,4 @@ private:
     QVector<Ass::StylePtr> styles;
     QVector<Ass::LinePtr> lines;
 };
-
 }
-
-#endif // VIVY_CRTP_DOCUMENT_H
diff --git a/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc b/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b465093754d4fa0cf412182ce733eb34bdc1ff10
--- /dev/null
+++ b/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc
@@ -0,0 +1,45 @@
+#include "../CRTPSubDocument.hh"
+#include "../../JsonBuilder.hh"
+
+using namespace Vivy;
+
+// Init a ASS sub-document from a file
+void
+AssSubDocument::initFromPath(const QString &path)
+{
+    Ass::AssFactory factory(path);
+    factory.getStyles(styles);
+    factory.getLines(lines);
+}
+
+QString
+AssSubDocument::getElementName() const noexcept
+{
+    return "AssSubDocument";
+}
+
+const QVector<Ass::LinePtr> &
+AssSubDocument::getLines() const noexcept
+{
+    return lines;
+}
+
+const QVector<Ass::StylePtr> &
+AssSubDocument::getStyles() const noexcept
+{
+    return styles;
+}
+
+QJsonDocument
+AssSubDocument::getProperties() const noexcept
+{
+    QJsonObject styleObject;
+    for (const Ass::StylePtr &style : styles) {
+        styleObject.insert(style->getElementName(), style->getProperties().object());
+    }
+
+    return JsonBuilder::createOrderedJsonDocument({
+        { "File", filePath },
+        { "Styles", styleObject },
+    });
+}
diff --git a/src/Lib/Document/CRTPSubDocument/AudioSubDocument.cc b/src/Lib/Document/CRTPSubDocument/AudioSubDocument.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c91c3752109af9df968d21d46d32ca433557c1b9
--- /dev/null
+++ b/src/Lib/Document/CRTPSubDocument/AudioSubDocument.cc
@@ -0,0 +1,30 @@
+#include "../CRTPSubDocument.hh"
+#include "../../JsonBuilder.hh"
+
+using namespace Vivy;
+
+// Init an audio sub-document from a file
+void
+AudioSubDocument::initFromPath(const QString &path)
+{
+    if (contextPtr)
+        qDebug() << "Replacing the audio contetx by a new one for file" << path;
+    contextPtr.reset(new AudioContext(path)); // May throw
+
+    qDebug() << "Audio OK for" << path;
+}
+
+QString
+AudioSubDocument::getElementName() const noexcept
+{
+    return "AudioSubDocument";
+}
+
+QJsonDocument
+AudioSubDocument::getProperties() const noexcept
+{
+    return JsonBuilder::createOrderedJsonDocument({
+        { "File", filePath },
+        { "Audio context", contextPtr->getProperties().object() },
+    });
+}
diff --git a/src/Lib/Document/CRTPSubDocument/VideoSubDocument.cc b/src/Lib/Document/CRTPSubDocument/VideoSubDocument.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6f837e9c219e4747f71100191cad27c9298f102e
--- /dev/null
+++ b/src/Lib/Document/CRTPSubDocument/VideoSubDocument.cc
@@ -0,0 +1,30 @@
+#include "../CRTPSubDocument.hh"
+#include "../../JsonBuilder.hh"
+
+using namespace Vivy;
+
+// Init a video sub-document from a file
+void
+VideoSubDocument::initFromPath(const QString &path)
+{
+    if (contextPtr)
+        qDebug() << "Replacing the video contetx by a new one for file" << path;
+    contextPtr.reset(new VideoContext(path)); // May throw
+
+    qDebug() << "Video OK for" << path;
+}
+
+QString
+VideoSubDocument::getElementName() const noexcept
+{
+    return "VideoSubDocument";
+}
+
+QJsonDocument
+VideoSubDocument::getProperties() const noexcept
+{
+    return JsonBuilder::createOrderedJsonDocument({
+        { "Video context", contextPtr->getProperties().object() },
+        { "File", filePath },
+    });
+}
diff --git a/src/Lib/Document/VivyDocument.cc b/src/Lib/Document/VivyDocument.cc
index ae4c12355717d9ab4bb5246724259ed58a4cc389..6749fcc3cce6e5acb44bf584ccb9c345903835cc 100644
--- a/src/Lib/Document/VivyDocument.cc
+++ b/src/Lib/Document/VivyDocument.cc
@@ -1,25 +1,148 @@
 #include "VivyDocument.hh"
 #include "../Utils.hh"
-
-#include <QFileInfo>
-#include <QString>
-#include <QStringList>
-#include <QJsonArray>
-#include <QJsonObject>
+#include "../JsonBuilder.hh"
 
 using namespace Vivy;
 
+// Runtime document version, don't set it in the header!!!
+QString VivyDocument::runtimeVersion = QStringLiteral("alpha");
+
 VivyDocument::VivyDocument(const QString &documentPath, Options opt)
     : AbstractDocument(AbstractDocument::Type::Vivy, documentPath)
     , documentOptions(opt)
 {
-    QFileInfo file(name);
-    documentName     = file.baseName();
-    documentLocation = file.absoluteDir();
+    // Ignore some things in case of memory document creation because no real
+    // document with an extension is available.
+    if (opt & MemoryDocumentCreation) {
+        documentName     = documentPath;
+        documentLocation = QDir::home();
+    }
+
+    // Load a real document
+    else {
+        QFileInfo fileInfo(name);
+        QFile file(name);
+        documentName     = fileInfo.baseName();
+        documentLocation = fileInfo.absoluteDir();
+
+        if ((fileInfo.completeSuffix() != fileSuffix))
+            throw std::runtime_error("Invalid file suffix");
+
+        if (!file.exists() || !fileInfo.isFile() ||
+            !file.open(QIODevice::ReadOnly | QIODevice::Text))
+            throw std::runtime_error("Document doesn't exists");
+
+        QTextStream fileContent(&file);
+        QJsonParseError jsonError;
+        QJsonDocument json = QJsonDocument::fromJson(fileContent.readAll().toUtf8(), &jsonError);
+        if (json.isNull())
+            throw std::runtime_error("Json error " + jsonError.errorString().toStdString());
+
+        loadSaveJsonDocumentFile(json);
+    }
+}
+
+void
+VivyDocument::save()
+{
+    if (documentOptions & MemoryDocumentCreation)
+        throw std::logic_error("You can't save a memory document");
+
+    const bool needExt     = !documentName.endsWith(fileSuffix);
+    const QString filepath = documentLocation.absolutePath() + QDir::separator() + documentName +
+                             (needExt ? "." + fileSuffix : "");
+    QFile file(filepath);
+
+    if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
+        throw std::runtime_error("Failed to open file in WO+Text mode: " + filepath.toStdString());
+
+    QTextStream writter(&file);
+    QJsonDocument json = getSaveJsonDocumentFile();
+    writter << json.toJson(QJsonDocument::Compact);
+    file.close();
+    documentOptions = static_cast<Options>(documentOptions & (~UntouchedByDefault));
+}
+
+void
+VivyDocument::loadSaveJsonDocumentFile_ALPHA(VivyDocument *const self, const QJsonDocument &json)
+{
+    // Init from the JSON
+    self->documentType = json[KeyCapabilities].toString().toULongLong() & possibleCapabilities;
+    if (json[KeyName].toString() != self->documentName)
+        throw std::runtime_error("The document was edited outside of Vivy");
+
+    if (QJsonValue audio = json[KeySubDocuments][KeySubAudio];
+        !audio.isNull() && !self->loadSubDocument(audio.toString(), Capabilities::AudioAble))
+        throw std::runtime_error("Failed to load audio sub document");
+
+    if (QJsonValue video = json[KeySubDocuments][KeySubVideo];
+        !video.isNull() && !self->loadSubDocument(video.toString(), Capabilities::VideoAble))
+        throw std::runtime_error("Failed to load video sub document");
+
+    if (QJsonValue internalAssSource = json[KeySubDocuments][KeyInternalAssSource];
+        !internalAssSource.isNull() && internalAssSource.toBool()) {
+        // ASS is inside Vivy document
+        throw std::runtime_error("The internal ASS feature is not supported for now");
+    } else if (QJsonValue ass = json[KeySubDocuments][KeySubAss]; !ass.isNull()) {
+        // ASS in its own ASS file
+        if (!self->loadSubDocument(ass.toString(), Capabilities::AssAble))
+            throw std::runtime_error("Failed to load ASS sub document");
+    }
+}
+
+void
+VivyDocument::loadSaveJsonDocumentFile(const QJsonDocument &json)
+{
+    if (json.isNull())
+        throw std::logic_error("Json is null, you can't pass a null json "
+                               "document to this function");
+
+    const QJsonValue savedVersionJson = json[KeyDocumentVersion];
+    if (savedVersionJson.isNull())
+        throw std::runtime_error("No version present in the saved VivyDocument");
+
+    const QString savedVersion = savedVersionJson.toString();
+    for (const auto &[version, callback] : loadFunctions) {
+        if (savedVersion == QString::fromUtf8(version))
+            return callback(this, json);
+    }
+
+    throw std::runtime_error("Version is undefined! No way has been "
+                             "found to load the document");
+}
+
+QJsonDocument
+VivyDocument::getSaveJsonDocumentFile() const
+{
+    QJsonDocument ret;
+    QJsonObject json;
+    QJsonObject subDocumentJson;
+
+    json.insert(KeyCapabilities, QString::number(possibleCapabilities & documentType));
+    json.insert(KeyName, documentName);
+    json.insert(KeyDocumentVersion, runtimeVersion);
 
-    loadSubDocument(name);
-    qDebug() << "CONSTRUCTOR: VivyDocument(" << name << "," << opt << ","
-             << getDocumentCapabilitiesString() << ")";
+    if (documentType & Capabilities::AudioAble)
+        subDocumentJson.insert(KeySubAudio, audioDocument->getFilePath());
+
+    if (documentType & Capabilities::VideoAble)
+        subDocumentJson.insert(KeySubVideo, videoDocument->getFilePath());
+
+    if (documentType & Capabilities::AssAble) {
+        // ASS is inside Vivy document
+        if ((assDocument->getFilePath() == getName())) {
+            subDocumentJson.insert(KeyInternalAssSource, true);
+            throw std::runtime_error("Unsupported save of internal ASS for now");
+        }
+
+        // ASS is ints own ASS file
+        else
+            subDocumentJson.insert(KeySubAss, assDocument->getFilePath());
+    }
+
+    json.insert(KeySubDocuments, subDocumentJson);
+    ret.setObject(json);
+    return ret;
 }
 
 bool
@@ -82,7 +205,7 @@ VivyDocument::loadSubDocument(const QString &subName, VivyDocument::Capabilities
 }
 
 bool
-VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *ableType) noexcept
+VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *const ableType) noexcept
 {
     Vivy::Utils::DocumentType docType;
     bool rc = Vivy::Utils::detectDocumentType(file, &docType);
@@ -103,18 +226,68 @@ VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *ableType)
     return rc;
 }
 
-bool
-VivyDocument::rename(const QString &newName) noexcept
+void
+VivyDocument::copy(const QString &newName)
+{
+    const QFileInfo newPath = computeFileInfo(newName);
+
+    // Rename + create the disk on disk as it's a memory document
+    if (documentOptions & MemoryDocumentCreation)
+        saveMemoryFile(newPath);
+
+    // Compute new paths, the document is really on disk initially
+    else {
+        qDebug() << "Renaming a real file";
+        copyWith(newPath, [=, this]() noexcept -> void {
+            documentLocation = newPath.dir();
+            documentName     = newPath.baseName();
+            name             = newPath.absoluteFilePath();
+        });
+    }
+
+    emit documentChanged();
+}
+
+void
+VivyDocument::rename(const QString &newName)
+{
+    const QFileInfo newPath = computeFileInfo(newName);
+
+    // Rename + create the disk on disk as it's a memory document
+    if (documentOptions & MemoryDocumentCreation)
+        saveMemoryFile(newPath);
+
+    // Compute new paths, the document is really on disk initially
+    else {
+        qDebug() << "Renaming a real file";
+        renameWith(newPath, [=, this]() noexcept -> void {
+            documentLocation = newPath.dir();
+            documentName     = newPath.baseName();
+            name             = newPath.absoluteFilePath();
+        });
+    }
+
+    emit documentChanged();
+}
+
+QFileInfo
+VivyDocument::computeFileInfo(const QString &newName) const noexcept
 {
-    /* Compute new paths */
     const QString newNameWithExtension =
-        newName.right(filePrefix.size()) == filePrefix ? newName : newName + "." + filePrefix;
-    const QFileInfo newPath(documentLocation, newNameWithExtension);
+        (newName.right(fileSuffix.size()) == fileSuffix) ? newName : newName + "." + fileSuffix;
+    return QFileInfo(documentLocation, newNameWithExtension);
+}
 
-    return renameWith(newPath, [=, this]() noexcept -> void {
-        documentLocation = newPath.dir();
-        documentName     = newPath.baseName();
-    });
+void
+VivyDocument::saveMemoryFile(const QFileInfo &newPath)
+{
+    documentName     = newPath.baseName();
+    documentLocation = newPath.absoluteDir();
+    name             = newPath.absoluteFilePath();
+    documentOptions  = static_cast<Options>(documentOptions & (~MemoryDocumentCreation));
+    qDebug().nospace() << "Renaming a memory file => create it on disk with { " << documentLocation
+                       << ", " << documentName << " }";
+    save();
 }
 
 std::shared_ptr<AudioSubDocument>
@@ -152,14 +325,11 @@ VivyDocument::getDocumentCapabilitiesString() const noexcept
 {
     QStringList ret;
     if (documentType & AudioAble)
-        ret.push_back("AudioAble" +
-                      QString(audioDocument ? QStringLiteral("") : QStringLiteral("(ack)")));
+        ret.push_back("AudioAble" + QString::fromUtf8(audioDocument ? "" : "(ack)"));
     if (documentType & VideoAble)
-        ret.push_back("VideoAble" +
-                      QString(videoDocument ? QStringLiteral("") : QStringLiteral("(ack)")));
+        ret.push_back("VideoAble" + QString::fromUtf8(videoDocument ? "" : "(ack)"));
     if (documentType & AssAble)
-        ret.push_back("AssAble" +
-                      QString(assDocument ? QStringLiteral("") : QStringLiteral("(ack)")));
+        ret.push_back("AssAble" + QString::fromUtf8(assDocument ? "" : "(ack)"));
     return ret.join(", ");
 }
 
@@ -170,7 +340,7 @@ VivyDocument::setAudioSubDocument(const QString filename) noexcept
     QFileInfo fileInfo(filename);
     const QString baseName = fileInfo.baseName();
 
-    if (auto doc = audioDocument.get()) {
+    if ([[maybe_unused]] auto doc = audioDocument.get()) {
         // Here we may want to do some confirmation / cleanup
         audioDocument.reset();
     }
@@ -187,7 +357,7 @@ VivyDocument::setVideoSubDocument(const QString filename) noexcept
     QFileInfo fileInfo(filename);
     const QString baseName = fileInfo.baseName();
 
-    if (auto doc = videoDocument.get()) {
+    if ([[maybe_unused]] auto doc = videoDocument.get()) {
         // Here we may want to do some confirmation / cleanup
         videoDocument.reset();
     }
@@ -204,7 +374,7 @@ VivyDocument::setAssSubDocument(const QString filename) noexcept
     QFileInfo fileInfo(filename);
     const QString baseName = fileInfo.baseName();
 
-    if (auto doc = assDocument.get()) {
+    if ([[maybe_unused]] auto doc = assDocument.get()) {
         // Here we may want to do some confirmation / cleanup
         assDocument.reset();
     }
@@ -231,21 +401,13 @@ VivyDocument::getElementName() const noexcept
 QJsonDocument
 VivyDocument::getProperties() const noexcept
 {
-    QJsonDocument ret;
-    QJsonObject object;
-    QJsonObject subDocObject;
-
-    subDocObject.insert("Audio sub-document",
-                        audioDocument ? audioDocument->getProperties().object() : QJsonValue());
-    subDocObject.insert("Video sub-document",
-                        videoDocument ? videoDocument->getProperties().object() : QJsonValue());
-    subDocObject.insert("ASS sub-document",
-                        assDocument ? assDocument->getProperties().object() : QJsonValue());
-
-    object.insert("Uuid", getUuid().toString());
-    object.insert("Name", getName());
-    object.insert("Sub documents", subDocObject);
-
-    ret.setObject(object);
-    return ret;
+    return JsonBuilder::createOrderedJsonDocument(
+        { { QStringLiteral("Uuid"), getUuid().toString() },
+          { QStringLiteral("Name"), getName() },
+          { QStringLiteral("Sub documents"),
+            JsonBuilder::createOrderedJsonObject({
+                JsonBuilder::getSubDocumentJson("Audio sub document", audioDocument),
+                JsonBuilder::getSubDocumentJson("Video sub document", videoDocument),
+                JsonBuilder::getSubDocumentJson("ASS sub document", assDocument),
+            }) } });
 }
diff --git a/src/Lib/Document/VivyDocument.hh b/src/Lib/Document/VivyDocument.hh
index dc36549f76177c90d5d9d19c8f2c7a441546d2c2..b1476d7ca29f272adcd8807a804da9f9e6e00501 100644
--- a/src/Lib/Document/VivyDocument.hh
+++ b/src/Lib/Document/VivyDocument.hh
@@ -10,10 +10,7 @@
 #include "../Uuid.hh"
 #include "CRTPSubDocument.hh"
 
-#include <memory>
-#include <QString>
-#include <QDir>
-#include <QJsonDocument>
+#define DCL_VIVY_SAVE_KEY(name) static const inline QString Key##name = QStringLiteral(#name);
 
 namespace Vivy
 {
@@ -21,23 +18,34 @@ class VivyDocument final : public AbstractDocument {
     Q_OBJECT
     VIVY_UNMOVABLE_OBJECT(VivyDocument)
 
-public:
-    enum Capabilities : quint64 {
-        AudioAble = (1 << 1),
-        VideoAble = (1 << 2),
-        AssAble   = (1 << 3),
-    };
+    DCL_VIVY_SAVE_KEY(Capabilities)
+    DCL_VIVY_SAVE_KEY(Name)
+    DCL_VIVY_SAVE_KEY(SubDocuments)
+    DCL_VIVY_SAVE_KEY(SubAudio)
+    DCL_VIVY_SAVE_KEY(SubAss)
+    DCL_VIVY_SAVE_KEY(SubVideo)
+    DCL_VIVY_SAVE_KEY(InternalAssSource)
+    DCL_VIVY_SAVE_KEY(DocumentVersion)
 
+    using LoadFunctionForVersion =
+        std::pair<const char *, void (*)(VivyDocument *const, const QJsonDocument &)>;
+
+public:
+    enum Capabilities : quint64 { AudioAble = (1 << 1), VideoAble = (1 << 2), AssAble = (1 << 3) };
     enum Options : quint64 {
-        NoOption           = 0,
-        UntouchedByDefault = (1 << 1),
+        NoOption               = 0,
+        UntouchedByDefault     = (1 << 1),
+        MemoryDocumentCreation = (1 << 2)
     };
 
-    static inline const QString filePrefix{ "vivy" };
-    static inline constexpr Utils::DocumentType type = Utils::DocumentType::Vivy;
+    static QString runtimeVersion;
+
+    static inline const QString fileSuffix               = QStringLiteral("vivy");
+    static inline constexpr Utils::DocumentType type     = Utils::DocumentType::Vivy;
+    static inline constexpr quint64 possibleCapabilities = AudioAble | VideoAble | AssAble;
 
 private:
-    /* The document name */
+    /* The document name, the filepath is {location}/{name}.{suffix} */
     QString documentName;
     QDir documentLocation{ QDir::current() };
 
@@ -46,19 +54,31 @@ private:
     quint64 documentOptions{ 0 };
 
     /* Links to other files, they're not embeded inside the vivy file */
-    std::shared_ptr<AudioSubDocument> audioDocument{};
-    std::shared_ptr<VideoSubDocument> videoDocument{};
-    std::shared_ptr<AssSubDocument> assDocument{};
+    std::shared_ptr<AudioSubDocument> audioDocument{ nullptr };
+    std::shared_ptr<VideoSubDocument> videoDocument{ nullptr };
+    std::shared_ptr<AssSubDocument> assDocument{ nullptr };
 
-    static bool detectDocumentType(const QFileInfo &file, Capabilities *) noexcept;
+    static bool detectDocumentType(const QFileInfo &file, Capabilities *const) noexcept;
 
     void addDocumentType(Capabilities) noexcept;
+    void saveMemoryFile(const QFileInfo &);
+    QFileInfo computeFileInfo(const QString &) const noexcept;
+
+    QJsonDocument getSaveJsonDocumentFile() const;
+    void loadSaveJsonDocumentFile(const QJsonDocument &);
+
+    static void loadSaveJsonDocumentFile_ALPHA(VivyDocument *const, const QJsonDocument &);
+
+    static constexpr inline auto loadFunctions = { LoadFunctionForVersion{
+        "alpha", loadSaveJsonDocumentFile_ALPHA } };
 
 public:
     // Create an empty document
     explicit VivyDocument(const QString &name, Options opt = NoOption);
 
-    bool rename(const QString &) noexcept override;
+    void copy(const QString &) override;
+    void rename(const QString &) override;
+    void save() override;
     bool loadSubDocument(const QString &) noexcept;
     bool loadSubDocument(const QString &, Capabilities) noexcept;
 
diff --git a/src/Lib/Document/VivyDocumentStore.cc b/src/Lib/Document/VivyDocumentStore.cc
index 5e2ca6fb98915c88d3c89d10dd0eeee6c57649f0..909d9e270367c90526b04c3c5c5f52d75d0fd93a 100644
--- a/src/Lib/Document/VivyDocumentStore.cc
+++ b/src/Lib/Document/VivyDocumentStore.cc
@@ -1,13 +1,14 @@
 #include "VivyDocumentStore.hh"
 #include "VivyDocument.hh"
 
-#include <stdexcept>
-
 using namespace Vivy;
 
 std::shared_ptr<VivyDocument>
 VivyDocumentStore::newDocument(VivyDocument::Options opt)
 {
+    // Add the memory flag
+    opt = static_cast<VivyDocument::Options>(opt | VivyDocument::MemoryDocumentCreation);
+
     const QString newDocName = newDocumentBaseName + QString::number(newDocumentNumber++);
     auto ret                 = std::make_shared<VivyDocument>(newDocName, opt);
 
diff --git a/src/Lib/HostOsInfo.cc b/src/Lib/HostOsInfo.cc
index 4951b7c5ef605b150267f43a56c8e274b6463e06..48f5bc6d82b88abad196be8db98ad895555cd467 100644
--- a/src/Lib/HostOsInfo.cc
+++ b/src/Lib/HostOsInfo.cc
@@ -1,7 +1,5 @@
 #include "HostOsInfo.hh"
 
-#include <QCoreApplication>
-
 #if !defined(QT_NO_OPENGL) && defined(QT_GUI_LIB)
 #include <QOpenGLContext>
 #endif
diff --git a/src/Lib/HostOsInfo.hh b/src/Lib/HostOsInfo.hh
index 090135fcd2516c2c5c9f4e7e42d44b31dbf27daf..97b2889621b1348bfdea6b5c88afd68db1090417 100644
--- a/src/Lib/HostOsInfo.hh
+++ b/src/Lib/HostOsInfo.hh
@@ -1,7 +1,6 @@
 #pragma once
 
 #include "Utils.hh"
-#include <QString>
 
 #ifdef Q_OS_WIN
 #define VIVY_HOST_EXE_SUFFIX VIVY_WIN_EXE_SUFFIX
diff --git a/src/Lib/JsonBuilder.cc b/src/Lib/JsonBuilder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..105445ea825b94a0a6abac131139ea2621f38ffa
--- /dev/null
+++ b/src/Lib/JsonBuilder.cc
@@ -0,0 +1,25 @@
+#include "JsonBuilder.hh"
+
+using namespace Vivy;
+
+QJsonArray
+JsonBuilder::createOrderedJsonObject(
+    std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept
+{
+    QJsonArray ret;
+    int index = 0;
+
+    for (const QPair<QString, QJsonValue> &elem : elements) {
+        ret.insert(index, QJsonObject({ elem }));
+        ++index;
+    }
+
+    return ret;
+}
+
+QJsonDocument
+JsonBuilder::createOrderedJsonDocument(
+    std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept
+{
+    return QJsonDocument(createOrderedJsonObject(elements));
+}
diff --git a/src/Lib/JsonBuilder.hh b/src/Lib/JsonBuilder.hh
new file mode 100644
index 0000000000000000000000000000000000000000..4534b46381f19e29a24b084c3771c32b46f13adf
--- /dev/null
+++ b/src/Lib/JsonBuilder.hh
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "Utils.hh"
+
+namespace Vivy
+{
+// Struct used to englobe all json build facilities. This is a class used as a
+// private-able namespace that can't be 'using' and thus create unreadable
+// code. You can't build this thing, everything is static inside.
+struct JsonBuilder final {
+    VIVY_UNMOVABLE_OBJECT(JsonBuilder)
+
+    static QJsonArray
+    createOrderedJsonObject(std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept;
+
+    static QJsonDocument
+    createOrderedJsonDocument(std::initializer_list<QPair<QString, QJsonValue>> elements) noexcept;
+
+    template <PropertyConstViewable T> static QPair<QString, QJsonValue>
+    getSubDocumentJson(const QString &name, std::shared_ptr<T> subDocPtr) noexcept
+    {
+        if (subDocPtr == nullptr)
+            return QPair{ name, QJsonValue() };
+        else
+            return QPair{ name, subDocPtr->getProperties().object() };
+    }
+
+private:
+    JsonBuilder() noexcept {}
+};
+}
diff --git a/src/Lib/Script/CRTPLuaScriptObject.hh b/src/Lib/Script/CRTPLuaScriptObject.hh
index 8e9920da1f8c51f0a158168bd9ebcda2c73d5e7c..3a5a51e7477de51cd507d6afe569d9f0e9f172b9 100644
--- a/src/Lib/Script/CRTPLuaScriptObject.hh
+++ b/src/Lib/Script/CRTPLuaScriptObject.hh
@@ -4,8 +4,6 @@
 #include "ScriptOption.hh"
 
 #include "lua.hpp"
-#include <vector>
-#include <cstdio>
 
 namespace Vivy::Script
 {
@@ -48,12 +46,12 @@ getJobIteratorTypeFromString(const std::string_view it) noexcept
 // CRTP to expose objects to Lua
 template <class Object> class CRTPLuaScriptObject {
 protected:
-    static void __attribute__((__noreturn__))
-    luaGlobalError(lua_State *const L, const std::string &str) noexcept
+    [[noreturn]] static void luaGlobalError(lua_State *const L, const std::string &str) noexcept
     {
         const auto *const context = LuaContext::getContext(L);
         lua_pushfstring(L, "%s:0:%s", context->getCurrentLuaFile().c_str(), str.c_str());
         lua_error(L);
+        exit(EXIT_FAILURE); // lua_error should not return anything
     }
 
     static bool PushFunctionFromRegistry(lua_State *const L, const int key) noexcept
@@ -72,11 +70,7 @@ protected:
     {
         luaL_checktype(L, tblIndex, LUA_TTABLE);
         const std::size_t count = lua_rawlen(L, tblIndex);
-        if (count >= std::numeric_limits<int>::max())
-            luaL_error(L, "To many items (%lu) in table to iterate over!", count);
-
-        const int countInt = static_cast<const int>(count);
-        for (int i = 1; i <= countInt; ++i) {
+        for (int i = 1; std::cmp_less(i, count); ++i) {
             luaL_checktype(L, tblIndex, LUA_TTABLE);
             lua_pushinteger(L, i);     // i -> top stack
             lua_gettable(L, tblIndex); // T[i] -> top stack and pop i
diff --git a/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc
index b2c3127aa97ce23e229ac651ca7b6c7ae7c6a5fe..981491023738cdd7b2b0a3f03ce238b9497dd2ad 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc
@@ -1,7 +1,4 @@
 #include "../CRTPLuaScriptObject.hh"
-#include "lua.hpp"
-#include <QFileInfo>
-#include <QString>
 
 using namespace Vivy::Script;
 
diff --git a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
index 82afe1daa0b10b2824dbd4b9b1fbda7588f9f371..13fd29aa064784ebb97fd2eb404bb7fb40737d1d 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
@@ -1,7 +1,4 @@
 #include "../CRTPLuaScriptObject.hh"
-#include "lua.hpp"
-#include <QFileInfo>
-#include <QString>
 
 using namespace Vivy::Script;
 
diff --git a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
index 39c676d96f83d05072484122761ea0b91dc34d59..f55a170b30fb15fab2b424ddfd0da5f94394673c 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
@@ -1,7 +1,4 @@
 #include "../CRTPLuaScriptObject.hh"
-#include "lua.hpp"
-#include <QFileInfo>
-#include <QString>
 
 using namespace Vivy::Script;
 
diff --git a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
index b904ecae569de30341dc05f14799fe4d08400ecc..5d535ea67c07b15911b7efff0225ea5dca6a3bc9 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
@@ -1,8 +1,4 @@
 #include "../CRTPLuaScriptObject.hh"
-#include "lua.hpp"
-#include <QFileInfo>
-#include <QString>
-#include <cstring>
 
 using namespace Vivy::Script;
 
diff --git a/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc
index 7030865fadffd8f6d7c8a896f6f31d937ce05f47..3efa0998034804d90a5bd21946f7788ce7b2aaac 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc
@@ -1,7 +1,4 @@
 #include "../CRTPLuaScriptObject.hh"
-#include "lua.hpp"
-#include <QFileInfo>
-#include <QString>
 
 using namespace Vivy::Script;
 
diff --git a/src/Lib/Script/LuaContext.cc b/src/Lib/Script/LuaContext.cc
index a7c26e659ec30d9e216967d501df263b5c84c285..6d5f32e54fa19cc88720710353817d6813c7e6ea 100644
--- a/src/Lib/Script/LuaContext.cc
+++ b/src/Lib/Script/LuaContext.cc
@@ -1,16 +1,11 @@
 #include "CRTPLuaScriptObject.hh"
 #include "ScriptDocument.hh"
 #include "LuaContext.hh"
-#include "lua.hpp"
-#include <cstdio>
-#include <QtGlobal>
-#include <QString>
-#include <QFile>
 
 // LuaContext implementation
 using namespace Vivy::Script;
 
-LuaContext::LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept
+LuaContext::LuaContext(LoggerType &out, LoggerType &err) noexcept
     : L(luaL_newstate())
     , streamOut(out)
     , streamErr(err)
@@ -129,8 +124,7 @@ LuaContext::getLastLuaError() noexcept
         return QStringLiteral("No lua error detected...");
     }
 
-    const char *error = lua_tostring(L, -1);
-    QString ret       = QStringLiteral("%1").arg(error);
+    QString ret = QString::fromUtf8(lua_tostring(L, -1));
     lua_pop(L, 1);
     return ret;
 }
diff --git a/src/Lib/Script/LuaContext.hh b/src/Lib/Script/LuaContext.hh
index a852aac6ab4528084e026251bbc6906ccef2b541..afc55e82b59d60d58631d393aa54d16bb6b5e5fb 100644
--- a/src/Lib/Script/LuaContext.hh
+++ b/src/Lib/Script/LuaContext.hh
@@ -1,12 +1,8 @@
 #pragma once
 
-#include "ScriptStore.hh"
-#include <sstream>
-#include <vector>
-#include <map>
+#include "../Utils.hh"
 
 struct lua_State;
-class QString;
 
 namespace Vivy
 {
@@ -15,6 +11,7 @@ class ScriptDocument;
 
 namespace Vivy::Script
 {
+class ScriptStore;
 class ModuleDeclaration;
 class FunctionDeclaration;
 class JobDeclaration;
@@ -26,6 +23,10 @@ namespace Vivy::Script
 class LuaContext final {
     VIVY_UNMOVABLE_OBJECT(LuaContext)
 
+public:
+    using LoggerType = std::stringstream;
+
+private:
     using string_view = const std::string_view;
 
     lua_State *L{ nullptr };
@@ -40,15 +41,15 @@ class LuaContext final {
 
     static inline std::map<const lua_State *const, LuaContext *const> contextList = {};
 
-    ScriptStore::LoggerType &streamOut;
-    ScriptStore::LoggerType &streamErr;
+    LoggerType &streamOut;
+    LoggerType &streamErr;
 
 public:
     enum class Code { Success, Error };
     static constexpr inline Code Success = Code::Success;
     static constexpr inline Code Error   = Code::Error;
 
-    LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept;
+    LuaContext(LoggerType &out, LoggerType &err) noexcept;
     ~LuaContext() noexcept;
 
     Code loadDocument(std::shared_ptr<ScriptDocument>) noexcept;
diff --git a/src/Lib/Script/ScriptDocument.cc b/src/Lib/Script/ScriptDocument.cc
index 2f4483e324d5ea3d03a5e8037a7d30306a89409e..6294656caf585053e565d7926239742f00dd9926 100644
--- a/src/Lib/Script/ScriptDocument.cc
+++ b/src/Lib/Script/ScriptDocument.cc
@@ -1,6 +1,8 @@
 #include "ScriptDocument.hh"
 
 #include <QFileInfo>
+#include <QFile>
+#include <QTextStream>
 
 using namespace Vivy;
 
@@ -12,10 +14,42 @@ ScriptDocument::ScriptDocument(const QString &path)
         throw std::runtime_error("File don't exists, don't create ScriptDocument");
 }
 
-bool
-ScriptDocument::rename(const QString &newName) noexcept
+void
+ScriptDocument::copy(const QString &newName)
 {
     /* Compute new paths */
     const QFileInfo newPath(newName);
-    return renameWith(newPath, [=, this]() noexcept { name = newName; });
+    copyWith(newPath, [=, this]() noexcept { name = newName; });
+    emit documentChanged();
+}
+
+void
+ScriptDocument::rename(const QString &newName)
+{
+    /* Compute new paths */
+    const QFileInfo newPath(newName);
+    renameWith(newPath, [=, this]() noexcept { name = newName; });
+    emit documentChanged();
+}
+
+void
+ScriptDocument::save()
+{
+    if (!textDocument)
+        throw std::runtime_error("no text document attached to the ScriptDocument!");
+
+    QFile file(name);
+    if (file.open(QIODevice::WriteOnly, QIODevice::Text)) {
+        throw std::runtime_error("Failed to open file in WO+Text mode: " + name.toStdString());
+    }
+
+    QTextStream writter(&file);
+    writter << textDocument->toPlainText();
+    file.close();
+}
+
+void
+ScriptDocument::attachTextDocument(const QTextDocument *const txt) noexcept
+{
+    textDocument = txt;
 }
diff --git a/src/Lib/Script/ScriptDocument.hh b/src/Lib/Script/ScriptDocument.hh
index 6bb03570dfd53d1c1ba6753cb29f0ab2b5012370..3799669c62b2b804c16ab65fe8f952b9cbb5eb35 100644
--- a/src/Lib/Script/ScriptDocument.hh
+++ b/src/Lib/Script/ScriptDocument.hh
@@ -7,12 +7,21 @@
 #include "../AbstractDocument.hh"
 #include "../Uuid.hh"
 
+class QTextDocument;
+
 namespace Vivy
 {
 class ScriptDocument final : public AbstractDocument {
 public:
     explicit ScriptDocument(const QString &name);
 
-    bool rename(const QString &) noexcept override;
+    void copy(const QString &) override;
+    void rename(const QString &) override;
+    void save() override;
+
+    void attachTextDocument(const QTextDocument *const) noexcept;
+
+private:
+    const QTextDocument *textDocument{ nullptr };
 };
 }
diff --git a/src/Lib/Script/ScriptOption.hh b/src/Lib/Script/ScriptOption.hh
index db4076b86fab2f02ca62d72bdecd86525acb8815..0cfef834096ac036983e9b6c9539de012d422ee8 100644
--- a/src/Lib/Script/ScriptOption.hh
+++ b/src/Lib/Script/ScriptOption.hh
@@ -1,8 +1,5 @@
 #pragma once
 
-#include <string>
-#include <string_view>
-
 namespace Vivy::Script
 {
 // The options
diff --git a/src/Lib/Script/ScriptStore.cc b/src/Lib/Script/ScriptStore.cc
index 61a970d59026592d3b7d10c4e8222f56ba7185f0..593809f7bb314fe5692c92be568b9d05f7c552da 100644
--- a/src/Lib/Script/ScriptStore.cc
+++ b/src/Lib/Script/ScriptStore.cc
@@ -1,6 +1,5 @@
 #include "ScriptStore.hh"
 #include "../Uuid.hh"
-#include <iostream>
 
 using namespace Vivy;
 using namespace std::string_literals;
@@ -63,7 +62,7 @@ ScriptStore::executeScript(Uuid id) noexcept
         QRegExp errorDetect(QStringLiteral("^([^\\:]*)\\:(\\d+)\\:(.*)\\n?$"));
         while (std::getline(streamErr, buffer, '\n')) {
             std::cout << '\t' << buffer << '\n';
-            if (errorDetect.indexIn(buffer.c_str()) != -1) {
+            if (errorDetect.indexIn(QString::fromUtf8(buffer.c_str())) != -1) {
                 std::string errorDesc = errorDetect.cap(3).toStdString();
                 return std::tuple{ QString(errorDetect.cap(2)).toInt(), Utils::trim(errorDesc) };
             }
diff --git a/src/Lib/Script/ScriptStore.hh b/src/Lib/Script/ScriptStore.hh
index dd4266a998dc370119156b000c1444d8dbc9ad09..21d0b98309535e90ec5a3b183b79af5783bc28dd 100644
--- a/src/Lib/Script/ScriptStore.hh
+++ b/src/Lib/Script/ScriptStore.hh
@@ -2,8 +2,7 @@
 
 #include "../CRTPStore.hh"
 #include "ScriptDocument.hh"
-#include <sstream>
-#include <optional>
+#include "LuaContext.hh"
 
 namespace Vivy::Script
 {
@@ -20,7 +19,7 @@ class ScriptStore final : public CRTPStore<ScriptStore, ScriptDocument> {
 
 public:
     using Item       = std::tuple<Uuid, QString>;
-    using LoggerType = std::stringstream;
+    using LoggerType = Script::LuaContext::LoggerType;
 
     explicit ScriptStore() noexcept;
 
diff --git a/src/Lib/Utils.cc b/src/Lib/Utils.cc
index cbce31685828d8155449a1de096616c39ef3899d..d023dd7bd8ae39c87b06f71242bab94c8e2373f3 100644
--- a/src/Lib/Utils.cc
+++ b/src/Lib/Utils.cc
@@ -1,8 +1,5 @@
 #include "Utils.hh"
 
-#include <QRegExp>
-#include <QFileInfo>
-
 using namespace Vivy;
 
 std::string &
@@ -162,6 +159,13 @@ Utils::Time::toString() const noexcept
            "." + QString::number(centisecond);
 }
 
+QString
+Utils::getBaseName(const QString &file) noexcept
+{
+    QFileInfo info(file);
+    return info.baseName();
+}
+
 const QString &
 Utils::getAudioFileSuffixFilter() noexcept
 {
@@ -262,7 +266,29 @@ Utils::getAnyDocumentFileSuffixFilter() noexcept
     isInitialized           = true;
     ret = anyFilter + separator + getVivyDocumentFileSuffixFilter() + separator +
           getScriptFileSuffixFilter() + separator + getAudioFileSuffixFilter() + separator +
-          getVideoFileSuffixFilter() + separator + getAssFileSuffixFilter() + separator;
+          getVideoFileSuffixFilter() + separator + getAssFileSuffixFilter();
+    return ret;
+}
+
+const QString &
+Utils::getAnyTopLevelDocumentFileSuffixFilter() noexcept
+{
+    static bool isInitialized = false;
+    static QString ret        = QStringLiteral("");
+    static QString anyFilter  = QStringLiteral("Any Top Level Vivy File (*.");
+
+    if (isInitialized)
+        return ret;
+
+    anyFilter.append(scriptFileSuffix.join(" *."));
+    anyFilter.append(QStringLiteral(" *."));
+    anyFilter.append(vivyFileSuffix.join(" *."));
+    anyFilter.append(QStringLiteral(")"));
+
+    const QString separator = QStringLiteral(";;");
+    isInitialized           = true;
+    ret = anyFilter + separator + getVivyDocumentFileSuffixFilter() + separator +
+          getScriptFileSuffixFilter();
     return ret;
 }
 
diff --git a/src/Lib/Utils.hh b/src/Lib/Utils.hh
index 3283a2c8707f61376a5e96e0baa9ac92dbb57ce6..eca3e66396085bfef587c7bca936e44898774dda 100644
--- a/src/Lib/Utils.hh
+++ b/src/Lib/Utils.hh
@@ -4,16 +4,6 @@
 #error "This is a C++ header"
 #endif
 
-#include <QString>
-#include <QFileInfo>
-#include <QStringList>
-#include <QDebug>
-#include <QMessageLogger>
-#include <QJsonDocument>
-#include <QtGlobal>
-#include <type_traits>
-#include <chrono>
-
 #include <qglobal.h>
 
 #define VIVY_PRAGMA(x) _Pragma(#x)
@@ -61,6 +51,9 @@ concept PropertyConstViewable = requires(T element)
     { element.getProperties() }  -> std::same_as<QJsonDocument>;
     // clang-format on
 };
+
+template <class T, class U>
+concept Derived = std::is_base_of<U, T>::value;
 }
 
 namespace Vivy::Utils
@@ -81,6 +74,7 @@ const QString &getAssFileSuffixFilter() noexcept;
 const QString &getScriptFileSuffixFilter() noexcept;
 const QString &getVivyDocumentFileSuffixFilter() noexcept;
 const QString &getAnyDocumentFileSuffixFilter() noexcept;
+const QString &getAnyTopLevelDocumentFileSuffixFilter() noexcept;
 
 static constexpr std::size_t pointerAlignement = alignof(void *);
 
@@ -167,6 +161,8 @@ bool decodeLineToBoolean(const QString &item, const QString &error);
 int decodeLineToInteger(const QString &item, const QString &error);
 float decodeLineToFloating(const QString &item, const QString &error);
 
+QString getBaseName(const QString &) noexcept;
+
 void writeAssertLocation(const char *msg);
 
 struct OsSpecificAspects final {
diff --git a/src/Lib/Uuid.hh b/src/Lib/Uuid.hh
index a45b43700e293f3d52bdcdb95b532887c8ed291d..068277deeac9f8b33673fa2243a0c5e30e9c2223 100644
--- a/src/Lib/Uuid.hh
+++ b/src/Lib/Uuid.hh
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <QUuid>
-
 namespace Vivy
 {
 class Uuid : public QUuid {
diff --git a/src/Lib/Video.cc b/src/Lib/Video.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e6b9a07022e0e77f352b5a90e664ea2111c9d882
--- /dev/null
+++ b/src/Lib/Video.cc
@@ -0,0 +1,57 @@
+#include "Video.hh"
+#include "Utils.hh"
+
+using namespace Vivy;
+
+VideoStream::VideoStream(AVCodec *streamCodec, AVFormatContext *formatArg, AVStream *streamArg,
+                         int index)
+    : Super(streamCodec, formatArg, streamArg, index)
+{
+}
+
+QJsonObject
+VideoStream::getProperties() const noexcept
+{
+    return Super::getProperties();
+}
+
+VideoContext::VideoContext(const QString &path)
+    : Super(path)
+{
+}
+
+QJsonDocument
+VideoContext::getProperties() const noexcept
+{
+    return Super::getProperties();
+}
+
+QString
+VideoContext::getElementName() const noexcept
+{
+    return "Video" + Super::getElementName();
+}
+
+int
+VideoStream::getWidth() const noexcept
+{
+    return codecContext->width;
+}
+
+int
+VideoStream::getHeight() const noexcept
+{
+    return codecContext->height;
+}
+
+qint64
+VideoStream::getDuration() const noexcept
+{
+    return dataFormat->duration;
+}
+
+double
+VideoStream::getFramesPerSecond() const noexcept
+{
+    return av_q2d(stream->r_frame_rate);
+}
diff --git a/src/Lib/Video.hh b/src/Lib/Video.hh
new file mode 100644
index 0000000000000000000000000000000000000000..400feb30480d89cae9741a98c5cd9b2d7edabe2d
--- /dev/null
+++ b/src/Lib/Video.hh
@@ -0,0 +1,38 @@
+#pragma once
+
+#ifndef __cplusplus
+#error "This is a C++ header"
+#endif
+
+#include "AbstractMediaContext.hh"
+
+namespace Vivy
+{
+// Hold all the data for a video stream. Should only be owned by the parent
+// VideoContext instance.
+class VideoStream final : public AbstractMediaStream<AVMEDIA_TYPE_VIDEO> {
+    VIVY_UNMOVABLE_OBJECT(VideoStream)
+
+public:
+    VideoStream(AVCodec *, AVFormatContext *, AVStream *, int index);
+    ~VideoStream() noexcept override = default;
+
+    int getWidth() const noexcept;
+    int getHeight() const noexcept;
+    qint64 getDuration() const noexcept;
+    double getFramesPerSecond() const noexcept;
+
+    QJsonObject getProperties() const noexcept override;
+};
+
+// Like an audio context, but for videos.
+class VideoContext final : public AbstractMediaContext<VideoStream> {
+    VIVY_UNMOVABLE_OBJECT(VideoContext)
+
+public:
+    VideoContext(const QString &path);
+
+    QString getElementName() const noexcept override;
+    QJsonDocument getProperties() const noexcept override;
+};
+}
diff --git a/src/UI/AboutWindow.cc b/src/UI/AboutWindow.cc
index 4bb7e87b24f480d6f45cb70eb4e6c33ac08cd0a9..420dae7bfbd3ef45a4273245400656a912d64962 100644
--- a/src/UI/AboutWindow.cc
+++ b/src/UI/AboutWindow.cc
@@ -1,14 +1,5 @@
 #include "AboutWindow.hh"
-
-#include <QCloseEvent>
-#include <QLabel>
-#include <QPushButton>
-#include <QVBoxLayout>
-#include <QHBoxLayout>
-#include <QEvent>
-#include <QMouseEvent>
-#include <QApplication>
-#include <QTextEdit>
+#include "../VivyApplication.hh"
 
 using namespace Vivy;
 
@@ -46,13 +37,16 @@ static const char *libContent =
     "  </ul>"
     "</body>";
 
+AboutWindow::SimpleLabel::~SimpleLabel() noexcept {}
+AboutWindow::LicenceLabel::~LicenceLabel() noexcept {}
+
 AboutWindow::SimpleLabel::SimpleLabel(QWidget *parent, const char *text)
     : QLabel(parent)
 {
     setTextFormat(Qt::RichText);
     setTextInteractionFlags(Qt::NoTextInteraction | Qt::LinksAccessibleByMouse |
                             Qt::LinksAccessibleByKeyboard);
-    setText(QString(text));
+    setText(QString::fromUtf8(text));
     setAlignment(Qt::AlignJustify | Qt::AlignTop);
     setWordWrap(true);
 }
@@ -78,8 +72,8 @@ AboutWindow::LicenceLabel::LicenceLabel(QWidget *parent, const QString &url,
 
     switch (format) {
     case Qt::PlainText:
-    case Qt::RichText: setText(QString(content.readAll())); break;
-    case Qt::MarkdownText: setMarkdown(content.readAll()); break;
+    case Qt::RichText: setText(QString::fromUtf8(content.readAll())); break;
+    case Qt::MarkdownText: setMarkdown(QString::fromUtf8(content.readAll())); break;
     case Qt::AutoText: qCritical() << "Invalid text format for LicenceLabel" << format;
     }
 }
diff --git a/src/UI/AboutWindow.hh b/src/UI/AboutWindow.hh
index acbd437c0a2b3513f3f1bdcdf356771d8dc8c705..9bff80cfce81f53374d4e7658698224387bb150d 100644
--- a/src/UI/AboutWindow.hh
+++ b/src/UI/AboutWindow.hh
@@ -4,11 +4,6 @@
 #error "This is a C++ header"
 #endif
 
-#include <QMainWindow>
-#include <QTabWidget>
-#include <QLabel>
-#include <QTextEdit>
-
 namespace Vivy
 {
 class AboutWindow final : public QMainWindow {
@@ -20,17 +15,19 @@ class AboutWindow final : public QMainWindow {
     class SimpleLabel final : public QLabel {
     public:
         explicit SimpleLabel(QWidget *parent, const char *text);
+        ~SimpleLabel() noexcept override;
     };
 
     // Simple QLabel for licences
     class LicenceLabel final : public QTextEdit {
     public:
         explicit LicenceLabel(QWidget *parent, const QString &url, const Qt::TextFormat format);
+        ~LicenceLabel() noexcept override;
     };
 
 public:
     explicit AboutWindow(QWidget *parent) noexcept;
-    ~AboutWindow() noexcept = default;
+    ~AboutWindow() noexcept override = default;
 
 signals:
     void closed();
diff --git a/src/UI/AbstractDocumentView.cc b/src/UI/AbstractDocumentView.cc
index 51ece8ec6952c8fcd8fa0fcdef8cf5109c7af82c..d56a35bd2e8523d1a0511f5843403cbfa9ae9389 100644
--- a/src/UI/AbstractDocumentView.cc
+++ b/src/UI/AbstractDocumentView.cc
@@ -1,12 +1,5 @@
 #include "AbstractDocumentView.hh"
 
-#include <QtGlobal>
-#include <QDockWidget>
-#include <QLayoutItem>
-#include <QLayout>
-#include <QWidget>
-#include <functional>
-
 using namespace Vivy;
 
 AbstractDocumentView::AbstractDocumentView(AbstractDocumentView::Type type,
@@ -22,6 +15,14 @@ AbstractDocumentView::AbstractDocumentView(AbstractDocumentView::Type type,
     setTabPosition(Qt::RightDockWidgetArea, QTabWidget::East);
 }
 
+void
+AbstractDocumentView::allowToCloseAllDocks() noexcept
+{
+    // Allow to close all dock widgets
+    for (UnclosableDockWidget *const dock : dockWidgets)
+        dock->allowClose();
+}
+
 // A utility function to delete all the child widgets
 void
 AbstractDocumentView::deleteAllContent() noexcept
@@ -52,7 +53,7 @@ AbstractDocumentView::getType() const noexcept
 }
 
 void
-AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept
+AbstractDocumentView::delDockWidget(UnclosableDockWidget **dock) noexcept
 {
     // Remove the toggle view action
     QAction *act = (*dock)->toggleViewAction();
@@ -60,6 +61,7 @@ AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept
         viewsActions.removeAll(act);
 
     // Remove the widget
+    (*dock)->allowClose();
     removeDockWidget(*dock);
     delete *dock;
     *dock = nullptr;
@@ -68,7 +70,7 @@ AbstractDocumentView::delDockWidget(QDockWidget **dock) noexcept
     emit viewActionsChanged();
 }
 void
-AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dock,
+AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, UnclosableDockWidget *dock,
                                     Qt::Orientation orientation) noexcept
 {
     // dock->setTitleBarWidget(new QWidget(dock)); // <- to disable the header bar
@@ -76,6 +78,8 @@ AbstractDocumentView::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dock,
     QAction *act = dock->toggleViewAction();
     if (!viewsActions.contains(act))
         viewsActions.prepend(act);
+    if (!dockWidgets.contains(dock))
+        dockWidgets.append(dock);
     emit viewActionsChanged();
 }
 
diff --git a/src/UI/AbstractDocumentView.hh b/src/UI/AbstractDocumentView.hh
index 7b1f4c807333e799fc4aa795b90fd548fc6f4eb3..7745665b35e2bc5f669c86c4223afc0b839a0060 100644
--- a/src/UI/AbstractDocumentView.hh
+++ b/src/UI/AbstractDocumentView.hh
@@ -6,9 +6,7 @@
 
 #include "../Lib/Utils.hh"
 #include "../Lib/AbstractDocument.hh"
-#include <QMainWindow>
-#include <QDockWidget>
-#include <QAction>
+#include "UnclosableDockWidget.hh"
 
 namespace Vivy
 {
@@ -43,15 +41,18 @@ public:
 
 signals:
     void viewActionsChanged();
+    void documentPropertyChanged();
 
 protected:
     void deleteAllContent() noexcept;
+    void allowToCloseAllDocks() noexcept;
 
-    void delDockWidget(QDockWidget **) noexcept;
-    void addDockWidget(Qt::DockWidgetArea, QDockWidget *, Qt::Orientation) noexcept;
+    void delDockWidget(UnclosableDockWidget **) noexcept;
+    void addDockWidget(Qt::DockWidgetArea, UnclosableDockWidget *, Qt::Orientation) noexcept;
     QList<QAction *> viewsActions{};
 
 private:
     const Type documentType;
+    QVector<UnclosableDockWidget *> dockWidgets{};
 };
 }
diff --git a/src/UI/DockWidgetTitleBar.cc b/src/UI/DockWidgetTitleBar.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2e1cdaa099d9246e785e46919a317dd51cbb6785
--- /dev/null
+++ b/src/UI/DockWidgetTitleBar.cc
@@ -0,0 +1,24 @@
+#include "DockWidgetTitleBar.hh"
+
+using namespace Vivy;
+
+DockWidgetTitleBar::DockWidgetTitleBar(QDockWidget *parent) noexcept
+    : QWidget(parent)
+    , attachedDock(parent)
+{
+    if (parent == nullptr)
+        qFatal("Can't pass a nullptr as a parent widget pointer");
+
+    auto *box = new QHBoxLayout(this);
+    box->addWidget(new QLabel(parent->windowTitle(), this));
+    qobject_cast<QHBoxLayout *>(layout())->setStretch(0, 1);
+}
+
+void
+DockWidgetTitleBar::addToDock(QDockWidget *const dock) noexcept
+{
+    DockWidgetTitleBar *const titleBar = new DockWidgetTitleBar(dock);
+    qDebug() << "Adding" << dock->windowTitle() << "to dock...";
+    Utils::setTransparentBackgroundForWidget(titleBar);
+    dock->setTitleBarWidget(titleBar);
+}
diff --git a/src/UI/DockWidgetTitleBar.hh b/src/UI/DockWidgetTitleBar.hh
new file mode 100644
index 0000000000000000000000000000000000000000..620c9a947e3537b566ee45a69be521cf63b97005
--- /dev/null
+++ b/src/UI/DockWidgetTitleBar.hh
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "Utils.hh"
+
+namespace Vivy
+{
+class DockWidgetTitleBar final : public QWidget {
+    Q_OBJECT
+    VIVY_UNMOVABLE_OBJECT(DockWidgetTitleBar)
+
+    QDockWidget *attachedDock{ nullptr };
+    explicit DockWidgetTitleBar(QDockWidget *parent) noexcept;
+
+public:
+    static void addToDock(QDockWidget *const) noexcept;
+};
+}
diff --git a/src/UI/DocumentViews/AssLinesModel.hh b/src/UI/DocumentViews/AssLinesModel.hh
index 2dfdbcd1c709a55ce9c02e902f628358109dc2ed..60cc550b4cbdcbbf56b921187f8a671d42bf844c 100644
--- a/src/UI/DocumentViews/AssLinesModel.hh
+++ b/src/UI/DocumentViews/AssLinesModel.hh
@@ -2,8 +2,6 @@
 
 #include "../../Lib/Utils.hh"
 #include "../../Lib/Ass/Ass.hh"
-#include <QAbstractItemModel>
-#include <QStringList>
 
 namespace Vivy
 {
@@ -21,9 +19,7 @@ private:
 
         enum class Field : int {
             Text,
-
-            // Last, the count
-            TotalFieldCount,
+            TotalFieldCount // Last, the count
         };
 
         bool getIsComment() const noexcept;
@@ -39,7 +35,7 @@ private:
 
 public:
     explicit AssLinesModel(const QVector<Ass::LinePtr> &) noexcept;
-    ~AssLinesModel() noexcept;
+    ~AssLinesModel() noexcept override;
 
     QVariant data(const QModelIndex &, int role) const noexcept override;
     bool setData(const QModelIndex &, const QVariant &v, int r = Qt::EditRole) noexcept override;
diff --git a/src/UI/DocumentViews/AssLinesView.cc b/src/UI/DocumentViews/AssLinesView.cc
index 9134238f1f64d578ba6636f883fda227b1504cc4..022b8cfdcd0010806296269aef26132bb3ce367b 100644
--- a/src/UI/DocumentViews/AssLinesView.cc
+++ b/src/UI/DocumentViews/AssLinesView.cc
@@ -1,8 +1,5 @@
 #include "AssLinesView.hh"
 #include "../../VivyApplication.hh"
-#include <string_view>
-#include <QPaintEvent>
-#include <QHeaderView>
 
 using namespace Vivy;
 
diff --git a/src/UI/DocumentViews/AssLinesView.hh b/src/UI/DocumentViews/AssLinesView.hh
index 54c58c660067f55270af7fa92d3ede42074823ae..635b320cd95f7fea2661f8492a3dff7899f4c47e 100644
--- a/src/UI/DocumentViews/AssLinesView.hh
+++ b/src/UI/DocumentViews/AssLinesView.hh
@@ -1,9 +1,6 @@
 #pragma once
 
 #include "../../Lib/Utils.hh"
-#include <QAbstractItemModel>
-#include <QTableView>
-#include <QStyledItemDelegate>
 
 namespace Vivy
 {
@@ -23,7 +20,7 @@ private:
 
     public:
         Delegate(AssLinesView *, QWidget *parent = nullptr) noexcept;
-        ~Delegate() noexcept = default;
+        ~Delegate() noexcept override = default;
 
         void initStyleOption(QStyleOptionViewItem *option,
                              const QModelIndex &index) const noexcept override;
@@ -38,7 +35,7 @@ private:
 
 public:
     AssLinesView(QAbstractItemModel *, QWidget *parent = nullptr) noexcept;
-    ~AssLinesView() noexcept = default;
+    ~AssLinesView() noexcept override = default;
 
     void paintEvent(QPaintEvent *) noexcept override;
 
diff --git a/src/UI/DocumentViews/AudioVisualizer.cc b/src/UI/DocumentViews/AudioVisualizer.cc
index b7813c425d8f0c39d88a0d10f96b5cece7dd2c62..a0215bda855b3e744f4cfe23ad6cec2b06f8ebaa 100644
--- a/src/UI/DocumentViews/AudioVisualizer.cc
+++ b/src/UI/DocumentViews/AudioVisualizer.cc
@@ -1,14 +1,6 @@
 #include "AudioVisualizer.hh"
 #include "../../Lib/Audio.hh"
 
-#include <algorithm>
-#include <QGraphicsPixmapItem>
-#include <QLabel>
-#include <QMessageBox>
-#include <QScrollArea>
-#include <QScrollBar>
-#include <QVBoxLayout>
-
 using namespace Vivy;
 
 #define MAXPIXVALUE 7 // Some magix AV magic stuff
diff --git a/src/UI/DocumentViews/AudioVisualizer.hh b/src/UI/DocumentViews/AudioVisualizer.hh
index 451d50800979bca9f137b3fd2dfcd3842715629b..47032201caaf6ef875e0555c8c46205542c44150 100644
--- a/src/UI/DocumentViews/AudioVisualizer.hh
+++ b/src/UI/DocumentViews/AudioVisualizer.hh
@@ -7,8 +7,6 @@
 
 #include "TimingView.hh"
 #include "../../Lib/Audio.hh"
-#include <QWidget>
-#include <QString>
 
 namespace Vivy
 {
@@ -33,7 +31,7 @@ private:
 
 public:
     explicit AudioVisualizer(AudioContext::StreamPtr, QWidget *parent = nullptr);
-    ~AudioVisualizer() noexcept = default;
+    ~AudioVisualizer() noexcept override = default;
 
 public slots:
     void printSpectrum(QImage) noexcept;
diff --git a/src/UI/DocumentViews/MpvContainer.cc b/src/UI/DocumentViews/MpvContainer.cc
index ef64b08f76860e1aed2df00318ea67590e7ebdea..425346ed36a1ef646bb5644b76034ccd307f773b 100644
--- a/src/UI/DocumentViews/MpvContainer.cc
+++ b/src/UI/DocumentViews/MpvContainer.cc
@@ -1,7 +1,5 @@
 #include "MpvContainer.hh"
 
-#include <mpv/client.h>
-
 using namespace Vivy;
 using namespace std::string_literals;
 
@@ -30,18 +28,19 @@ MpvContainer::MpvContainer(QWidget *parent)
     mpv_set_option_string(mpv, "sid", "no");
     mpv_set_option_string(mpv, "input-default-bindings", "no");
     mpv_set_option_string(mpv, "input-vo-keyboard", "no");
+    mpv_set_option_string(mpv, "no-ytdl", "yes");
     mpv_request_log_messages(mpv, "info");
     mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
     mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
     mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
 
-    connect(this, &MpvContainer::mpvEvent, this, &MpvContainer::onMpvEvent, Qt::QueuedConnection);
-    mpv_set_wakeup_callback(mpv, &MpvContainer::mpvEventWakeUpCB, this);
-
     if (int rc = mpv_initialize(mpv); rc < 0) {
         printMpvError(rc);
         throw std::runtime_error("Failed to initialize the mpv context");
     }
+
+    connect(this, &MpvContainer::mpvEvent, this, &MpvContainer::onMpvEvent, Qt::QueuedConnection);
+    mpv_set_wakeup_callback(mpv, &MpvContainer::mpvEventWakeUpCB, this);
 }
 
 void
@@ -77,10 +76,9 @@ MpvContainer::closeMpv() noexcept
 MpvContainer::~MpvContainer() noexcept { closeMpv(); }
 
 void
-MpvContainer::handleMpvEvent(mpv_event *event) noexcept
+MpvContainer::handleMpvEvent(const mpv_event *const event) noexcept
 {
     // Declare here variables that can be used in the switch-case statements
-    qint64 w, h;
     double time;
     QString msgText;
     union {
@@ -88,25 +86,17 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
         mpv_event_property *prop;
     };
 
-    auto checkProp = [](mpv_event_property *prop_, const std::string &str,
-                        int format) noexcept -> bool {
+    auto checkProp = [](const mpv_event_property *const prop_, const std::string_view str,
+                        const int format) noexcept -> bool {
         return (prop_->name == str) && (prop_->format == format);
     };
 
     switch (event->event_id) {
     case MPV_EVENT_SHUTDOWN: closeMpv(); break;
 
-    case MPV_EVENT_VIDEO_RECONFIG:
-        // TODO: Those are sync calls, prefer async calls
-        if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
-            mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 && (w > 0 && h > 0)) {
-            qDebug() << "Reconfigure video to" << w << "x" << h;
-        }
-        break;
-
     case MPV_EVENT_LOG_MESSAGE:
         msg     = reinterpret_cast<mpv_event_log_message *>(event->data);
-        msgText = QString(msg->text);
+        msgText = QString::fromUtf8(msg->text);
         msgText.replace('\n', "");
         qDebug().nospace().noquote()
             << "MPV - MSG [" << msg->prefix << "] " << msg->level << ": " << msgText;
@@ -114,17 +104,17 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
 
     case MPV_EVENT_PROPERTY_CHANGE:
         prop = reinterpret_cast<mpv_event_property *>(event->data);
-        if (checkProp(prop, "time-pos"s, MPV_FORMAT_DOUBLE) && mpvTimeCallback) {
+        if (checkProp(prop, "time-pos", MPV_FORMAT_DOUBLE) && mpvTimeCallback) {
             time = *reinterpret_cast<double *>(prop->data);
             mpvTimeCallback(time);
         }
 
-        else if (checkProp(prop, "duration"s, MPV_FORMAT_DOUBLE) && mpvDurationCallback) {
+        else if (checkProp(prop, "duration", MPV_FORMAT_DOUBLE) && mpvDurationCallback) {
             time = *reinterpret_cast<double *>(prop->data);
             mpvDurationCallback(time);
         }
 
-        else if (checkProp(prop, "pause"s, MPV_FORMAT_FLAG)) {
+        else if (checkProp(prop, "pause", MPV_FORMAT_FLAG)) {
             isPlaybackPaused = *reinterpret_cast<bool *>(prop->data);
             emit mpvPlaybackToggled(!isPlaybackPaused);
             qDebug() << "MPV -> set to" << (isPlaybackPaused ? "pause" : "play");
@@ -145,7 +135,6 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
         break;
 
     case MPV_EVENT_START_FILE: qDebug() << "MPV: Begin of file"; break;
-
     case MPV_EVENT_END_FILE: qDebug() << "MPV: Reached end of file!"; break;
 
     case MPV_EVENT_COMMAND_REPLY:
@@ -154,6 +143,7 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept
         break;
 
     // Explicitly ignored
+    case MPV_EVENT_VIDEO_RECONFIG:
     case MPV_EVENT_NONE:
     case MPV_EVENT_GET_PROPERTY_REPLY:
     case MPV_EVENT_SET_PROPERTY_REPLY:
@@ -179,7 +169,7 @@ MpvContainer::getAssSid() const noexcept
 {
     bool conversionOk  = false;
     const char *result = mpv_get_property_string(mpv, "sid");
-    const int ret      = QString(result).toInt(&conversionOk);
+    const int ret      = QString::fromUtf8(result).toInt(&conversionOk);
     return (result == nullptr || !conversionOk) ? -1 : ret;
 }
 
@@ -220,7 +210,7 @@ void
 MpvContainer::onMpvEvent() noexcept
 {
     while (mpv) {
-        mpv_event *event = mpv_wait_event(mpv, 0);
+        const mpv_event *const event = mpv_wait_event(mpv, 0);
         if (event == nullptr || event->event_id == MPV_EVENT_NONE)
             break;
         handleMpvEvent(event);
@@ -234,7 +224,8 @@ MpvContainer::onMpvEvent() noexcept
 void
 MpvContainer::loadFile(const QString &filename) noexcept
 {
-    if (filename.isEmpty())
+    qDebug() << "Loading file" << filename;
+    if (filename.size() == 0)
         return;
 
     const QByteArray cFileName = filename.toUtf8();
diff --git a/src/UI/DocumentViews/MpvContainer.hh b/src/UI/DocumentViews/MpvContainer.hh
index 8296cac69656bfd2b8d14b3eb2bac70722744884..760d5e4a32c75fc5ef6ab4d04abe4033bd4cb184 100644
--- a/src/UI/DocumentViews/MpvContainer.hh
+++ b/src/UI/DocumentViews/MpvContainer.hh
@@ -6,9 +6,6 @@
 
 #include "../../Lib/Utils.hh"
 
-#include <functional>
-#include <initializer_list>
-
 extern "C" {
 struct mpv_handle;
 struct mpv_event;
@@ -27,12 +24,12 @@ class MpvContainer final : public QWidget {
         ReloadAss,
         UnloadAss,
         SeekTime,
-        TogglePlayback,
+        TogglePlayback
     };
 
 private:
     bool isPlaybackPaused{ true };
-    mpv_handle *mpv{ nullptr };
+    mpv_handle *volatile mpv{ nullptr };
     qint64 sid{ -1 };
     std::function<void(double)> mpvTimeCallback{ nullptr };
     std::function<void(double)> mpvDurationCallback{ nullptr };
@@ -53,7 +50,7 @@ public:
     void registerMpvDurationCallback(std::function<void(double)>) noexcept;
 
 private:
-    void handleMpvEvent(mpv_event *) noexcept;
+    void handleMpvEvent(const mpv_event *const) noexcept;
     void closeMpv() noexcept;
     void printMpvError(int) const noexcept;
     void asyncCommand(const AsyncCmdType, std::initializer_list<const char *>) noexcept;
diff --git a/src/UI/DocumentViews/MpvControls.cc b/src/UI/DocumentViews/MpvControls.cc
index 696ea6bb716952f0202f79e9294a9415dbc401f3..931239ac66d8dd4ed04255ac40f46ca73699c50c 100644
--- a/src/UI/DocumentViews/MpvControls.cc
+++ b/src/UI/DocumentViews/MpvControls.cc
@@ -1,10 +1,6 @@
 #include "MpvControls.hh"
 #include "MpvContainer.hh"
 
-#include <QSlider>
-#include <QHBoxLayout>
-#include <QPushButton>
-
 using namespace Vivy;
 
 MpvControls::MpvControls(MpvContainer *passedContainer, QWidget *parent) noexcept
diff --git a/src/UI/DocumentViews/MpvControls.hh b/src/UI/DocumentViews/MpvControls.hh
index db00d5900753f8411c2c2f2896aab0d8458708dd..51b1e127450c31a2fd291c46db08b5663aa05f38 100644
--- a/src/UI/DocumentViews/MpvControls.hh
+++ b/src/UI/DocumentViews/MpvControls.hh
@@ -6,7 +6,6 @@
 
 #include "../../Lib/Utils.hh"
 #include "../../VivyApplication.hh"
-#include <QIcon>
 
 namespace Vivy
 {
diff --git a/src/UI/DocumentViews/TimingBar.cc b/src/UI/DocumentViews/TimingBar.cc
index 9353c174b4b778aff7fcc129f627b6863d69891a..ac5fed95ed31e9700ce3fbe69eeaded26d5c975f 100644
--- a/src/UI/DocumentViews/TimingBar.cc
+++ b/src/UI/DocumentViews/TimingBar.cc
@@ -1,15 +1,5 @@
 #include "TimingBar.hh"
 
-#include <QApplication>
-#include <QGraphicsSceneMouseEvent>
-#include <QLabel>
-#include <QMessageBox>
-#include <QMouseEvent>
-#include <QPen>
-#include <QScrollArea>
-#include <QScrollBar>
-#include <QVBoxLayout>
-
 using namespace Vivy;
 
 TimingBar::TimingBar(QLine line, QColor color) noexcept
diff --git a/src/UI/DocumentViews/TimingBar.hh b/src/UI/DocumentViews/TimingBar.hh
index 6edf64eede549c9284a1a332d05aaca6b8bfdc6a..583ee164782b1f1909a2c8f23a6dde6454d625cd 100644
--- a/src/UI/DocumentViews/TimingBar.hh
+++ b/src/UI/DocumentViews/TimingBar.hh
@@ -1,19 +1,15 @@
-#ifndef VIVY_TIMING_BAR_H
-#define VIVY_TIMING_BAR_H
+#pragma once
 
 #ifndef __cplusplus
 #error "This is a C++ header"
 #endif
 
-#include <QGraphicsItem>
-#include <QGraphicsLineItem>
-
 namespace Vivy
 {
 class TimingBar final : public QGraphicsLineItem {
 public:
     explicit TimingBar(QLine, QColor) noexcept;
-    ~TimingBar() noexcept = default;
+    ~TimingBar() noexcept override = default;
 
 private:
     static inline constexpr int bar_demi_width = 2;
@@ -27,7 +23,4 @@ protected:
     void hoverLeaveEvent (QGraphicsSceneHoverEvent*) override;
     */
 };
-
 }
-
-#endif // VIVY_TIMING_BAR_H
diff --git a/src/UI/DocumentViews/TimingScene.hh b/src/UI/DocumentViews/TimingScene.hh
index 02f20e90904772202d507760dbb6a119ab40ff00..cb81a141d1e83c61d7f24fab55e140e956c2eacb 100644
--- a/src/UI/DocumentViews/TimingScene.hh
+++ b/src/UI/DocumentViews/TimingScene.hh
@@ -1,17 +1,9 @@
-#ifndef VIVY_TIMING_SCENE_H
-#define VIVY_TIMING_SCENE_H
+#pragma once
 
 #include "../../Lib/Utils.hh"
 #include "../../Lib/Ass/Ass.hh"
 #include "TimingBar.hh"
 
-#include <QWidget>
-#include <QColor>
-#include <QVector>
-#include <QGraphicsView>
-#include <QGraphicsScene>
-#include <memory>
-
 namespace Vivy
 {
 class TimingScene final : public QGraphicsScene {
@@ -52,5 +44,3 @@ public slots:
 
 //Only if combinaisons of mode is allowed, it shouldn't be here
 //Q_DECLARE_OPERATORS_FOR_FLAGS(TimingScene::TimingModes)
-
-#endif // VIVY_TIMING_SCENE_H
diff --git a/src/UI/DocumentViews/TimingView.cc b/src/UI/DocumentViews/TimingView.cc
index a140ac56ef5e24214a63269cbeba76668e532d12..c5391b1611aff4110d1a73336206bf10c4f3a680 100644
--- a/src/UI/DocumentViews/TimingView.cc
+++ b/src/UI/DocumentViews/TimingView.cc
@@ -1,18 +1,5 @@
 #include "TimingView.hh"
 
-#include <QAbstractScrollArea>
-#include <QGraphicsLineItem>
-#include <QGraphicsPixmapItem>
-#include <QGraphicsView>
-#include <QLabel>
-#include <QMessageBox>
-#include <QMouseEvent>
-#include <QPainter>
-#include <QScrollArea>
-#include <QScrollBar>
-#include <QVBoxLayout>
-#include <QtGlobal>
-
 using namespace Vivy;
 
 TimingView::TimingView(QImage img, quint64 soundLength, QWidget *parent) noexcept
diff --git a/src/UI/DocumentViews/TimingView.hh b/src/UI/DocumentViews/TimingView.hh
index 5b0fc0e03971d36de6aa48a578997d330a8d563e..0d68b4fa9f09d3e3fe9a42b8c5364894153ad25b 100644
--- a/src/UI/DocumentViews/TimingView.hh
+++ b/src/UI/DocumentViews/TimingView.hh
@@ -1,5 +1,4 @@
-#ifndef VIVY_TIMING_VIEW_H
-#define VIVY_TIMING_VIEW_H
+#pragma once
 
 #ifndef __cplusplus
 #error "This is a C++ header"
@@ -9,11 +8,6 @@
 #include "TimingBar.hh"
 #include "TimingScene.hh"
 
-#include <QWidget>
-#include <QColor>
-#include <QVector>
-#include <QGraphicsView>
-
 namespace Vivy
 {
 class TimingView final : public QGraphicsView {
@@ -26,7 +20,7 @@ public:
     static inline constexpr QColor endColour   = QColor(0, 127, 0);
 
     explicit TimingView(QImage, quint64, QWidget * = nullptr) noexcept;
-    ~TimingView() noexcept = default;
+    ~TimingView() noexcept override = default;
 
     void wheelEvent(QWheelEvent *) noexcept override;
 
@@ -37,7 +31,4 @@ public slots:
     void mousePressEvent(QMouseEvent *event) noexcept override;
     void moveScrollBarToBottom(int, int) noexcept;
 };
-
 }
-
-#endif // VIVY_TIMING_VIEW_H
diff --git a/src/UI/DocumentViews/VideoView.cc b/src/UI/DocumentViews/VideoView.cc
index bc73922e714944733f788251d866f26441bba4e0..30eee0e0863deaac58c2187232336f20395939a0 100644
--- a/src/UI/DocumentViews/VideoView.cc
+++ b/src/UI/DocumentViews/VideoView.cc
@@ -2,8 +2,6 @@
 #include "MpvContainer.hh"
 #include "MpvControls.hh"
 
-#include <QVBoxLayout>
-
 using namespace Vivy;
 
 VideoView::VideoView(QWidget *parent) noexcept
diff --git a/src/UI/FakeVim/FakeVimActions.cc b/src/UI/FakeVim/FakeVimActions.cc
index 1c36b444d5647feb830b9a31ad0ce16468ba748c..7042af0745e7a304c5e9534e61439ffce665f387 100644
--- a/src/UI/FakeVim/FakeVimActions.cc
+++ b/src/UI/FakeVim/FakeVimActions.cc
@@ -2,12 +2,16 @@
 #include "FakeVimHandler.hh"
 
 #include "../../Lib/Utils.hh"
-#include <QDebug>
 
 namespace FakeVim::Internal
 {
 #ifdef FAKEVIM_STANDALONE
 FvBaseAspect::FvBaseAspect() {}
+FvBaseAspect::~FvBaseAspect() {}
+FvBoolAspect::~FvBoolAspect() {}
+FvIntegerAspect::~FvIntegerAspect() {}
+FvStringAspect::~FvStringAspect() {}
+FvAspectContainer::~FvAspectContainer() {}
 
 void
 FvBaseAspect::setValue(const QVariant &value)
diff --git a/src/UI/FakeVim/FakeVimActions.hh b/src/UI/FakeVim/FakeVimActions.hh
index b1cea03d6697830e2637ca2efbbca89ff8ecdf0e..13895c395ead827054ff3c7dc76ea6ceb4f8b7c8 100644
--- a/src/UI/FakeVim/FakeVimActions.hh
+++ b/src/UI/FakeVim/FakeVimActions.hh
@@ -2,18 +2,12 @@
 
 #define FAKEVIM_STANDALONE
 
-#include <QCoreApplication>
-#include <QHash>
-#include <QObject>
-#include <QString>
-#include <QVariant>
-
 namespace FakeVim::Internal
 {
 class FvBaseAspect {
 public:
     FvBaseAspect();
-    virtual ~FvBaseAspect() {}
+    virtual ~FvBaseAspect();
 
     void setValue(const QVariant &value);
     QVariant value() const;
@@ -34,21 +28,25 @@ private:
 
 class FvBoolAspect : public FvBaseAspect {
 public:
+    ~FvBoolAspect() override;
     bool value() const { return FvBaseAspect::value().toBool(); }
 };
 
 class FvIntegerAspect : public FvBaseAspect {
 public:
+    ~FvIntegerAspect() override;
     qint64 value() const { return FvBaseAspect::value().toLongLong(); }
 };
 
 class FvStringAspect : public FvBaseAspect {
 public:
+    ~FvStringAspect() override;
     QString value() const { return FvBaseAspect::value().toString(); }
 };
 
 class FvAspectContainer : public FvBaseAspect {
 public:
+    ~FvAspectContainer() override;
 };
 
 class FakeVimSettings final : public FvAspectContainer {
diff --git a/src/UI/FakeVim/FakeVimHandler.cc b/src/UI/FakeVim/FakeVimHandler.cc
index 153b5a53ddf3c9ba9695da5e4da3c554f133e930..3d455d8b3b63e5900169c59ab00a53fd737fdb12 100644
--- a/src/UI/FakeVim/FakeVimHandler.cc
+++ b/src/UI/FakeVim/FakeVimHandler.cc
@@ -33,36 +33,6 @@
 #include "FakeVimTr.hh"
 #include "../../Lib/Utils.hh"
 
-#include <QDebug>
-#include <QFile>
-#include <QObject>
-#include <QPointer>
-#include <QProcess>
-#include <QRegularExpression>
-#include <QTextStream>
-#include <QTimer>
-#include <QStack>
-
-#include <QApplication>
-#include <QClipboard>
-#include <QInputMethodEvent>
-#include <QKeyEvent>
-#include <QLineEdit>
-#include <QPlainTextEdit>
-#include <QScrollBar>
-#include <QTextBlock>
-#include <QTextCursor>
-#include <QTextDocumentFragment>
-#include <QTextEdit>
-#include <QMimeData>
-#include <QSharedPointer>
-#include <QDir>
-
-#include <algorithm>
-#include <climits>
-#include <ctype.h>
-#include <functional>
-
 //#define DEBUG_KEY  1
 #if defined(DEBUG_KEY) && DEBUG_KEY
 #define KEY_DEBUG(s) qDebug() << s
diff --git a/src/UI/FakeVim/FakeVimHandler.hh b/src/UI/FakeVim/FakeVimHandler.hh
index 1894680b7715fd951180df9af6772252b5b89b01..a84a97ee71e5033f7976aec554c2f929d7d9a209 100644
--- a/src/UI/FakeVim/FakeVimHandler.hh
+++ b/src/UI/FakeVim/FakeVimHandler.hh
@@ -2,12 +2,6 @@
 
 #define FAKEVIM_STANDALONE
 
-#include <QObject>
-#include <QTextEdit>
-
-#include <functional>
-#include <vector>
-
 namespace FakeVim::Internal
 {
 enum RangeMode {
diff --git a/src/UI/FakeVim/FakeVimTr.hh b/src/UI/FakeVim/FakeVimTr.hh
index b298d4f77421612544e68be9f83d66aca95fed24..d2cf884ba9c40e54daabeb5f603100fa59bbf450 100644
--- a/src/UI/FakeVim/FakeVimTr.hh
+++ b/src/UI/FakeVim/FakeVimTr.hh
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <QCoreApplication>
-
 namespace FakeVim
 {
 struct Tr {
diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc
index 31d2a7cc395210bc8797868b9741dfa4640e2a22..d789104ff1878a53cfab37214a84f64492798a48 100644
--- a/src/UI/MainWindow.cc
+++ b/src/UI/MainWindow.cc
@@ -6,27 +6,6 @@
 #include "../Lib/Utils.hh"
 #include "../VivyApplication.hh"
 
-#include <mutex>
-#include <algorithm>
-#include <functional>
-#include <QWindow>
-#include <optional>
-#include <QTreeView>
-#include <QPushButton>
-#include <QDialogButtonBox>
-#include <QFileInfo>
-#include <QStandardPaths>
-#include <QPixmap>
-#include <QVBoxLayout>
-#include <QMessageBox>
-#include <QFileDialog>
-#include <QStatusBar>
-#include <QMenuBar>
-#include <QImage>
-#include <QToolBar>
-#include <QTabWidget>
-#include <QEventLoop>
-
 #define DCL_MENU(menu, name) [[maybe_unused]] QMenu *menu##Menu = menuBar()->addMenu(name);
 
 #define DCL_ACTION(method, name, tip, menu)                              \
@@ -62,6 +41,7 @@ MainWindow::MainWindow() noexcept
     DCL_ACTION(openDocument, "&Open document", "Open a document", file);
     DCL_ACTION(saveFile, "&Save file", "Save the current document", file);
     DCL_ACTION(saveFileAs, "&Save file as", "Save the current document as", file);
+    DCL_ACTION(renameFile, "&Rename the file", "Rename the current document", file);
     fileMenu->addSeparator();
     DCL_ACTION(loadSubDocumentAudio, "&Load audio", "Load an audio file as a sub-document", file);
     DCL_ACTION(loadSubDocumentVideo, "&Load video", "Load a vide file as a sub-document", file);
@@ -73,6 +53,7 @@ MainWindow::MainWindow() noexcept
     ACTION_ADD_ICON(openDocument, VIVY_ICON_OPEN);
     ACTION_ADD_ICON(saveFile, VIVY_ICON_SAVE);
     ACTION_ADD_ICON(saveFileAs, VIVY_ICON_SAVE_AS);
+    ACTION_ADD_ICON(renameFile, VIVY_ICON_RENAME);
     ACTION_ADD_ICON(openDialogHelp, VIVY_ICON_ABOUT);
 
     ACTION_ADD_SHORTCUT(newDocument, QKeySequence::New);
@@ -117,10 +98,11 @@ MainWindow::MainWindow() noexcept
     loadSubDocumentVideoAct->setEnabled(false);
     loadSubDocumentAudioAct->setEnabled(false);
 
-    // Enable actions if the document is save-able
-    auto enableSaveOnDocument = [this](auto *widget, int index) noexcept -> void {
+    // Enable "Save As" action
+    auto enableSaveAsOnDocument = [this](auto *widget, int index) noexcept -> void {
         if (index >= 0) {
-            auto type = static_cast<AbstractDocumentView *>(documents->widget(index))->getType();
+            const auto doc  = static_cast<AbstractDocumentView *>(documents->widget(index));
+            const auto type = doc->getType();
             widget->setEnabled(type == AbstractDocumentView::Type::Vivy ||
                                type == AbstractDocumentView::Type::Script);
         } else {
@@ -128,6 +110,22 @@ MainWindow::MainWindow() noexcept
         }
     };
 
+    // Enable actions if the document is save-able
+    auto enableSaveOnDocument = [this](auto *widget, int index) noexcept -> void {
+        if (index >= 0) {
+            const auto doc  = static_cast<AbstractDocumentView *>(documents->widget(index));
+            const auto type = doc->getType();
+            const bool isVivyMemoryDoc =
+                (type == AbstractDocumentView::Type::Vivy) &&
+                dynamic_cast<VivyDocument *>(doc->getDocument())
+                    ->checkDocumentOption(VivyDocument::MemoryDocumentCreation);
+            widget->setEnabled((!isVivyMemoryDoc) && (type == AbstractDocumentView::Type::Vivy ||
+                                                      type == AbstractDocumentView::Type::Script));
+        } else {
+            widget->setEnabled(false);
+        }
+    };
+
     // Enable load sub-document on sub-document able objects
     auto enableLoadSubOnDocument = [this](auto *widget, int index) noexcept -> void {
         if (index >= 0) {
@@ -138,22 +136,18 @@ MainWindow::MainWindow() noexcept
         }
     };
 
-    {
-#define CONNECT_ENABLE(act, func) \
-    connect(documents, &QTabWidget::currentChanged, act, std::bind_front(func, act));
-
-        connect(documents, &QTabWidget::currentChanged, this,
-                [this](int) noexcept -> void { documentViewActionsChanged(); });
-
-        CONNECT_ENABLE(saveFileAct, enableSaveOnDocument);
-        CONNECT_ENABLE(saveFileAsAct, enableSaveOnDocument);
+    auto enableConnection = [this](QAction *const act, auto func) noexcept -> void {
+        connect(documents, &QTabWidget::currentChanged, act, std::bind_front(func, act));
+    };
 
-        CONNECT_ENABLE(loadSubDocumentAssAct, enableLoadSubOnDocument);
-        CONNECT_ENABLE(loadSubDocumentVideoAct, enableLoadSubOnDocument);
-        CONNECT_ENABLE(loadSubDocumentAudioAct, enableLoadSubOnDocument);
+    connect(documents, &QTabWidget::currentChanged, this,
+            [this](int) noexcept -> void { documentViewActionsChanged(); });
 
-#undef CONNECT_ENABLE
-    }
+    enableConnection(saveFileAct, enableSaveOnDocument);
+    enableConnection(saveFileAsAct, enableSaveAsOnDocument);
+    enableConnection(loadSubDocumentAssAct, enableLoadSubOnDocument);
+    enableConnection(loadSubDocumentVideoAct, enableLoadSubOnDocument);
+    enableConnection(loadSubDocumentAudioAct, enableLoadSubOnDocument);
 
     // Main window has finished its construction
     statusBar()->showMessage("QSimulate has started");
@@ -166,19 +160,25 @@ MainWindow::MainWindow() noexcept
     newDocument();
 }
 
+void
+MainWindow::closeEvent(QCloseEvent *event) noexcept
+{
+    qDebug() << "Closing the main window!";
+    forEachViews<VivyDocumentView>([](VivyDocumentView *view, int) { view->closeDocument(); });
+    QMainWindow::closeEvent(event);
+}
+
 void
 MainWindow::updateFakeVimUsage(bool yes) noexcept
 {
-    const int count = documents->count();
-    for (int index = 0; index < count; ++index) {
-        AbstractDocumentView *docView = getTab(index);
-        const bool documentExists     = docView && docView->getDocument();
+    forEachViews<AbstractDocumentView>([yes](AbstractDocumentView *docView, int) noexcept -> void {
+        const bool documentExists = docView && docView->getDocument();
         const bool isScriptEditor =
             documentExists && (docView->getDocument()->getType() == AbstractDocument::Type::Script);
 
         if (isScriptEditor)
-            static_cast<ScriptDocumentView *const>(docView)->setUseFakeVimEditor(yes);
-    }
+            static_cast<ScriptDocumentView *>(docView)->setUseFakeVimEditor(yes);
+    });
 }
 
 void
@@ -211,24 +211,42 @@ MainWindow::openDialogHelp() noexcept
 }
 
 AbstractDocument *
-MainWindow::getCurrentDocument() const noexcept
+MainWindow::getCurrentDocument() const
+{
+    return getCurrentDocumentView()->getDocument();
+}
+
+void
+MainWindow::saveFile() noexcept
 {
     try {
-        return getCurrentDocumentView()->getDocument();
-    } catch (const std::runtime_error &e) {
-        qCritical() << "No current view in the main window:" << e.what();
-        return nullptr;
+        const auto document = getCurrentDocument();
+        document->save();
+    }
+
+    catch (const std::runtime_error &e) {
+        qCritical() << "Failed to save current document:" << e.what();
     }
 }
 
 void
-MainWindow::saveFile() noexcept
+MainWindow::renameFile() noexcept
 {
     try {
-        if (auto document = getCurrentDocument()) {
-            qDebug() << "Request to save the document" << document->getName();
-        }
-    } catch (const std::runtime_error &e) {
+        const auto docView = getCurrentDocumentView();
+        auto document      = docView->getDocument();
+        const QString filename =
+            dialogSaveFileName("Select the target file to rename the current file to",
+                               QDir::homePath(), Utils::getVivyDocumentFileSuffixFilter());
+
+        if (filename.isEmpty())
+            throw std::runtime_error("No filename passed");
+
+        else
+            document->rename(filename); // Kubat: Save is called by rename!
+    }
+
+    catch (const std::runtime_error &e) {
         qCritical() << "Failed to save current document:" << e.what();
     }
 }
@@ -237,10 +255,20 @@ void
 MainWindow::saveFileAs() noexcept
 {
     try {
-        if (auto document = getCurrentDocument()) {
-            qDebug() << "Request to save the document" << document->getName();
-        }
-    } catch (const std::runtime_error &e) {
+        const auto docView = getCurrentDocumentView();
+        auto document      = docView->getDocument();
+        const QString filename =
+            dialogSaveFileName("Select the target file to save into", QDir::homePath(),
+                               Utils::getVivyDocumentFileSuffixFilter());
+
+        if (filename.isEmpty())
+            throw std::runtime_error("No filename passed");
+
+        else
+            document->copy(filename); // Kubat: Save is called by copy!
+    }
+
+    catch (const std::runtime_error &e) {
         qCritical() << "Failed to save current document:" << e.what();
     }
 }
@@ -251,13 +279,12 @@ MainWindow::closeDocument(AbstractDocumentView *const view) noexcept
     if (view == nullptr || !view->getDocument())
         return;
 
-    const int count = documents->count();
-    for (int index = 0; index < count; ++index) {
-        const AbstractDocumentView *const documentView = getTab(index);
-        const bool documentExists = documentView && documentView->getDocument();
-        if (documentExists && (*documentView->getDocument()) == (*view->getDocument()))
-            closeDocument(index);
-    }
+    forEachViews<AbstractDocumentView>(
+        [this, view](AbstractDocumentView *documentView, int index) noexcept -> void {
+            const bool documentExists = documentView && documentView->getDocument();
+            if (documentExists && (*documentView->getDocument()) == (*view->getDocument()))
+                closeDocument(index);
+        });
 }
 
 void
@@ -293,7 +320,7 @@ void
 MainWindow::openDocument() noexcept
 {
     const QString filename = dialogOpenFileName("Select a document to open", QDir::homePath(),
-                                                Utils::getAnyDocumentFileSuffixFilter());
+                                                Utils::getAnyTopLevelDocumentFileSuffixFilter());
     if (filename.isEmpty()) {
         qWarning() << "Found an empty filename, don't open a file";
         return;
@@ -309,21 +336,17 @@ MainWindow::openDocument() noexcept
 
     // Handle the different types here
     try {
-        if ((fileType == Utils::DocumentType::Video) || (fileType == Utils::DocumentType::Vivy) ||
-            (fileType == Utils::DocumentType::Audio) || (fileType == Utils::DocumentType::ASS))
+        if (fileType == Utils::DocumentType::Vivy)
             addTab(new VivyDocumentView(vivyApp->documentStore.loadDocument(filename), documents));
 
         else if (fileType == Utils::DocumentType::VivyScript) {
-            // FIXME: First interpretation of the file to get the error, can do better.
-            std::shared_ptr<ScriptDocument> scriptDocument =
-                vivyApp->scriptStore.loadDocument(filename);
-            std::optional<std::tuple<int, std::string>> errorTuple =
-                vivyApp->scriptStore.executeScript(scriptDocument->getUuid());
+            auto scriptDocument = vivyApp->scriptStore.loadDocument(filename);
+            auto errorTuple     = vivyApp->scriptStore.executeScript(scriptDocument->getUuid());
             ScriptDocumentView *newView = new ScriptDocumentView(scriptDocument, documents);
 
             if (errorTuple.has_value()) {
                 const auto &[line, desc] = errorTuple.value();
-                emit newView->luaErrorFound(line, QString{ desc.c_str() });
+                emit newView->luaErrorFound(line, QString::fromUtf8(desc.c_str()));
             }
 
             addTab(newView);
@@ -369,7 +392,7 @@ MainWindow::loadSubDocumentAudio() noexcept
 }
 
 void
-MainWindow::addTab(AbstractDocumentView *tab)
+MainWindow::addTab(AbstractDocumentView *const tab)
 {
     int index = -1;
     if (const int untouched_index = findFirstUntouchedDocument(); untouched_index >= 0) {
@@ -384,10 +407,48 @@ MainWindow::addTab(AbstractDocumentView *tab)
     documents->setCurrentIndex(index);
     connect(tab, &AbstractDocumentView::viewActionsChanged, this,
             &MainWindow::documentViewActionsChanged);
+    connect(tab, &AbstractDocumentView::documentPropertyChanged, this,
+            [this, tab]() noexcept -> void {
+                int tabIndex = getIndexOfTab(tab);
+                if (tabIndex < 0) {
+                    qDebug() << "Tab was not found!";
+                } else {
+                    qDebug() << "Tab was found at index" << tabIndex;
+                    documents->setTabToolTip(tabIndex, tab->getDocumentTabToolTip());
+                    documents->setTabText(tabIndex, tab->getDocumentTabName());
+                }
+            });
     documentViewActionsChanged();
     qDebug() << "View constructed successfully";
 }
 
+int
+MainWindow::getIndexOfTab(const AbstractDocumentView *const viewA) const noexcept
+{
+    if (viewA == nullptr)
+        return -1;
+
+    const AbstractDocument *const docA = viewA->getDocument();
+    if (docA == nullptr)
+        return -1;
+
+    int returnIndex = -1;
+    forEachViews<AbstractDocumentView>(
+        [docA, &returnIndex](AbstractDocumentView *viewB, int index) noexcept -> void {
+            if (viewB == nullptr)
+                return;
+
+            const AbstractDocument *const docB = viewB->getDocument();
+            if (docB == nullptr)
+                return;
+
+            if (*docB == *docA)
+                returnIndex = index;
+        });
+
+    return returnIndex;
+}
+
 AbstractDocumentView *
 MainWindow::getCurrentDocumentView() const
 {
@@ -412,17 +473,14 @@ MainWindow::getTab(const int index) const noexcept
 int
 MainWindow::findFirstUntouchedDocument() const noexcept
 {
-    const int count = documents->count();
-    for (int index = 0; index < count; ++index) {
-        AbstractDocumentView *documentView = getTab(index);
-        if (documentView && (documentView->getType() == AbstractDocumentView::Type::Vivy) &&
-            static_cast<VivyDocumentView *>(documentView)
-                ->getDocument()
-                ->checkDocumentOption(VivyDocument::UntouchedByDefault))
-            return index;
-    }
+    int returnIndex = -1;
+    forEachViews<VivyDocumentView>(
+        [&returnIndex](VivyDocumentView *view, int index) noexcept -> void {
+            if (view->getDocument()->checkDocumentOption(VivyDocument::UntouchedByDefault))
+                returnIndex = index;
+        });
 
-    return -1;
+    return returnIndex;
 }
 
 void
@@ -438,27 +496,23 @@ MainWindow::documentViewActionsChanged() noexcept
         qInfo() << "No view to display:" << e.what();
     }
 }
-QString
-MainWindow::dialogOpenFileName(const QString &title, const QString &folder,
-                               const QString &filter) noexcept
+
+static inline QString
+executeDialog(MainWindow *const self, QFileDialog *const dialog) noexcept
 {
-    QFileDialog dialog(this, title, folder, filter);
     bool dialogAccepted = false;
     std::unique_ptr<VivyFileIconProvider> iconProvider(new VivyFileIconProvider());
 
-    dialog.setOption(QFileDialog::ReadOnly);
-    dialog.setIconProvider(iconProvider.get());
-    dialog.setFileMode(QFileDialog::ExistingFile);
-    connect(&dialog, &QFileDialog::accepted, this,
-            [&dialogAccepted]() noexcept -> void { dialogAccepted = true; });
+    dialog->setIconProvider(iconProvider.get());
+    QFileDialog::connect(dialog, &QFileDialog::accepted, self,
+                         [&dialogAccepted]() noexcept -> void { dialogAccepted = true; });
 
-    dialog.exec();
+    dialog->exec();
 
-    if (!dialogAccepted) {
+    if (!dialogAccepted)
         return QStringLiteral("");
-    }
 
-    const QStringList resList = dialog.selectedFiles();
+    const QStringList resList = dialog->selectedFiles();
     if (resList.size() != 1) {
         qCritical() << "You must select only one file";
         return QStringLiteral("");
@@ -466,3 +520,24 @@ MainWindow::dialogOpenFileName(const QString &title, const QString &folder,
 
     return resList.at(0);
 }
+
+QString
+MainWindow::dialogOpenFileName(const QString &title, const QString &folder,
+                               const QString &filter) noexcept
+{
+    QFileDialog dialog(this, title, folder, filter);
+    dialog.setOption(QFileDialog::ReadOnly);
+    dialog.setAcceptMode(QFileDialog::AcceptOpen);
+    dialog.setFileMode(QFileDialog::ExistingFile);
+    return executeDialog(this, &dialog);
+}
+
+QString
+MainWindow::dialogSaveFileName(const QString &title, const QString &folder,
+                               const QString &filter) noexcept
+{
+    QFileDialog dialog(this, title, folder, filter);
+    dialog.setOption(QFileDialog::ReadOnly, false);
+    dialog.setAcceptMode(QFileDialog::AcceptSave);
+    return executeDialog(this, &dialog);
+}
diff --git a/src/UI/MainWindow.hh b/src/UI/MainWindow.hh
index 3a5d921232f76d2a2808f3bb36eabda6627d772b..7d375a94be296067d5ef6b2a700f04c86921f536 100644
--- a/src/UI/MainWindow.hh
+++ b/src/UI/MainWindow.hh
@@ -9,11 +9,8 @@
 #include "../Lib/Document/VivyDocumentStore.hh"
 #include "DocumentViews/AudioVisualizer.hh"
 #include "VivyDocumentView.hh"
+#include "ScriptDocumentView.hh"
 #include "AboutWindow.hh"
-#include <QMenu>
-#include <QFileDialog>
-#include <QMutex>
-#include <QMainWindow>
 
 namespace Vivy
 {
@@ -29,14 +26,14 @@ class MainWindow final : public QMainWindow {
 public:
     explicit MainWindow() noexcept;
 
-    AbstractDocument *getCurrentDocument() const noexcept;
-    template <class Document> Document *getCurrentDocument() const noexcept
+    AbstractDocument *getCurrentDocument() const;
+    template <class Document> Document *getCurrentDocument() const
     {
         if (AbstractDocumentView *currentView = getCurrentDocumentView();
             currentView->getType() == Document::type) {
             return currentView->getDocument();
         } else {
-            return nullptr;
+            throw std::runtime_error("No main document view was found");
         }
     }
 
@@ -44,14 +41,17 @@ public slots:
     void closeDocument(AbstractDocumentView *const) noexcept;
 
 private:
-    void addTab(AbstractDocumentView *);
+    void addTab(AbstractDocumentView *const);
     AbstractDocumentView *getTab(const int) const noexcept;
     AbstractDocumentView *getCurrentDocumentView() const;
 
+    int getIndexOfTab(const AbstractDocumentView *const) const noexcept;
     int findFirstUntouchedDocument() const noexcept;
     void updateFakeVimUsage(bool yes) noexcept;
     QString dialogOpenFileName(const QString &title, const QString &folder,
                                const QString &filter) noexcept;
+    QString dialogSaveFileName(const QString &title, const QString &folder,
+                               const QString &filter) noexcept;
 
     // Do an action with the selected filename. The 'call' variable must be
     // callable like this: call(DocumentView, Document, QString)
@@ -77,6 +77,22 @@ private:
         }
     }
 
+    // Walk throu all views in the QTabWidget and also get the index of the tab
+    template <Derived<AbstractDocumentView> DocViewType> void forEachViews(auto callback) const
+    {
+        const int count = documents->count();
+        for (int index = 0; index < count; ++index) {
+            AbstractDocumentView *docView = getTab(index);
+            if constexpr (std::is_same_v<AbstractDocumentView, DocViewType>)
+                callback(docView, index);
+            else if (DocViewType *view = dynamic_cast<DocViewType *>(docView); view != nullptr)
+                callback(view, index);
+        }
+    }
+
+protected:
+    void closeEvent(QCloseEvent *) noexcept override;
+
 private slots:
     void newDocument() noexcept;
     void openDocument() noexcept;
@@ -90,6 +106,7 @@ private slots:
 
     void saveFile() noexcept;
     void saveFileAs() noexcept;
+    void renameFile() noexcept;
 
     void openDialogHelp() noexcept;
 
diff --git a/src/UI/PropertyModel.cc b/src/UI/PropertyModel.cc
index cadec56e0d0749326d54d0de2bceda71d3005297..9530ea77c4ee8315188235eb09b7e8cecf7d7c02 100644
--- a/src/UI/PropertyModel.cc
+++ b/src/UI/PropertyModel.cc
@@ -1,5 +1,4 @@
 #include "PropertyModel.hh"
-#include <QDebug>
 
 using namespace Vivy;
 
@@ -34,10 +33,11 @@ PropertyModel::Item::getChildCount() const noexcept
     return childs.count();
 }
 
+// Can't be const because of indexOf...
 int
-PropertyModel::Item::getRow() const noexcept
+PropertyModel::Item::getRow() noexcept
 {
-    return parent ? parent->childs.indexOf(const_cast<Item *>(this)) : 0;
+    return parent ? parent->childs.indexOf(this) : 0;
 }
 
 void
@@ -82,6 +82,54 @@ PropertyModel::Item::getChilds() const noexcept
     return childs;
 }
 
+bool
+PropertyModel::Item::isOrderedJsonObject(const QJsonValue &value) noexcept
+{
+    if (value.isArray()) {
+        for (const QJsonValue v : value.toArray()) {
+            if (!v.isObject() || v.toObject().count() != 1)
+                return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+PropertyModel::Item *
+PropertyModel::Item::createChild(Item *root, QJsonObject::ConstIterator const &it) noexcept
+{
+    const QString key  = it.key();
+    const QJsonValue v = it.value();
+    Item *child        = fromJson(v, root);
+    child->setKey(key);
+    child->setType(v.type());
+    return child;
+}
+
+PropertyModel::Item *
+PropertyModel::Item::createChild(Item *root, QJsonObject const &singleElem) noexcept
+{
+    Q_ASSERT(singleElem.count() == 1);
+
+    const QString key    = singleElem.keys().at(0);
+    const QJsonValue val = singleElem.value(key);
+
+    Item *child = fromJson(val, root);
+    child->setKey(key);
+    child->setType(val.type());
+
+    return child;
+}
+
+PropertyModel::Item *
+PropertyModel::Item::createChild(Item *root, int pos, QJsonValue const &val) noexcept
+{
+    Item *child = fromJson(val, root);
+    child->setKey(QString::number(pos));
+    child->setType(val.type());
+    return child;
+}
+
 PropertyModel::Item *
 PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *parent)
 {
@@ -89,22 +137,20 @@ PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *pare
     root->setKey("root");
 
     if (value.isObject()) {
-        for (const QString &key : value.toObject().keys()) {
-            QJsonValue v = value.toObject().value(key);
-            Item *child  = fromJson(v, root);
-            child->setKey(key);
-            child->setType(v.type());
-            root->appendChild(child);
-        }
+        const QJsonObject objValue = value.toObject();
+        const auto end             = objValue.constEnd();
+        for (auto it = objValue.constBegin(); it != end; ++it)
+            root->appendChild(createChild(root, it));
+    }
 
+    else if (isOrderedJsonObject(value)) {
+        for (const QJsonValue v : value.toArray())
+            root->appendChild(createChild(root, v.toObject()));
     }
 
     else if (value.isArray()) {
         for (int index = 0; const QJsonValue v : value.toArray()) {
-            Item *child = fromJson(v, root);
-            child->setKey(QString::number(index));
-            child->setType(v.type());
-            root->appendChild(child);
+            root->appendChild(createChild(root, index, v));
             ++index;
         }
     }
@@ -120,18 +166,14 @@ PropertyModel::Item::fromJson(const QJsonValue &value, PropertyModel::Item *pare
 PropertyModel::PropertyModel(QObject *parent) noexcept
     : QAbstractItemModel(parent)
 {
-    headers.append("key");
-    headers.append("value");
+    headers.append(QStringLiteral("Key"));
+    headers.append(QStringLiteral("Value"));
 }
 
-PropertyModel::PropertyModel(const QJsonDocument &properties, QObject *parent) noexcept
+PropertyModel::PropertyModel(const QJsonDocument &properties, QObject *parent)
     : PropertyModel(parent)
 {
-    try {
-        loadJson(properties);
-    } catch (const std::runtime_error &e) {
-        qCritical() << "Failed to create the property model object:" << e.what();
-    }
+    loadJson(properties);
 }
 
 void
@@ -173,28 +215,12 @@ PropertyModel::data(const QModelIndex &index, int role) const noexcept
             return QString("%1").arg(item->getValue());
     }
 
-    else if (Qt::EditRole == role) {
-        if (index.column() == 1) {
-            return QString("%1").arg(item->getValue());
-        }
-    }
-
     return QVariant();
 }
 
 bool
-PropertyModel::setData(const QModelIndex &index, const QVariant &value, int role) noexcept
+PropertyModel::setData(const QModelIndex &, const QVariant &, int) noexcept
 {
-    const int col = index.column();
-    if (Qt::EditRole == role) {
-        if (col == 1) {
-            Item *item = static_cast<Item *>(index.internalPointer());
-            item->setValue(value.toString());
-            emit dataChanged(index, index, { Qt::EditRole });
-            return true;
-        }
-    }
-
     return false;
 }
 
@@ -250,7 +276,7 @@ PropertyModel::rowCount(const QModelIndex &parent) const noexcept
 }
 
 int
-PropertyModel::columnCount(const QModelIndex & /* parent */) const noexcept
+PropertyModel::columnCount(const QModelIndex &) const noexcept
 {
     return 2;
 }
@@ -261,40 +287,12 @@ PropertyModel::flags(const QModelIndex &index) const noexcept
     return QAbstractItemModel::flags(index);
 }
 
-// Get the json stored inside the model
-QJsonDocument
-PropertyModel::getJson() const noexcept
-{
-    QJsonValue v = generateJson(root.get());
-    return v.isObject() ? QJsonDocument(v.toObject()) : QJsonDocument(v.toArray());
-}
-
-// Get the json stored inside the model by calculating it
-QJsonValue
-PropertyModel::generateJson(PropertyModel::Item *item) const noexcept
-{
-    if (QJsonValue::Type type = item->getType(); QJsonValue::Object == type) {
-        QJsonObject jo;
-        for (const auto &child : item->getChilds())
-            jo.insert(child->getKey(), generateJson(child));
-        return jo;
-    }
-
-    else if (QJsonValue::Array == type) {
-        QJsonArray arr;
-        for (const auto &child : item->getChilds())
-            arr.append(generateJson(child));
-        return arr;
-    }
-
-    else {
-        QJsonValue va(item->getValue());
-        return va;
-    }
-}
-
-QString
-PropertyModel::getName() const noexcept
+QTreeView *
+PropertyModel::getView(QWidget *parent, PropertyModel *ptr) noexcept
 {
-    return root ? "Property for " + root->getKey() : "Property view";
+    QTreeView *view = new QTreeView(parent);
+    view->setModel(ptr);
+    view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+    view->expandAll();
+    return view;
 }
diff --git a/src/UI/PropertyModel.hh b/src/UI/PropertyModel.hh
index d22ab6eccb28c96e34f3e00419b66a519d12f3b8..45a21ab01096912dd8a8ee68f2de4982b2111f69 100644
--- a/src/UI/PropertyModel.hh
+++ b/src/UI/PropertyModel.hh
@@ -1,14 +1,7 @@
-#ifndef VIVY_UI_PROPERTY_VIEW_H
-#define VIVY_UI_PROPERTY_VIEW_H
+#pragma once
 
 #include "../Lib/Utils.hh"
 
-#include <QAbstractItemModel>
-#include <QJsonDocument>
-#include <QJsonValue>
-#include <QJsonArray>
-#include <QJsonObject>
-
 //  auto model          = new PropertyModel(doc);
 //  QVBoxLayout *layout = new QVBoxLayout(this);
 //  QTreeView *view     = new QTreeView(this);
@@ -40,7 +33,7 @@ private:
         const QVector<Item *> getChilds() const noexcept;
         Item *getParent() const noexcept;
         int getChildCount() const noexcept;
-        int getRow() const noexcept;
+        int getRow() noexcept;
         void setKey(const QString &) noexcept;
         void setValue(const QString &) noexcept;
         void setType(const QJsonValue::Type &) noexcept;
@@ -50,8 +43,12 @@ private:
 
         static Item *fromJson(const QJsonValue &, Item *parent = nullptr);
 
-    protected:
     private:
+        static bool isOrderedJsonObject(QJsonValue const &) noexcept;
+        static Item *createChild(Item *root, QJsonObject::ConstIterator const &) noexcept;
+        static Item *createChild(Item *root, QJsonObject const &) noexcept;
+        static Item *createChild(Item *root, int pos, QJsonValue const &) noexcept;
+
         QString key{};
         QString value{};
         QJsonValue::Type type{ QJsonValue::Null };
@@ -61,9 +58,8 @@ private:
 
 public:
     explicit PropertyModel(QObject *parent = nullptr) noexcept;
-    explicit PropertyModel(const QJsonDocument &, QObject *parent = nullptr) noexcept;
-    explicit PropertyModel(const PropertyConstViewable auto &object,
-                           QObject *parent = nullptr) noexcept
+    explicit PropertyModel(const QJsonDocument &, QObject *parent = nullptr);
+    explicit PropertyModel(const PropertyConstViewable auto &object, QObject *parent = nullptr)
         : PropertyModel(object.getProperties(), parent)
     {
         // With concepts it seems that the function must be implemented where
@@ -71,7 +67,7 @@ public:
         root->setKey(object.getElementName());
     }
 
-    void loadJson(const QJsonDocument &json);
+    [[nodiscard]] static QTreeView *getView(QWidget *parent, PropertyModel *) noexcept;
 
     QVariant data(const QModelIndex &, int role) const noexcept override;
     bool setData(const QModelIndex &, const QVariant &v, int r = Qt::EditRole) noexcept override;
@@ -85,16 +81,11 @@ public:
     int columnCount(const QModelIndex &parent = QModelIndex()) const noexcept override;
 
     Qt::ItemFlags flags(const QModelIndex &) const noexcept override;
-    QJsonDocument getJson() const noexcept;
-
-    QString getName() const noexcept;
 
 private:
-    QJsonValue generateJson(Item *) const noexcept;
+    void loadJson(const QJsonDocument &json);
+
     std::unique_ptr<Item> root{ nullptr };
     QStringList headers{};
 };
-
 }
-
-#endif // VIVY_UI_PROPERTY_VIEW_H
diff --git a/src/UI/ScriptDocumentView.cc b/src/UI/ScriptDocumentView.cc
index b91e3830cc759f1ca3df43d7ab3e572a159ede0a..b05c137d8e37f7ed26d9f66eb193d9f62a4eec3f 100644
--- a/src/UI/ScriptDocumentView.cc
+++ b/src/UI/ScriptDocumentView.cc
@@ -3,9 +3,6 @@
 #include "ScriptViews/ScriptHighlighter.hh"
 #include "../VivyApplication.hh"
 
-#include <QRegExp>
-#include <QVBoxLayout>
-
 using namespace Vivy;
 
 ScriptDocumentView::ScriptDocumentView(std::shared_ptr<ScriptDocument> ptr, QWidget *parent)
@@ -19,13 +16,15 @@ ScriptDocumentView::ScriptDocumentView(std::shared_ptr<ScriptDocument> ptr, QWid
         throw std::runtime_error("Failed to open script file");
     }
 
-    editor->setPlainText(textFile.readAll());
+    editor->setPlainText(QString::fromUtf8(textFile.readAll()));
     setCentralWidget(editor);
     editor->setFocus(Qt::OtherFocusReason);
 
     setUseFakeVimEditor(vivyApp->getUseFakeVimEditor());
 
     connect(this, &ScriptDocumentView::luaErrorFound, editor, &ScriptEditor::updateLastLuaError);
+    connect(document.get(), &AbstractDocument::documentChanged, this,
+            [=, this]() noexcept -> void { emit documentPropertyChanged(); });
 
     // Same style as the editor
     setStyleSheet(QStringLiteral("* {"
@@ -53,9 +52,11 @@ ScriptDocumentView::setUseFakeVimEditor(bool yes) noexcept
         connect(proxy, &EditorProxy::requestQuit, this, [this, mw]() noexcept -> void {
             mw->closeDocument(static_cast<AbstractDocumentView *>(this));
         });
-        TODO(Implement the save and save + quit things)
-        // connect(proxy, &EditorProxy::requestSave, document, &AbstractDocument::save); // TODO
-        // connect(proxy, &EditorProxy::requestSaveAndQuit, document, &AbstractDocument::save + &MainWindow::closeTab); // TODO
+        connect(proxy, &EditorProxy::requestSave, this, [this]() { document->save(); });
+        connect(proxy, &EditorProxy::requestSaveAndQuit, this, [this, mw]() noexcept -> void {
+            document->save();
+            mw->closeDocument(static_cast<AbstractDocumentView *>(this));
+        });
         initHandler(handler);
         clearUndoRedo(editor);
     }
diff --git a/src/UI/ScriptDocumentView.hh b/src/UI/ScriptDocumentView.hh
index 7f2c333bae28c31c6be079cf0faa5839ed40850e..c1acc9c31524571e2b7eedf0703991a12296b8fe 100644
--- a/src/UI/ScriptDocumentView.hh
+++ b/src/UI/ScriptDocumentView.hh
@@ -6,25 +6,12 @@
 #endif
 
 #include "../Lib/Utils.hh"
+#include "../Lib/Script/ScriptDocument.hh"
 #include "AbstractDocumentView.hh"
-#include <QWidget>
-#include <QString>
-#include <memory>
-
-class QPlainTextEdit;
-
-namespace FakeVim::Internal
-{
-class FakeVimHandler;
-}
-
-namespace Vivy
-{
-class ScriptEditor;
-class ScriptHighlighter;
-class ScriptDocument;
-class EditorProxy;
-}
+#include "FakeVim/FakeVimHandler.hh"
+#include "ScriptViews/EditorProxy.hh"
+#include "ScriptViews/ScriptHighlighter.hh"
+#include "ScriptViews/ScriptEditor.hh"
 
 namespace Vivy
 {
diff --git a/src/UI/ScriptViews/EditorProxy.cc b/src/UI/ScriptViews/EditorProxy.cc
index 16845293ca0bb5e1e2a9f40be4d2f82a4e6f699c..cff3cbd913c2ed04071fd60e8846a6032ea2d4a8 100644
--- a/src/UI/ScriptViews/EditorProxy.cc
+++ b/src/UI/ScriptViews/EditorProxy.cc
@@ -3,11 +3,6 @@
 #include "../FakeVim/FakeVimActions.hh"
 #include "../../VivyApplication.hh"
 
-#include <QMessageBox>
-#include <QStatusBar>
-#include <QMainWindow>
-#include <QTemporaryFile>
-
 using namespace Vivy;
 
 Vivy::EditorProxy *
diff --git a/src/UI/ScriptViews/EditorProxy.hh b/src/UI/ScriptViews/EditorProxy.hh
index 1c2e4bdadf2ca56f044915d10f43d2928b8518d1..c93a3823f753320f5521b446ddcb309bc5090d39 100644
--- a/src/UI/ScriptViews/EditorProxy.hh
+++ b/src/UI/ScriptViews/EditorProxy.hh
@@ -1,7 +1,5 @@
 #pragma once
 
-#include <QObject>
-#include <QTextEdit>
 #include "ScriptEditor.hh"
 
 class QMainWindow;
diff --git a/src/UI/ScriptViews/ScriptEditor.cc b/src/UI/ScriptViews/ScriptEditor.cc
index 56dff1a6c6a6e6483c44437f4e49c8dcba29efcd..80b88fcfdfaaa8bf24a6107c32b5413f92661bcd 100644
--- a/src/UI/ScriptViews/ScriptEditor.cc
+++ b/src/UI/ScriptViews/ScriptEditor.cc
@@ -1,11 +1,6 @@
 #include "ScriptEditor.hh"
 #include "ScriptHighlighter.hh"
 
-#include <QTextCursor>
-#include <QBrush>
-#include <QKeyEvent>
-#include <QFrame>
-
 using namespace Vivy;
 
 ScriptEditor::LineNumberArea::LineNumberArea(ScriptEditor *editor) noexcept
diff --git a/src/UI/ScriptViews/ScriptEditor.hh b/src/UI/ScriptViews/ScriptEditor.hh
index 491a427d8bf9b76636764bb20701488724604d67..5a68ef9c71bdd6afe31211179ae7c28dbeed5c8c 100644
--- a/src/UI/ScriptViews/ScriptEditor.hh
+++ b/src/UI/ScriptViews/ScriptEditor.hh
@@ -4,7 +4,6 @@
 #error "This is a C++ header"
 #endif
 
-#include <QPlainTextEdit>
 #include "../../Lib/Utils.hh"
 
 namespace Vivy
diff --git a/src/UI/ScriptViews/ScriptHighlighter.cc b/src/UI/ScriptViews/ScriptHighlighter.cc
index 85ea10a8ce763a8bc918fb5928cbbf574f77bda3..2baf555a1ac8e625e4f3e6b4c87546ef4b9b6265 100644
--- a/src/UI/ScriptViews/ScriptHighlighter.cc
+++ b/src/UI/ScriptViews/ScriptHighlighter.cc
@@ -1,8 +1,6 @@
 #include "ScriptHighlighter.hh"
 #include "../../Lib/Utils.hh"
 
-#include <QTextDocument>
-
 using namespace Vivy;
 
 ScriptHighlighter::HighlightingRule::HighlightingRule(QRegExp pttrn, QTextCharFormat frmt)
@@ -102,7 +100,7 @@ ScriptHighlighter::previousBlockState() noexcept
         state != Utils::toUnderlying(BlockState::Comment))
         return BlockState::None;
 
-    return static_cast<const BlockState>(state);
+    return static_cast<BlockState>(state);
 }
 
 void
diff --git a/src/UI/ScriptViews/ScriptHighlighter.hh b/src/UI/ScriptViews/ScriptHighlighter.hh
index 752c8023455608d13e6177704274d5f9ffd73617..492259dad9adc6795952277f02b958396fccc425 100644
--- a/src/UI/ScriptViews/ScriptHighlighter.hh
+++ b/src/UI/ScriptViews/ScriptHighlighter.hh
@@ -4,10 +4,6 @@
 #error "This is a C++ header"
 #endif
 
-#include <QSyntaxHighlighter>
-#include <QTextCharFormat>
-#include <QBrush>
-
 class QTextDocument;
 
 namespace Vivy
diff --git a/src/UI/UnclosableDockWidget.cc b/src/UI/UnclosableDockWidget.cc
new file mode 100644
index 0000000000000000000000000000000000000000..e08d2f73c83f754169d92306aba2ddcac6d4b490
--- /dev/null
+++ b/src/UI/UnclosableDockWidget.cc
@@ -0,0 +1,33 @@
+#include "UnclosableDockWidget.hh"
+#include "DockWidgetTitleBar.hh"
+
+using namespace Vivy;
+
+UnclosableDockWidget::UnclosableDockWidget(const QString &name, QWidget *parent) noexcept
+    : QDockWidget(name, parent)
+{
+    DockWidgetTitleBar::addToDock(this);
+}
+
+UnclosableDockWidget::~UnclosableDockWidget() noexcept
+{
+    QWidget *w = widget();
+    if (w != nullptr)
+        delete w;
+    setWidget(nullptr);
+}
+
+void
+UnclosableDockWidget::closeEvent(QCloseEvent *event) noexcept
+{
+    if (allowedToClose)
+        QDockWidget::closeEvent(event);
+    else
+        event->ignore();
+}
+
+void
+UnclosableDockWidget::allowClose() noexcept
+{
+    allowedToClose = true;
+}
diff --git a/src/UI/UnclosableDockWidget.hh b/src/UI/UnclosableDockWidget.hh
new file mode 100644
index 0000000000000000000000000000000000000000..204a11720b207591c8d50e2422fa8d4555688a19
--- /dev/null
+++ b/src/UI/UnclosableDockWidget.hh
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "Utils.hh"
+
+namespace Vivy
+{
+class UnclosableDockWidget final : public QDockWidget {
+    Q_OBJECT
+    VIVY_UNMOVABLE_OBJECT(UnclosableDockWidget)
+
+public:
+    explicit UnclosableDockWidget(const QString &, QWidget *parent) noexcept;
+    ~UnclosableDockWidget() noexcept override;
+    void allowClose() noexcept;
+
+protected:
+    void closeEvent(QCloseEvent *event) noexcept override;
+
+private:
+    bool allowedToClose{ false };
+};
+}
diff --git a/src/UI/Utils.cc b/src/UI/Utils.cc
index 95f9269d01b74fab6579a1a68ae28878cea2c62d..531e05119df8a7d1a2022581f2bc75197ccc8334 100644
--- a/src/UI/Utils.cc
+++ b/src/UI/Utils.cc
@@ -1,5 +1,4 @@
 #include "Utils.hh"
-#include <QWidget>
 
 using namespace Vivy;
 
@@ -10,3 +9,16 @@ Utils::setTransparentBackgroundForWidget(QWidget *const w) noexcept
     w->setAttribute(Qt::WA_TranslucentBackground);
     w->setAttribute(Qt::WA_TransparentForMouseEvents);
 }
+
+const QString Utils::untouchedTabIndicator = QStringLiteral("(*)");
+const QString Utils::unsavedTabIndicator   = QStringLiteral("(+)");
+
+void
+Utils::deleteInternalWidget(QDockWidget *const dock) noexcept
+{
+    QWidget *widget = dock->widget();
+    if (widget != nullptr) {
+        delete widget;
+        dock->setWidget(nullptr);
+    }
+}
diff --git a/src/UI/Utils.hh b/src/UI/Utils.hh
index 34b57e2a119bcae629be2901e2332a26d7341836..e5b71db13c6228348c797ba619b9be074ceae86d 100644
--- a/src/UI/Utils.hh
+++ b/src/UI/Utils.hh
@@ -4,9 +4,14 @@
 #error "This is a C++ header"
 #endif
 
+#include "../Lib/Utils.hh"
+
 class QWidget;
 
 namespace Vivy::Utils
 {
 void setTransparentBackgroundForWidget(QWidget *const) noexcept;
+void deleteInternalWidget(QDockWidget *const) noexcept;
+extern const QString untouchedTabIndicator;
+extern const QString unsavedTabIndicator;
 }
diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc
index a82725074114229ae36477d7aa9ef5a4a5f84f35..be7437916ed688742d5beeb24454295e5ea124b2 100644
--- a/src/UI/VivyDocumentView.cc
+++ b/src/UI/VivyDocumentView.cc
@@ -7,34 +7,25 @@
 #include "../VivyApplication.hh"
 #include "../Lib/Document/VivyDocument.hh"
 
-#include <QHeaderView>
-#include <QTreeView>
-#include <QVBoxLayout>
-#include <QTableView>
-#include <QWidget>
-#include <QDockWidget>
-
 using namespace Vivy;
 
 VivyDocumentView::VivyDocumentView(std::shared_ptr<VivyDocument> doc, QWidget *parent) noexcept
     : AbstractDocumentView(AbstractDocumentView::Type::Vivy, parent)
     , document(doc)
 {
-    qDebug() << "Create view for document" << doc->getName() << "with capabilities"
-             << doc->getDocumentCapabilitiesString();
+    setDockNestingEnabled(true);
 
     loadAudioView();
     loadVideoView();
     loadAssView();
 
-    setDockNestingEnabled(true);
-
     // Add some actions...
-    QAction *openPropertiesAct = new QAction("Open properties", this);
+    openPropertiesAct = new QAction("Open properties", this);
     connect(openPropertiesAct, &QAction::triggered, this, &VivyDocumentView::openProperties);
-    connect(document.get(), &AbstractDocument::documentChanged, this, [=, this]() noexcept -> void {
+    connect(document.get(), &AbstractDocument::documentChanged, this, [=, this]() {
         if (property)
             openProperties();
+        emit documentPropertyChanged();
     });
     viewsActions.append(openPropertiesAct);
 
@@ -49,7 +40,8 @@ VivyDocumentView::~VivyDocumentView() noexcept
     qDebug()
         << "Deleting the document view: ref count on document" << document->getName() << "is"
         << document.use_count() << "and" << (visualizer ? "has visualizer" : "without visualizer");
-    closeDocument();
+
+    deleteAllContent();
 }
 
 VivyDocument *
@@ -61,8 +53,10 @@ VivyDocumentView::getDocument() const noexcept
 QString
 VivyDocumentView::getDocumentTabName() const noexcept
 {
-    return (document->checkDocumentOption(VivyDocument::UntouchedByDefault) ? "(*) " : "") +
-           document->getName();
+    return (document->checkDocumentOption(VivyDocument::UntouchedByDefault)
+                ? (Utils::untouchedTabIndicator + " ")
+                : QString::fromUtf8("")) +
+           Utils::getBaseName(document->getName());
 }
 
 QString
@@ -73,89 +67,79 @@ VivyDocumentView::getDocumentTabToolTip() const noexcept
            QStringLiteral("}") +
            (document->checkDocumentOption(VivyDocument::UntouchedByDefault)
                 ? QStringLiteral("\nUntouched document")
+                : QStringLiteral("")) +
+           (document->checkDocumentOption(VivyDocument::MemoryDocumentCreation)
+                ? QStringLiteral("\nMemory document")
                 : QStringLiteral(""));
 }
 
 void
 VivyDocumentView::loadVideoView() noexcept
 {
-    if (document->checkDocumentCapabilities(VivyDocument::Capabilities::VideoAble)) {
-        if (!videoView) {
-            videoView = new QDockWidget("Video View", this);
-            videoView->setAllowedAreas(Qt::AllDockWidgetAreas);
-            videoView->setFeatures(QDockWidget::DockWidgetMovable |
-                                   QDockWidget::DockWidgetFloatable |
-                                   QDockWidget::DockWidgetClosable);
-            addDockWidget(Qt::BottomDockWidgetArea, videoView, Qt::Vertical);
-            videoView->setTitleBarWidget(new QWidget(this));
-            Utils::setTransparentBackgroundForWidget(videoView->titleBarWidget());
-        }
-
-        // Kubat: because the dock is "closable", when closed the widget itself
-        // is not deleted, it will be hidden and the content will be deleted.
-        // TODO: Check if it works on more platforms.
-        videoView->setWidget(new VideoView(videoView));
-        qobject_cast<VideoView *>(videoView->widget())
-            ->loadFile(document->getVideoSubDocument()->getFilePath());
+    if (!document->checkDocumentCapabilities(VivyDocument::Capabilities::VideoAble))
+        return;
+
+    if (!videoView) {
+        videoView = new UnclosableDockWidget("Video View", this);
+        videoView->setAllowedAreas(Qt::AllDockWidgetAreas);
+        videoView->setFeatures(allDockFeatures);
+        addDockWidget(Qt::BottomDockWidgetArea, videoView, Qt::Vertical);
     }
+
+    Utils::deleteInternalWidget(videoView);
+    videoView->setWidget(new VideoView(videoView));
+    qobject_cast<VideoView *>(videoView->widget())
+        ->loadFile(document->getVideoSubDocument()->getFilePath());
 }
 
 void
 VivyDocumentView::loadAssView() noexcept
 {
-    if (document->checkDocumentCapabilities(VivyDocument::Capabilities::AssAble)) {
-        if (!assLines) {
-            assLines = new QDockWidget("ASS Lines", this);
-            assLines->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable);
-            assLines->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea |
-                                      Qt::BottomDockWidgetArea);
-            addDockWidget(Qt::BottomDockWidgetArea, assLines, Qt::Vertical);
-            assLines->setTitleBarWidget(new QWidget(this));
-            Utils::setTransparentBackgroundForWidget(assLines->titleBarWidget());
-        }
-
-        assModel.reset(new AssLinesModel(document->getAssSubDocument()->getLines()));
-        assLines->setWidget(new AssLinesView(assModel.get(), assLines));
+    if (!document->checkDocumentCapabilities(VivyDocument::Capabilities::AssAble))
+        return;
+
+    if (!assLines) {
+        assLines = new UnclosableDockWidget("ASS Lines", this);
+        assLines->setFeatures(onlyClosableDockFeatures);
+        assLines->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea |
+                                  Qt::BottomDockWidgetArea);
+        addDockWidget(Qt::BottomDockWidgetArea, assLines, Qt::Vertical);
     }
+
+    assModel.reset(new AssLinesModel(document->getAssSubDocument()->getLines()));
+    Utils::deleteInternalWidget(assLines);
+    assLines->setWidget(new AssLinesView(assModel.get(), assLines));
 }
 
 void
 VivyDocumentView::loadAudioView() noexcept
 {
-    if (document->checkDocumentCapabilities(VivyDocument::AudioAble)) {
-        std::shared_ptr<AudioSubDocument> audioDocument = document->getAudioSubDocument();
-        qDebug() << "Create an audio vizualizer for the audio sub document"
-                 << audioDocument->getFilePath();
-
-        AudioContext::StreamPtr stream = audioDocument->getDefaultStream();
-        if (stream == nullptr) {
-            qCritical() << "Failed to get default audio stream";
-            return;
-        }
-
-        if (!visualizer) {
-            visualizer = new QDockWidget("Visualizer", this);
-
-            visualizer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
-            visualizer->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::TopDockWidgetArea |
-                                        Qt::BottomDockWidgetArea);
-            visualizer->setFeatures(QDockWidget::DockWidgetMovable |
-                                    QDockWidget::DockWidgetClosable);
-            addDockWidget(Qt::LeftDockWidgetArea, visualizer, Qt::Horizontal);
-            visualizer->setTitleBarWidget(new QWidget(this));
-            Utils::setTransparentBackgroundForWidget(visualizer->titleBarWidget());
-        }
-
-        // Kubat: don't check, may throw an error but don't think we can
-        // recover from it.
-        AudioVisualizer *visualizerInner = new AudioVisualizer(stream, visualizer);
-        visualizer->setWidget(visualizerInner);
-        visualizer->layout()->setAlignment(visualizerInner, Qt::AlignTop);
+    if (!document->checkDocumentCapabilities(VivyDocument::AudioAble))
+        return;
+
+    std::shared_ptr<AudioSubDocument> audioDocument = document->getAudioSubDocument();
+    AudioContext::StreamPtr stream                  = audioDocument->getDefaultStream();
+
+    if (stream == nullptr) {
+        qCritical() << "Failed to get default audio stream";
+        return;
     }
 
-    else {
-        qDebug() << "The document" << document->getName() << "is not AudioAble";
+    if (!visualizer) {
+        visualizer = new UnclosableDockWidget("Visualizer", this);
+        visualizer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
+        visualizer->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::TopDockWidgetArea |
+                                    Qt::BottomDockWidgetArea);
+        visualizer->setFeatures(onlyClosableDockFeatures);
+        addDockWidget(Qt::LeftDockWidgetArea, visualizer, Qt::Horizontal);
     }
+
+    // Kubat: don't check, may throw an error but don't think we can
+    // recover from it.
+    AudioVisualizer *visualizerInner = new AudioVisualizer(stream, visualizer);
+    Utils::deleteInternalWidget(visualizer);
+    visualizer->setWidget(visualizerInner);
+    visualizer->layout()->setAlignment(visualizerInner, Qt::AlignTop);
 }
 
 void
@@ -164,12 +148,7 @@ VivyDocumentView::closeDocument() noexcept
     qDebug() << "Closing the document:" << document->getName() << "( ref count is"
              << document.use_count() << ")";
     vivyApp->documentStore.closeDocument(document->getUuid());
-
-    // Kubat: the visualizer pointer should have been deleted by the
-    // deleteAllContent() call if it was created.
-    deleteAllContent();
-    visualizer = nullptr;
-    property   = nullptr;
+    allowToCloseAllDocks();
 }
 
 QIcon
@@ -181,19 +160,16 @@ VivyDocumentView::getDocumentTabIcon() const noexcept
 void
 VivyDocumentView::openProperties() noexcept
 {
-    propertyModel.reset(new PropertyModel(*document.get()));
-    QTreeView *view = new QTreeView(property);
-    view->setModel(propertyModel.get());
-    view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
-    view->expandAll();
+    propertyModel   = std::make_unique<PropertyModel>(*document.get());
+    QTreeView *view = PropertyModel::getView(property, propertyModel.get());
 
     if (!property) {
-        property = new QDockWidget("Properties", this);
+        property = new UnclosableDockWidget("Properties", this);
         property->setAllowedAreas(Qt::AllDockWidgetAreas);
+        property->setFeatures(allDockFeatures);
         addDockWidget(Qt::RightDockWidgetArea, property, Qt::Vertical);
-        property->setTitleBarWidget(new QWidget(this));
-        Utils::setTransparentBackgroundForWidget(property->titleBarWidget());
     }
 
     property->setWidget(view);
+    openPropertiesAct->setVisible(false);
 }
diff --git a/src/UI/VivyDocumentView.hh b/src/UI/VivyDocumentView.hh
index 4cdcc8efc8d9cd6d1a68be9c0627b68f410f82ab..0a6f13b4519362f6468632348a854051a44e4b64 100644
--- a/src/UI/VivyDocumentView.hh
+++ b/src/UI/VivyDocumentView.hh
@@ -6,21 +6,25 @@
 #endif
 
 #include "AbstractDocumentView.hh"
-
-class QDockWidget;
-class QWidget;
+#include "../Lib/Document/VivyDocument.hh"
+#include "DocumentViews/VideoView.hh"
+#include "DocumentViews/AssLinesView.hh"
+#include "DocumentViews/AssLinesModel.hh"
+#include "PropertyModel.hh"
+#include "UnclosableDockWidget.hh"
 
 namespace Vivy
 {
-class PropertyModel;
-class AssLinesModel;
-class VivyDocument;
-class VideoView;
-
 class VivyDocumentView final : public AbstractDocumentView {
     Q_OBJECT
     VIVY_UNMOVABLE_OBJECT(VivyDocumentView)
 
+    static inline constexpr QDockWidget::DockWidgetFeatures allDockFeatures =
+        QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
+        QDockWidget::DockWidgetMovable;
+    static inline constexpr QDockWidget::DockWidgetFeatures onlyClosableDockFeatures =
+        QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable;
+
 public:
     explicit VivyDocumentView(std::shared_ptr<VivyDocument>, QWidget *parent) noexcept;
     ~VivyDocumentView() noexcept override;
@@ -43,10 +47,11 @@ private:
     std::shared_ptr<VivyDocument> document;
     std::unique_ptr<PropertyModel> propertyModel{ nullptr };
     std::unique_ptr<AssLinesModel> assModel{ nullptr };
-    QDockWidget *visualizer{ nullptr };
-    QDockWidget *property{ nullptr };
-    QDockWidget *assLines{ nullptr };
-    QDockWidget *videoView{ nullptr };
+    UnclosableDockWidget *visualizer{ nullptr };
+    UnclosableDockWidget *property{ nullptr };
+    UnclosableDockWidget *assLines{ nullptr };
+    UnclosableDockWidget *videoView{ nullptr };
+    QAction *openPropertiesAct{ nullptr };
 };
 
 }
diff --git a/src/UI/VivyFileIconProvider.hh b/src/UI/VivyFileIconProvider.hh
index c042aefc963790af8ac2e8f913c16450a80f4416..d219b799ff9ce4677c226021d6c9179cbd8a07a5 100644
--- a/src/UI/VivyFileIconProvider.hh
+++ b/src/UI/VivyFileIconProvider.hh
@@ -6,10 +6,6 @@
 
 #include "../Lib/Utils.hh"
 #include "../VivyApplication.hh"
-#include <QtGlobal>
-#include <QFileIconProvider>
-#include <QFileInfo>
-#include <QIcon>
 
 namespace Vivy
 {
diff --git a/src/VivyApplication.cc b/src/VivyApplication.cc
index 075638a9b25784b45ea64be5195a208210b3e330..efb10c01c29927a412fa85c03a65975524f00250 100644
--- a/src/VivyApplication.cc
+++ b/src/VivyApplication.cc
@@ -1,11 +1,6 @@
 #include "VivyApplication.hh"
 #include "UI/MainWindow.hh"
 
-#include <QtGlobal>
-#include <QIcon>
-#include <QFontDatabase>
-#include <locale>
-
 using namespace Vivy;
 
 VivyApplication::VivyApplication(int &argc, char **argv)
@@ -35,7 +30,8 @@ VivyApplication::setTheme(Theme theme) noexcept
 int
 VivyApplication::exec() noexcept
 {
-    // For MPV
+    // For MPV & Qt
+    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
     std::setlocale(LC_NUMERIC, "C");
 
     // Add fonts
diff --git a/src/VivyApplication.hh b/src/VivyApplication.hh
index 3826063a9838fcb173a0dcc0ff72b6575ce3d5ec..0bfa830a8a20267695db7d08faf19f7ed076da55 100644
--- a/src/VivyApplication.hh
+++ b/src/VivyApplication.hh
@@ -16,6 +16,7 @@
 #define VIVY_ICON_NEW     ":icons/dark/document-new.svg"
 #define VIVY_ICON_SAVE    ":icons/dark/document-save.svg"
 #define VIVY_ICON_SAVE_AS ":icons/dark/document-save-as.svg"
+#define VIVY_ICON_RENAME  ":icons/dark/document-rename.svg"
 #define VIVY_ICON_ABOUT   ":icons/dark/help-about.svg"
 #define VIVY_ICON_FILE    ":icons/dark/text-x-generic.svg"
 #define VIVY_ICON_FOLDER  ":icons/dark/folder.svg"
@@ -23,16 +24,14 @@
 #define VIVY_ICON_PAUSE   ":icons/dark/media-pause.svg"
 #define VIVY_ICON_STOP    ":icons/dark/media-stop.svg"
 
-#include <QApplication>
-#include <QPixmap>
-#include <QFont>
-
 // Detect MacOS
 #if defined(Q_OS_DARWIN) || defined(Q_OS_MACOS)
 #define VIVY_MACOS
 #endif
 
+#include "Lib/Script/ScriptStore.hh"
 #include "Lib/Document/VivyDocumentStore.hh"
+#include "UI/MainWindow.hh"
 
 namespace Vivy
 {
diff --git a/src/VivyCli.cc b/src/VivyCli.cc
index 1305babcbb026ba9c5984a00aae8529bfcdb2202..0dedba63eb20d103bb628c7a8ad915dfa44ce38d 100644
--- a/src/VivyCli.cc
+++ b/src/VivyCli.cc
@@ -1,24 +1,27 @@
 #include "VivyCli.hh"
 #include <iostream>
+#include <QTextCodec>
 
 using namespace Vivy;
 
 VivyCli::VivyCli(int &argc, char **argv) noexcept
 {
     if (argc >= 2) {
-        selectedDoc = scriptStore.loadDocument(QString(argv[1]));
+        selectedDoc = scriptStore.loadDocument(QString::fromUtf8(argv[1]));
     }
 }
 
 int
 VivyCli::exec() noexcept
 {
+    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
+
     if ((selectedDoc == nullptr) || (!scriptStore.executeScript(selectedDoc->getUuid())))
         return 1;
 
     for (const auto &str : scriptStore.getLoadedModules()) {
         std::cout << "Module " << str << " was loaded!\n";
-        const auto *mod = scriptStore.getModule(str);
+        [[maybe_unused]] const auto *mod = scriptStore.getModule(str);
     }
     return 0;
 }