localimageloader.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  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 "storage/localimageloader.h"
  8. #include "api/api_text_entities.h"
  9. #include "api/api_sending.h"
  10. #include "data/data_document.h"
  11. #include "data/data_session.h"
  12. #include "data/data_user.h"
  13. #include "core/file_utilities.h"
  14. #include "core/mime_type.h"
  15. #include "base/options.h"
  16. #include "base/unixtime.h"
  17. #include "base/random.h"
  18. #include "editor/scene/scene_item_sticker.h"
  19. #include "editor/scene/scene.h"
  20. #include "media/audio/media_audio.h"
  21. #include "media/clip/media_clip_reader.h"
  22. #include "mtproto/facade.h"
  23. #include "lottie/lottie_animation.h"
  24. #include "history/history.h"
  25. #include "history/history_item.h"
  26. #include "boxes/abstract_box.h"
  27. #include "boxes/send_files_box.h"
  28. #include "boxes/premium_limits_box.h"
  29. #include "ui/boxes/confirm_box.h"
  30. #include "ui/chat/attach/attach_prepare.h"
  31. #include "ui/image/image_prepare.h"
  32. #include "lang/lang_keys.h"
  33. #include "storage/file_download.h"
  34. #include "storage/storage_media_prepare.h"
  35. #include "window/themes/window_theme_preview.h"
  36. #include "mainwidget.h"
  37. #include "mainwindow.h"
  38. #include "main/main_session.h"
  39. #include <QtCore/QBuffer>
  40. #include <QtGui/QImageWriter>
  41. namespace {
  42. constexpr auto kThumbnailQuality = 87;
  43. constexpr auto kThumbnailSize = 320;
  44. constexpr auto kPhotoUploadPartSize = 32 * 1024;
  45. constexpr auto kRecompressAfterBpp = 4;
  46. using Ui::ValidateThumbDimensions;
  47. base::options::toggle SendLargePhotos({
  48. .id = kOptionSendLargePhotos,
  49. .name = "Send large photos",
  50. .description = "Increase the side limit on compressed images to 2560px.",
  51. });
  52. std::atomic<bool> SendLargePhotosAtomic/* = false*/;
  53. struct PreparedFileThumbnail {
  54. uint64 id = 0;
  55. QString name;
  56. QImage image;
  57. QByteArray bytes;
  58. MTPPhotoSize mtpSize = MTP_photoSizeEmpty(MTP_string());
  59. };
  60. [[nodiscard]] PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
  61. const auto width = original.width();
  62. const auto height = original.height();
  63. if (!ValidateThumbDimensions(width, height)) {
  64. return {};
  65. }
  66. auto result = PreparedFileThumbnail();
  67. result.id = base::RandomValue<uint64>();
  68. const auto scaled = (width > kThumbnailSize || height > kThumbnailSize);
  69. const auto scaledWidth = [&] {
  70. return (width > height)
  71. ? kThumbnailSize
  72. : int(base::SafeRound(kThumbnailSize * width / float64(height)));
  73. };
  74. const auto scaledHeight = [&] {
  75. return (width > height)
  76. ? int(base::SafeRound(kThumbnailSize * height / float64(width)))
  77. : kThumbnailSize;
  78. };
  79. result.image = scaled
  80. ? original.scaled(
  81. scaledWidth(),
  82. scaledHeight(),
  83. Qt::IgnoreAspectRatio,
  84. Qt::SmoothTransformation)
  85. : std::move(original);
  86. result.mtpSize = MTP_photoSize(
  87. MTP_string(),
  88. MTP_int(result.image.width()),
  89. MTP_int(result.image.height()),
  90. MTP_int(0));
  91. return result;
  92. }
  93. [[nodiscard]] bool FileThumbnailUploadRequired(
  94. const QString &filemime,
  95. int64 filesize) {
  96. constexpr auto kThumbnailUploadBySize = 5 * int64(1024 * 1024);
  97. const auto kThumbnailKnownMimes = {
  98. "image/jpeg",
  99. "image/gif",
  100. "image/png",
  101. "image/webp",
  102. "video/mp4",
  103. };
  104. return (filesize > kThumbnailUploadBySize)
  105. || (ranges::find(kThumbnailKnownMimes, filemime.toLower())
  106. == end(kThumbnailKnownMimes));
  107. }
  108. [[nodiscard]] PreparedFileThumbnail FinalizeFileThumbnail(
  109. PreparedFileThumbnail &&prepared,
  110. const QString &filemime,
  111. int64 filesize,
  112. bool isSticker) {
  113. prepared.name = isSticker ? u"thumb.webp"_q : u"thumb.jpg"_q;
  114. if (FileThumbnailUploadRequired(filemime, filesize)) {
  115. const auto format = isSticker ? "WEBP" : "JPG";
  116. auto buffer = QBuffer(&prepared.bytes);
  117. prepared.image.save(&buffer, format, kThumbnailQuality);
  118. }
  119. return std::move(prepared);
  120. }
  121. [[nodiscard]] auto FindAlbumItem(
  122. std::vector<SendingAlbum::Item> &items,
  123. not_null<HistoryItem*> item) {
  124. const auto result = ranges::find(
  125. items,
  126. item->fullId(),
  127. &SendingAlbum::Item::msgId);
  128. Ensures(result != end(items));
  129. return result;
  130. }
  131. [[nodiscard]] MTPInputSingleMedia PrepareAlbumItemMedia(
  132. not_null<HistoryItem*> item,
  133. const MTPInputMedia &media,
  134. uint64 randomId) {
  135. auto caption = item->originalText();
  136. TextUtilities::Trim(caption);
  137. auto sentEntities = Api::EntitiesToMTP(
  138. &item->history()->session(),
  139. caption.entities,
  140. Api::ConvertOption::SkipLocal);
  141. const auto flags = !sentEntities.v.isEmpty()
  142. ? MTPDinputSingleMedia::Flag::f_entities
  143. : MTPDinputSingleMedia::Flag(0);
  144. return MTP_inputSingleMedia(
  145. MTP_flags(flags),
  146. media,
  147. MTP_long(randomId),
  148. MTP_string(caption.text),
  149. sentEntities);
  150. }
  151. [[nodiscard]] std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
  152. not_null<const Ui::PreparedFileInformation::Image*> info) {
  153. const auto allItems = info->modifications.paint->items();
  154. return ranges::views::all(
  155. allItems
  156. ) | ranges::views::filter([](const Editor::Scene::ItemPtr &i) {
  157. return i->isVisible() && (i->type() == Editor::ItemSticker::Type);
  158. }) | ranges::views::transform([](const Editor::Scene::ItemPtr &i) {
  159. return static_cast<Editor::ItemSticker*>(i.get())->sticker();
  160. }) | ranges::to_vector;
  161. }
  162. [[nodiscard]] QByteArray ComputePhotoJpegBytes(
  163. QImage &full,
  164. const QByteArray &bytes,
  165. const QByteArray &format) {
  166. if (!bytes.isEmpty()
  167. && (bytes.size()
  168. <= full.width() * full.height() * kRecompressAfterBpp / 8)
  169. && (format == u"jpeg"_q)) {
  170. if (!Images::IsProgressiveJpeg(bytes)) {
  171. if (const auto result = Images::MakeProgressiveJpeg(bytes)
  172. ; !result.isEmpty()) {
  173. return result;
  174. }
  175. } else {
  176. return bytes;
  177. }
  178. }
  179. auto result = QByteArray();
  180. QBuffer buffer(&result);
  181. QImageWriter writer(&buffer, "JPEG");
  182. writer.setQuality(87);
  183. writer.setProgressiveScanWrite(true);
  184. writer.write(full);
  185. buffer.close();
  186. return result;
  187. }
  188. [[nodiscard]] int PhotoSideLimit(bool large) {
  189. return large ? 2560 : 1280;
  190. }
  191. [[nodiscard]] int PhotoSideLimitAtomic() {
  192. return PhotoSideLimit(SendLargePhotosAtomic.load());
  193. }
  194. } // namespace
  195. const char kOptionSendLargePhotos[] = "send-large-photos";
  196. int PhotoSideLimit() {
  197. return PhotoSideLimit(SendLargePhotos.value());
  198. }
  199. TaskQueue::TaskQueue(crl::time stopTimeoutMs) {
  200. if (stopTimeoutMs > 0) {
  201. _stopTimer = new QTimer(this);
  202. connect(_stopTimer, SIGNAL(timeout()), this, SLOT(stop()));
  203. _stopTimer->setSingleShot(true);
  204. _stopTimer->setInterval(int(stopTimeoutMs));
  205. }
  206. }
  207. TaskId TaskQueue::addTask(std::unique_ptr<Task> &&task) {
  208. const auto result = task->id();
  209. {
  210. QMutexLocker lock(&_tasksToProcessMutex);
  211. _tasksToProcess.push_back(std::move(task));
  212. }
  213. wakeThread();
  214. return result;
  215. }
  216. void TaskQueue::addTasks(std::vector<std::unique_ptr<Task>> &&tasks) {
  217. {
  218. QMutexLocker lock(&_tasksToProcessMutex);
  219. for (auto &task : tasks) {
  220. _tasksToProcess.push_back(std::move(task));
  221. }
  222. }
  223. wakeThread();
  224. }
  225. void TaskQueue::wakeThread() {
  226. if (!_thread) {
  227. _thread = new QThread();
  228. _worker = new TaskQueueWorker(this);
  229. _worker->moveToThread(_thread);
  230. connect(this, SIGNAL(taskAdded()), _worker, SLOT(onTaskAdded()));
  231. connect(_worker, SIGNAL(taskProcessed()), this, SLOT(onTaskProcessed()));
  232. _thread->start();
  233. }
  234. if (_stopTimer) _stopTimer->stop();
  235. taskAdded();
  236. }
  237. void TaskQueue::cancelTask(TaskId id) {
  238. const auto removeFrom = [&](std::deque<std::unique_ptr<Task>> &queue) {
  239. const auto proj = [](const std::unique_ptr<Task> &task) {
  240. return task->id();
  241. };
  242. auto i = ranges::find(queue, id, proj);
  243. if (i != queue.end()) {
  244. queue.erase(i);
  245. }
  246. };
  247. {
  248. QMutexLocker lock(&_tasksToProcessMutex);
  249. removeFrom(_tasksToProcess);
  250. if (_taskInProcessId == id) {
  251. _taskInProcessId = TaskId();
  252. }
  253. }
  254. QMutexLocker lock(&_tasksToFinishMutex);
  255. removeFrom(_tasksToFinish);
  256. }
  257. void TaskQueue::onTaskProcessed() {
  258. do {
  259. auto task = std::unique_ptr<Task>();
  260. {
  261. QMutexLocker lock(&_tasksToFinishMutex);
  262. if (_tasksToFinish.empty()) break;
  263. task = std::move(_tasksToFinish.front());
  264. _tasksToFinish.pop_front();
  265. }
  266. task->finish();
  267. } while (true);
  268. if (_stopTimer) {
  269. QMutexLocker lock(&_tasksToProcessMutex);
  270. if (_tasksToProcess.empty() && !_taskInProcessId) {
  271. _stopTimer->start();
  272. }
  273. }
  274. }
  275. void TaskQueue::stop() {
  276. if (_thread) {
  277. _thread->requestInterruption();
  278. _thread->quit();
  279. DEBUG_LOG(("Waiting for taskThread to finish"));
  280. _thread->wait();
  281. delete base::take(_worker);
  282. delete base::take(_thread);
  283. }
  284. _tasksToProcess.clear();
  285. _tasksToFinish.clear();
  286. _taskInProcessId = TaskId();
  287. }
  288. TaskQueue::~TaskQueue() {
  289. stop();
  290. delete _stopTimer;
  291. }
  292. void TaskQueueWorker::onTaskAdded() {
  293. if (_inTaskAdded) return;
  294. _inTaskAdded = true;
  295. bool someTasksLeft = false;
  296. do {
  297. auto task = std::unique_ptr<Task>();
  298. {
  299. QMutexLocker lock(&_queue->_tasksToProcessMutex);
  300. if (!_queue->_tasksToProcess.empty()) {
  301. task = std::move(_queue->_tasksToProcess.front());
  302. _queue->_tasksToProcess.pop_front();
  303. _queue->_taskInProcessId = task->id();
  304. }
  305. }
  306. if (task) {
  307. task->process();
  308. bool emitTaskProcessed = false;
  309. {
  310. QMutexLocker lockToProcess(&_queue->_tasksToProcessMutex);
  311. if (_queue->_taskInProcessId == task->id()) {
  312. _queue->_taskInProcessId = TaskId();
  313. someTasksLeft = !_queue->_tasksToProcess.empty();
  314. QMutexLocker lockToFinish(&_queue->_tasksToFinishMutex);
  315. emitTaskProcessed = _queue->_tasksToFinish.empty();
  316. _queue->_tasksToFinish.push_back(std::move(task));
  317. }
  318. }
  319. if (emitTaskProcessed) {
  320. taskProcessed();
  321. }
  322. }
  323. QCoreApplication::processEvents();
  324. } while (someTasksLeft && !thread()->isInterruptionRequested());
  325. _inTaskAdded = false;
  326. }
  327. SendingAlbum::SendingAlbum() : groupId(base::RandomValue<uint64>()) {
  328. }
  329. void SendingAlbum::fillMedia(
  330. not_null<HistoryItem*> item,
  331. const MTPInputMedia &media,
  332. uint64 randomId) {
  333. const auto i = FindAlbumItem(items, item);
  334. Assert(!i->media);
  335. i->randomId = randomId;
  336. i->media = PrepareAlbumItemMedia(item, media, randomId);
  337. }
  338. void SendingAlbum::refreshMediaCaption(not_null<HistoryItem*> item) {
  339. const auto i = FindAlbumItem(items, item);
  340. if (!i->media) {
  341. return;
  342. }
  343. i->media = i->media->match([&](const MTPDinputSingleMedia &data) {
  344. return PrepareAlbumItemMedia(
  345. item,
  346. data.vmedia(),
  347. data.vrandom_id().v);
  348. });
  349. }
  350. void SendingAlbum::removeItem(not_null<HistoryItem*> item) {
  351. const auto localId = item->fullId();
  352. const auto i = ranges::find(items, localId, &Item::msgId);
  353. const auto moveCaption = (items.size() > 1) && (i == begin(items));
  354. Assert(i != end(items));
  355. items.erase(i);
  356. if (moveCaption) {
  357. const auto caption = item->originalText();
  358. const auto firstId = items.front().msgId;
  359. if (const auto first = item->history()->owner().message(firstId)) {
  360. // We don't need to finishEdition() here, because the whole
  361. // album will be rebuilt after one item was removed from it.
  362. first->setText(caption);
  363. refreshMediaCaption(first);
  364. }
  365. }
  366. }
  367. SendingAlbum::Item::Item(TaskId taskId)
  368. : taskId(taskId) {
  369. }
  370. FilePrepareResult::FilePrepareResult(FilePrepareDescriptor &&descriptor)
  371. : taskId(descriptor.taskId)
  372. , id(descriptor.id)
  373. , to(std::move(descriptor.to))
  374. , album(std::move(descriptor.album))
  375. , type(descriptor.type)
  376. , caption(std::move(descriptor.caption))
  377. , spoiler(descriptor.spoiler) {
  378. }
  379. void FilePrepareResult::setFileData(const QByteArray &filedata) {
  380. if (filedata.isEmpty()) {
  381. partssize = 0;
  382. } else {
  383. partssize = filedata.size();
  384. fileparts.reserve(
  385. (partssize + kPhotoUploadPartSize - 1) / kPhotoUploadPartSize);
  386. for (int32 i = 0, part = 0; i < partssize; i += kPhotoUploadPartSize, ++part) {
  387. fileparts.push_back(filedata.mid(i, kPhotoUploadPartSize));
  388. }
  389. filemd5.resize(32);
  390. hashMd5Hex(filedata.constData(), filedata.size(), filemd5.data());
  391. }
  392. }
  393. void FilePrepareResult::setThumbData(const QByteArray &thumbdata) {
  394. if (!thumbdata.isEmpty()) {
  395. thumbbytes = thumbdata;
  396. int32 size = thumbdata.size();
  397. thumbparts.reserve(
  398. (size + kPhotoUploadPartSize - 1) / kPhotoUploadPartSize);
  399. for (int32 i = 0, part = 0; i < size; i += kPhotoUploadPartSize, ++part) {
  400. thumbparts.push_back(thumbdata.mid(i, kPhotoUploadPartSize));
  401. }
  402. thumbmd5.resize(32);
  403. hashMd5Hex(thumbdata.constData(), thumbdata.size(), thumbmd5.data());
  404. }
  405. }
  406. std::shared_ptr<FilePrepareResult> MakePreparedFile(
  407. FilePrepareDescriptor &&descriptor) {
  408. return std::make_shared<FilePrepareResult>(std::move(descriptor));
  409. }
  410. FileLoadTask::FileLoadTask(
  411. not_null<Main::Session*> session,
  412. const QString &filepath,
  413. const QByteArray &content,
  414. std::unique_ptr<Ui::PreparedFileInformation> information,
  415. std::unique_ptr<FileLoadTask> videoCover,
  416. SendMediaType type,
  417. const FileLoadTo &to,
  418. const TextWithTags &caption,
  419. bool spoiler,
  420. std::shared_ptr<SendingAlbum> album,
  421. uint64 idOverride)
  422. : _id(idOverride ? idOverride : base::RandomValue<uint64>())
  423. , _session(session)
  424. , _dcId(session->mainDcId())
  425. , _to(to)
  426. , _album(std::move(album))
  427. , _filepath(filepath)
  428. , _content(content)
  429. , _videoCover(std::move(videoCover))
  430. , _information(std::move(information))
  431. , _type(type)
  432. , _caption(caption)
  433. , _spoiler(spoiler) {
  434. Expects(to.options.scheduled
  435. || to.options.shortcutId
  436. || !to.replaceMediaOf
  437. || IsServerMsgId(to.replaceMediaOf));
  438. SendLargePhotosAtomic = SendLargePhotos.value();
  439. }
  440. FileLoadTask::FileLoadTask(
  441. not_null<Main::Session*> session,
  442. const QByteArray &voice,
  443. crl::time duration,
  444. const VoiceWaveform &waveform,
  445. bool video,
  446. const FileLoadTo &to,
  447. const TextWithTags &caption)
  448. : _id(base::RandomValue<uint64>())
  449. , _session(session)
  450. , _dcId(session->mainDcId())
  451. , _to(to)
  452. , _content(voice)
  453. , _duration(duration)
  454. , _waveform(waveform)
  455. , _type(video ? SendMediaType::Round : SendMediaType::Audio)
  456. , _caption(caption) {
  457. }
  458. FileLoadTask::~FileLoadTask() = default;
  459. auto FileLoadTask::ReadMediaInformation(
  460. const QString &filepath,
  461. const QByteArray &content,
  462. const QString &filemime)
  463. -> std::unique_ptr<Ui::PreparedFileInformation> {
  464. auto result = std::make_unique<Ui::PreparedFileInformation>();
  465. result->filemime = filemime;
  466. if (CheckForSong(filepath, content, result)) {
  467. return result;
  468. } else if (CheckForVideo(filepath, content, result)) {
  469. return result;
  470. } else if (CheckForImage(filepath, content, result)) {
  471. return result;
  472. }
  473. return result;
  474. }
  475. template <typename Mimes, typename Extensions>
  476. bool FileLoadTask::CheckMimeOrExtensions(
  477. const QString &filepath,
  478. const QString &filemime,
  479. Mimes &mimes,
  480. Extensions &extensions) {
  481. if (std::find(std::begin(mimes), std::end(mimes), filemime) != std::end(mimes)) {
  482. return true;
  483. }
  484. if (std::find_if(std::begin(extensions), std::end(extensions), [&filepath](auto &extension) {
  485. return filepath.endsWith(extension, Qt::CaseInsensitive);
  486. }) != std::end(extensions)) {
  487. return true;
  488. }
  489. return false;
  490. }
  491. bool FileLoadTask::CheckForSong(
  492. const QString &filepath,
  493. const QByteArray &content,
  494. std::unique_ptr<Ui::PreparedFileInformation> &result) {
  495. static const auto mimes = {
  496. u"audio/mp3"_q,
  497. u"audio/m4a"_q,
  498. u"audio/aac"_q,
  499. u"audio/ogg"_q,
  500. u"audio/flac"_q,
  501. u"audio/opus"_q,
  502. };
  503. static const auto extensions = {
  504. u".mp3"_q,
  505. u".m4a"_q,
  506. u".aac"_q,
  507. u".ogg"_q,
  508. u".flac"_q,
  509. u".opus"_q,
  510. u".oga"_q,
  511. };
  512. if (!filepath.isEmpty()
  513. && !CheckMimeOrExtensions(
  514. filepath,
  515. result->filemime,
  516. mimes,
  517. extensions)) {
  518. return false;
  519. }
  520. auto media = v::get<Ui::PreparedFileInformation::Song>(
  521. Media::Player::PrepareForSending(filepath, content).media);
  522. if (media.duration < 0) {
  523. return false;
  524. }
  525. if (!ValidateThumbDimensions(media.cover.width(), media.cover.height())) {
  526. media.cover = QImage();
  527. }
  528. result->media = std::move(media);
  529. return true;
  530. }
  531. bool FileLoadTask::CheckForVideo(
  532. const QString &filepath,
  533. const QByteArray &content,
  534. std::unique_ptr<Ui::PreparedFileInformation> &result) {
  535. static const auto mimes = {
  536. u"video/mp4"_q,
  537. u"video/quicktime"_q,
  538. };
  539. static const auto extensions = {
  540. u".mp4"_q,
  541. u".mov"_q,
  542. u".m4v"_q,
  543. u".webm"_q,
  544. };
  545. if (!CheckMimeOrExtensions(filepath, result->filemime, mimes, extensions)) {
  546. return false;
  547. }
  548. auto media = v::get<Ui::PreparedFileInformation::Video>(
  549. Media::Clip::PrepareForSending(filepath, content).media);
  550. if (media.duration < 0) {
  551. return false;
  552. }
  553. auto coverWidth = media.thumbnail.width();
  554. auto coverHeight = media.thumbnail.height();
  555. if (!ValidateThumbDimensions(coverWidth, coverHeight)) {
  556. return false;
  557. }
  558. if (filepath.endsWith(u".mp4"_q, Qt::CaseInsensitive)) {
  559. result->filemime = u"video/mp4"_q;
  560. }
  561. result->media = std::move(media);
  562. return true;
  563. }
  564. bool FileLoadTask::CheckForImage(
  565. const QString &filepath,
  566. const QByteArray &content,
  567. std::unique_ptr<Ui::PreparedFileInformation> &result) {
  568. auto read = [&] {
  569. if (filepath.endsWith(u".tgs"_q, Qt::CaseInsensitive)) {
  570. auto image = Lottie::ReadThumbnail(
  571. Lottie::ReadContent(content, filepath));
  572. const auto success = !image.isNull();
  573. if (success) {
  574. result->filemime = u"application/x-tgsticker"_q;
  575. }
  576. return Images::ReadResult{
  577. .image = std::move(image),
  578. .animated = success,
  579. };
  580. }
  581. return Images::Read({
  582. .path = filepath,
  583. .content = content,
  584. .returnContent = true,
  585. });
  586. }();
  587. return FillImageInformation(
  588. std::move(read.image),
  589. read.animated,
  590. result,
  591. std::move(read.content),
  592. std::move(read.format));
  593. }
  594. bool FileLoadTask::FillImageInformation(
  595. QImage &&image,
  596. bool animated,
  597. std::unique_ptr<Ui::PreparedFileInformation> &result,
  598. QByteArray content,
  599. QByteArray format) {
  600. Expects(result != nullptr);
  601. if (image.isNull()) {
  602. return false;
  603. }
  604. auto media = Ui::PreparedFileInformation::Image();
  605. media.data = std::move(image);
  606. media.bytes = std::move(content);
  607. media.format = std::move(format);
  608. media.animated = animated;
  609. result->media = media;
  610. return true;
  611. }
  612. void FileLoadTask::process(Args &&args) {
  613. _result = MakePreparedFile({
  614. .taskId = id(),
  615. .id = _id,
  616. .to = _to,
  617. .caption = _caption,
  618. .spoiler = _spoiler,
  619. .album = _album,
  620. });
  621. if (const auto cover = _videoCover.get()) {
  622. cover->process();
  623. if (const auto &result = cover->peekResult()) {
  624. if (result->type == SendMediaType::Photo
  625. && !result->fileparts.empty()) {
  626. _result->videoCover = result;
  627. }
  628. }
  629. }
  630. QString filename, filemime;
  631. qint64 filesize = 0;
  632. QByteArray filedata;
  633. auto isAnimation = false;
  634. auto isSong = false;
  635. auto isVideo = false;
  636. auto isVoice = (_type == SendMediaType::Audio);
  637. auto isRound = (_type == SendMediaType::Round);
  638. auto isSticker = false;
  639. auto fullimage = QImage();
  640. auto fullimagebytes = QByteArray();
  641. auto fullimageformat = QByteArray();
  642. auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath);
  643. if (info.exists()) {
  644. if (info.isDir()) {
  645. _result->filesize = -1;
  646. return;
  647. }
  648. // Voice sending is supported only from memory for now.
  649. // Because for voice we force mime type and don't read MediaInformation.
  650. // For a real file we always read mime type and read MediaInformation.
  651. Assert(!isVoice && !isRound);
  652. filesize = info.size();
  653. filename = info.fileName();
  654. if (!_information) {
  655. _information = readMediaInformation(Core::MimeTypeForFile(info).name());
  656. }
  657. filemime = _information->filemime;
  658. if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
  659. &_information->media)) {
  660. fullimage = base::take(image->data);
  661. fullimagebytes = base::take(image->bytes);
  662. fullimageformat = base::take(image->format);
  663. if (!Core::IsMimeSticker(filemime)
  664. && fullimageformat != u"jpeg"_q) {
  665. fullimage = Images::Opaque(std::move(fullimage));
  666. fullimagebytes = fullimageformat = QByteArray();
  667. }
  668. isAnimation = image->animated;
  669. }
  670. } else if (!_content.isEmpty()) {
  671. filesize = _content.size();
  672. if (isVoice) {
  673. filename = filedialogDefaultName(u"audio"_q, u".ogg"_q, QString(), true);
  674. filemime = "audio/ogg";
  675. } else if (isRound) {
  676. filename = filedialogDefaultName(u"round"_q, u".mp4"_q, QString(), true);
  677. filemime = "video/mp4";
  678. } else {
  679. if (_information) {
  680. if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
  681. &_information->media)) {
  682. fullimage = base::take(image->data);
  683. fullimagebytes = base::take(image->bytes);
  684. fullimageformat = base::take(image->format);
  685. }
  686. }
  687. const auto mimeType = Core::MimeTypeForData(_content);
  688. filemime = mimeType.name();
  689. if (!Core::IsMimeSticker(filemime)
  690. && fullimageformat != u"jpeg"_q) {
  691. fullimage = Images::Opaque(std::move(fullimage));
  692. fullimagebytes = fullimageformat = QByteArray();
  693. }
  694. if (filemime == "image/jpeg") {
  695. filename = filedialogDefaultName(u"photo"_q, u".jpg"_q, QString(), true);
  696. } else if (filemime == "image/png") {
  697. filename = filedialogDefaultName(u"image"_q, u".png"_q, QString(), true);
  698. } else {
  699. QString ext;
  700. QStringList patterns = mimeType.globPatterns();
  701. if (!patterns.isEmpty()) {
  702. ext = patterns.front().replace('*', QString());
  703. }
  704. filename = filedialogDefaultName(u"file"_q, ext, QString(), true);
  705. }
  706. }
  707. } else {
  708. if (_information) {
  709. if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
  710. &_information->media)) {
  711. fullimage = base::take(image->data);
  712. fullimagebytes = base::take(image->bytes);
  713. fullimageformat = base::take(image->format);
  714. }
  715. }
  716. if (!fullimage.isNull() && fullimage.width() > 0) {
  717. if (_type == SendMediaType::Photo) {
  718. if (ValidateThumbDimensions(fullimage.width(), fullimage.height())) {
  719. filesize = -1; // Fill later.
  720. filemime = Core::MimeTypeForName("image/jpeg").name();
  721. filename = filedialogDefaultName(u"image"_q, u".jpg"_q, QString(), true);
  722. } else {
  723. _type = SendMediaType::File;
  724. }
  725. }
  726. if (_type == SendMediaType::File) {
  727. filemime = Core::MimeTypeForName("image/png").name();
  728. filename = filedialogDefaultName(u"image"_q, u".png"_q, QString(), true);
  729. {
  730. QBuffer buffer(&_content);
  731. fullimage.save(&buffer, "PNG");
  732. }
  733. filesize = _content.size();
  734. }
  735. fullimage = Images::Opaque(std::move(fullimage));
  736. fullimagebytes = fullimageformat = QByteArray();
  737. }
  738. }
  739. _result->filesize = qMin(filesize, qint64(UINT_MAX));
  740. if (!filesize || filesize > kFileSizePremiumLimit) {
  741. return;
  742. }
  743. PreparedPhotoThumbs photoThumbs;
  744. QVector<MTPPhotoSize> photoSizes;
  745. QImage goodThumbnail;
  746. QByteArray goodThumbnailBytes;
  747. QVector<MTPDocumentAttribute> attributes(1, MTP_documentAttributeFilename(MTP_string(filename)));
  748. auto thumbnail = PreparedFileThumbnail();
  749. auto photo = MTP_photoEmpty(MTP_long(0));
  750. auto document = MTP_documentEmpty(MTP_long(0));
  751. if (isRound) {
  752. _information = readMediaInformation(u"video/mp4"_q);
  753. if (auto video = std::get_if<Ui::PreparedFileInformation::Video>(
  754. &_information->media)) {
  755. isVideo = true;
  756. auto coverWidth = video->thumbnail.width();
  757. auto coverHeight = video->thumbnail.height();
  758. if (video->isGifv && !_album) {
  759. attributes.push_back(MTP_documentAttributeAnimated());
  760. }
  761. auto flags = MTPDdocumentAttributeVideo::Flags(
  762. MTPDdocumentAttributeVideo::Flag::f_round_message);
  763. if (video->supportsStreaming) {
  764. flags |= MTPDdocumentAttributeVideo::Flag::f_supports_streaming;
  765. }
  766. const auto realSeconds = video->duration / 1000.;
  767. attributes.push_back(MTP_documentAttributeVideo(
  768. MTP_flags(flags),
  769. MTP_double(realSeconds),
  770. MTP_int(coverWidth),
  771. MTP_int(coverHeight),
  772. MTPint(), // preload_prefix_size
  773. MTPdouble(), // video_start_ts
  774. MTPstring())); // video_codec
  775. if (args.generateGoodThumbnail) {
  776. goodThumbnail = video->thumbnail;
  777. {
  778. QBuffer buffer(&goodThumbnailBytes);
  779. goodThumbnail.save(&buffer, "JPG", kThumbnailQuality);
  780. }
  781. }
  782. thumbnail = PrepareFileThumbnail(std::move(video->thumbnail));
  783. }
  784. } else if (!isVoice) {
  785. if (!_information) {
  786. _information = readMediaInformation(filemime);
  787. filemime = _information->filemime;
  788. }
  789. if (auto song = std::get_if<Ui::PreparedFileInformation::Song>(
  790. &_information->media)) {
  791. isSong = true;
  792. const auto seconds = song->duration / 1000;
  793. auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer;
  794. attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(seconds), MTP_string(song->title), MTP_string(song->performer), MTPstring()));
  795. thumbnail = PrepareFileThumbnail(std::move(song->cover));
  796. } else if (auto video = std::get_if<Ui::PreparedFileInformation::Video>(
  797. &_information->media)) {
  798. isVideo = true;
  799. auto coverWidth = video->thumbnail.width();
  800. auto coverHeight = video->thumbnail.height();
  801. if (video->isGifv && !_album) {
  802. attributes.push_back(MTP_documentAttributeAnimated());
  803. }
  804. auto flags = MTPDdocumentAttributeVideo::Flags(0);
  805. if (video->supportsStreaming) {
  806. flags |= MTPDdocumentAttributeVideo::Flag::f_supports_streaming;
  807. }
  808. const auto realSeconds = video->duration / 1000.;
  809. attributes.push_back(MTP_documentAttributeVideo(
  810. MTP_flags(flags),
  811. MTP_double(realSeconds),
  812. MTP_int(coverWidth),
  813. MTP_int(coverHeight),
  814. MTPint(), // preload_prefix_size
  815. MTPdouble(), // video_start_ts
  816. MTPstring())); // video_codec
  817. if (args.generateGoodThumbnail) {
  818. goodThumbnail = video->thumbnail;
  819. {
  820. QBuffer buffer(&goodThumbnailBytes);
  821. goodThumbnail.save(&buffer, "JPG", kThumbnailQuality);
  822. }
  823. }
  824. thumbnail = PrepareFileThumbnail(std::move(video->thumbnail));
  825. } else if (filemime == u"application/x-tdesktop-theme"_q
  826. || filemime == u"application/x-tgtheme-tdesktop"_q) {
  827. goodThumbnail = Window::Theme::GeneratePreview(_content, _filepath);
  828. if (!goodThumbnail.isNull()) {
  829. QBuffer buffer(&goodThumbnailBytes);
  830. goodThumbnail.save(&buffer, "JPG", kThumbnailQuality);
  831. thumbnail = PrepareFileThumbnail(base::duplicate(goodThumbnail));
  832. }
  833. }
  834. }
  835. if (!fullimage.isNull() && fullimage.width() > 0 && !isSong && !isVideo && !isVoice && !isRound) {
  836. auto w = fullimage.width(), h = fullimage.height();
  837. attributes.push_back(MTP_documentAttributeImageSize(MTP_int(w), MTP_int(h)));
  838. if (ValidateThumbDimensions(w, h)) {
  839. isSticker = Core::IsMimeSticker(filemime)
  840. && (filesize < Storage::kMaxStickerBytesSize)
  841. && (Core::IsMimeStickerAnimated(filemime)
  842. || (_type == SendMediaType::File
  843. && GoodStickerDimensions(w, h)));
  844. if (isSticker) {
  845. attributes.push_back(MTP_documentAttributeSticker(
  846. MTP_flags(0),
  847. MTP_string(),
  848. MTP_inputStickerSetEmpty(),
  849. MTPMaskCoords()));
  850. if (isAnimation && args.generateGoodThumbnail) {
  851. goodThumbnail = fullimage;
  852. {
  853. QBuffer buffer(&goodThumbnailBytes);
  854. goodThumbnail.save(&buffer, "WEBP", kThumbnailQuality);
  855. }
  856. }
  857. } else if (isAnimation) {
  858. attributes.push_back(MTP_documentAttributeAnimated());
  859. } else if (filemime.startsWith(u"image/"_q)
  860. && _type != SendMediaType::File) {
  861. if (Core::IsMimeSticker(filemime)) {
  862. fullimage = Images::Opaque(std::move(fullimage));
  863. }
  864. auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
  865. const auto limit = PhotoSideLimitAtomic();
  866. const auto downscaled = (w > limit || h > limit);
  867. auto full = downscaled ? fullimage.scaled(limit, limit, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
  868. if (downscaled) {
  869. fullimagebytes = fullimageformat = QByteArray();
  870. }
  871. filedata = ComputePhotoJpegBytes(full, fullimagebytes, fullimageformat);
  872. photoThumbs.emplace('m', PreparedPhotoThumb{ .image = medium });
  873. photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
  874. photoThumbs.emplace('y', PreparedPhotoThumb{
  875. .image = full,
  876. .bytes = filedata
  877. });
  878. photoSizes.push_back(MTP_photoSize(MTP_string("y"), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)));
  879. photo = MTP_photo(
  880. MTP_flags(0),
  881. MTP_long(_id),
  882. MTP_long(0),
  883. MTP_bytes(),
  884. MTP_int(base::unixtime::now()),
  885. MTP_vector<MTPPhotoSize>(photoSizes),
  886. MTPVector<MTPVideoSize>(),
  887. MTP_int(_dcId));
  888. if (filesize < 0) {
  889. filesize = _result->filesize = filedata.size();
  890. }
  891. }
  892. thumbnail = PrepareFileThumbnail(std::move(fullimage));
  893. }
  894. }
  895. thumbnail = FinalizeFileThumbnail(
  896. std::move(thumbnail),
  897. filemime,
  898. filesize,
  899. isSticker);
  900. if (_type == SendMediaType::Photo && photoThumbs.empty()) {
  901. _type = SendMediaType::File;
  902. }
  903. if (isVoice) {
  904. const auto seconds = _duration / 1000;
  905. auto flags = MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform;
  906. attributes[0] = MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(seconds), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(_waveform)));
  907. attributes.resize(1);
  908. document = MTP_document(
  909. MTP_flags(0),
  910. MTP_long(_id),
  911. MTP_long(0),
  912. MTP_bytes(),
  913. MTP_int(base::unixtime::now()),
  914. MTP_string(filemime),
  915. MTP_long(filesize),
  916. MTP_vector<MTPPhotoSize>(1, thumbnail.mtpSize),
  917. MTPVector<MTPVideoSize>(),
  918. MTP_int(_dcId),
  919. MTP_vector<MTPDocumentAttribute>(attributes));
  920. } else if (_type != SendMediaType::Photo) {
  921. document = MTP_document(
  922. MTP_flags(0),
  923. MTP_long(_id),
  924. MTP_long(0),
  925. MTP_bytes(),
  926. MTP_int(base::unixtime::now()),
  927. MTP_string(filemime),
  928. MTP_long(filesize),
  929. MTP_vector<MTPPhotoSize>(1, thumbnail.mtpSize),
  930. MTPVector<MTPVideoSize>(),
  931. MTP_int(_dcId),
  932. MTP_vector<MTPDocumentAttribute>(attributes));
  933. _type = isRound ? SendMediaType::Round : SendMediaType::File;
  934. }
  935. if (_information) {
  936. if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
  937. &_information->media)) {
  938. if (image->modifications.paint) {
  939. const auto documents = ExtractStickersFromScene(image);
  940. _result->attachedStickers = documents
  941. | ranges::views::transform(&DocumentData::mtpInput)
  942. | ranges::to_vector;
  943. }
  944. }
  945. }
  946. _result->type = _type;
  947. _result->filepath = _filepath;
  948. _result->content = _content;
  949. _result->filename = filename;
  950. _result->filemime = filemime;
  951. _result->setFileData(filedata);
  952. _result->thumbId = thumbnail.id;
  953. _result->thumbname = thumbnail.name;
  954. _result->setThumbData(thumbnail.bytes);
  955. _result->thumb = std::move(thumbnail.image);
  956. _result->goodThumbnail = std::move(goodThumbnail);
  957. _result->goodThumbnailBytes = std::move(goodThumbnailBytes);
  958. _result->photo = photo;
  959. _result->document = document;
  960. _result->photoThumbs = photoThumbs;
  961. }
  962. void FileLoadTask::finish() {
  963. const auto session = _session.get();
  964. if (!session) {
  965. return;
  966. }
  967. const auto premium = session->user()->isPremium();
  968. if (!_result || !_result->filesize || _result->filesize < 0) {
  969. Ui::show(
  970. Ui::MakeInformBox(
  971. tr::lng_send_image_empty(tr::now, lt_name, _filepath)),
  972. Ui::LayerOption::KeepOther);
  973. removeFromAlbum();
  974. } else if (_result->filesize > kFileSizePremiumLimit
  975. || (_result->filesize > kFileSizeLimit && !premium)) {
  976. Ui::show(
  977. Box(FileSizeLimitBox, session, _result->filesize, nullptr),
  978. Ui::LayerOption::KeepOther);
  979. removeFromAlbum();
  980. } else {
  981. Api::SendConfirmedFile(session, _result);
  982. }
  983. }
  984. const std::shared_ptr<FilePrepareResult> &FileLoadTask::peekResult() const {
  985. return _result;
  986. }
  987. std::unique_ptr<Ui::PreparedFileInformation> FileLoadTask::readMediaInformation(
  988. const QString &filemime) const {
  989. return ReadMediaInformation(_filepath, _content, filemime);
  990. }
  991. void FileLoadTask::removeFromAlbum() {
  992. if (!_album) {
  993. return;
  994. }
  995. const auto proj = [](const SendingAlbum::Item &item) {
  996. return item.taskId;
  997. };
  998. const auto it = ranges::find(_album->items, id(), proj);
  999. Assert(it != _album->items.end());
  1000. _album->items.erase(it);
  1001. }