mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-26 00:12:25 +00:00
Generate high quality video thumbnail when loaded.
This commit is contained in:
parent
8f387891e2
commit
da358615e0
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document_good_thumbnail.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "mainwidget.h"
|
||||
@ -528,6 +529,31 @@ void DocumentData::setattributes(const QVector<MTPDocumentAttribute> &attributes
|
||||
_additional = nullptr;
|
||||
}
|
||||
}
|
||||
validateGoodThumbnail();
|
||||
}
|
||||
|
||||
Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const {
|
||||
return Data::DocumentThumbCacheKey(_dc, id);
|
||||
}
|
||||
|
||||
Image *DocumentData::goodThumbnail() const {
|
||||
return _goodThumbnail.get();
|
||||
}
|
||||
|
||||
void DocumentData::validateGoodThumbnail() {
|
||||
if (!isVideoFile() && !isAnimation()) {
|
||||
_goodThumbnail = nullptr;
|
||||
} else if (!_goodThumbnail && hasRemoteLocation()) {
|
||||
_goodThumbnail = std::make_unique<Image>(
|
||||
std::make_unique<Data::GoodThumbSource>(this));
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentData::refreshGoodThumbnail() {
|
||||
if (_goodThumbnail && !_goodThumbnail->loaded()) {
|
||||
_goodThumbnail->replaceSource(
|
||||
std::make_unique<Data::GoodThumbSource>(this));
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::saveToCache() const {
|
||||
@ -706,6 +732,8 @@ bool DocumentData::loaded(FilePathResolveType type) const {
|
||||
|| (that->sticker() && !that->sticker()->img->isNull())) {
|
||||
ActiveCache().up(that);
|
||||
}
|
||||
|
||||
that->refreshGoodThumbnail();
|
||||
destroyLoaderDelayed();
|
||||
}
|
||||
_session->data().notifyDocumentLayoutChanged(this);
|
||||
@ -1281,6 +1309,7 @@ void DocumentData::setRemoteLocation(
|
||||
}
|
||||
}
|
||||
}
|
||||
validateGoodThumbnail();
|
||||
}
|
||||
|
||||
void DocumentData::setContentUrl(const QString &url) {
|
||||
|
@ -154,6 +154,11 @@ public:
|
||||
|
||||
bool hasGoodStickerThumb() const;
|
||||
|
||||
Image *goodThumbnail() const;
|
||||
Storage::Cache::Key goodThumbnailCacheKey() const;
|
||||
void validateGoodThumbnail();
|
||||
void refreshGoodThumbnail();
|
||||
|
||||
void setRemoteLocation(
|
||||
int32 dc,
|
||||
uint64 access,
|
||||
@ -218,6 +223,8 @@ private:
|
||||
QString _mimeString;
|
||||
WebFileLocation _urlLocation;
|
||||
|
||||
std::unique_ptr<Image> _goodThumbnail;
|
||||
|
||||
not_null<AuthSession*> _session;
|
||||
|
||||
FileLocation _location;
|
||||
|
217
Telegram/SourceFiles/data/data_document_good_thumbnail.cpp
Normal file
217
Telegram/SourceFiles/data/data_document_good_thumbnail.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
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_good_thumbnail.h"
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "auth_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kGoodThumbQuality = 87;
|
||||
|
||||
} // namespace
|
||||
|
||||
GoodThumbSource::GoodThumbSource(not_null<DocumentData*> document)
|
||||
: _document(document) {
|
||||
}
|
||||
|
||||
void GoodThumbSource::generate(base::binary_guard &&guard) {
|
||||
if (!guard.alive()) {
|
||||
return;
|
||||
}
|
||||
const auto data = _document->data();
|
||||
auto location = _document->location().isEmpty()
|
||||
? nullptr
|
||||
: std::make_unique<FileLocation>(_document->location());
|
||||
if (data.isEmpty() && !location) {
|
||||
_empty = true;
|
||||
return;
|
||||
}
|
||||
crl::async([
|
||||
=,
|
||||
guard = std::move(guard),
|
||||
location = std::move(location)
|
||||
]() mutable {
|
||||
const auto filepath = (location && location->accessEnable())
|
||||
? location->name()
|
||||
: QString();
|
||||
auto result = Media::Clip::PrepareForSending(filepath, data);
|
||||
auto bytes = QByteArray();
|
||||
if (!result.thumbnail.isNull()) {
|
||||
QBuffer buffer(&bytes);
|
||||
result.thumbnail.save(&buffer, "JPG", kGoodThumbQuality);
|
||||
}
|
||||
if (!filepath.isEmpty()) {
|
||||
location->accessDisable();
|
||||
}
|
||||
ready(
|
||||
std::move(guard),
|
||||
std::move(result.thumbnail),
|
||||
std::move(bytes));
|
||||
});
|
||||
}
|
||||
|
||||
// NB: This method is called from crl::async(), 'this' is unreliable.
|
||||
void GoodThumbSource::ready(
|
||||
base::binary_guard &&guard,
|
||||
QImage &&image,
|
||||
QByteArray &&bytes) {
|
||||
crl::on_main([
|
||||
=,
|
||||
guard = std::move(guard),
|
||||
image = std::move(image),
|
||||
bytes = std::move(bytes)
|
||||
]() mutable {
|
||||
if (!guard.alive()) {
|
||||
return;
|
||||
}
|
||||
if (image.isNull()) {
|
||||
_empty = true;
|
||||
return;
|
||||
}
|
||||
_loaded = std::move(image);
|
||||
_width = _loaded.width();
|
||||
_height = _loaded.height();
|
||||
if (!bytes.isEmpty()) {
|
||||
Auth().data().cache().put(
|
||||
_document->goodThumbnailCacheKey(),
|
||||
Storage::Cache::Database::TaggedValue{
|
||||
std::move(bytes),
|
||||
Data::kImageCacheTag });
|
||||
}
|
||||
Auth().downloaderTaskFinished().notify();
|
||||
});
|
||||
}
|
||||
|
||||
void GoodThumbSource::load(
|
||||
Data::FileOrigin origin,
|
||||
bool loadFirst,
|
||||
bool prior) {
|
||||
if (loading() || _empty) {
|
||||
return;
|
||||
}
|
||||
auto [left, right] = base::make_binary_guard();
|
||||
_loading = std::move(left);
|
||||
|
||||
auto callback = [=, guard = std::move(right)](
|
||||
QByteArray &&value) mutable {
|
||||
if (value.isEmpty()) {
|
||||
crl::on_main([=, guard = std::move(guard)]() mutable {
|
||||
generate(std::move(guard));
|
||||
});
|
||||
return;
|
||||
}
|
||||
crl::async([
|
||||
=,
|
||||
guard = std::move(guard),
|
||||
value = std::move(value)
|
||||
]() mutable {
|
||||
ready(std::move(guard), App::readImage(value));
|
||||
});
|
||||
};
|
||||
|
||||
Auth().data().cache().get(
|
||||
_document->goodThumbnailCacheKey(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void GoodThumbSource::loadEvenCancelled(
|
||||
Data::FileOrigin origin,
|
||||
bool loadFirst,
|
||||
bool prior) {
|
||||
_empty = false;
|
||||
load(origin, loadFirst, prior);
|
||||
}
|
||||
|
||||
QImage GoodThumbSource::takeLoaded() {
|
||||
return std::move(_loaded);
|
||||
}
|
||||
|
||||
void GoodThumbSource::unload() {
|
||||
_loaded = QImage();
|
||||
cancel();
|
||||
}
|
||||
|
||||
void GoodThumbSource::automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) {
|
||||
}
|
||||
|
||||
void GoodThumbSource::automaticLoadSettingsChanged() {
|
||||
}
|
||||
|
||||
bool GoodThumbSource::loading() {
|
||||
return _loading.alive();
|
||||
}
|
||||
|
||||
bool GoodThumbSource::displayLoading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GoodThumbSource::cancel() {
|
||||
_loading.kill();
|
||||
}
|
||||
|
||||
float64 GoodThumbSource::progress() {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
int GoodThumbSource::loadOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const StorageImageLocation &GoodThumbSource::location() {
|
||||
return StorageImageLocation::Null;
|
||||
}
|
||||
|
||||
void GoodThumbSource::refreshFileReference(const QByteArray &data) {
|
||||
}
|
||||
|
||||
std::optional<Storage::Cache::Key> GoodThumbSource::cacheKey() {
|
||||
return _document->goodThumbnailCacheKey();
|
||||
}
|
||||
|
||||
void GoodThumbSource::setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) {
|
||||
}
|
||||
|
||||
void GoodThumbSource::performDelayedLoad(Data::FileOrigin origin) {
|
||||
}
|
||||
|
||||
bool GoodThumbSource::isDelayedStorageImage() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GoodThumbSource::setImageBytes(const QByteArray &bytes) {
|
||||
if (!bytes.isEmpty()) {
|
||||
cancel();
|
||||
_loaded = App::readImage(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
int GoodThumbSource::width() {
|
||||
return _width;
|
||||
}
|
||||
|
||||
int GoodThumbSource::height() {
|
||||
return _height;
|
||||
}
|
||||
|
||||
void GoodThumbSource::setInformation(int size, int width, int height) {
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
QByteArray GoodThumbSource::bytesForCache() {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
} // namespace Data
|
75
Telegram/SourceFiles/data/data_document_good_thumbnail.h
Normal file
75
Telegram/SourceFiles/data/data_document_good_thumbnail.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/image/image.h"
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class GoodThumbSource : public Images::Source {
|
||||
public:
|
||||
explicit GoodThumbSource(not_null<DocumentData*> document);
|
||||
|
||||
void load(
|
||||
Data::FileOrigin origin,
|
||||
bool loadFirst,
|
||||
bool prior) override;
|
||||
void loadEvenCancelled(
|
||||
Data::FileOrigin origin,
|
||||
bool loadFirst,
|
||||
bool prior) override;
|
||||
QImage takeLoaded() override;
|
||||
void unload() override;
|
||||
|
||||
void automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) override;
|
||||
void automaticLoadSettingsChanged() override;
|
||||
|
||||
bool loading() override;
|
||||
bool displayLoading() override;
|
||||
void cancel() override;
|
||||
float64 progress() override;
|
||||
int loadOffset() override;
|
||||
|
||||
const StorageImageLocation &location() override;
|
||||
void refreshFileReference(const QByteArray &data) override;
|
||||
std::optional<Storage::Cache::Key> cacheKey() override;
|
||||
void setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) override;
|
||||
void performDelayedLoad(Data::FileOrigin origin) override;
|
||||
bool isDelayedStorageImage() const override;
|
||||
void setImageBytes(const QByteArray &bytes) override;
|
||||
|
||||
int width() override;
|
||||
int height() override;
|
||||
void setInformation(int size, int width, int height) override;
|
||||
|
||||
QByteArray bytesForCache() override;
|
||||
|
||||
private:
|
||||
void generate(base::binary_guard &&guard);
|
||||
|
||||
// NB: This method is called from crl::async(), 'this' is unreliable.
|
||||
void ready(
|
||||
base::binary_guard &&guard,
|
||||
QImage &&image,
|
||||
QByteArray &&bytes = {});
|
||||
|
||||
not_null<DocumentData*> _document;
|
||||
QImage _loaded;
|
||||
base::binary_guard _loading;
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
bool _empty = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
@ -17,6 +17,8 @@ namespace {
|
||||
|
||||
constexpr auto kDocumentCacheTag = 0x0000000000000100ULL;
|
||||
constexpr auto kDocumentCacheMask = 0x00000000000000FFULL;
|
||||
constexpr auto kDocumentThumbCacheTag = 0x0000000000000200ULL;
|
||||
constexpr auto kDocumentThumbCacheMask = 0x00000000000000FFULL;
|
||||
constexpr auto kStorageCacheTag = 0x0000010000000000ULL;
|
||||
constexpr auto kStorageCacheMask = 0x000000FFFFFFFFFFULL;
|
||||
constexpr auto kWebDocumentCacheTag = 0x0000020000000000ULL;
|
||||
@ -35,6 +37,14 @@ Storage::Cache::Key DocumentCacheKey(int32 dcId, uint64 id) {
|
||||
};
|
||||
}
|
||||
|
||||
Storage::Cache::Key DocumentThumbCacheKey(int32 dcId, uint64 id) {
|
||||
const auto part = (uint64(dcId) & Data::kDocumentThumbCacheMask);
|
||||
return Storage::Cache::Key{
|
||||
Data::kDocumentThumbCacheTag | part,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
Storage::Cache::Key StorageCacheKey(const StorageImageLocation &location) {
|
||||
const auto dcId = uint64(location.dc()) & 0xFFULL;
|
||||
return Storage::Cache::Key{
|
||||
|
@ -38,6 +38,7 @@ struct UploadState {
|
||||
};
|
||||
|
||||
Storage::Cache::Key DocumentCacheKey(int32 dcId, uint64 id);
|
||||
Storage::Cache::Key DocumentThumbCacheKey(int32 dcId, uint64 id);
|
||||
Storage::Cache::Key StorageCacheKey(const StorageImageLocation &location);
|
||||
Storage::Cache::Key WebDocumentCacheKey(const WebFileLocation &location);
|
||||
Storage::Cache::Key UrlCacheKey(const QString &location);
|
||||
|
@ -817,6 +817,8 @@ QSize HistoryVideo::countOptimalSize() {
|
||||
}
|
||||
|
||||
_thumbw = qMax(tw, 1);
|
||||
_thumbh = qMax(th, 1);
|
||||
|
||||
auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
|
||||
minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
|
||||
auto maxWidth = qMax(_thumbw, minWidth);
|
||||
@ -852,6 +854,7 @@ QSize HistoryVideo::countCurrentSize(int newWidth) {
|
||||
}
|
||||
|
||||
_thumbw = qMax(tw, 1);
|
||||
_thumbh = qMax(th, 1);
|
||||
auto minWidth = qMax(st::minPhotoSize, _parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
|
||||
minWidth = qMax(minWidth, documentMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()));
|
||||
newWidth = qMax(_thumbw, minWidth);
|
||||
@ -905,7 +908,16 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, Tim
|
||||
auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
|
||||
| ((isBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
|
||||
QRect rthumb(rtlrect(paintx, painty, paintw, painth, width()));
|
||||
p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, 0, paintw, painth, roundRadius, roundCorners));
|
||||
|
||||
const auto good = _data->goodThumbnail();
|
||||
if (good && good->loaded()) {
|
||||
p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners));
|
||||
} else {
|
||||
if (good) {
|
||||
good->load({});
|
||||
}
|
||||
p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, paintw, painth, roundRadius, roundCorners));
|
||||
}
|
||||
if (selected) {
|
||||
App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
|
||||
}
|
||||
@ -2257,7 +2269,15 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
||||
const auto good = _data->goodThumbnail();
|
||||
if (good && good->loaded()) {
|
||||
p.drawPixmap(rthumb.topLeft(), good->pixSingle({}, _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
||||
} else {
|
||||
if (good) {
|
||||
good->load({});
|
||||
}
|
||||
p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_realParent->fullId(), _thumbw, _thumbh, usew, painth, roundRadius, roundCorners));
|
||||
}
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
|
@ -311,7 +311,8 @@ private:
|
||||
void updateStatusText() const;
|
||||
|
||||
not_null<DocumentData*> _data;
|
||||
int _thumbw;
|
||||
int _thumbw = 1;
|
||||
int _thumbh = 1;
|
||||
Text _caption;
|
||||
|
||||
};
|
||||
|
@ -80,14 +80,14 @@ QPixmap PrepareFrame(const FrameRequest &request, const QImage &original, bool h
|
||||
|
||||
} // namespace
|
||||
|
||||
Reader::Reader(const QString &filepath, Callback &&callback, Mode mode, int64 seekMs)
|
||||
Reader::Reader(const QString &filepath, Callback &&callback, Mode mode, TimeMs seekMs)
|
||||
: _callback(std::move(callback))
|
||||
, _mode(mode)
|
||||
, _seekPositionMs(seekMs) {
|
||||
init(FileLocation(filepath), QByteArray());
|
||||
}
|
||||
|
||||
Reader::Reader(not_null<DocumentData*> document, FullMsgId msgId, Callback &&callback, Mode mode, int64 seekMs)
|
||||
Reader::Reader(not_null<DocumentData*> document, FullMsgId msgId, Callback &&callback, Mode mode, TimeMs seekMs)
|
||||
: _callback(std::move(callback))
|
||||
, _mode(mode)
|
||||
, _audioMsgId(document, msgId, (mode == Mode::Video) ? rand_value<uint32>() : 0)
|
||||
@ -870,12 +870,14 @@ FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteA
|
||||
auto durationMs = reader->durationMs();
|
||||
if (durationMs > 0) {
|
||||
result.isGifv = reader->isGifv();
|
||||
if (!result.isGifv) {
|
||||
auto middleMs = durationMs / 2;
|
||||
if (!reader->inspectAt(middleMs)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// Use first video frame as a thumbnail.
|
||||
// All other apps and server do that way.
|
||||
//if (!result.isGifv) {
|
||||
// auto middleMs = durationMs / 2;
|
||||
// if (!reader->inspectAt(middleMs)) {
|
||||
// return result;
|
||||
// }
|
||||
//}
|
||||
auto hasAlpha = false;
|
||||
auto readResult = reader->readFramesTill(-1, getms());
|
||||
auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success);
|
||||
|
@ -136,6 +136,8 @@
|
||||
<(src_loc)/data/data_channel_admins.h
|
||||
<(src_loc)/data/data_document.cpp
|
||||
<(src_loc)/data/data_document.h
|
||||
<(src_loc)/data/data_document_good_thumbnail.cpp
|
||||
<(src_loc)/data/data_document_good_thumbnail.h
|
||||
<(src_loc)/data/data_drafts.cpp
|
||||
<(src_loc)/data/data_drafts.h
|
||||
<(src_loc)/data/data_feed.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user