Fix sending of .tgs stickers.

This commit is contained in:
John Preston 2019-07-03 13:03:01 +02:00
parent da48a78f7c
commit 7034df49e9
5 changed files with 72 additions and 39 deletions

View file

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/grouped_layout.h" #include "ui/grouped_layout.h"
#include "ui/text_options.h" #include "ui/text_options.h"
#include "ui/special_buttons.h" #include "ui/special_buttons.h"
#include "lottie/lottie_single_player.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -41,6 +42,7 @@ constexpr auto kMinPreviewWidth = 20;
constexpr auto kShrinkDuration = crl::time(150); constexpr auto kShrinkDuration = crl::time(150);
constexpr auto kDragDuration = crl::time(200); constexpr auto kDragDuration = crl::time(200);
const auto kStickerMimeString = qstr("image/webp"); const auto kStickerMimeString = qstr("image/webp");
const auto kAnimatedStickerMimeString = qstr("application/x-tgsticker");
class SingleMediaPreview : public Ui::RpWidget { class SingleMediaPreview : public Ui::RpWidget {
public: public:
@ -82,6 +84,7 @@ private:
int _previewWidth = 0; int _previewWidth = 0;
int _previewHeight = 0; int _previewHeight = 0;
Media::Clip::ReaderPointer _gifPreview; Media::Clip::ReaderPointer _gifPreview;
std::unique_ptr<Lottie::SinglePlayer> _lottiePreview;
}; };
@ -590,7 +593,8 @@ SingleMediaPreview *SingleMediaPreview::Create(
preview.height())) { preview.height())) {
return nullptr; return nullptr;
} }
const auto sticker = (file.information->filemime == kStickerMimeString); const auto sticker = (file.information->filemime == kStickerMimeString)
|| (file.information->filemime == kAnimatedStickerMimeString);
return Ui::CreateChild<SingleMediaPreview>( return Ui::CreateChild<SingleMediaPreview>(
parent, parent,
controller, controller,
@ -627,7 +631,7 @@ void SingleMediaPreview::preparePreview(
const QString &animatedPreviewPath) { const QString &animatedPreviewPath) {
auto maxW = 0; auto maxW = 0;
auto maxH = 0; auto maxH = 0;
if (_animated) { if (_animated && !_sticker) {
auto limitW = st::sendMediaPreviewSize; auto limitW = st::sendMediaPreviewSize;
auto limitH = st::confirmMaxHeight; auto limitH = st::confirmMaxHeight;
maxW = qMax(preview.width(), 1); maxW = qMax(preview.width(), 1);
@ -683,7 +687,17 @@ void SingleMediaPreview::preparePreview(
void SingleMediaPreview::prepareAnimatedPreview( void SingleMediaPreview::prepareAnimatedPreview(
const QString &animatedPreviewPath) { const QString &animatedPreviewPath) {
if (!animatedPreviewPath.isEmpty()) { if (_sticker && _animated) {
const auto box = QSize(_previewWidth, _previewHeight)
* cIntRetinaFactor();
_lottiePreview = std::make_unique<Lottie::SinglePlayer>(
Lottie::ReadContent(QByteArray(), animatedPreviewPath),
Lottie::FrameRequest{ box });
_lottiePreview->updates(
) | rpl::start_with_next([=] {
update();
}, lifetime());
} else if (!animatedPreviewPath.isEmpty()) {
auto callback = [=](Media::Clip::Notification notification) { auto callback = [=](Media::Clip::Notification notification) {
clipCallback(notification); clipCallback(notification);
}; };
@ -734,10 +748,21 @@ void SingleMediaPreview::paintEvent(QPaintEvent *e) {
auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer); auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now()); auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame); p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), frame);
} else if (_lottiePreview && _lottiePreview->ready()) {
const auto frame = _lottiePreview->frame();
const auto size = frame.size() / cIntRetinaFactor();
p.drawImage(
QRect(
_previewLeft + (_previewWidth - size.width()) / 2,
st::boxPhotoPadding.top() + (_previewHeight - size.height()) / 2,
size.width(),
size.height()),
frame);
_lottiePreview->markFrameShown();
} else { } else {
p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview); p.drawPixmap(_previewLeft, st::boxPhotoPadding.top(), _preview);
} }
if (_animated && !_gifPreview) { if (_animated && !_gifPreview && !_lottiePreview) {
auto inner = QRect(_previewLeft + (_previewWidth - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_previewHeight - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); auto inner = QRect(_previewLeft + (_previewWidth - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (_previewHeight - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::msgDateImgBg); p.setBrush(st::msgDateImgBg);

View file

@ -18,6 +18,7 @@ MimeType::MimeType(Known type) : _type(type) {
QStringList MimeType::globPatterns() const { QStringList MimeType::globPatterns() const {
switch (_type) { switch (_type) {
case Known::WebP: return QStringList(qsl("*.webp")); case Known::WebP: return QStringList(qsl("*.webp"));
case Known::Tgs: return QStringList(qsl("*.tgs"));
case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme")); case Known::TDesktopTheme: return QStringList(qsl("*.tdesktop-theme"));
case Known::TDesktopPalette: return QStringList(qsl("*.tdesktop-palette")); case Known::TDesktopPalette: return QStringList(qsl("*.tdesktop-palette"));
default: break; default: break;
@ -28,6 +29,7 @@ QStringList MimeType::globPatterns() const {
QString MimeType::filterString() const { QString MimeType::filterString() const {
switch (_type) { switch (_type) {
case Known::WebP: return qsl("WebP image (*.webp)"); case Known::WebP: return qsl("WebP image (*.webp)");
case Known::Tgs: return qsl("Telegram sticker (*.tgs)");
case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)"); case Known::TDesktopTheme: return qsl("Theme files (*.tdesktop-theme)");
case Known::TDesktopPalette: return qsl("Palette files (*.tdesktop-palette)"); case Known::TDesktopPalette: return qsl("Palette files (*.tdesktop-palette)");
default: break; default: break;
@ -38,6 +40,7 @@ QString MimeType::filterString() const {
QString MimeType::name() const { QString MimeType::name() const {
switch (_type) { switch (_type) {
case Known::WebP: return qsl("image/webp"); case Known::WebP: return qsl("image/webp");
case Known::Tgs: return qsl("application/x-tgsticker");
case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme"); case Known::TDesktopTheme: return qsl("application/x-tdesktop-theme");
case Known::TDesktopPalette: return qsl("application/x-tdesktop-palette"); case Known::TDesktopPalette: return qsl("application/x-tdesktop-palette");
default: break; default: break;
@ -48,6 +51,8 @@ QString MimeType::name() const {
MimeType MimeTypeForName(const QString &mime) { MimeType MimeTypeForName(const QString &mime) {
if (mime == qstr("image/webp")) { if (mime == qstr("image/webp")) {
return MimeType(MimeType::Known::WebP); return MimeType(MimeType::Known::WebP);
} else if (mime == qstr("application/x-tgsticker")) {
return MimeType(MimeType::Known::Tgs);
} else if (mime == qstr("application/x-tdesktop-theme")) { } else if (mime == qstr("application/x-tdesktop-theme")) {
return MimeType(MimeType::Known::TDesktopTheme); return MimeType(MimeType::Known::TDesktopTheme);
} else if (mime == qstr("application/x-tdesktop-palette")) { } else if (mime == qstr("application/x-tdesktop-palette")) {
@ -62,6 +67,8 @@ MimeType MimeTypeForFile(const QFileInfo &file) {
QString path = file.absoluteFilePath(); QString path = file.absoluteFilePath();
if (path.endsWith(qstr(".webp"), Qt::CaseInsensitive)) { if (path.endsWith(qstr(".webp"), Qt::CaseInsensitive)) {
return MimeType(MimeType::Known::WebP); return MimeType(MimeType::Known::WebP);
} else if (path.endsWith(qstr(".tgs"), Qt::CaseInsensitive)) {
return MimeType(MimeType::Known::Tgs);
} else if (path.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) { } else if (path.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive)) {
return MimeType(MimeType::Known::TDesktopTheme); return MimeType(MimeType::Known::TDesktopTheme);
} else if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) { } else if (path.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive)) {

View file

@ -20,6 +20,7 @@ public:
TDesktopTheme, TDesktopTheme,
TDesktopPalette, TDesktopPalette,
WebP, WebP,
Tgs,
}; };
explicit MimeType(const QMimeType &type); explicit MimeType(const QMimeType &type);

View file

@ -78,13 +78,8 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
PreparedFileThumbnail PrepareAnimatedStickerThumbnail( PreparedFileThumbnail PrepareAnimatedStickerThumbnail(
const QString &file, const QString &file,
const QByteArray &bytes) { const QByteArray &bytes) {
return PrepareFileThumbnail(Lottie::ReadThumbnail([&] { return PrepareFileThumbnail(
if (!bytes.isEmpty()) { Lottie::ReadThumbnail(Lottie::ReadContent(bytes, file)));
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) {
@ -683,14 +678,23 @@ bool FileLoadTask::CheckForImage(
const QByteArray &content, const QByteArray &content,
std::unique_ptr<FileMediaInformation> &result) { std::unique_ptr<FileMediaInformation> &result) {
auto animated = false; auto animated = false;
auto image = ([&filepath, &content, &animated] { auto image = [&] {
if (filepath.endsWith(qstr(".tgs"), Qt::CaseInsensitive)) {
auto image = Lottie::ReadThumbnail(
Lottie::ReadContent(content, filepath));
if (!image.isNull()) {
animated = true;
result->filemime = qstr("application/x-tgsticker");
}
return image;
}
if (!content.isEmpty()) { if (!content.isEmpty()) {
return App::readImage(content, nullptr, false, &animated); return App::readImage(content, nullptr, false, &animated);
} else if (!filepath.isEmpty()) { } else if (!filepath.isEmpty()) {
return App::readImage(filepath, nullptr, false, &animated); return App::readImage(filepath, nullptr, false, &animated);
} }
return QImage(); return QImage();
})(); }();
return FillImageInformation(std::move(image), animated, result); return FillImageInformation(std::move(image), animated, result);
} }
@ -712,6 +716,7 @@ bool FileLoadTask::FillImageInformation(
void FileLoadTask::process() { void FileLoadTask::process() {
const auto stickerMime = qsl("image/webp"); const auto stickerMime = qsl("image/webp");
const auto animatedStickerMime = qsl("application/x-tgsticker");
_result = std::make_shared<FileLoadResult>( _result = std::make_shared<FileLoadResult>(
id(), id(),
@ -754,7 +759,7 @@ void FileLoadTask::process() {
if (auto image = base::get_if<FileMediaInformation::Image>( if (auto image = base::get_if<FileMediaInformation::Image>(
&_information->media)) { &_information->media)) {
fullimage = base::take(image->data); fullimage = base::take(image->data);
if (auto opaque = (filemime != stickerMime)) { if (filemime != stickerMime && filemime != animatedStickerMime) {
fullimage = Images::prepareOpaque(std::move(fullimage)); fullimage = Images::prepareOpaque(std::move(fullimage));
} }
isAnimation = image->animated; isAnimation = image->animated;
@ -773,7 +778,7 @@ void FileLoadTask::process() {
} }
const auto mimeType = Core::MimeTypeForData(_content); const auto mimeType = Core::MimeTypeForData(_content);
filemime = mimeType.name(); filemime = mimeType.name();
if (filemime != stickerMime) { if (filemime != stickerMime && filemime != animatedStickerMime) {
fullimage = Images::prepareOpaque(std::move(fullimage)); fullimage = Images::prepareOpaque(std::move(fullimage));
} }
if (filemime == "image/jpeg") { if (filemime == "image/jpeg") {
@ -830,10 +835,6 @@ 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();
@ -872,8 +873,6 @@ void FileLoadTask::process() {
} }
thumbnail = PrepareFileThumbnail(std::move(video->thumbnail)); thumbnail = PrepareFileThumbnail(std::move(video->thumbnail));
} else if (checkAnimatedSticker) {
thumbnail = PrepareAnimatedStickerThumbnail(_filepath, _content);
} }
} }
@ -882,7 +881,20 @@ void FileLoadTask::process() {
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h))); attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h)));
if (ValidateThumbDimensions(w, h)) { if (ValidateThumbDimensions(w, h)) {
if (isAnimation) { isSticker = (filemime == stickerMime
|| filemime == animatedStickerMime)
&& (w > 0)
&& (h > 0)
&& (w <= StickerMaxSize)
&& (h <= StickerMaxSize)
&& (filesize < Storage::kMaxStickerInMemory);
if (isSticker) {
attributes.push_back(MTP_documentAttributeSticker(
MTP_flags(0),
MTP_string(QString()),
MTP_inputStickerSetEmpty(),
MTPMaskCoords()));
} else if (isAnimation) {
attributes.push_back(MTP_documentAttributeAnimated()); attributes.push_back(MTP_documentAttributeAnimated());
} else if (_type != SendMediaType::File) { } else if (_type != SendMediaType::File) {
auto thumb = (w > 100 || h > 100) ? fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage; auto thumb = (w > 100 || h > 100) ? fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
@ -915,21 +927,6 @@ void FileLoadTask::process() {
filesize = _result->filesize = filedata.size(); filesize = _result->filesize = filedata.size();
} }
} }
isSticker = !isAnimation
&& (filemime == stickerMime)
&& (w > 0)
&& (h > 0)
&& (w <= StickerMaxSize)
&& (h <= StickerMaxSize)
&& (filesize < Storage::kMaxStickerInMemory);
if (isSticker) {
attributes.push_back(MTP_documentAttributeSticker(
MTP_flags(0),
MTP_string(QString()),
MTP_inputStickerSetEmpty(),
MTPMaskCoords()));
}
thumbnail = PrepareFileThumbnail(std::move(fullimage)); thumbnail = PrepareFileThumbnail(std::move(fullimage));
} }
} }
@ -937,7 +934,7 @@ void FileLoadTask::process() {
std::move(thumbnail), std::move(thumbnail),
filemime, filemime,
filesize, filesize,
isSticker || checkAnimatedSticker); isSticker);
if (_type == SendMediaType::Photo && photo.type() == mtpc_photoEmpty) { if (_type == SendMediaType::Photo && photo.type() == mtpc_photoEmpty) {
_type = SendMediaType::File; _type = SendMediaType::File;

View file

@ -316,7 +316,10 @@ bool PreparedList::canAddCaption(bool isAlbum, bool compressImages) const {
if (files.empty() || compressImages) { if (files.empty() || compressImages) {
return false; return false;
} }
return (files.front().mime == qstr("image/webp")); return (files.front().mime == qstr("image/webp"))
|| files.front().path.endsWith(
qstr(".tgs"),
Qt::CaseInsensitive);
}; };
return isAlbum || (files.size() == 1 && !isSticker()); return isAlbum || (files.size() == 1 && !isSticker());
} }