2021-08-29 06:20:23 +03:00
|
|
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
2022-01-01 06:57:53 +03:00
|
|
|
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
2021-08-29 06:20:23 +03:00
|
|
|
//
|
|
|
|
// 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()
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!room_)
|
|
|
|
return;
|
|
|
|
if (eventId_.isEmpty())
|
|
|
|
return;
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
auto event = room_->eventById(eventId_);
|
|
|
|
if (!event) {
|
|
|
|
nhlog::ui()->error("Failed to load media for event {}, event not found.",
|
|
|
|
eventId_.toStdString());
|
|
|
|
return;
|
|
|
|
}
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
QByteArray mimeType = QString::fromStdString(mtx::accessors::mimetype(*event)).toUtf8();
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2022-09-30 05:03:39 +03:00
|
|
|
static const auto formats = QMovie::supportedFormats();
|
|
|
|
animatable_ = formats.contains(mimeType.split('/').back());
|
2021-09-18 01:22:33 +03:00
|
|
|
animatableChanged();
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!animatable_)
|
|
|
|
return;
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2021-12-29 00:30:12 +03:00
|
|
|
QString mxcUrl = QString::fromStdString(mtx::accessors::url(*event));
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
auto encryptionInfo = mtx::accessors::file(*event);
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2021-09-18 01:22:33 +03:00
|
|
|
// If the message is a link to a non mxcUrl, don't download it
|
2021-12-29 06:28:08 +03:00
|
|
|
if (!mxcUrl.startsWith(QLatin1String("mxc://"))) {
|
2021-09-18 01:22:33 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
|
|
|
|
|
|
|
|
const auto url = mxcUrl.toStdString();
|
2021-12-29 06:28:08 +03:00
|
|
|
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
|
2021-12-29 00:30:12 +03:00
|
|
|
QFileInfo filename(
|
|
|
|
QStringLiteral("%1/media_cache/media/%2.%3")
|
|
|
|
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
|
2021-09-18 01:22:33 +03:00
|
|
|
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;
|
|
|
|
|
2022-09-30 05:03:39 +03:00
|
|
|
auto processBuffer = [this, mimeType, encryptionInfo, self](QIODevice &device) {
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!self)
|
|
|
|
return;
|
2021-08-29 06:20:23 +03:00
|
|
|
|
2022-05-04 09:26:03 +03:00
|
|
|
try {
|
|
|
|
if (buffer.isOpen()) {
|
2022-09-30 05:03:39 +03:00
|
|
|
movie.stop();
|
|
|
|
movie.setDevice(nullptr);
|
2022-05-04 09:26:03 +03:00
|
|
|
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();
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
nhlog::net()->error("Failed to setup animated image buffer: {}", e.what());
|
2021-08-29 06:20:23 +03:00
|
|
|
}
|
|
|
|
|
2022-09-30 05:03:39 +03:00
|
|
|
QTimer::singleShot(0, this, [this, mimeType] {
|
2021-09-18 01:22:33 +03:00
|
|
|
nhlog::ui()->info(
|
|
|
|
"Playing movie with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
|
2022-09-30 05:03:39 +03:00
|
|
|
movie.setFormat(mimeType);
|
|
|
|
movie.setDevice(&buffer);
|
2022-01-02 23:46:29 +03:00
|
|
|
|
|
|
|
if (height() != 0 && width() != 0)
|
2022-09-30 05:03:39 +03:00
|
|
|
movie.setScaledSize(this->size().toSize());
|
|
|
|
if (buffer.bytesAvailable() <
|
|
|
|
4LL * 1024 * 1024 * 1024) // cache images smaller than 4MB in RAM
|
|
|
|
movie.setCacheMode(QMovie::CacheAll);
|
2021-09-18 01:22:33 +03:00
|
|
|
if (play_)
|
2022-09-30 05:03:39 +03:00
|
|
|
movie.start();
|
2021-09-18 01:22:33 +03:00
|
|
|
else
|
2022-09-30 05:03:39 +03:00
|
|
|
movie.jumpToFrame(0);
|
2021-09-18 01:22:33 +03:00
|
|
|
emit loadedChanged();
|
|
|
|
update();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
});
|
2021-08-29 06:20:23 +03:00
|
|
|
}
|
|
|
|
|
2022-01-02 23:46:29 +03:00
|
|
|
void
|
|
|
|
MxcAnimatedImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
|
|
|
{
|
|
|
|
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
|
|
|
|
|
|
|
if (newGeometry.size() != oldGeometry.size()) {
|
2022-04-26 17:46:25 +03:00
|
|
|
if (height() != 0 && width() != 0) {
|
2022-09-30 05:03:39 +03:00
|
|
|
QSizeF r = movie.scaledSize();
|
2022-04-26 17:46:25 +03:00
|
|
|
r.scale(newGeometry.size(), Qt::KeepAspectRatio);
|
2022-09-30 05:03:39 +03:00
|
|
|
movie.setScaledSize(r.toSize());
|
2022-05-11 13:28:27 +03:00
|
|
|
imageDirty = true;
|
|
|
|
update();
|
2022-04-26 17:46:25 +03:00
|
|
|
}
|
2022-01-02 23:46:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 06:20:23 +03:00
|
|
|
QSGNode *
|
|
|
|
MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
|
|
|
|
{
|
2021-09-18 01:22:33 +03:00
|
|
|
if (!imageDirty)
|
|
|
|
return oldNode;
|
|
|
|
|
|
|
|
imageDirty = false;
|
|
|
|
QSGImageNode *n = static_cast<QSGImageNode *>(oldNode);
|
|
|
|
if (!n) {
|
|
|
|
n = window()->createImageNode();
|
|
|
|
n->setOwnsTexture(true);
|
|
|
|
// n->setFlags(QSGNode::OwnedByParent | QSGNode::OwnsGeometry |
|
|
|
|
// GSGNode::OwnsMaterial);
|
|
|
|
n->setFlags(QSGNode::OwnedByParent);
|
|
|
|
}
|
|
|
|
|
2022-09-30 05:03:39 +03:00
|
|
|
auto img = movie.currentImage();
|
|
|
|
n->setSourceRect(img.rect());
|
|
|
|
if (!img.isNull())
|
|
|
|
n->setTexture(window()->createTextureFromImage(std::move(img)));
|
2021-09-18 01:22:33 +03:00
|
|
|
else {
|
|
|
|
delete n;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-09-30 05:03:39 +03:00
|
|
|
QSizeF r = img.size();
|
2022-05-11 13:28:27 +03:00
|
|
|
r.scale(size(), Qt::KeepAspectRatio);
|
2022-04-26 17:46:25 +03:00
|
|
|
|
|
|
|
n->setRect((width() - r.width()) / 2, (height() - r.height()) / 2, r.width(), r.height());
|
2022-01-02 23:46:29 +03:00
|
|
|
n->setFiltering(QSGTexture::Linear);
|
2021-12-30 08:10:19 +03:00
|
|
|
n->setMipmapFiltering(QSGTexture::None);
|
2021-09-18 01:22:33 +03:00
|
|
|
|
|
|
|
return n;
|
2021-08-29 06:20:23 +03:00
|
|
|
}
|