From e7dc330b0f4391712dbeb1c2b3a1f785079e974e Mon Sep 17 00:00:00 2001 From: cumdev1337 Date: Tue, 12 May 2026 02:06:49 +0300 Subject: [PATCH] feat: support 8K video uploads and playback --- .../SourceFiles/ffmpeg/ffmpeg_utility.cpp | 2 +- .../media/clip/media_clip_ffmpeg.cpp | 10 ++++++- .../media/clip/media_clip_ffmpeg.h | 1 + .../media/clip/media_clip_implementation.h | 1 + .../media/clip/media_clip_reader.cpp | 27 ++++++++++++------- .../media/player/media_player_button.cpp | 2 ++ .../media/streaming/media_streaming_common.h | 2 +- .../SourceFiles/storage/localimageloader.cpp | 17 +++++++++--- .../ui/chat/attach/attach_prepare.h | 2 ++ 9 files changed, 48 insertions(+), 16 deletions(-) diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index 194a464862f49..0fdb2015f753e 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -46,7 +46,7 @@ constexpr auto kAlignImageBy = 64; constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied; constexpr auto kMaxScaleByAspectRatio = 16; constexpr auto kAvioBlockSize = 4096; -constexpr auto kMaxFrameStorageBytes = 64 * 1024 * 1024; +constexpr auto kMaxFrameStorageBytes = 128 * 1024 * 1024; constexpr auto kMaxPixelsPaddingRatio = 32; constexpr auto kMaxPixelsFixedPadding = 64 * 1024; constexpr auto kTimeUnknown = std::numeric_limits::min(); diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index eca860d9bd1be..fe384e8d4f9c6 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -18,7 +18,7 @@ namespace { constexpr auto kSkipInvalidDataPackets = 10; constexpr auto kMaxInlineArea = 1280 * 720; -constexpr auto kMaxSendingArea = 3840 * 2160; // usual 4K +constexpr auto kMaxSendingArea = 7680 * 4320; // usual 8K [[nodiscard]] auto MaxAreaForMode(ReaderImplementation::Mode mode) { return (mode == ReaderImplementation::Mode::Inspecting) @@ -168,6 +168,14 @@ crl::time FFMpegReaderImplementation::framePresentationTime() const { return qMax(_frameTime + _frameTimeCorrection, crl::time(0)); } +QSize FFMpegReaderImplementation::frameSize() const { + auto result = QSize(_frame->width, _frame->height); + if (rotationSwapWidthHeight()) { + result.transpose(); + } + return result; +} + crl::time FFMpegReaderImplementation::durationMs() const { const auto rebase = [](int64_t duration, const AVRational &base) { return (duration * 1000LL * base.num) / base.den; diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h index 2fbea399c2694..99709318da308 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.h @@ -32,6 +32,7 @@ class FFMpegReaderImplementation : public ReaderImplementation { crl::time frameRealTime() const override; crl::time framePresentationTime() const override; + QSize frameSize() const override; bool renderFrame( QImage &to, diff --git a/Telegram/SourceFiles/media/clip/media_clip_implementation.h b/Telegram/SourceFiles/media/clip/media_clip_implementation.h index be5daf47b2b78..3afcfad92c80b 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_implementation.h +++ b/Telegram/SourceFiles/media/clip/media_clip_implementation.h @@ -40,6 +40,7 @@ class ReaderImplementation { // Get current frame real and presentation time. virtual crl::time frameRealTime() const = 0; virtual crl::time framePresentationTime() const = 0; + virtual QSize frameSize() const = 0; // Render current frame to an image with specific size. virtual bool renderFrame( diff --git a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp index ee7487269bbce..001dcc32fdeda 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_reader.cpp @@ -35,6 +35,7 @@ namespace { constexpr auto kClipThreadsCount = 8; constexpr auto kAverageGifSize = 320 * 240; +constexpr auto kPreparedVideoThumbnailSide = 320; constexpr auto kWaitBeforeGifPause = crl::time(200); QImage PrepareFrame( @@ -493,13 +494,12 @@ class ReaderPrivate { if (reader->start(internal::ReaderImplementation::Mode::Silent, firstFramePositionMs)) { auto firstFrameReadResult = reader->readFramesTill(-1, ms); if (firstFrameReadResult == internal::ReaderImplementation::ReadResult::Success) { - if (reader->renderFrame(frame()->original, frame()->alpha, frame()->index, QSize())) { - frame()->original.fill(QColor(0, 0, 0)); - + const auto dimensions = reader->frameSize(); + if (!dimensions.isEmpty()) { frame()->positionMs = _seekPositionMs; - _width = frame()->original.width(); - _height = frame()->original.height(); + _width = dimensions.width(); + _height = dimensions.height(); _durationMs = _implementation->durationMs(); return ProcessResult::Started; } @@ -510,13 +510,14 @@ class ReaderPrivate { } else if (readResult != internal::ReaderImplementation::ReadResult::Success) { // Read the first frame. return error(); } - if (!_implementation->renderFrame(frame()->original, frame()->alpha, frame()->index, QSize())) { + const auto dimensions = _implementation->frameSize(); + if (dimensions.isEmpty()) { return error(); } frame()->positionMs = _implementation->frameRealTime(); - _width = frame()->original.width(); - _height = frame()->original.height(); + _width = dimensions.width(); + _height = dimensions.height(); _durationMs = _implementation->durationMs(); return ProcessResult::Started; } @@ -784,6 +785,8 @@ bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, c if (result == ProcessResult::Started) { _loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - kAverageGifSize); + it.key()->_width = reader->_width; + it.key()->_height = reader->_height; it.key()->_durationMs = reader->_durationMs; } // See if we need to pause GIF because it is not displayed right now. @@ -990,7 +993,13 @@ Ui::PreparedFileInformation PrepareForSending( auto hasAlpha = false; auto readResult = reader->readFramesTill(-1, crl::now()); auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success); - if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, index, QSize())) { + const auto dimensions = reader->frameSize(); + result.dimensions = dimensions; + const auto thumbnailSize = dimensions.scaled( + kPreparedVideoThumbnailSide, + kPreparedVideoThumbnailSide, + Qt::KeepAspectRatio); + if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, index, thumbnailSize)) { if (hasAlpha && !result.isWebmSticker) { result.thumbnail = Images::Opaque(std::move(result.thumbnail)); } diff --git a/Telegram/SourceFiles/media/player/media_player_button.cpp b/Telegram/SourceFiles/media/player/media_player_button.cpp index 59fd6c8743dc7..deb1194171499 100644 --- a/Telegram/SourceFiles/media/player/media_player_button.cpp +++ b/Telegram/SourceFiles/media/player/media_player_button.cpp @@ -440,6 +440,8 @@ void SettingsButton::prepareFrame() { const auto height = _quality.height; const auto text = !height ? QString() + : (height > 4000) + ? u"8K"_q : (height > 2000) ? u"4K"_q : (height > 1000) diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_common.h b/Telegram/SourceFiles/media/streaming/media_streaming_common.h index 5b54153bb9f79..94a8883c17c0f 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_common.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_common.h @@ -23,7 +23,7 @@ bool SupportsSpeedControl(); namespace Streaming { -inline constexpr auto kMaxFrameArea = 3840 * 2160; +inline constexpr auto kMaxFrameArea = 7680 * 4320; inline bool SupportsSpeedControl() { return Media::Audio::SupportsSpeedControl(); diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 970899da55fce..4dd9ef43e7ff4 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -94,6 +94,13 @@ struct PreparedFileThumbnail { return result; } +[[nodiscard]] QSize VideoDimensions( + const Ui::PreparedFileInformation::Video &video) { + return video.dimensions.isEmpty() + ? video.thumbnail.size() + : video.dimensions; +} + [[nodiscard]] bool FileThumbnailUploadRequired( const QString &filemime, int64 filesize) { @@ -815,8 +822,9 @@ void FileLoadTask::process(ProcessArgs &&args) { if (auto video = std::get_if( &_information->media)) { isVideo = true; - auto coverWidth = video->thumbnail.width(); - auto coverHeight = video->thumbnail.height(); + const auto dimensions = VideoDimensions(*video); + auto coverWidth = dimensions.width(); + auto coverHeight = dimensions.height(); if (video->isGifv && !_album) { attributes.push_back(MTP_documentAttributeAnimated()); } @@ -859,8 +867,9 @@ void FileLoadTask::process(ProcessArgs &&args) { } else if (auto video = std::get_if( &_information->media)) { isVideo = true; - auto coverWidth = video->thumbnail.width(); - auto coverHeight = video->thumbnail.height(); + const auto dimensions = VideoDimensions(*video); + auto coverWidth = dimensions.width(); + auto coverHeight = dimensions.height(); if (!_forceFile) { if (video->isGifv && !_album) { attributes.push_back(MTP_documentAttributeAnimated()); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index b4089055b4e87..a94deb5ac90ee 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -12,6 +12,7 @@ For license and copyright information please follow this link: #include "ui/rect_part.h" #include +#include #include class QPainter; @@ -44,6 +45,7 @@ struct PreparedFileInformation { bool isWebmSticker = false; bool supportsStreaming = false; crl::time duration = -1; + QSize dimensions; QImage thumbnail; };