data_document_media.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "data/data_document_media.h"
  8. #include "data/data_document.h"
  9. #include "data/data_document_resolver.h"
  10. #include "data/data_session.h"
  11. #include "data/data_file_origin.h"
  12. #include "media/clip/media_clip_reader.h"
  13. #include "main/main_session.h"
  14. #include "main/main_session_settings.h"
  15. #include "lottie/lottie_animation.h"
  16. #include "lottie/lottie_frame_generator.h"
  17. #include "ffmpeg/ffmpeg_frame_generator.h"
  18. #include "history/history_item.h"
  19. #include "history/history.h"
  20. #include "window/themes/window_theme_preview.h"
  21. #include "core/core_settings.h"
  22. #include "core/application.h"
  23. #include "core/mime_type.h"
  24. #include "storage/file_download.h"
  25. #include "ui/chat/attach/attach_prepare.h"
  26. #include <QtCore/QBuffer>
  27. #include <QtGui/QImageReader>
  28. namespace Data {
  29. namespace {
  30. constexpr auto kReadAreaLimit = 12'032 * 9'024;
  31. constexpr auto kWallPaperThumbnailLimit = 960;
  32. constexpr auto kGoodThumbQuality = 87;
  33. enum class FileType {
  34. Video,
  35. VideoSticker,
  36. AnimatedSticker,
  37. WallPaper,
  38. WallPatternPNG,
  39. WallPatternSVG,
  40. Theme,
  41. };
  42. [[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) {
  43. return owner->isVideoFile()
  44. || owner->isAnimation()
  45. || owner->isWallPaper()
  46. || owner->isTheme()
  47. || (owner->sticker() && owner->sticker()->isAnimated());
  48. }
  49. [[nodiscard]] QImage PrepareGoodThumbnail(
  50. const QString &path,
  51. QByteArray data,
  52. FileType type) {
  53. if (type == FileType::Video || type == FileType::VideoSticker) {
  54. auto result = v::get<Ui::PreparedFileInformation::Video>(
  55. ::Media::Clip::PrepareForSending(path, data).media);
  56. if (result.isWebmSticker && type == FileType::Video) {
  57. result.thumbnail = Images::Opaque(std::move(result.thumbnail));
  58. }
  59. return result.thumbnail;
  60. } else if (type == FileType::AnimatedSticker) {
  61. return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
  62. } else if (type == FileType::Theme) {
  63. return Window::Theme::GeneratePreview(data, path);
  64. } else if (type == FileType::WallPatternSVG) {
  65. return Images::Read({
  66. .path = path,
  67. .content = std::move(data),
  68. .maxSize = QSize(
  69. kWallPaperThumbnailLimit,
  70. kWallPaperThumbnailLimit),
  71. .gzipSvg = true,
  72. }).image;
  73. }
  74. auto buffer = QBuffer(&data);
  75. auto file = QFile(path);
  76. auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
  77. auto reader = QImageReader(device);
  78. const auto size = reader.size();
  79. if (!reader.canRead()
  80. || (size.width() * size.height() > kReadAreaLimit)) {
  81. return QImage();
  82. }
  83. auto result = reader.read();
  84. if (!result.width() || !result.height()) {
  85. return QImage();
  86. }
  87. return (result.width() > kWallPaperThumbnailLimit
  88. || result.height() > kWallPaperThumbnailLimit)
  89. ? result.scaled(
  90. kWallPaperThumbnailLimit,
  91. kWallPaperThumbnailLimit,
  92. Qt::KeepAspectRatio,
  93. Qt::SmoothTransformation)
  94. : result;
  95. }
  96. } // namespace
  97. VideoPreviewState::VideoPreviewState(DocumentMedia *media)
  98. : _media(media)
  99. , _usingThumbnail(media ? media->owner()->hasVideoThumbnail() : false) {
  100. }
  101. void VideoPreviewState::automaticLoad(Data::FileOrigin origin) const {
  102. Expects(_media != nullptr);
  103. if (_usingThumbnail) {
  104. _media->videoThumbnailWanted(origin);
  105. } else {
  106. _media->automaticLoad(origin, nullptr);
  107. }
  108. }
  109. ::Media::Clip::ReaderPointer VideoPreviewState::makeAnimation(
  110. Fn<void(::Media::Clip::Notification)> callback) const {
  111. Expects(_media != nullptr);
  112. Expects(loaded());
  113. return _usingThumbnail
  114. ? ::Media::Clip::MakeReader(
  115. _media->videoThumbnailContent(),
  116. std::move(callback))
  117. : ::Media::Clip::MakeReader(
  118. _media->owner()->location(),
  119. _media->bytes(),
  120. std::move(callback));
  121. }
  122. bool VideoPreviewState::usingThumbnail() const {
  123. return _usingThumbnail;
  124. }
  125. bool VideoPreviewState::loading() const {
  126. return _usingThumbnail
  127. ? _media->owner()->videoThumbnailLoading()
  128. : _media
  129. ? _media->owner()->loading()
  130. : false;
  131. }
  132. bool VideoPreviewState::loaded() const {
  133. return _usingThumbnail
  134. ? !_media->videoThumbnailContent().isEmpty()
  135. : _media
  136. ? _media->loaded()
  137. : false;
  138. }
  139. DocumentMedia::DocumentMedia(not_null<DocumentData*> owner)
  140. : _owner(owner) {
  141. }
  142. // NB! Right now DocumentMedia can outlive Main::Session!
  143. // In DocumentData::collectLocalData a shared_ptr is sent on_main.
  144. // In case this is a problem the ~Gif code should be rewritten.
  145. DocumentMedia::~DocumentMedia() = default;
  146. not_null<DocumentData*> DocumentMedia::owner() const {
  147. return _owner;
  148. }
  149. void DocumentMedia::goodThumbnailWanted() {
  150. _flags |= Flag::GoodThumbnailWanted;
  151. }
  152. Image *DocumentMedia::goodThumbnail() const {
  153. Expects((_flags & Flag::GoodThumbnailWanted) != 0);
  154. if (!_goodThumbnail) {
  155. ReadOrGenerateThumbnail(_owner);
  156. }
  157. return _goodThumbnail.get();
  158. }
  159. void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
  160. if (!(_flags & Flag::GoodThumbnailWanted)) {
  161. return;
  162. }
  163. _goodThumbnail = std::make_unique<Image>(std::move(thumbnail));
  164. _owner->session().notifyDownloaderTaskFinished();
  165. }
  166. Image *DocumentMedia::thumbnailInline() const {
  167. if (!_inlineThumbnail && !_owner->inlineThumbnailIsPath()) {
  168. const auto bytes = _owner->inlineThumbnailBytes();
  169. if (!bytes.isEmpty()) {
  170. auto image = Images::FromInlineBytes(bytes);
  171. if (image.isNull()) {
  172. _owner->clearInlineThumbnailBytes();
  173. } else {
  174. _inlineThumbnail = std::make_unique<Image>(std::move(image));
  175. }
  176. }
  177. }
  178. return _inlineThumbnail.get();
  179. }
  180. const QPainterPath &DocumentMedia::thumbnailPath() const {
  181. if (_pathThumbnail.isEmpty() && _owner->inlineThumbnailIsPath()) {
  182. const auto bytes = _owner->inlineThumbnailBytes();
  183. if (!bytes.isEmpty()) {
  184. _pathThumbnail = Images::PathFromInlineBytes(bytes);
  185. if (_pathThumbnail.isEmpty()) {
  186. _owner->clearInlineThumbnailBytes();
  187. }
  188. }
  189. }
  190. return _pathThumbnail;
  191. }
  192. Image *DocumentMedia::thumbnail() const {
  193. return _thumbnail.get();
  194. }
  195. void DocumentMedia::thumbnailWanted(Data::FileOrigin origin) {
  196. if (!_thumbnail) {
  197. _owner->loadThumbnail(origin);
  198. }
  199. }
  200. QSize DocumentMedia::thumbnailSize() const {
  201. if (const auto image = _thumbnail.get()) {
  202. return image->size();
  203. }
  204. const auto &location = _owner->thumbnailLocation();
  205. return { location.width(), location.height() };
  206. }
  207. void DocumentMedia::setThumbnail(QImage thumbnail) {
  208. _thumbnail = std::make_unique<Image>(std::move(thumbnail));
  209. _owner->session().notifyDownloaderTaskFinished();
  210. }
  211. QByteArray DocumentMedia::videoThumbnailContent() const {
  212. return _videoThumbnailBytes;
  213. }
  214. QSize DocumentMedia::videoThumbnailSize() const {
  215. const auto &location = _owner->videoThumbnailLocation();
  216. return { location.width(), location.height() };
  217. }
  218. void DocumentMedia::videoThumbnailWanted(Data::FileOrigin origin) {
  219. if (_videoThumbnailBytes.isEmpty()) {
  220. _owner->loadVideoThumbnail(origin);
  221. }
  222. }
  223. void DocumentMedia::setVideoThumbnail(QByteArray content) {
  224. _videoThumbnailBytes = std::move(content);
  225. _videoThumbnailBytes.detach();
  226. }
  227. void DocumentMedia::checkStickerLarge() {
  228. if (_sticker) {
  229. return;
  230. }
  231. const auto data = _owner->sticker();
  232. if (!data) {
  233. return;
  234. }
  235. automaticLoad(_owner->stickerSetOrigin(), nullptr);
  236. if (data->isAnimated() || !loaded()) {
  237. return;
  238. }
  239. if (_bytes.isEmpty()) {
  240. const auto &loc = _owner->location(true);
  241. if (loc.accessEnable()) {
  242. _sticker = std::make_unique<Image>(loc.name());
  243. loc.accessDisable();
  244. }
  245. } else {
  246. _sticker = std::make_unique<Image>(_bytes);
  247. }
  248. }
  249. void DocumentMedia::automaticLoad(
  250. Data::FileOrigin origin,
  251. const HistoryItem *item) {
  252. if (_owner->status != FileReady || loaded() || _owner->cancelled()) {
  253. return;
  254. } else if (!item && !_owner->sticker() && !_owner->isAnimation()) {
  255. return;
  256. }
  257. const auto toCache = _owner->saveToCache();
  258. if (!toCache && !Core::App().canSaveFileWithoutAskingForPath()) {
  259. // We need a filename, but we're supposed to ask user for it.
  260. // No automatic download in this case.
  261. return;
  262. }
  263. const auto indata = _owner->filename();
  264. const auto filename = toCache
  265. ? QString()
  266. : DocumentFileNameForSave(_owner);
  267. const auto shouldLoadFromCloud = (indata.isEmpty()
  268. || Core::DetectNameType(indata) != Core::NameType::Executable)
  269. && (item
  270. ? Data::AutoDownload::Should(
  271. _owner->session().settings().autoDownload(),
  272. item->history()->peer,
  273. _owner)
  274. : Data::AutoDownload::Should(
  275. _owner->session().settings().autoDownload(),
  276. _owner));
  277. const auto loadFromCloud = shouldLoadFromCloud
  278. ? LoadFromCloudOrLocal
  279. : LoadFromLocalOnly;
  280. _owner->save(
  281. origin,
  282. filename,
  283. loadFromCloud,
  284. true);
  285. }
  286. void DocumentMedia::collectLocalData(not_null<DocumentMedia*> local) {
  287. if (const auto image = local->_goodThumbnail.get()) {
  288. _goodThumbnail = std::make_unique<Image>(image->original());
  289. }
  290. if (const auto image = local->_inlineThumbnail.get()) {
  291. _inlineThumbnail = std::make_unique<Image>(image->original());
  292. }
  293. if (const auto image = local->_thumbnail.get()) {
  294. _thumbnail = std::make_unique<Image>(image->original());
  295. }
  296. if (const auto image = local->_sticker.get()) {
  297. _sticker = std::make_unique<Image>(image->original());
  298. }
  299. _bytes = local->_bytes;
  300. _videoThumbnailBytes = local->_videoThumbnailBytes;
  301. _flags = local->_flags;
  302. }
  303. void DocumentMedia::setBytes(const QByteArray &bytes) {
  304. if (!bytes.isEmpty()) {
  305. _bytes = bytes;
  306. }
  307. }
  308. QByteArray DocumentMedia::bytes() const {
  309. return _bytes;
  310. }
  311. bool DocumentMedia::loaded(bool check) const {
  312. return !_bytes.isEmpty() || !_owner->filepath(check).isEmpty();
  313. }
  314. float64 DocumentMedia::progress() const {
  315. return (_owner->uploading() || _owner->loading())
  316. ? _owner->progress()
  317. : (loaded() ? 1. : 0.);
  318. }
  319. bool DocumentMedia::canBePlayed(HistoryItem *item) const {
  320. return !_owner->inappPlaybackFailed()
  321. && _owner->useStreamingLoader()
  322. && (loaded() || _owner->canBeStreamed(item));
  323. }
  324. bool DocumentMedia::thumbnailEnoughForSticker() const {
  325. const auto &location = owner()->thumbnailLocation();
  326. const auto size = _thumbnail
  327. ? QSize(_thumbnail->width(), _thumbnail->height())
  328. : location.valid()
  329. ? QSize(location.width(), location.height())
  330. : QSize();
  331. return (size.width() >= 128) || (size.height() >= 128);
  332. }
  333. void DocumentMedia::checkStickerSmall() {
  334. const auto data = _owner->sticker();
  335. if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
  336. _owner->loadThumbnail(_owner->stickerSetOrigin());
  337. if (data && data->isAnimated()) {
  338. automaticLoad(_owner->stickerSetOrigin(), nullptr);
  339. }
  340. } else {
  341. checkStickerLarge();
  342. }
  343. }
  344. Image *DocumentMedia::getStickerLarge() {
  345. checkStickerLarge();
  346. return _sticker.get();
  347. }
  348. Image *DocumentMedia::getStickerSmall() {
  349. const auto data = _owner->sticker();
  350. if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
  351. return thumbnail();
  352. }
  353. return _sticker.get();
  354. }
  355. void DocumentMedia::checkStickerLarge(not_null<FileLoader*> loader) {
  356. if (_sticker || !_owner->sticker()) {
  357. return;
  358. }
  359. if (auto image = loader->imageData(); !image.isNull()) {
  360. _sticker = std::make_unique<Image>(std::move(image));
  361. }
  362. }
  363. void DocumentMedia::GenerateGoodThumbnail(
  364. not_null<DocumentData*> document,
  365. QByteArray data) {
  366. const auto type = document->isPatternWallPaperSVG()
  367. ? FileType::WallPatternSVG
  368. : document->isPatternWallPaperPNG()
  369. ? FileType::WallPatternPNG
  370. : document->isWallPaper()
  371. ? FileType::WallPaper
  372. : document->isTheme()
  373. ? FileType::Theme
  374. : !document->sticker()
  375. ? FileType::Video
  376. : document->sticker()->isLottie()
  377. ? FileType::AnimatedSticker
  378. : FileType::VideoSticker;
  379. auto location = document->location().isEmpty()
  380. ? nullptr
  381. : std::make_unique<Core::FileLocation>(document->location());
  382. if (data.isEmpty() && !location) {
  383. document->setGoodThumbnailChecked(false);
  384. return;
  385. }
  386. const auto guard = base::make_weak(&document->owner().session());
  387. crl::async([=, location = std::move(location)] {
  388. const auto filepath = (location && location->accessEnable())
  389. ? location->name()
  390. : QString();
  391. auto result = PrepareGoodThumbnail(filepath, data, type);
  392. auto bytes = QByteArray();
  393. if (!result.isNull()) {
  394. auto buffer = QBuffer(&bytes);
  395. const auto format = (type == FileType::AnimatedSticker
  396. || type == FileType::VideoSticker)
  397. ? "WEBP"
  398. : (type == FileType::WallPatternPNG
  399. || type == FileType::WallPatternSVG)
  400. ? "PNG"
  401. : "JPG";
  402. result.save(&buffer, format, kGoodThumbQuality);
  403. }
  404. if (!filepath.isEmpty()) {
  405. location->accessDisable();
  406. }
  407. const auto cache = bytes.isEmpty() ? QByteArray("(failed)") : bytes;
  408. crl::on_main(guard, [=] {
  409. document->setGoodThumbnailChecked(true);
  410. if (const auto active = document->activeMediaView()) {
  411. active->setGoodThumbnail(result);
  412. }
  413. document->owner().cache().put(
  414. document->goodThumbnailCacheKey(),
  415. Storage::Cache::Database::TaggedValue{
  416. base::duplicate(cache),
  417. kImageCacheTag });
  418. });
  419. });
  420. }
  421. void DocumentMedia::CheckGoodThumbnail(not_null<DocumentData*> document) {
  422. if (!document->goodThumbnailChecked()) {
  423. ReadOrGenerateThumbnail(document);
  424. }
  425. }
  426. void DocumentMedia::ReadOrGenerateThumbnail(
  427. not_null<DocumentData*> document) {
  428. if (document->goodThumbnailGenerating()
  429. || document->goodThumbnailNoData()
  430. || !MayHaveGoodThumbnail(document)) {
  431. return;
  432. }
  433. document->setGoodThumbnailGenerating();
  434. const auto guard = base::make_weak(&document->session());
  435. const auto active = document->activeMediaView();
  436. const auto got = [=](QByteArray value) {
  437. if (value.isEmpty()) {
  438. const auto bytes = active ? active->bytes() : QByteArray();
  439. crl::on_main(guard, [=] {
  440. GenerateGoodThumbnail(document, bytes);
  441. });
  442. } else if (active) {
  443. crl::async([=] {
  444. auto image = Images::Read({ .content = value }).image;
  445. crl::on_main(guard, [=, image = std::move(image)]() mutable {
  446. document->setGoodThumbnailChecked(true);
  447. if (const auto active = document->activeMediaView()) {
  448. active->setGoodThumbnail(std::move(image));
  449. }
  450. });
  451. });
  452. } else {
  453. crl::on_main(guard, [=] {
  454. document->setGoodThumbnailChecked(true);
  455. });
  456. }
  457. };
  458. document->owner().cache().get(document->goodThumbnailCacheKey(), got);
  459. }
  460. auto DocumentIconFrameGenerator(not_null<DocumentMedia*> media)
  461. -> FnMut<std::unique_ptr<Ui::FrameGenerator>()> {
  462. if (!media->loaded()) {
  463. return nullptr;
  464. }
  465. using Type = StickerType;
  466. const auto document = media->owner();
  467. const auto content = media->bytes();
  468. const auto fromFile = content.isEmpty();
  469. const auto type = document->sticker()
  470. ? document->sticker()->type
  471. : (document->isVideoFile() || document->isAnimation())
  472. ? Type::Webm
  473. : Type::Webp;
  474. const auto &location = media->owner()->location(true);
  475. if (fromFile && !location.accessEnable()) {
  476. return nullptr;
  477. }
  478. return [=]() -> std::unique_ptr<Ui::FrameGenerator> {
  479. const auto bytes = Lottie::ReadContent(content, location.name());
  480. if (fromFile) {
  481. location.accessDisable();
  482. }
  483. if (bytes.isEmpty()) {
  484. return nullptr;
  485. }
  486. switch (type) {
  487. case Type::Tgs:
  488. return std::make_unique<Lottie::FrameGenerator>(bytes);
  489. case Type::Webm:
  490. return std::make_unique<FFmpeg::FrameGenerator>(bytes);
  491. case Type::Webp:
  492. return std::make_unique<Ui::ImageFrameGenerator>(bytes);
  493. }
  494. Unexpected("Document type in DocumentIconFrameGenerator.");
  495. };
  496. }
  497. auto DocumentIconFrameGenerator(const std::shared_ptr<DocumentMedia> &media)
  498. -> FnMut<std::unique_ptr<Ui::FrameGenerator>()> {
  499. return DocumentIconFrameGenerator(media.get());
  500. }
  501. } // namespace Data