From c3736c6fa3a30e5ce367b235fed83b470830d2db Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 8 Jul 2018 23:19:31 +0300 Subject: [PATCH] Improve photo/video/sticker/GIF export layout. --- Telegram/Resources/export_html/css/style.css | 96 ++++- .../export_html/images/media_photo.png | Bin 0 -> 243 bytes .../export_html/images/media_photo@2x.png | Bin 0 -> 458 bytes .../export_html/images/media_video.png | Bin 0 -> 235 bytes .../export_html/images/media_video@2x.png | Bin 0 -> 411 bytes Telegram/Resources/qrc/telegram.qrc | 4 + .../export/data/export_data_types.cpp | 59 ++- .../export/data/export_data_types.h | 9 + .../SourceFiles/export/export_api_wrap.cpp | 9 +- Telegram/SourceFiles/export/export_pch.h | 1 + .../export/output/export_output_html.cpp | 372 ++++++++++++++++-- 11 files changed, 497 insertions(+), 53 deletions(-) create mode 100644 Telegram/Resources/export_html/images/media_photo.png create mode 100644 Telegram/Resources/export_html/images/media_photo@2x.png create mode 100644 Telegram/Resources/export_html/images/media_video.png create mode 100644 Telegram/Resources/export_html/images/media_video@2x.png diff --git a/Telegram/Resources/export_html/css/style.css b/Telegram/Resources/export_html/css/style.css index 05ae10c12..4fa6823d5 100644 --- a/Telegram/Resources/export_html/css/style.css +++ b/Telegram/Resources/export_html/css/style.css @@ -56,6 +56,7 @@ pre { } .page_header { position: fixed; + z-index: 10; background-color: #ffffff; width: 100%; border-bottom: 1px solid #e3e6e8; @@ -126,7 +127,8 @@ pre { } .color_green, .userpic2, -.media_call.success .fill { +.media_call.success .fill, +.media_photo .fill { background-color: #64bf47; } .color_yellow, @@ -152,7 +154,8 @@ pre { } .color_sea, .userpic7, -.media_location .fill { +.media_location .fill, +.media_video .fill { background-color: #47bcd1; } .color_orange, @@ -313,6 +316,63 @@ a.block_link:hover { padding-top: 4px; font-size: 13px; } +.default .video_file_wrap, +.default .animated_wrap { + position: relative; +} +.default .video_file, +.default .animated, +.default .photo, +.default .sticker { + display: block; +} +.video_duration { + background: rgba(0, 0, 0, .4); + padding: 0px 5px; + position: absolute; + z-index: 2; + border-radius: 2px; + right: 3px; + bottom: 3px; + color: #ffffff; + font-size: 11px; +} +.video_play_bg { + background: rgba(0, 0, 0, .4); + width: 40px; + height: 40px; + line-height: 0; + position: absolute; + z-index: 2; + border-radius: 50%; + overflow: hidden; + margin: -20px auto 0 -20px; + top: 50%; + left: 50%; + pointer-events: none; +} +.video_play { + position: absolute; + display: inline-block; + top: 50%; + left: 50%; + margin-left: -5px; + margin-top: -9px; + z-index: 1; + width: 0; + height: 0; + border-style: solid; + border-width: 9px 0 9px 14px; + border-color: transparent transparent transparent #fff; +} +.gif_play { + font-weight: 700; + color: #FFF; + display: block; + line-height: 40px; + font-size: 13px; + text-align: center; +} .pagination { text-align: center; padding: 20px; @@ -349,32 +409,38 @@ a.block_link:hover { .page_header a.content { background-image: url(../images/back.png); } -.media_call .thumb { +.media_call .fill { background-image: url(../images/media_call.png) } -.media_contact .thumb { +.media_contact .fill { background-image: url(../images/media_contact.png) } -.media_file .thumb { +.media_file .fill { background-image: url(../images/media_file.png) } -.media_game .thumb { +.media_game .fill { background-image: url(../images/media_game.png) } -.media_live_location .thumb, -.media_location .thumb, -.media_venue .thumb { +.media_live_location .fill, +.media_location .fill, +.media_venue .fill { background-image: url(../images/media_location.png) } -.media_audio_file .thumb { +.media_audio_file .fill { background-image: url(../images/media_music.png) } -.media_invoice .thumb { +.media_invoice .fill { background-image: url(../images/media_shop.png) } -.media_voice_message .thumb { +.media_voice_message .fill { background-image: url(../images/media_voice.png) } +.media_photo .fill { + background-image: url(../images/media_photo.png) +} +.media_video .fill { + background-image: url(../images/media_video.png) +} @media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) { .section.calls { @@ -433,4 +499,10 @@ a.block_link:hover { .media_voice_message .fill { background-image: url(../images/media_voice@2x.png) } +.media_photo .fill { + background-image: url(../images/media_photo@2x.png) +} +.media_video .fill { + background-image: url(../images/media_video@2x.png) +} } diff --git a/Telegram/Resources/export_html/images/media_photo.png b/Telegram/Resources/export_html/images/media_photo.png new file mode 100644 index 0000000000000000000000000000000000000000..d229387677a7f50f652eafd5ce527a078cbaa6d3 GIT binary patch literal 243 zcmVQ&RK**BB$?T(zL(gjHl5(R1^rJ7mXfxOtK3G) zy$o2m{~XARyj3X|w@bAIQ{WCfipC@0?DBG8mMRzkM;E&Qhl=}i0){DqT}87P`*~;ZBMNFHKPCk002ovPDHLkV1g34Ua9IOt4;-FdF#4ZIz!AEd**B9^+9GrF4RVRyY;HZOcE<))nNZYB4sDnd4 zho;b!b~4|B_1~HF zhMuIlF{T|?tXM8Qll$<}Aj4@=(e?y7l2(dHl!2}>W;L#06ByrkiM<68nQx4Vop0Mh ze*!Py8ZY1)FW?$4;2JOB8ZVH6g*+E4TVP+(gQRNVlVV=+^vmBm@D{FhIYEDPc)cLp4>ZTtJD3)MMc{?Oi+_;N(}-E`9oTR!Pyud7dY*vlSkF<+T0@$^oJ)a|n7*5l z`Y!Ng5dgSyC9ntd6G9yW%fRao;fEZSEwBdkEP?t!Yr^mVA3!aOK$cU9SxJwQHdEgf zJ1t4ul0JtKBExe$l{i=a;U%di=_2h#{#?j4UcfaomReWQ!`<}%#re@mD%hUDT>(L| za-UTA*B?0AlIoHg8f>Ss>4vlGVHS_ay{hQ&{*PByA*hDyJLI z;Du-KiMTbf!u6Ox+;x*NP@a2t>1=-uoF2j{C4j29hp%}Ow?r6N@%`*B(^q$BPUJK; zA8pX&`N$TP@vRoE0%}_`40-1*M!kc{XBzzu&I~sJxY`A4(3{Fi0^s79RKRgkf~2ja l-WlE;?Rci_XSeXX+yEnZ#~5?y`bq!*002ovPDHLkV1gZbVnhG{ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/export_html/images/media_video@2x.png b/Telegram/Resources/export_html/images/media_video@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2b554b0280bb5a9e7fd34695b56d33069c544d1a GIT binary patch literal 411 zcmV;M0c8G(P)LMDqJFMauC7+5Iq#ohg)EhekkHdW<7~_YzIxGdD0fKXLtY0 zTDz7Z2!bF80!N4lA~F$?m55X_K2<&Tq@K&vQVK=j7I+74PW)ee15@D3(y1u^M!ryL z=A1q}^&Inzx)PAOJ*79b6{vvgq3JT&0#`@wm%vB!LXj+iCXha8t(t-4j^7FoZ2|2O z@_-s%!=pfUc9cAaJi-%a*ttH%l)**-4}a*(|5Chh1nte%7mm z=Ur&O5+*zeKo1FT0$`NzC;$ctP2eZ_EhD!k+o2Mgz-w2h5qasjpGvIK0&N?;1K#+b z4_gnw+R`cK!`7PJ7qe$z(=*&LHucz3jNyYI2!bH+#xH#Cc4CjDR^b2u002ovPDHLk FV1iaavJL../export_html/images/media_location@2x.png ../export_html/images/media_music.png ../export_html/images/media_music@2x.png + ../export_html/images/media_photo.png + ../export_html/images/media_photo@2x.png ../export_html/images/media_shop.png ../export_html/images/media_shop@2x.png + ../export_html/images/media_video.png + ../export_html/images/media_video@2x.png ../export_html/images/media_voice.png ../export_html/images/media_voice@2x.png ../export_html/images/section_calls.png diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 949530e5d..539adfb51 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -501,41 +501,66 @@ UserpicsSlice ParseUserpicsSlice( return result; } -QString WriteImageThumb( +std::pair WriteImageThumb( const QString &basePath, const QString &largePath, - int width, - int height, + Fn convertSize, + base::optional format, + base::optional quality, const QString &postfix) { if (largePath.isEmpty()) { - return QString(); + return {}; } const auto path = basePath + largePath; QImageReader reader(path); if (!reader.canRead()) { - return QString(); + return {}; } const auto size = reader.size(); if (size.isEmpty() || size.width() >= kMaxImageSize || size.height() >= kMaxImageSize) { - return QString(); + return {}; } auto image = reader.read(); if (image.isNull()) { - return QString(); + return {}; } - const auto format = reader.format(); + const auto finalSize = convertSize(image.size()); + if (finalSize.isEmpty()) { + return {}; + } + image = std::move(image).scaled( + finalSize, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + const auto finalFormat = format ? *format : reader.format(); + const auto finalQuality = quality ? *quality : reader.quality(); const auto lastSlash = largePath.lastIndexOf('/'); const auto firstDot = largePath.indexOf('.', lastSlash + 1); const auto thumb = (firstDot >= 0) ? largePath.mid(0, firstDot) + postfix + largePath.mid(firstDot) : largePath + postfix; const auto result = Output::File::PrepareRelativePath(basePath, thumb); - if (!image.save(basePath + result, reader.format(), reader.quality())) { - return QString(); + if (!image.save(basePath + result, finalFormat, finalQuality)) { + return {}; } - return result; + return { result, finalSize }; +} + +QString WriteImageThumb( + const QString &basePath, + const QString &largePath, + int width, + int height, + const QString &postfix) { + return WriteImageThumb( + basePath, + largePath, + [=](QSize size) { return QSize(width, height); }, + base::none, + base::none, + postfix).first; } ContactInfo ParseContactInfo(const MTPUser &data) { @@ -780,7 +805,7 @@ Media ParseMedia( auto result = Media(); data.match([&](const MTPDmessageMediaPhoto &data) { - result.content = data.has_photo() + auto photo = data.has_photo() ? ParsePhoto( data.vphoto, folder + "photos/" @@ -788,7 +813,9 @@ Media ParseMedia( : Photo(); if (data.has_ttl_seconds()) { result.ttl = data.vttl_seconds.v; + photo.image.file = File(); } + result.content = photo; }, [&](const MTPDmessageMediaGeo &data) { result.content = ParseGeoPoint(data.vgeo); }, [&](const MTPDmessageMediaContact &data) { @@ -796,12 +823,14 @@ Media ParseMedia( }, [&](const MTPDmessageMediaUnsupported &data) { result.content = UnsupportedMedia(); }, [&](const MTPDmessageMediaDocument &data) { - result.content = data.has_document() + auto document = data.has_document() ? ParseDocument(context, data.vdocument, folder) : Document(); if (data.has_ttl_seconds()) { result.ttl = data.vttl_seconds.v; + document.file = File(); } + result.content = document; }, [&](const MTPDmessageMediaWebPage &data) { // Ignore web pages. }, [&](const MTPDmessageMediaVenue &data) { @@ -1053,6 +1082,10 @@ Message ParseMessage( context, data.vmedia, mediaFolder); + if (result.media.ttl && !data.is_out()) { + result.media.file() = File(); + result.media.thumb().file = File(); + } context.botId = 0; } result.text = ParseText( diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index d14dc0ddd..840eb457f 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/optional.h" #include "base/variant.h" +#include #include #include @@ -82,6 +83,14 @@ struct Image { File file; }; +std::pair WriteImageThumb( + const QString &basePath, + const QString &largePath, + Fn convertSize, + base::optional format = base::none, + base::optional quality = base::none, + const QString &postfix = "_thumb"); + QString WriteImageThumb( const QString &basePath, const QString &largePath, diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 148fe2472..3ccd8b62e 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -1096,6 +1096,11 @@ void ApiWrap::appendChatsSlice( auto filtered = ranges::view::all( info.list ) | ranges::view::filter([&](const Data::DialogInfo &info) { +#ifdef _DEBUG + return (info.name == "Anta"); +#else +#error "test" +#endif return (types & SettingsFromDialogsType(info.type)) != 0; }); auto &list = to.info.list; @@ -1365,10 +1370,12 @@ bool ApiWrap::processFileLoad( return Type::Photo; }) : Type(0); + const auto limit = _settings->media.sizeLimit; if ((_settings->media.types & type) != type) { file.skipReason = SkipReason::FileType; return true; - } else if (file.size >= _settings->media.sizeLimit) { + } else if ((message ? message->file().size : file.size) >= limit) { + // Don't load thumbs for large files that we skip. file.skipReason = SkipReason::FileSize; return true; } diff --git a/Telegram/SourceFiles/export/export_pch.h b/Telegram/SourceFiles/export/export_pch.h index 8a0835457..8c420524a 100644 --- a/Telegram/SourceFiles/export/export_pch.h +++ b/Telegram/SourceFiles/export/export_pch.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include #include diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 3836e46aa..4bde5fa31 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "export/data/export_data_types.h" #include "core/utils.h" +#include #include #include @@ -28,6 +29,14 @@ constexpr auto kServiceMessagePhotoSize = 60; constexpr auto kHistoryUserpicSize = 42; constexpr auto kSavedMessagesColorIndex = 3; constexpr auto kJoinWithinSeconds = 900; +constexpr auto kPhotoMaxWidth = 520; +constexpr auto kPhotoMaxHeight = 520; +constexpr auto kPhotoMinWidth = 80; +constexpr auto kPhotoMinHeight = 80; +constexpr auto kStickerMaxWidth = 384; +constexpr auto kStickerMaxHeight = 384; +constexpr auto kStickerMinWidth = 80; +constexpr auto kStickerMinHeight = 80; const auto kLineBreak = QByteArrayLiteral("
"); @@ -41,6 +50,50 @@ bool IsGlobalLink(const QString &link) { || link.startsWith(qstr("https://"), Qt::CaseInsensitive); } +QByteArray NoFileDescription(Data::File::SkipReason reason) { + using SkipReason = Data::File::SkipReason; + switch (reason) { + case SkipReason::Unavailable: + return "Unavailable, please try again later."; + case SkipReason::FileSize: + return "Exceeds maximum size, " + "change data exporting settings to download."; + case SkipReason::FileType: + return "Not included, " + "change data exporting settings to download."; + case SkipReason::None: + return ""; + } + Unexpected("Skip reason in NoFileDescription."); +} + +auto CalculateThumbSize( + int maxWidth, + int maxHeight, + int minWidth, + int minHeight, + bool expandForRetina = false) { + return [=](QSize largeSize) { + const auto multiplier = (expandForRetina ? 2 : 1); + const auto checkWidth = largeSize.width() * multiplier; + const auto checkHeight = largeSize.height() * multiplier; + const auto smallSize = (checkWidth > maxWidth + || checkHeight > maxHeight) + ? largeSize.scaled( + maxWidth, + maxHeight, + Qt::KeepAspectRatio) + : largeSize; + const auto retinaSize = QSize( + smallSize.width() & ~0x01, + smallSize.height() & ~0x01); + return (retinaSize.width() < kPhotoMinWidth + || retinaSize.height() < kPhotoMinHeight) + ? QSize() + : retinaSize; + }; +} + QByteArray SerializeString(const QByteArray &value) { const auto size = value.size(); const auto begin = value.data(); @@ -517,11 +570,6 @@ public: const QString &basePath, const PeersMap &peers, const QString &internalLinksDomain); - [[nodiscard]] QByteArray pushMedia( - const Data::Message &message, - const QString &basePath, - const PeersMap &peers, - const QString &internalLinksDomain); [[nodiscard]] Result writeBlock(const QByteArray &block); @@ -554,6 +602,24 @@ private: const QString &basePath, const PeersMap &peers, const QString &internalLinksDomain) const; + [[nodiscard]] QByteArray pushMedia( + const Data::Message &message, + const QString &basePath, + const PeersMap &peers, + const QString &internalLinksDomain); + [[nodiscard]] QByteArray pushGenericMedia(const MediaData &data); + [[nodiscard]] QByteArray pushStickerMedia( + const Data::Document &data, + const QString &basePath); + [[nodiscard]] QByteArray pushAnimatedMedia( + const Data::Document &data, + const QString &basePath); + [[nodiscard]] QByteArray pushVideoFileMedia( + const Data::Document &data, + const QString &basePath); + [[nodiscard]] QByteArray pushPhotoMedia( + const Data::Photo &data, + const QString &basePath); File _file; bool _closed = false; @@ -1137,9 +1203,29 @@ QByteArray HtmlWriter::Wrap::pushMedia( basePath, peers, internalLinksDomain); - if (data.classes.isEmpty()) { - return QByteArray(); + if (!data.classes.isEmpty()) { + return pushGenericMedia(data); } + const auto &content = message.media.content; + if (const auto document = base::get_if(&content)) { + Assert(!message.media.ttl); + if (document->isSticker) { + return pushStickerMedia(*document, basePath); + } else if (document->isAnimated) { + return pushAnimatedMedia(*document, basePath); + } else if (document->isVideoFile) { + return pushVideoFileMedia(*document, basePath); + } + Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia."); + } else if (const auto photo = base::get_if(&content)) { + Assert(!message.media.ttl); + return pushPhotoMedia(*photo, basePath); + } + Assert(!content.has_value()); + return QByteArray(); +} + +QByteArray HtmlWriter::Wrap::pushGenericMedia(const MediaData &data) { auto result = pushDiv("media_wrap clearfix"); if (data.link.isEmpty()) { result.append(pushDiv("media clearfix pull_left " + data.classes)); @@ -1189,6 +1275,222 @@ QByteArray HtmlWriter::Wrap::pushMedia( return result; } +QByteArray HtmlWriter::Wrap::pushStickerMedia( + const Data::Document &data, + const QString &basePath) { + using namespace Data; + + const auto [thumb, size] = WriteImageThumb( + basePath, + data.file.relativePath, + CalculateThumbSize( + kStickerMaxWidth, + kStickerMaxHeight, + kStickerMinWidth, + kStickerMinHeight), + "PNG", + -1); + if (thumb.isEmpty()) { + auto generic = MediaData(); + generic.title = "Sticker"; + generic.status = data.stickerEmoji; + if (data.file.relativePath.isEmpty()) { + generic.status += ", " + FormatFileSize(data.file.size); + } else { + generic.link = data.file.relativePath; + } + generic.description = NoFileDescription(data.file.skipReason); + generic.classes = "media_photo"; + return pushGenericMedia(generic); + } + auto result = pushDiv("media_wrap clearfix"); + result.append(pushTag("a", { + { "class", "sticker_wrap clearfix pull_left" }, + { + "href", + relativePath(data.file.relativePath).toUtf8() + } + })); + const auto sizeStyle = "width: " + + NumberToString(size.width() / 2) + + "px; height: " + + NumberToString(size.height() / 2) + + "px"; + result.append(pushTag("img", { + { "class", "sticker" }, + { "style", sizeStyle }, + { "src", relativePath(thumb).toUtf8() }, + { "empty", "" } + })); + result.append(popTag()); + result.append(popTag()); + return result; +} + +QByteArray HtmlWriter::Wrap::pushAnimatedMedia( + const Data::Document &data, + const QString &basePath) { + using namespace Data; + + auto size = QSize(data.width, data.height); + auto thumbSize = CalculateThumbSize( + kPhotoMaxWidth, + kPhotoMaxHeight, + kPhotoMinWidth, + kPhotoMinHeight, + true)(size); + if (data.thumb.file.relativePath.isEmpty() + || data.file.relativePath.isEmpty() + || !thumbSize.width() + || !thumbSize.height()) { + auto generic = MediaData(); + generic.title = "Animation"; + generic.status = FormatFileSize(data.file.size); + generic.link = data.file.relativePath; + generic.description = NoFileDescription(data.file.skipReason); + generic.classes = "media_video"; + return pushGenericMedia(generic); + } + auto result = pushDiv("media_wrap clearfix"); + result.append(pushTag("a", { + { "class", "animated_wrap clearfix pull_left" }, + { + "href", + relativePath(data.file.relativePath).toUtf8() + } + })); + result.append(pushDiv("video_play_bg")); + result.append(pushDiv("gif_play")); + result.append("GIF"); + result.append(popTag()); + result.append(popTag()); + const auto sizeStyle = "width: " + + NumberToString(thumbSize.width() / 2) + + "px; height: " + + NumberToString(thumbSize.height() / 2) + + "px"; + result.append(pushTag("img", { + { "class", "animated" }, + { "style", sizeStyle }, + { "src", relativePath(data.thumb.file.relativePath).toUtf8() }, + { "empty", "" } + })); + result.append(popTag()); + result.append(popTag()); + return result; +} + +QByteArray HtmlWriter::Wrap::pushVideoFileMedia( + const Data::Document &data, + const QString &basePath) { + using namespace Data; + + auto size = QSize(data.width, data.height); + auto thumbSize = CalculateThumbSize( + kPhotoMaxWidth, + kPhotoMaxHeight, + kPhotoMinWidth, + kPhotoMinHeight, + true)(size); + if (data.thumb.file.relativePath.isEmpty() + || data.file.relativePath.isEmpty() + || !thumbSize.width() + || !thumbSize.height()) { + auto generic = MediaData(); + generic.title = "Video file"; + generic.status = FormatDuration(data.duration); + if (data.file.relativePath.isEmpty()) { + generic.status += ", " + FormatFileSize(data.file.size); + } else { + generic.link = data.file.relativePath; + } + generic.description = NoFileDescription(data.file.skipReason); + generic.classes = "media_video"; + return pushGenericMedia(generic); + } + auto result = pushDiv("media_wrap clearfix"); + result.append(pushTag("a", { + { "class", "video_file_wrap clearfix pull_left" }, + { + "href", + relativePath(data.file.relativePath).toUtf8() + } + })); + result.append(pushDiv("video_play_bg")); + result.append(pushDiv("video_play")); + result.append(popTag()); + result.append(popTag()); + result.append(pushDiv("video_duration")); + result.append(FormatDuration(data.duration)); + result.append(popTag()); + const auto sizeStyle = "width: " + + NumberToString(thumbSize.width() / 2) + + "px; height: " + + NumberToString(thumbSize.height() / 2) + + "px"; + result.append(pushTag("img", { + { "class", "video_file" }, + { "style", sizeStyle }, + { "src", relativePath(data.thumb.file.relativePath).toUtf8() }, + { "empty", "" } + })); + result.append(popTag()); + result.append(popTag()); + return result; +} + +QByteArray HtmlWriter::Wrap::pushPhotoMedia( + const Data::Photo &data, + const QString &basePath) { + using namespace Data; + + const auto [thumb, size] = WriteImageThumb( + basePath, + data.image.file.relativePath, + CalculateThumbSize( + kPhotoMaxWidth, + kPhotoMaxHeight, + kPhotoMinWidth, + kPhotoMinHeight)); + if (thumb.isEmpty()) { + auto generic = MediaData(); + generic.title = "Photo"; + generic.status = NumberToString(data.image.width) + + "x" + + NumberToString(data.image.height); + if (data.image.file.relativePath.isEmpty()) { + generic.status += ", " + FormatFileSize(data.image.file.size); + } else { + generic.link = data.image.file.relativePath; + } + generic.description = NoFileDescription(data.image.file.skipReason); + generic.classes = "media_photo"; + return pushGenericMedia(generic); + } + auto result = pushDiv("media_wrap clearfix"); + result.append(pushTag("a", { + { "class", "photo_wrap clearfix pull_left" }, + { + "href", + relativePath(data.image.file.relativePath).toUtf8() + } + })); + const auto sizeStyle = "width: " + + NumberToString(size.width() / 2) + + "px; height: " + + NumberToString(size.height() / 2) + + "px"; + result.append(pushTag("img", { + { "class", "photo" }, + { "style", sizeStyle }, + { "src", relativePath(thumb).toUtf8() }, + { "empty", "" } + })); + result.append(popTag()); + result.append(popTag()); + return result; +} + MediaData HtmlWriter::Wrap::prepareMediaData( const Data::Message &message, const QString &basePath, @@ -1222,50 +1524,64 @@ MediaData HtmlWriter::Wrap::prepareMediaData( return result; } - message.media.content.match([&](const Photo &photo) { - // #TODO export: photo + self destruct (ttl) - result.title = "Photo"; - result.status = NumberToString(photo.image.width) - + "x" - + NumberToString(photo.image.height); - result.classes = "media_file"; // #TODO export - result.link = photo.image.file.relativePath; + message.media.content.match([&](const Photo &data) { + if (message.media.ttl) { + result.title = "Self-destructing photo"; + result.status = data.id + ? "Please view it on your mobile" + : "Expired"; + result.classes = "media_photo"; + return; + } + // At least try to pushPhotoMedia. }, [&](const Document &data) { - // #TODO export: sticker + thumb (video, video message) + self destruct (ttl) + if (message.media.ttl) { + result.title = "Self-destructing video"; + result.status = data.id + ? "Please view it on your mobile" + : "Expired"; + result.classes = "media_video"; + return; + } + const auto hasFile = !data.file.relativePath.isEmpty(); result.link = data.file.relativePath; + result.description = NoFileDescription(data.file.skipReason); if (data.isSticker) { - result.title = "Sticker"; - result.status = data.stickerEmoji; - result.classes = "media_file"; // #TODO export + // At least try to pushStickerMedia. } else if (data.isVideoMessage) { result.title = "Video message"; result.status = FormatDuration(data.duration); + if (!hasFile) { + result.status += ", " + FormatFileSize(data.file.size); + } result.thumb = data.thumb.file.relativePath; - result.classes = "media_file"; + result.classes = "media_video"; } else if (data.isVoiceMessage) { result.title = "Voice message"; result.status = FormatDuration(data.duration); + if (!hasFile) { + result.status += ", " + FormatFileSize(data.file.size); + } result.classes = "media_voice_message"; } else if (data.isAnimated) { - result.title = "Animation"; - result.status = FormatFileSize(data.duration); - result.classes = "media_file"; // #TODO export + // At least try to pushAnimatedMedia. } else if (data.isVideoFile) { - result.title = "Video file"; - result.status = FormatDuration(data.duration); - result.classes = "media_file"; // #TODO export + // At least try to pushVideoFileMedia. } else if (data.isAudioFile) { result.title = (data.songPerformer.isEmpty() || data.songTitle.isEmpty()) ? QByteArray("Audio file") : data.songPerformer + " \xe2\x80\x93 " + data.songTitle; result.status = FormatDuration(data.duration); + if (!hasFile) { + result.status += ", " + FormatFileSize(data.file.size); + } result.classes = "media_audio_file"; } else { result.title = data.name.isEmpty() ? QByteArray("File") : data.name; - result.status = FormatFileSize(data.duration); + result.status = FormatFileSize(data.file.size); result.classes = "media_file"; } }, [&](const SharedContact &data) { @@ -1434,7 +1750,9 @@ Result HtmlWriter::start( "images/media_game.png", "images/media_location.png", "images/media_music.png", + "images/media_photo.png", "images/media_shop.png", + "images/media_video.png", "images/media_voice.png", "images/section_calls.png", "images/section_chats.png",