mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 10:11:41 -05:00
Allow sending animated stickers.
This commit is contained in:
parent
af85aec33b
commit
8b804d1995
3 changed files with 75 additions and 36 deletions
|
@ -21,8 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kMaxSize = 1024 * 1024;
|
|
||||||
|
|
||||||
QByteArray UnpackGzip(const QByteArray &bytes) {
|
QByteArray UnpackGzip(const QByteArray &bytes) {
|
||||||
z_stream stream;
|
z_stream stream;
|
||||||
stream.zalloc = nullptr;
|
stream.zalloc = nullptr;
|
||||||
|
@ -36,7 +34,7 @@ QByteArray UnpackGzip(const QByteArray &bytes) {
|
||||||
}
|
}
|
||||||
const auto guard = gsl::finally([&] { inflateEnd(&stream); });
|
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.avail_in = bytes.size();
|
||||||
stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(bytes.data()));
|
stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(bytes.data()));
|
||||||
stream.avail_out = 0;
|
stream.avail_out = 0;
|
||||||
|
@ -84,34 +82,58 @@ std::unique_ptr<Animation> FromData(const QByteArray &data) {
|
||||||
return std::make_unique<Animation>(base::duplicate(data));
|
return std::make_unique<Animation>(base::duplicate(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
Animation::Animation(QByteArray &&content)
|
auto Init(QByteArray &&content)
|
||||||
: _timer([=] { checkNextFrameRender(); }) {
|
-> base::variant<std::unique_ptr<SharedState>, Error> {
|
||||||
const auto weak = base::make_weak(this);
|
if (content.size() > kMaxFileSize) {
|
||||||
crl::async([=, content = base::take(content)]() mutable {
|
|
||||||
content = UnpackGzip(content);
|
|
||||||
if (content.size() > kMaxSize) {
|
|
||||||
qWarning()
|
qWarning()
|
||||||
<< "Lottie Error: Too large file: "
|
<< "Lottie Error: Too large file: "
|
||||||
<< content.size();
|
<< content.size();
|
||||||
crl::on_main(weak, [=] {
|
return Error::ParseFailed;
|
||||||
parseFailed();
|
}
|
||||||
});
|
content = UnpackGzip(content);
|
||||||
return;
|
if (content.size() > kMaxFileSize) {
|
||||||
|
qWarning()
|
||||||
|
<< "Lottie Error: Too large file: "
|
||||||
|
<< content.size();
|
||||||
|
return Error::ParseFailed;
|
||||||
}
|
}
|
||||||
const auto document = JsonDocument(std::move(content));
|
const auto document = JsonDocument(std::move(content));
|
||||||
if (const auto error = document.error()) {
|
if (const auto error = document.error()) {
|
||||||
qWarning()
|
qWarning()
|
||||||
<< "Lottie Error: Parse failed with code: "
|
<< "Lottie Error: Parse failed with code: "
|
||||||
<< error;
|
<< error;
|
||||||
crl::on_main(weak, [=] {
|
return Error::ParseFailed;
|
||||||
parseFailed();
|
}
|
||||||
});
|
auto result = std::make_unique<SharedState>(document.root());
|
||||||
} else {
|
auto information = result->information();
|
||||||
auto state = std::make_unique<SharedState>(document.root());
|
if (!information.frameRate
|
||||||
crl::on_main(weak, [this, result = std::move(state)]() mutable {
|
|| information.framesCount <= 0
|
||||||
parseDone(std::move(result));
|
|| 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 {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,12 +148,6 @@ void Animation::parseDone(std::unique_ptr<SharedState> state) {
|
||||||
Expects(state != nullptr);
|
Expects(state != nullptr);
|
||||||
|
|
||||||
auto information = state->information();
|
auto information = state->information();
|
||||||
if (!information.frameRate
|
|
||||||
|| information.framesCount <= 0
|
|
||||||
|| information.size.isEmpty()) {
|
|
||||||
_updates.fire_error(Error::NotSupported);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_state = state.get();
|
_state = state.get();
|
||||||
_state->start(this, crl::now());
|
_state->start(this, crl::now());
|
||||||
_renderer = FrameRenderer::Instance();
|
_renderer = FrameRenderer::Instance();
|
||||||
|
@ -144,8 +160,8 @@ void Animation::parseDone(std::unique_ptr<SharedState> state) {
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::parseFailed() {
|
void Animation::parseFailed(Error error) {
|
||||||
_updates.fire_error(Error::ParseFailed);
|
_updates.fire_error(std::move(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage Animation::frame(const FrameRequest &request) const {
|
QImage Animation::frame(const FrameRequest &request) const {
|
||||||
|
|
|
@ -25,6 +25,8 @@ class QByteArray;
|
||||||
|
|
||||||
namespace Lottie {
|
namespace Lottie {
|
||||||
|
|
||||||
|
constexpr auto kMaxFileSize = 1024 * 1024;
|
||||||
|
|
||||||
class Animation;
|
class Animation;
|
||||||
class SharedState;
|
class SharedState;
|
||||||
class FrameRenderer;
|
class FrameRenderer;
|
||||||
|
@ -33,6 +35,8 @@ bool ValidateFile(const QString &path);
|
||||||
std::unique_ptr<Animation> FromFile(const QString &path);
|
std::unique_ptr<Animation> FromFile(const QString &path);
|
||||||
std::unique_ptr<Animation> FromData(const QByteArray &data);
|
std::unique_ptr<Animation> FromData(const QByteArray &data);
|
||||||
|
|
||||||
|
QImage ReadThumbnail(QByteArray &&content);
|
||||||
|
|
||||||
class Animation final : public base::has_weak_ptr {
|
class Animation final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
explicit Animation(QByteArray &&content);
|
explicit Animation(QByteArray &&content);
|
||||||
|
@ -54,7 +58,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void parseDone(std::unique_ptr<SharedState> state);
|
void parseDone(std::unique_ptr<SharedState> state);
|
||||||
void parseFailed();
|
void parseFailed(Error error);
|
||||||
|
|
||||||
void checkNextFrameAvailability();
|
void checkNextFrameAvailability();
|
||||||
void checkNextFrameRender();
|
void checkNextFrameRender();
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/mime_type.h"
|
#include "core/mime_type.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "media/clip/media_clip_reader.h"
|
#include "media/clip/media_clip_reader.h"
|
||||||
|
#include "lottie/lottie_animation.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "boxes/send_files_box.h"
|
#include "boxes/send_files_box.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
|
@ -74,6 +75,18 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
|
||||||
return result;
|
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) {
|
bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
|
||||||
constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024;
|
constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024;
|
||||||
const auto kThumbnailKnownMimes = {
|
const auto kThumbnailKnownMimes = {
|
||||||
|
@ -817,6 +830,10 @@ void FileLoadTask::process() {
|
||||||
QByteArray goodThumbnailBytes;
|
QByteArray goodThumbnailBytes;
|
||||||
|
|
||||||
QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(filename)));
|
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();
|
auto thumbnail = PreparedFileThumbnail();
|
||||||
|
|
||||||
|
@ -855,6 +872,8 @@ void FileLoadTask::process() {
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnail = PrepareFileThumbnail(std::move(video->thumbnail));
|
thumbnail = PrepareFileThumbnail(std::move(video->thumbnail));
|
||||||
|
} else if (checkAnimatedSticker) {
|
||||||
|
thumbnail = PrepareAnimatedStickerThumbnail(_filepath, _content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -918,7 +937,7 @@ void FileLoadTask::process() {
|
||||||
std::move(thumbnail),
|
std::move(thumbnail),
|
||||||
filemime,
|
filemime,
|
||||||
filesize,
|
filesize,
|
||||||
isSticker);
|
isSticker || checkAnimatedSticker);
|
||||||
|
|
||||||
if (_type == SendMediaType::Photo && photo.type() == mtpc_photoEmpty) {
|
if (_type == SendMediaType::Photo && photo.type() == mtpc_photoEmpty) {
|
||||||
_type = SendMediaType::File;
|
_type = SendMediaType::File;
|
||||||
|
|
Loading…
Add table
Reference in a new issue