diff options
author | Nicolas Werner <nicolas.werner@hotmail.de> | 2021-08-29 05:20:23 +0200 |
---|---|---|
committer | Nicolas Werner <nicolas.werner@hotmail.de> | 2021-08-29 16:32:28 +0200 |
commit | ef068ac2b30bd5f9ed0f299dbc75eb3ace000042 (patch) | |
tree | 579d0ee8fac2001ba956ac8536f168f3d32472ae /src/ui | |
parent | Use in memory media player instead of storing unencrypted files on disk (diff) | |
download | nheko-ef068ac2b30bd5f9ed0f299dbc75eb3ace000042.tar.xz |
Support animated images
fixes #461
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/MxcAnimatedImage.cpp | 164 | ||||
-rw-r--r-- | src/ui/MxcAnimatedImage.h | 79 | ||||
-rw-r--r-- | src/ui/MxcMediaProxy.cpp | 4 |
3 files changed, 245 insertions, 2 deletions
diff --git a/src/ui/MxcAnimatedImage.cpp b/src/ui/MxcAnimatedImage.cpp new file mode 100644 index 00000000..cfc03827 --- /dev/null +++ b/src/ui/MxcAnimatedImage.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MxcAnimatedImage.h" + +#include <QDir> +#include <QFileInfo> +#include <QMimeDatabase> +#include <QQuickWindow> +#include <QSGImageNode> +#include <QStandardPaths> + +#include "EventAccessors.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "timeline/TimelineModel.h" + +void +MxcAnimatedImage::startDownload() +{ + if (!room_) + return; + if (eventId_.isEmpty()) + return; + + auto event = room_->eventById(eventId_); + if (!event) { + nhlog::ui()->error("Failed to load media for event {}, event not found.", + eventId_.toStdString()); + return; + } + + QByteArray mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)).toUtf8(); + + animatable_ = QMovie::supportedFormats().contains(mimeType.split('/').back()); + animatableChanged(); + + if (!animatable_) + return; + + QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event)); + QString originalFilename = QString::fromStdString(mtx::accessors::filename(*event)); + + auto encryptionInfo = mtx::accessors::file(*event); + + // If the message is a link to a non mxcUrl, don't download it + if (!mxcUrl.startsWith("mxc://")) { + return; + } + + QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix(); + + const auto url = mxcUrl.toStdString(); + const auto name = QString(mxcUrl).remove("mxc://"); + QFileInfo filename(QString("%1/media_cache/media/%2.%3") + .arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) + .arg(name) + .arg(suffix)); + if (QDir::cleanPath(name) != name) { + nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url); + return; + } + + QDir().mkpath(filename.path()); + + QPointer<MxcAnimatedImage> self = this; + + auto processBuffer = [this, mimeType, encryptionInfo, self](QIODevice &device) { + if (!self) + return; + + if (buffer.isOpen()) { + movie.stop(); + movie.setDevice(nullptr); + buffer.close(); + } + + if (encryptionInfo) { + QByteArray ba = device.readAll(); + std::string temp(ba.constData(), ba.size()); + temp = mtx::crypto::to_string( + mtx::crypto::decrypt_file(temp, encryptionInfo.value())); + buffer.setData(temp.data(), temp.size()); + } else { + buffer.setData(device.readAll()); + } + buffer.open(QIODevice::ReadOnly); + buffer.reset(); + + QTimer::singleShot(0, this, [this, mimeType] { + nhlog::ui()->info("Playing movie with size: {}, {}", + buffer.bytesAvailable(), + buffer.isOpen()); + movie.setFormat(mimeType); + movie.setDevice(&buffer); + movie.start(); + emit loadedChanged(); + }); + }; + + if (filename.isReadable()) { + QFile f(filename.filePath()); + if (f.open(QIODevice::ReadOnly)) { + processBuffer(f); + return; + } + } + + http::client()->download( + url, + [filename, url, processBuffer](const std::string &data, + const std::string &, + const std::string &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve media {}: {} {}", + url, + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } + + try { + QFile file(filename.filePath()); + + if (!file.open(QIODevice::WriteOnly)) + return; + + QByteArray ba(data.data(), (int)data.size()); + file.write(ba); + file.close(); + + QBuffer buf(&ba); + buf.open(QBuffer::ReadOnly); + processBuffer(buf); + } catch (const std::exception &e) { + nhlog::ui()->warn("Error while saving file to: {}", e.what()); + } + }); +} + +QSGNode * +MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) +{ + imageDirty = false; + QSGImageNode *n = static_cast<QSGImageNode *>(oldNode); + if (!n) + n = window()->createImageNode(); + + // n->setTexture(nullptr); + auto img = movie.currentImage(); + if (!img.isNull()) + n->setTexture(window()->createTextureFromImage(img)); + else + return nullptr; + + n->setSourceRect(img.rect()); + n->setRect(QRect(0, 0, width(), height())); + n->setFiltering(QSGTexture::Linear); + n->setMipmapFiltering(QSGTexture::Linear); + + return n; +} diff --git a/src/ui/MxcAnimatedImage.h b/src/ui/MxcAnimatedImage.h new file mode 100644 index 00000000..7b9502e0 --- /dev/null +++ b/src/ui/MxcAnimatedImage.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <QBuffer> +#include <QMovie> +#include <QObject> +#include <QQuickItem> + +class TimelineModel; + +// This is an AnimatedImage, that can draw encrypted images +class MxcAnimatedImage : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(TimelineModel *roomm READ room WRITE setRoom NOTIFY roomChanged REQUIRED) + Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged) + Q_PROPERTY(bool animatable READ animatable NOTIFY animatableChanged) + Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged) +public: + MxcAnimatedImage(QQuickItem *parent = nullptr) + : QQuickItem(parent) + { + connect(this, &MxcAnimatedImage::eventIdChanged, &MxcAnimatedImage::startDownload); + connect(this, &MxcAnimatedImage::roomChanged, &MxcAnimatedImage::startDownload); + connect(&movie, &QMovie::frameChanged, this, &MxcAnimatedImage::newFrame); + setFlag(QQuickItem::ItemHasContents); + // setAcceptHoverEvents(true); + } + + bool animatable() const { return animatable_; } + bool loaded() const { return buffer.size() > 0; } + QString eventId() const { return eventId_; } + TimelineModel *room() const { return room_; } + void setEventId(QString newEventId) + { + if (eventId_ != newEventId) { + eventId_ = newEventId; + emit eventIdChanged(); + } + } + void setRoom(TimelineModel *room) + { + if (room_ != room) { + room_ = room; + emit roomChanged(); + } + } + + QSGNode *updatePaintNode(QSGNode *oldNode, + QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override; + +signals: + void roomChanged(); + void eventIdChanged(); + void animatableChanged(); + void loadedChanged(); + +private slots: + void startDownload(); + void newFrame(int frame) + { + currentFrame = frame; + imageDirty = true; + update(); + } + +private: + TimelineModel *room_ = nullptr; + QString eventId_; + QString filename_; + bool animatable_ = false; + QBuffer buffer; + QMovie movie; + int currentFrame = 0; + bool imageDirty = true; +}; diff --git a/src/ui/MxcMediaProxy.cpp b/src/ui/MxcMediaProxy.cpp index c1de2c31..dc65de7c 100644 --- a/src/ui/MxcMediaProxy.cpp +++ b/src/ui/MxcMediaProxy.cpp @@ -91,11 +91,11 @@ MxcMediaProxy::startDownload() buffer.open(QIODevice::ReadOnly); buffer.reset(); - QTimer::singleShot(0, this, [this, self, filename] { + QTimer::singleShot(0, this, [this, filename] { nhlog::ui()->info("Playing buffer with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen()); - self->setMedia(QMediaContent(filename.fileName()), &buffer); + this->setMedia(QMediaContent(filename.fileName()), &buffer); emit loadedChanged(); }); }; |