From 8b804d1995d56cb6ddd694b5a870da0f2ef36d81 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 28 May 2019 17:45:30 +0200 Subject: [PATCH] Allow sending animated stickers. --- .../SourceFiles/lottie/lottie_animation.cpp | 84 +++++++++++-------- .../SourceFiles/lottie/lottie_animation.h | 6 +- .../SourceFiles/storage/localimageloader.cpp | 21 ++++- 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/lottie/lottie_animation.cpp b/Telegram/SourceFiles/lottie/lottie_animation.cpp index 1f833651d..d9bfc0785 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.cpp +++ b/Telegram/SourceFiles/lottie/lottie_animation.cpp @@ -21,8 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Lottie { namespace { -constexpr auto kMaxSize = 1024 * 1024; - QByteArray UnpackGzip(const QByteArray &bytes) { z_stream stream; stream.zalloc = nullptr; @@ -36,7 +34,7 @@ QByteArray UnpackGzip(const QByteArray &bytes) { } const auto guard = gsl::finally([&] { inflateEnd(&stream); }); - auto result = QByteArray(kMaxSize + 1, Qt::Uninitialized); + auto result = QByteArray(kMaxFileSize + 1, Qt::Uninitialized); stream.avail_in = bytes.size(); stream.next_in = reinterpret_cast(const_cast(bytes.data())); stream.avail_out = 0; @@ -84,34 +82,58 @@ std::unique_ptr FromData(const QByteArray &data) { return std::make_unique(base::duplicate(data)); } +auto Init(QByteArray &&content) +-> base::variant, Error> { + if (content.size() > kMaxFileSize) { + qWarning() + << "Lottie Error: Too large file: " + << content.size(); + return Error::ParseFailed; + } + content = UnpackGzip(content); + if (content.size() > kMaxFileSize) { + qWarning() + << "Lottie Error: Too large file: " + << content.size(); + return Error::ParseFailed; + } + const auto document = JsonDocument(std::move(content)); + if (const auto error = document.error()) { + qWarning() + << "Lottie Error: Parse failed with code: " + << error; + return Error::ParseFailed; + } + auto result = std::make_unique(document.root()); + auto information = result->information(); + if (!information.frameRate + || information.framesCount <= 0 + || information.size.isEmpty()) { + return Error::NotSupported; + } + return std::move(result); +} + +QImage ReadThumbnail(QByteArray &&content) { + return Init(std::move(content)).match([]( + const std::unique_ptr &state) { + return state->frameForPaint()->original; + }, [](Error) { + return QImage(); + }); +} + Animation::Animation(QByteArray &&content) : _timer([=] { checkNextFrameRender(); }) { const auto weak = base::make_weak(this); crl::async([=, content = base::take(content)]() mutable { - content = UnpackGzip(content); - if (content.size() > kMaxSize) { - qWarning() - << "Lottie Error: Too large file: " - << content.size(); - crl::on_main(weak, [=] { - parseFailed(); + crl::on_main(weak, [this, result = Init(std::move(content))]() mutable { + result.match([&](std::unique_ptr &state) { + parseDone(std::move(state)); + }, [&](Error error) { + parseFailed(error); }); - return; - } - const auto document = JsonDocument(std::move(content)); - if (const auto error = document.error()) { - qWarning() - << "Lottie Error: Parse failed with code: " - << error; - crl::on_main(weak, [=] { - parseFailed(); - }); - } else { - auto state = std::make_unique(document.root()); - crl::on_main(weak, [this, result = std::move(state)]() mutable { - parseDone(std::move(result)); - }); - } + }); }); } @@ -126,12 +148,6 @@ void Animation::parseDone(std::unique_ptr state) { Expects(state != nullptr); auto information = state->information(); - if (!information.frameRate - || information.framesCount <= 0 - || information.size.isEmpty()) { - _updates.fire_error(Error::NotSupported); - return; - } _state = state.get(); _state->start(this, crl::now()); _renderer = FrameRenderer::Instance(); @@ -144,8 +160,8 @@ void Animation::parseDone(std::unique_ptr state) { }, _lifetime); } -void Animation::parseFailed() { - _updates.fire_error(Error::ParseFailed); +void Animation::parseFailed(Error error) { + _updates.fire_error(std::move(error)); } QImage Animation::frame(const FrameRequest &request) const { diff --git a/Telegram/SourceFiles/lottie/lottie_animation.h b/Telegram/SourceFiles/lottie/lottie_animation.h index 5dd194001..812d2c5e6 100644 --- a/Telegram/SourceFiles/lottie/lottie_animation.h +++ b/Telegram/SourceFiles/lottie/lottie_animation.h @@ -25,6 +25,8 @@ class QByteArray; namespace Lottie { +constexpr auto kMaxFileSize = 1024 * 1024; + class Animation; class SharedState; class FrameRenderer; @@ -33,6 +35,8 @@ bool ValidateFile(const QString &path); std::unique_ptr FromFile(const QString &path); std::unique_ptr FromData(const QByteArray &data); +QImage ReadThumbnail(QByteArray &&content); + class Animation final : public base::has_weak_ptr { public: explicit Animation(QByteArray &&content); @@ -54,7 +58,7 @@ public: private: void parseDone(std::unique_ptr state); - void parseFailed(); + void parseFailed(Error error); void checkNextFrameAvailability(); void checkNextFrameRender(); diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 71e6a4137..c6ecc0486 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/mime_type.h" #include "media/audio/media_audio.h" #include "media/clip/media_clip_reader.h" +#include "lottie/lottie_animation.h" #include "history/history_item.h" #include "boxes/send_files_box.h" #include "boxes/confirm_box.h" @@ -74,6 +75,18 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) { return result; } +PreparedFileThumbnail PrepareAnimatedStickerThumbnail( + const QString &file, + const QByteArray &bytes) { + return PrepareFileThumbnail(Lottie::ReadThumbnail([&] { + if (!bytes.isEmpty()) { + return bytes; + } + auto f = QFile(file); + return f.open(QIODevice::ReadOnly) ? f.readAll() : QByteArray(); + }())); +} + bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) { constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024; const auto kThumbnailKnownMimes = { @@ -817,6 +830,10 @@ void FileLoadTask::process() { QByteArray goodThumbnailBytes; QVector attributes(1, MTP_documentAttributeFilename(MTP_string(filename))); + const auto checkAnimatedSticker = filename.endsWith(qstr(".tgs"), Qt::CaseInsensitive); + if (checkAnimatedSticker) { + filemime = "application/x-tgsticker"; + } auto thumbnail = PreparedFileThumbnail(); @@ -855,6 +872,8 @@ void FileLoadTask::process() { } thumbnail = PrepareFileThumbnail(std::move(video->thumbnail)); + } else if (checkAnimatedSticker) { + thumbnail = PrepareAnimatedStickerThumbnail(_filepath, _content); } } @@ -918,7 +937,7 @@ void FileLoadTask::process() { std::move(thumbnail), filemime, filesize, - isSticker); + isSticker || checkAnimatedSticker); if (_type == SendMediaType::Photo && photo.type() == mtpc_photoEmpty) { _type = SendMediaType::File;