| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "data/data_document_media.h"
- #include "data/data_document.h"
- #include "data/data_document_resolver.h"
- #include "data/data_session.h"
- #include "data/data_file_origin.h"
- #include "media/clip/media_clip_reader.h"
- #include "main/main_session.h"
- #include "main/main_session_settings.h"
- #include "lottie/lottie_animation.h"
- #include "lottie/lottie_frame_generator.h"
- #include "ffmpeg/ffmpeg_frame_generator.h"
- #include "history/history_item.h"
- #include "history/history.h"
- #include "window/themes/window_theme_preview.h"
- #include "core/core_settings.h"
- #include "core/application.h"
- #include "core/mime_type.h"
- #include "storage/file_download.h"
- #include "ui/chat/attach/attach_prepare.h"
- #include <QtCore/QBuffer>
- #include <QtGui/QImageReader>
- namespace Data {
- namespace {
- constexpr auto kReadAreaLimit = 12'032 * 9'024;
- constexpr auto kWallPaperThumbnailLimit = 960;
- constexpr auto kGoodThumbQuality = 87;
- enum class FileType {
- Video,
- VideoSticker,
- AnimatedSticker,
- WallPaper,
- WallPatternPNG,
- WallPatternSVG,
- Theme,
- };
- [[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) {
- return owner->isVideoFile()
- || owner->isAnimation()
- || owner->isWallPaper()
- || owner->isTheme()
- || (owner->sticker() && owner->sticker()->isAnimated());
- }
- [[nodiscard]] QImage PrepareGoodThumbnail(
- const QString &path,
- QByteArray data,
- FileType type) {
- if (type == FileType::Video || type == FileType::VideoSticker) {
- auto result = v::get<Ui::PreparedFileInformation::Video>(
- ::Media::Clip::PrepareForSending(path, data).media);
- if (result.isWebmSticker && type == FileType::Video) {
- result.thumbnail = Images::Opaque(std::move(result.thumbnail));
- }
- return result.thumbnail;
- } else if (type == FileType::AnimatedSticker) {
- return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
- } else if (type == FileType::Theme) {
- return Window::Theme::GeneratePreview(data, path);
- } else if (type == FileType::WallPatternSVG) {
- return Images::Read({
- .path = path,
- .content = std::move(data),
- .maxSize = QSize(
- kWallPaperThumbnailLimit,
- kWallPaperThumbnailLimit),
- .gzipSvg = true,
- }).image;
- }
- auto buffer = QBuffer(&data);
- auto file = QFile(path);
- auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
- auto reader = QImageReader(device);
- const auto size = reader.size();
- if (!reader.canRead()
- || (size.width() * size.height() > kReadAreaLimit)) {
- return QImage();
- }
- auto result = reader.read();
- if (!result.width() || !result.height()) {
- return QImage();
- }
- return (result.width() > kWallPaperThumbnailLimit
- || result.height() > kWallPaperThumbnailLimit)
- ? result.scaled(
- kWallPaperThumbnailLimit,
- kWallPaperThumbnailLimit,
- Qt::KeepAspectRatio,
- Qt::SmoothTransformation)
- : result;
- }
- } // namespace
- VideoPreviewState::VideoPreviewState(DocumentMedia *media)
- : _media(media)
- , _usingThumbnail(media ? media->owner()->hasVideoThumbnail() : false) {
- }
- void VideoPreviewState::automaticLoad(Data::FileOrigin origin) const {
- Expects(_media != nullptr);
- if (_usingThumbnail) {
- _media->videoThumbnailWanted(origin);
- } else {
- _media->automaticLoad(origin, nullptr);
- }
- }
- ::Media::Clip::ReaderPointer VideoPreviewState::makeAnimation(
- Fn<void(::Media::Clip::Notification)> callback) const {
- Expects(_media != nullptr);
- Expects(loaded());
- return _usingThumbnail
- ? ::Media::Clip::MakeReader(
- _media->videoThumbnailContent(),
- std::move(callback))
- : ::Media::Clip::MakeReader(
- _media->owner()->location(),
- _media->bytes(),
- std::move(callback));
- }
- bool VideoPreviewState::usingThumbnail() const {
- return _usingThumbnail;
- }
- bool VideoPreviewState::loading() const {
- return _usingThumbnail
- ? _media->owner()->videoThumbnailLoading()
- : _media
- ? _media->owner()->loading()
- : false;
- }
- bool VideoPreviewState::loaded() const {
- return _usingThumbnail
- ? !_media->videoThumbnailContent().isEmpty()
- : _media
- ? _media->loaded()
- : false;
- }
- DocumentMedia::DocumentMedia(not_null<DocumentData*> owner)
- : _owner(owner) {
- }
- // NB! Right now DocumentMedia can outlive Main::Session!
- // In DocumentData::collectLocalData a shared_ptr is sent on_main.
- // In case this is a problem the ~Gif code should be rewritten.
- DocumentMedia::~DocumentMedia() = default;
- not_null<DocumentData*> DocumentMedia::owner() const {
- return _owner;
- }
- void DocumentMedia::goodThumbnailWanted() {
- _flags |= Flag::GoodThumbnailWanted;
- }
- Image *DocumentMedia::goodThumbnail() const {
- Expects((_flags & Flag::GoodThumbnailWanted) != 0);
- if (!_goodThumbnail) {
- ReadOrGenerateThumbnail(_owner);
- }
- return _goodThumbnail.get();
- }
- void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
- if (!(_flags & Flag::GoodThumbnailWanted)) {
- return;
- }
- _goodThumbnail = std::make_unique<Image>(std::move(thumbnail));
- _owner->session().notifyDownloaderTaskFinished();
- }
- Image *DocumentMedia::thumbnailInline() const {
- if (!_inlineThumbnail && !_owner->inlineThumbnailIsPath()) {
- const auto bytes = _owner->inlineThumbnailBytes();
- if (!bytes.isEmpty()) {
- auto image = Images::FromInlineBytes(bytes);
- if (image.isNull()) {
- _owner->clearInlineThumbnailBytes();
- } else {
- _inlineThumbnail = std::make_unique<Image>(std::move(image));
- }
- }
- }
- return _inlineThumbnail.get();
- }
- const QPainterPath &DocumentMedia::thumbnailPath() const {
- if (_pathThumbnail.isEmpty() && _owner->inlineThumbnailIsPath()) {
- const auto bytes = _owner->inlineThumbnailBytes();
- if (!bytes.isEmpty()) {
- _pathThumbnail = Images::PathFromInlineBytes(bytes);
- if (_pathThumbnail.isEmpty()) {
- _owner->clearInlineThumbnailBytes();
- }
- }
- }
- return _pathThumbnail;
- }
- Image *DocumentMedia::thumbnail() const {
- return _thumbnail.get();
- }
- void DocumentMedia::thumbnailWanted(Data::FileOrigin origin) {
- if (!_thumbnail) {
- _owner->loadThumbnail(origin);
- }
- }
- QSize DocumentMedia::thumbnailSize() const {
- if (const auto image = _thumbnail.get()) {
- return image->size();
- }
- const auto &location = _owner->thumbnailLocation();
- return { location.width(), location.height() };
- }
- void DocumentMedia::setThumbnail(QImage thumbnail) {
- _thumbnail = std::make_unique<Image>(std::move(thumbnail));
- _owner->session().notifyDownloaderTaskFinished();
- }
- QByteArray DocumentMedia::videoThumbnailContent() const {
- return _videoThumbnailBytes;
- }
- QSize DocumentMedia::videoThumbnailSize() const {
- const auto &location = _owner->videoThumbnailLocation();
- return { location.width(), location.height() };
- }
- void DocumentMedia::videoThumbnailWanted(Data::FileOrigin origin) {
- if (_videoThumbnailBytes.isEmpty()) {
- _owner->loadVideoThumbnail(origin);
- }
- }
- void DocumentMedia::setVideoThumbnail(QByteArray content) {
- _videoThumbnailBytes = std::move(content);
- _videoThumbnailBytes.detach();
- }
- void DocumentMedia::checkStickerLarge() {
- if (_sticker) {
- return;
- }
- const auto data = _owner->sticker();
- if (!data) {
- return;
- }
- automaticLoad(_owner->stickerSetOrigin(), nullptr);
- if (data->isAnimated() || !loaded()) {
- return;
- }
- if (_bytes.isEmpty()) {
- const auto &loc = _owner->location(true);
- if (loc.accessEnable()) {
- _sticker = std::make_unique<Image>(loc.name());
- loc.accessDisable();
- }
- } else {
- _sticker = std::make_unique<Image>(_bytes);
- }
- }
- void DocumentMedia::automaticLoad(
- Data::FileOrigin origin,
- const HistoryItem *item) {
- if (_owner->status != FileReady || loaded() || _owner->cancelled()) {
- return;
- } else if (!item && !_owner->sticker() && !_owner->isAnimation()) {
- return;
- }
- const auto toCache = _owner->saveToCache();
- if (!toCache && !Core::App().canSaveFileWithoutAskingForPath()) {
- // We need a filename, but we're supposed to ask user for it.
- // No automatic download in this case.
- return;
- }
- const auto indata = _owner->filename();
- const auto filename = toCache
- ? QString()
- : DocumentFileNameForSave(_owner);
- const auto shouldLoadFromCloud = (indata.isEmpty()
- || Core::DetectNameType(indata) != Core::NameType::Executable)
- && (item
- ? Data::AutoDownload::Should(
- _owner->session().settings().autoDownload(),
- item->history()->peer,
- _owner)
- : Data::AutoDownload::Should(
- _owner->session().settings().autoDownload(),
- _owner));
- const auto loadFromCloud = shouldLoadFromCloud
- ? LoadFromCloudOrLocal
- : LoadFromLocalOnly;
- _owner->save(
- origin,
- filename,
- loadFromCloud,
- true);
- }
- void DocumentMedia::collectLocalData(not_null<DocumentMedia*> local) {
- if (const auto image = local->_goodThumbnail.get()) {
- _goodThumbnail = std::make_unique<Image>(image->original());
- }
- if (const auto image = local->_inlineThumbnail.get()) {
- _inlineThumbnail = std::make_unique<Image>(image->original());
- }
- if (const auto image = local->_thumbnail.get()) {
- _thumbnail = std::make_unique<Image>(image->original());
- }
- if (const auto image = local->_sticker.get()) {
- _sticker = std::make_unique<Image>(image->original());
- }
- _bytes = local->_bytes;
- _videoThumbnailBytes = local->_videoThumbnailBytes;
- _flags = local->_flags;
- }
- void DocumentMedia::setBytes(const QByteArray &bytes) {
- if (!bytes.isEmpty()) {
- _bytes = bytes;
- }
- }
- QByteArray DocumentMedia::bytes() const {
- return _bytes;
- }
- bool DocumentMedia::loaded(bool check) const {
- return !_bytes.isEmpty() || !_owner->filepath(check).isEmpty();
- }
- float64 DocumentMedia::progress() const {
- return (_owner->uploading() || _owner->loading())
- ? _owner->progress()
- : (loaded() ? 1. : 0.);
- }
- bool DocumentMedia::canBePlayed(HistoryItem *item) const {
- return !_owner->inappPlaybackFailed()
- && _owner->useStreamingLoader()
- && (loaded() || _owner->canBeStreamed(item));
- }
- bool DocumentMedia::thumbnailEnoughForSticker() const {
- const auto &location = owner()->thumbnailLocation();
- const auto size = _thumbnail
- ? QSize(_thumbnail->width(), _thumbnail->height())
- : location.valid()
- ? QSize(location.width(), location.height())
- : QSize();
- return (size.width() >= 128) || (size.height() >= 128);
- }
- void DocumentMedia::checkStickerSmall() {
- const auto data = _owner->sticker();
- if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
- _owner->loadThumbnail(_owner->stickerSetOrigin());
- if (data && data->isAnimated()) {
- automaticLoad(_owner->stickerSetOrigin(), nullptr);
- }
- } else {
- checkStickerLarge();
- }
- }
- Image *DocumentMedia::getStickerLarge() {
- checkStickerLarge();
- return _sticker.get();
- }
- Image *DocumentMedia::getStickerSmall() {
- const auto data = _owner->sticker();
- if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
- return thumbnail();
- }
- return _sticker.get();
- }
- void DocumentMedia::checkStickerLarge(not_null<FileLoader*> loader) {
- if (_sticker || !_owner->sticker()) {
- return;
- }
- if (auto image = loader->imageData(); !image.isNull()) {
- _sticker = std::make_unique<Image>(std::move(image));
- }
- }
- void DocumentMedia::GenerateGoodThumbnail(
- not_null<DocumentData*> document,
- QByteArray data) {
- const auto type = document->isPatternWallPaperSVG()
- ? FileType::WallPatternSVG
- : document->isPatternWallPaperPNG()
- ? FileType::WallPatternPNG
- : document->isWallPaper()
- ? FileType::WallPaper
- : document->isTheme()
- ? FileType::Theme
- : !document->sticker()
- ? FileType::Video
- : document->sticker()->isLottie()
- ? FileType::AnimatedSticker
- : FileType::VideoSticker;
- auto location = document->location().isEmpty()
- ? nullptr
- : std::make_unique<Core::FileLocation>(document->location());
- if (data.isEmpty() && !location) {
- document->setGoodThumbnailChecked(false);
- return;
- }
- const auto guard = base::make_weak(&document->owner().session());
- crl::async([=, location = std::move(location)] {
- const auto filepath = (location && location->accessEnable())
- ? location->name()
- : QString();
- auto result = PrepareGoodThumbnail(filepath, data, type);
- auto bytes = QByteArray();
- if (!result.isNull()) {
- auto buffer = QBuffer(&bytes);
- const auto format = (type == FileType::AnimatedSticker
- || type == FileType::VideoSticker)
- ? "WEBP"
- : (type == FileType::WallPatternPNG
- || type == FileType::WallPatternSVG)
- ? "PNG"
- : "JPG";
- result.save(&buffer, format, kGoodThumbQuality);
- }
- if (!filepath.isEmpty()) {
- location->accessDisable();
- }
- const auto cache = bytes.isEmpty() ? QByteArray("(failed)") : bytes;
- crl::on_main(guard, [=] {
- document->setGoodThumbnailChecked(true);
- if (const auto active = document->activeMediaView()) {
- active->setGoodThumbnail(result);
- }
- document->owner().cache().put(
- document->goodThumbnailCacheKey(),
- Storage::Cache::Database::TaggedValue{
- base::duplicate(cache),
- kImageCacheTag });
- });
- });
- }
- void DocumentMedia::CheckGoodThumbnail(not_null<DocumentData*> document) {
- if (!document->goodThumbnailChecked()) {
- ReadOrGenerateThumbnail(document);
- }
- }
- void DocumentMedia::ReadOrGenerateThumbnail(
- not_null<DocumentData*> document) {
- if (document->goodThumbnailGenerating()
- || document->goodThumbnailNoData()
- || !MayHaveGoodThumbnail(document)) {
- return;
- }
- document->setGoodThumbnailGenerating();
- const auto guard = base::make_weak(&document->session());
- const auto active = document->activeMediaView();
- const auto got = [=](QByteArray value) {
- if (value.isEmpty()) {
- const auto bytes = active ? active->bytes() : QByteArray();
- crl::on_main(guard, [=] {
- GenerateGoodThumbnail(document, bytes);
- });
- } else if (active) {
- crl::async([=] {
- auto image = Images::Read({ .content = value }).image;
- crl::on_main(guard, [=, image = std::move(image)]() mutable {
- document->setGoodThumbnailChecked(true);
- if (const auto active = document->activeMediaView()) {
- active->setGoodThumbnail(std::move(image));
- }
- });
- });
- } else {
- crl::on_main(guard, [=] {
- document->setGoodThumbnailChecked(true);
- });
- }
- };
- document->owner().cache().get(document->goodThumbnailCacheKey(), got);
- }
- auto DocumentIconFrameGenerator(not_null<DocumentMedia*> media)
- -> FnMut<std::unique_ptr<Ui::FrameGenerator>()> {
- if (!media->loaded()) {
- return nullptr;
- }
- using Type = StickerType;
- const auto document = media->owner();
- const auto content = media->bytes();
- const auto fromFile = content.isEmpty();
- const auto type = document->sticker()
- ? document->sticker()->type
- : (document->isVideoFile() || document->isAnimation())
- ? Type::Webm
- : Type::Webp;
- const auto &location = media->owner()->location(true);
- if (fromFile && !location.accessEnable()) {
- return nullptr;
- }
- return [=]() -> std::unique_ptr<Ui::FrameGenerator> {
- const auto bytes = Lottie::ReadContent(content, location.name());
- if (fromFile) {
- location.accessDisable();
- }
- if (bytes.isEmpty()) {
- return nullptr;
- }
- switch (type) {
- case Type::Tgs:
- return std::make_unique<Lottie::FrameGenerator>(bytes);
- case Type::Webm:
- return std::make_unique<FFmpeg::FrameGenerator>(bytes);
- case Type::Webp:
- return std::make_unique<Ui::ImageFrameGenerator>(bytes);
- }
- Unexpected("Document type in DocumentIconFrameGenerator.");
- };
- }
- auto DocumentIconFrameGenerator(const std::shared_ptr<DocumentMedia> &media)
- -> FnMut<std::unique_ptr<Ui::FrameGenerator>()> {
- return DocumentIconFrameGenerator(media.get());
- }
- } // namespace Data
|