diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 7f46a11e8..429b98e75 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -606,7 +606,8 @@ bool DocumentData::checkWallPaperProperties() { void DocumentData::updateThumbnails( const QByteArray &inlineThumbnailBytes, - const ImageWithLocation &thumbnail) { + const ImageWithLocation &thumbnail, + const ImageWithLocation &videoThumbnail) { if (!inlineThumbnailBytes.isEmpty() && _inlineThumbnailBytes.isEmpty()) { _inlineThumbnailBytes = inlineThumbnailBytes; @@ -636,10 +637,16 @@ void DocumentData::updateThumbnails( } } } -} - -const ImageLocation &DocumentData::thumbnailLocation() const { - return _thumbnailLocation; + if (videoThumbnail.location.valid() + && !_videoThumbnailLocation.valid()) { + _videoThumbnailLocation = videoThumbnail.location; + _videoThumbnailByteSize = videoThumbnail.bytesCount; + if (_videoThumbnailLoader) { + const auto origin + = base::take(_videoThumbnailLoader)->fileOrigin(); + loadVideoThumbnail(origin); + } + } } bool DocumentData::isWallPaper() const { @@ -702,6 +709,67 @@ void DocumentData::loadThumbnail(Data::FileOrigin origin) { _thumbnailLoader->start(); } +const ImageLocation &DocumentData::thumbnailLocation() const { + return _thumbnailLocation; +} + +bool DocumentData::hasVideoThumbnail() const { + return _videoThumbnailLocation.valid() + && (_videoThumbnailLocation.width() > 0) + && (_videoThumbnailLocation.height() > 0); +} + +bool DocumentData::videoThumbnailLoading() const { + return _videoThumbnailLoader != nullptr; +} + +bool DocumentData::videoThumbnailFailed() const { + return (_flags & Flag::VideoThumbnailFailed); +} + +void DocumentData::loadVideoThumbnail(Data::FileOrigin origin) { + if (_videoThumbnailLoader || (_flags & Flag::VideoThumbnailFailed)) { + return; + } else if (const auto active = activeMediaView()) { + if (!active->videoThumbnailContent().isEmpty()) { + return; + } + } + const auto autoLoading = false; + _videoThumbnailLoader = CreateFileLoader( + _videoThumbnailLocation.file(), + origin, + QString(), + _videoThumbnailByteSize, + UnknownFileLocation, + LoadToCacheAsWell, + LoadFromCloudOrLocal, + autoLoading, + Data::kAnimationCacheTag); + + _videoThumbnailLoader->updates( + ) | rpl::start_with_error_done([=](bool started) { + _videoThumbnailLoader = nullptr; + _flags |= Flag::VideoThumbnailFailed; + }, [=] { + if (_videoThumbnailLoader && !_videoThumbnailLoader->cancelled()) { + auto bytes = _videoThumbnailLoader->bytes(); + if (bytes.isEmpty()) { + _flags |= Flag::VideoThumbnailFailed; + } else if (const auto active = activeMediaView()) { + active->setVideoThumbnail(std::move(bytes)); + } + } + _videoThumbnailLoader = nullptr; + }, _videoThumbnailLoader->lifetime()); + + _videoThumbnailLoader->start(); +} + +const ImageLocation &DocumentData::videoThumbnailLocation() const { + return _videoThumbnailLocation; +} + Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const { return Data::DocumentThumbCacheKey(_dc, id); } diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 75d7a8f1d..a30d9e0e4 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -159,10 +159,18 @@ public: [[nodiscard]] bool thumbnailLoading() const; [[nodiscard]] bool thumbnailFailed() const; void loadThumbnail(Data::FileOrigin origin); + const ImageLocation &thumbnailLocation() const; + + [[nodiscard]] bool hasVideoThumbnail() const; + [[nodiscard]] bool videoThumbnailLoading() const; + [[nodiscard]] bool videoThumbnailFailed() const; + void loadVideoThumbnail(Data::FileOrigin origin); + const ImageLocation &videoThumbnailLocation() const; + void updateThumbnails( const QByteArray &inlineThumbnailBytes, - const ImageWithLocation &thumbnail); - const ImageLocation &thumbnailLocation() const; + const ImageWithLocation &thumbnail, + const ImageWithLocation &videoThumbnail); [[nodiscard]] QByteArray inlineThumbnailBytes() const { return _inlineThumbnailBytes; @@ -247,6 +255,7 @@ private: DownloadCancelled = 0x10, LoadedInMediaCache = 0x20, ThumbnailFailed = 0x40, + VideoThumbnailFailed = 0x80, }; using Flags = base::flags; friend constexpr bool is_flag_type(Flag) { return true; }; @@ -300,8 +309,11 @@ private: QByteArray _inlineThumbnailBytes; ImageLocation _thumbnailLocation; + ImageLocation _videoThumbnailLocation; std::unique_ptr _thumbnailLoader; + std::unique_ptr _videoThumbnailLoader; int _thumbnailByteSize = 0; + int _videoThumbnailByteSize = 0; std::unique_ptr _replyPreview; std::weak_ptr _media; PhotoData *_goodThumbnailPhoto = nullptr; diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 0056b8cfc..a469795dc 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -154,6 +154,25 @@ void DocumentMedia::setThumbnail(QImage thumbnail) { _owner->session().downloaderTaskFinished().notify(); } +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); +} + void DocumentMedia::checkStickerLarge() { if (_sticker) { return; diff --git a/Telegram/SourceFiles/data/data_document_media.h b/Telegram/SourceFiles/data/data_document_media.h index 919b7fabe..73b467c17 100644 --- a/Telegram/SourceFiles/data/data_document_media.h +++ b/Telegram/SourceFiles/data/data_document_media.h @@ -25,11 +25,17 @@ public: void setGoodThumbnail(QImage thumbnail); [[nodiscard]] Image *thumbnailInline() const; + [[nodiscard]] Image *thumbnail() const; [[nodiscard]] QSize thumbnailSize() const; void thumbnailWanted(Data::FileOrigin origin); void setThumbnail(QImage thumbnail); + [[nodiscard]] QByteArray videoThumbnailContent() const; + [[nodiscard]] QSize videoThumbnailSize() const; + void videoThumbnailWanted(Data::FileOrigin origin); + void setVideoThumbnail(QByteArray content); + void checkStickerLarge(); void checkStickerSmall(); [[nodiscard]] Image *getStickerSmall(); @@ -67,6 +73,7 @@ private: std::unique_ptr _thumbnail; std::unique_ptr _sticker; QByteArray _bytes; + QByteArray _videoThumbnailBytes; Flags _flags; }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index a4281b3f4..6acdc3027 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -165,6 +165,25 @@ MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) { : MTPPhotoSize(MTP_photoSizeEmpty(MTP_string())); } +std::optional FindDocumentVideoThumbnail( + const MTPDdocument &data) { + const auto area = [](const MTPVideoSize &size) { + static constexpr auto kInvalid = 0; + return size.match([](const MTPDvideoSize &data) { + return (data.vw().v * data.vh().v); + }); + }; + const auto thumbs = data.vvideo_thumbs(); + if (!thumbs) { + return std::nullopt; + } + const auto &list = thumbs->v; + const auto i = ranges::max_element(list, std::less<>(), area); + return (i != list.end() && area(*i) > 0) + ? std::make_optional(*i) + : std::nullopt; +} + rpl::producer PinnedDialogsCountMaxValue( not_null session) { return rpl::single( @@ -2382,6 +2401,7 @@ not_null Session::processDocument( qs(data.vmime_type()), QByteArray(), thumbnail, + ImageWithLocation(), data.vdc_id().v, data.vsize().v); }, [&](const MTPDdocumentEmpty &data) { @@ -2398,6 +2418,7 @@ not_null Session::document( const QString &mime, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &thumbnail, + const ImageWithLocation &videoThumbnail, int32 dc, int32 size) { const auto result = document(id); @@ -2410,6 +2431,7 @@ not_null Session::document( mime, inlineThumbnailBytes, thumbnail, + videoThumbnail, dc, size); return result; @@ -2478,6 +2500,7 @@ DocumentData *Session::documentFromWeb( data.vmime_type().v, QByteArray(), ImageWithLocation{ .location = thumbnailLocation }, + ImageWithLocation(), MTP::maindc(), int32(0)); // data.vsize().v result->setWebLocation(WebFileLocation( @@ -2498,6 +2521,7 @@ DocumentData *Session::documentFromWeb( data.vmime_type().v, QByteArray(), ImageWithLocation{ .location = thumbnailLocation }, + ImageWithLocation(), MTP::maindc(), int32(0)); // data.vsize().v result->setContentUrl(qs(data.vurl())); @@ -2517,10 +2541,14 @@ void Session::documentApplyFields( const MTPDdocument &data) { const auto inlineThumbnailBytes = FindDocumentInlineThumbnail(data); const auto thumbnailSize = FindDocumentThumbnail(data); + const auto videoThumbnailSize = FindDocumentVideoThumbnail(data); const auto prepared = Images::FromPhotoSize( _session, data, thumbnailSize); + const auto videoThumbnail = videoThumbnailSize + ? Images::FromVideoSize(_session, data, *videoThumbnailSize) + : ImageWithLocation(); documentApplyFields( document, data.vaccess_hash().v, @@ -2530,6 +2558,7 @@ void Session::documentApplyFields( qs(data.vmime_type()), inlineThumbnailBytes, prepared, + videoThumbnail, data.vdc_id().v, data.vsize().v); } @@ -2543,6 +2572,7 @@ void Session::documentApplyFields( const QString &mime, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &thumbnail, + const ImageWithLocation &videoThumbnail, int32 dc, int32 size) { if (!date) { @@ -2550,7 +2580,10 @@ void Session::documentApplyFields( } document->date = date; document->setMimeString(mime); - document->updateThumbnails(inlineThumbnailBytes, thumbnail); + document->updateThumbnails( + inlineThumbnailBytes, + thumbnail, + videoThumbnail); document->size = size; document->setattributes(attributes); diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 99ec73988..c4e6483ec 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -500,6 +500,7 @@ public: const QString &mime, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &thumbnail, + const ImageWithLocation &videoThumbnail, int32 dc, int32 size); void documentConvert( @@ -754,6 +755,7 @@ private: const QString &mime, const QByteArray &inlineThumbnailBytes, const ImageWithLocation &thumbnail, + const ImageWithLocation &videoThumbnail, int32 dc, int32 size); DocumentData *documentFromWeb( diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index c5e9696fe..a73e125ff 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -134,26 +134,41 @@ int Gif::resizeGetHeight(int width) { void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const { const auto document = getShownDocument(); const auto displayLoading = document->displayLoading(); - + const auto useVideoThumbnail = document->hasVideoThumbnail(); ensureDataMediaCreated(document); - _dataMedia->automaticLoad(fileOrigin(), nullptr); + if (!useVideoThumbnail) { + _dataMedia->automaticLoad(fileOrigin(), nullptr); + } - bool loaded = _dataMedia->loaded(), loading = document->loading(); + const auto loaded = useVideoThumbnail + ? !_dataMedia->videoThumbnailContent().isEmpty() + : _dataMedia->loaded(); + const auto loading = useVideoThumbnail + ? document->videoThumbnailLoading() + : document->loading(); if (loaded && !_gif && !_gif.isBad() && CanPlayInline(document)) { auto that = const_cast(this); - that->_gif = Media::Clip::MakeReader(_dataMedia.get(), FullMsgId(), [that](Media::Clip::Notification notification) { + const auto callback = [=](Media::Clip::Notification notification) { that->clipCallback(notification); - }); + }; + that->_gif = useVideoThumbnail + ? Media::Clip::MakeReader( + _dataMedia->videoThumbnailContent(), + callback) + : Media::Clip::MakeReader( + _dataMedia.get(), + FullMsgId(), + callback); } const auto animating = (_gif && _gif->started()); if (displayLoading) { ensureAnimation(); if (!_animation->radial.animating()) { - _animation->radial.start(_dataMedia->progress()); + _animation->radial.start(_dataMedia->progress()); // #TODO video_thumbs } } const auto radial = isRadialAnimation(); @@ -177,6 +192,10 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons p.drawPixmap(r.topLeft(), _thumb); } } + if (useVideoThumbnail) { + AssertIsDebug(); + p.fillRect(QRect(r.topLeft(), QSize(20, 20)), Qt::green); + } if (radial || _gif.isBad() || (!_gif && !loaded && !loading)) { auto radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1.; @@ -250,7 +269,7 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { bool wasactive = (_state & StateFlag::Over); if (active != wasactive) { ensureDataMediaCreated(getShownDocument()); - if (!_dataMedia->loaded()) { + if (!_dataMedia->loaded()) { // #TODO video_thumbs ensureAnimation(); auto from = active ? 0. : 1., to = active ? 1. : 0.; _animation->_a_over.start([this] { update(); }, from, to, st::stickersRowDuration); @@ -333,6 +352,7 @@ void Gif::ensureDataMediaCreated(not_null document) const { } _dataMedia = document->createMediaView(); _dataMedia->thumbnailWanted(fileOrigin()); + _dataMedia->videoThumbnailWanted(fileOrigin()); } void Gif::ensureAnimation() const { @@ -349,7 +369,7 @@ bool Gif::isRadialAnimation() const { return true; } else { ensureDataMediaCreated(getShownDocument()); - if (_dataMedia->loaded()) { + if (_dataMedia->loaded()) { // #TODO video_thumbs _animation = nullptr; } } @@ -362,7 +382,7 @@ void Gif::radialAnimationCallback(crl::time now) const { ensureDataMediaCreated(document); const auto updated = [&] { return _animation->radial.update( - _dataMedia->progress(), + _dataMedia->progress(), // #TODO video_thumbs !document->loading() || _dataMedia->loaded(), now); }(); diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 8eb501100..0f9633524 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -3945,6 +3945,7 @@ void importOldRecentStickers() { mime, QByteArray(), ImageWithLocation(), + ImageWithLocation(), dc, size); if (!doc->sticker()) { diff --git a/Telegram/SourceFiles/storage/serialize_document.cpp b/Telegram/SourceFiles/storage/serialize_document.cpp index 6fae8eb9e..a5507d938 100644 --- a/Telegram/SourceFiles/storage/serialize_document.cpp +++ b/Telegram/SourceFiles/storage/serialize_document.cpp @@ -143,6 +143,7 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & mime, QByteArray(), ImageWithLocation{ .location = *thumb }, + ImageWithLocation(), dc, size); } diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.cpp b/Telegram/SourceFiles/ui/image/image_location_factory.cpp index 220c1adeb..9e741691f 100644 --- a/Telegram/SourceFiles/ui/image/image_location_factory.cpp +++ b/Telegram/SourceFiles/ui/image/image_location_factory.cpp @@ -121,4 +121,26 @@ ImageLocation FromWebDocument(const MTPWebDocument &document) { }); } +ImageWithLocation FromVideoSize( + not_null session, + const MTPDdocument &document, + const MTPVideoSize &size) { + return size.match([&](const MTPDvideoSize &data) { + return ImageWithLocation{ + .location = ImageLocation( + DownloadLocation{ StorageFileLocation( + document.vdc_id().v, + session->userId(), + MTP_inputDocumentFileLocation( + document.vid(), + document.vaccess_hash(), + document.vfile_reference(), + data.vtype())) }, + data.vw().v, + data.vh().v), + .bytesCount = data.vsize().v + }; + }); +} + } // namespace Images diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.h b/Telegram/SourceFiles/ui/image/image_location_factory.h index 82dfc9c09..666b05660 100644 --- a/Telegram/SourceFiles/ui/image/image_location_factory.h +++ b/Telegram/SourceFiles/ui/image/image_location_factory.h @@ -19,6 +19,10 @@ namespace Images { not_null session, const MTPDdocument &document, const MTPPhotoSize &size); +[[nodiscard]] ImageWithLocation FromVideoSize( + not_null session, + const MTPDdocument &document, + const MTPVideoSize &size); [[nodiscard]] ImageWithLocation FromImageInMemory( const QImage &image, const char *format);