mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-24 12:24:02 +00:00
The following are commits related to removed variables. apiwrap.cppe050e27
: kSaveDraftBeforeQuitTimeout app.cpp113f665
: serviceImageCacheSize boxes/auto_download_box.cppa0c6104
: checked(Source source, Type type) boxes/background_preview_box.cppb6edf45
: resultBytesPerPixelfe21b5a
: ms boxes/calendar_box.cppae97704
: yearIndex, monthIndex99bb093
: ms boxes/connection_box.cppf794d8d
: ping boxes/dictionaries_manager.cpp8353867
: session boxes/peer_list_box.cpp2ce2a14
: grayedWidth boxes/peers/add_participants_box.cpp07e010d
: chat, channel boxes/self_destruction_box.cppfe9f02e
: count chat_helpers/emoji_suggestions_widget.cppa12bc60
: is(QLatin1String string) chat_helpers/field_autocomplete.cpp8c7a35c
: atwidth, hashwidth chat_helpers/gifs_list_widget.cppff65734
: inlineItems3d846fc
: newSelectedd1687ab
: kSaveDraftBeforeQuitTimeout chat_helpers/stickers_dice_pack.cppc83e297
: kZeroDiceDocumentId chat_helpers/stickers_emoji_pack.cppd298953
: length chat_helpers/stickers_list_widget.cppeb75859
: index, x core/crash_reports.cpp5940ae6
: LaunchedDateTimeStr, LaunchedBinaryName data/data_changes.cpp 3c4e959:clearRealtime data/data_cloud_file.cpp4b354b0
: fromCloud, cacheTag data/data_document_media.cpp7db5359
: kMaxVideoFrameArea data/data_messages.cpp794e315
: wasCount data/data_photo_media.cppe27d2bc
: index data/data_wall_paper.cppb6edf45
: resultBytesPerPixel data/data_types.cppaa8f62d
: kWebDocumentCacheTag, kStorageCacheMask history/admin_log/history_admin_log_inner.cpp794e315
: canDelete, canForward history/history_location_manager.cpp60f45ab
: kCoordPrecision9f90d3a
: kMaxHttpRedirects history/history_message.cppcedf8a6
: kPinnedMessageTextLimit history/history_widget.cppb305924
: serviceColorefa5fc4
: hasForward5e7aa4f
: kTabbedSelectorToggleTooltipTimeoutMs, kTabbedSelectorToggleTooltipCount history/view/history_view_context_menu.cppfe1a90b
: isVideoLink, isVoiceLink, isAudioLink settings.cppe2f54eb
: defaultRecent settings/settings_folders.cppe8bf5bb
: kRefreshSuggestedTimeout ui/filter_icon_panel.cppc4a0bc1
: kDelayedHideTimeoutMs window/themes/window_theme_preview.cppef927c8
: mutedCounter ----- Modified variables boxes/stickers_box.cpp554eb3a
: _rows[pressedIndex] -> set data/data_notify_settings.cpp734c410
: muteForSeconds -> muteUntil history/view/history_view_list_widget.cpp07528be
: _items[index] -> viewe5f3bed
: fromState, tillState history/history.cppcd3c1c6
: kStatusShowClientsideRecordVideo -> kStatusShowClientsideRecordVoice storage/download_manager_mtproto.cppae8fb14
: _queues[dcId] -> queue storage/localstorage.cpp357caf8
: MTP::Environment::Production -> production
471 lines
12 KiB
C++
471 lines
12 KiB
C++
/*
|
|
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_session.h"
|
|
#include "data/data_cloud_themes.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_auto_download.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 "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 "storage/file_download.h"
|
|
#include "ui/image/image.h"
|
|
#include "app.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,
|
|
AnimatedSticker,
|
|
WallPaper,
|
|
Theme,
|
|
};
|
|
|
|
[[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) {
|
|
return owner->isVideoFile()
|
|
|| owner->isAnimation()
|
|
|| owner->isWallPaper()
|
|
|| owner->isTheme()
|
|
|| (owner->sticker() && owner->sticker()->animated);
|
|
}
|
|
|
|
[[nodiscard]] QImage PrepareGoodThumbnail(
|
|
const QString &path,
|
|
QByteArray data,
|
|
FileType type) {
|
|
if (type == FileType::Video) {
|
|
return ::Media::Clip::PrepareForSending(path, data).thumbnail;
|
|
} else if (type == FileType::AnimatedSticker) {
|
|
return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
|
|
} else if (type == FileType::Theme) {
|
|
return Window::Theme::GeneratePreview(data, path);
|
|
}
|
|
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,
|
|
FullMsgId(),
|
|
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) {
|
|
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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void DocumentMedia::checkStickerLarge() {
|
|
if (_sticker) {
|
|
return;
|
|
}
|
|
const auto data = _owner->sticker();
|
|
if (!data) {
|
|
return;
|
|
}
|
|
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
|
if (data->animated || !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().settings().askDownloadPath()) {
|
|
// We need a filename, but we're supposed to ask user for it.
|
|
// No automatic download in this case.
|
|
return;
|
|
}
|
|
const auto filename = toCache
|
|
? QString()
|
|
: DocumentFileNameForSave(_owner);
|
|
const auto shouldLoadFromCloud = !Data::IsExecutableName(filename)
|
|
&& (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() const {
|
|
return !_owner->inappPlaybackFailed()
|
|
&& _owner->useStreamingLoader()
|
|
&& (loaded() || _owner->canBeStreamed());
|
|
}
|
|
|
|
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->animated) || thumbnailEnoughForSticker()) {
|
|
_owner->loadThumbnail(_owner->stickerSetOrigin());
|
|
if (data && data->animated) {
|
|
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
|
}
|
|
} else {
|
|
checkStickerLarge();
|
|
}
|
|
}
|
|
|
|
Image *DocumentMedia::getStickerLarge() {
|
|
checkStickerLarge();
|
|
return _sticker.get();
|
|
}
|
|
|
|
Image *DocumentMedia::getStickerSmall() {
|
|
const auto data = _owner->sticker();
|
|
if ((data && data->animated) || 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->isWallPaper()
|
|
? FileType::WallPaper
|
|
: document->isTheme()
|
|
? FileType::Theme
|
|
: document->sticker()
|
|
? FileType::AnimatedSticker
|
|
: FileType::Video;
|
|
auto location = document->location().isEmpty()
|
|
? nullptr
|
|
: std::make_unique<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)
|
|
? "WEBP"
|
|
: (type == FileType::WallPaper && result.hasAlphaChannel())
|
|
? "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([=] {
|
|
const auto image = App::readImage(value, nullptr, false);
|
|
crl::on_main(guard, [=] {
|
|
document->setGoodThumbnailChecked(true);
|
|
if (const auto active = document->activeMediaView()) {
|
|
active->setGoodThumbnail(image);
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
crl::on_main(guard, [=] {
|
|
document->setGoodThumbnailChecked(true);
|
|
});
|
|
}
|
|
};
|
|
document->owner().cache().get(document->goodThumbnailCacheKey(), got);
|
|
}
|
|
|
|
} // namespace Data
|