diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 644832952..442761ae1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -97,6 +97,9 @@ MTPVector ComposeSendingDocumentAttributes( if (document->isVideoMessage()) { flags |= MTPDdocumentAttributeVideo::Flag::f_round_message; } + if (document->supportsStreaming()) { + flags |= MTPDdocumentAttributeVideo::Flag::f_supports_streaming; + } attributes.push_back(MTP_documentAttributeVideo( MTP_flags(flags), MTP_int(duration), diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index a17954dcf..0ee90f56e 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -438,6 +438,8 @@ not_null DocumentData::session() const { } void DocumentData::setattributes(const QVector &attributes) { + _isImage = false; + _supportsStreaming = false; for (int32 i = 0, l = attributes.size(); i < l; ++i) { switch (attributes[i].type()) { case mtpc_documentAttributeImageSize: { @@ -467,6 +469,7 @@ void DocumentData::setattributes(const QVector &attributes type = d.is_round_message() ? RoundVideoDocument : VideoDocument; } _duration = d.vduration.v; + _supportsStreaming = d.is_supports_streaming(); dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAudio: { @@ -1295,14 +1298,17 @@ int32 DocumentData::duration() const { } bool DocumentData::isImage() const { - return !isAnimation() && !isVideoFile() && (_duration > 0); + return _isImage; +} + +bool DocumentData::supportsStreaming() const { + return _supportsStreaming; } void DocumentData::recountIsImage() { - if (isAnimation() || isVideoFile()) { - return; - } - _duration = fileIsImage(filename(), mimeString()) ? 1 : -1; // hack + _isImage = !isAnimation() + && !isVideoFile() + && fileIsImage(filename(), mimeString()); } bool DocumentData::hasGoodStickerThumb() const { diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 6c64d7682..66c5b57c4 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -152,6 +152,7 @@ public: int32 duration() const; bool isImage() const; void recountIsImage(); + bool supportsStreaming() const; void setData(const QByteArray &data) { _data = data; } @@ -237,6 +238,8 @@ private: QByteArray _data; std::unique_ptr _additional; int32 _duration = -1; + bool _isImage = false; + bool _supportsStreaming = false; ActionOnLoad _actionOnLoad = ActionOnLoadNone; FullMsgId _actionOnLoadMsgId; diff --git a/Telegram/SourceFiles/media/media_clip_check_streaming.cpp b/Telegram/SourceFiles/media/media_clip_check_streaming.cpp new file mode 100644 index 000000000..91228639c --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_check_streaming.cpp @@ -0,0 +1,91 @@ +/* +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 "media/media_clip_check_streaming.h" + + +namespace Media { +namespace Clip { +namespace { + +constexpr auto kHeaderSize = 8; +constexpr auto kFindMoovBefore = 128 * 1024; + +template +Type ReadBigEndian(bytes::const_span data) { + const auto bytes = data.subspan(0, sizeof(Type)).data(); + return qFromBigEndian(*reinterpret_cast(bytes)); +} + +bool IsAtom(bytes::const_span header, const char (&atom)[5]) { + const auto check = header.subspan(4, 4); + return bytes::compare( + header.subspan(4, 4), + bytes::make_span(atom).subspan(0, 4)) == 0; +} + +} // namespace + +bool CheckStreamingSupport( + const FileLocation &location, + QByteArray data) { + QBuffer buffer; + QFile file; + if (data.isEmpty()) { + file.setFileName(location.name()); + } else { + buffer.setBuffer(&data); + } + const auto size = data.isEmpty() + ? file.size() + : data.size(); + const auto device = data.isEmpty() + ? static_cast(&file) + : static_cast(&buffer); + + if (size < kHeaderSize || !device->open(QIODevice::ReadOnly)) { + return false; + } + + auto lastReadPosition = 0; + char atomHeader[kHeaderSize] = { 0 }; + auto atomHeaderBytes = bytes::make_span(atomHeader); + while (true) { + const auto position = device->pos(); + if (device->read(atomHeader, kHeaderSize) != kHeaderSize) { + break; + } + + if (lastReadPosition >= kFindMoovBefore) { + return false; + } else if (IsAtom(atomHeaderBytes, "moov")) { + return true; + } + + const auto length = [&] { + const auto result = ReadBigEndian(atomHeaderBytes); + if (result != 1) { + return uint64(result); + } + char atomSize64[kHeaderSize] = { 0 }; + if (device->read(atomSize64, kHeaderSize) != kHeaderSize) { + return uint64(-1); + } + auto atomSize64Bytes = bytes::make_span(atomSize64); + return ReadBigEndian(atomSize64Bytes); + }(); + if (position + length > size) { + break; + } + device->seek(position + length); + lastReadPosition = position; + } + return false; +} + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_check_streaming.h b/Telegram/SourceFiles/media/media_clip_check_streaming.h new file mode 100644 index 000000000..c2071e89f --- /dev/null +++ b/Telegram/SourceFiles/media/media_clip_check_streaming.h @@ -0,0 +1,18 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Media { +namespace Clip { + +bool CheckStreamingSupport( + const FileLocation &location, + QByteArray data); + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 80f5791c7..0324827fa 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/file_download.h" #include "media/media_clip_ffmpeg.h" #include "media/media_clip_qtgif.h" +#include "media/media_clip_check_streaming.h" #include "mainwidget.h" #include "mainwindow.h" @@ -892,6 +893,10 @@ FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteA } result.duration = static_cast(durationMs / 1000); } + + result.supportsStreaming = CheckStreamingSupport( + localLocation, + localData); } } return result; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index f4636b83b..0ae5a4669 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -582,6 +582,9 @@ void FileLoadTask::process() { attributes.push_back(MTP_documentAttributeAnimated()); } auto flags = MTPDdocumentAttributeVideo::Flags(0); + if (video->supportsStreaming) { + flags |= MTPDdocumentAttributeVideo::Flag::f_supports_streaming; + } attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(video->duration), MTP_int(coverWidth), MTP_int(coverHeight))); goodThumbnail = video->thumbnail; diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index baf334334..3c5db0a24 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -262,6 +262,7 @@ struct FileMediaInformation { }; struct Video { bool isGifv = false; + bool supportsStreaming = false; int duration = -1; QImage thumbnail; }; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index a1dd3ef01..e311af8fe 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -389,6 +389,8 @@ <(src_loc)/media/media_audio_track.h <(src_loc)/media/media_child_ffmpeg_loader.cpp <(src_loc)/media/media_child_ffmpeg_loader.h +<(src_loc)/media/media_clip_check_streaming.cpp +<(src_loc)/media/media_clip_check_streaming.h <(src_loc)/media/media_clip_ffmpeg.cpp <(src_loc)/media/media_clip_ffmpeg.h <(src_loc)/media/media_clip_implementation.cpp