Allow sending animated stickers.

This commit is contained in:
John Preston 2019-05-28 17:45:30 +02:00
parent af85aec33b
commit 8b804d1995
3 changed files with 75 additions and 36 deletions

View file

@ -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<Bytef*>(const_cast<char*>(bytes.data()));
stream.avail_out = 0;
@ -84,34 +82,58 @@ std::unique_ptr<Animation> FromData(const QByteArray &data) {
return std::make_unique<Animation>(base::duplicate(data));
}
auto Init(QByteArray &&content)
-> base::variant<std::unique_ptr<SharedState>, 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<SharedState>(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<SharedState> &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<SharedState> &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<SharedState>(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<SharedState> 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<SharedState> 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 {

View file

@ -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<Animation> FromFile(const QString &path);
std::unique_ptr<Animation> 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<SharedState> state);
void parseFailed();
void parseFailed(Error error);
void checkNextFrameAvailability();
void checkNextFrameRender();

View file

@ -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<MTPDocumentAttribute> 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;