Skip to content

Commit 580eb8b

Browse files
committed
Bring back original video quality option
1 parent dc2d6e9 commit 580eb8b

12 files changed

Lines changed: 167 additions & 43 deletions

Telegram/SourceFiles/core/core_settings.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,13 @@ void LogPosition(const WindowPosition &position, const QString &name) {
102102
auto result = Media::VideoQuality();
103103
const auto data = static_cast<void*>(&result);
104104
memcpy(data, &value, sizeof(result));
105-
return (result.height <= 4320) ? result : Media::VideoQuality();
105+
106+
const auto height = result.height;
107+
const auto offset = Media::kVideoQualityOriginalOffset;
108+
const auto max = 4320;
109+
return (height <= max || (height >= offset && height <= offset + max))
110+
? result
111+
: Media::VideoQuality();
106112
}
107113

108114
} // namespace

Telegram/SourceFiles/data/data_document.cpp

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -544,11 +544,12 @@ void DocumentData::setVideoQualities(
544544
return;
545545
}
546546
const auto good = [&](not_null<DocumentData*> document) {
547+
// Transcodes in alt_documents are always streamable,
548+
// even if the supports_streaming flag is missing
547549
return document->isVideoFile()
548550
&& !document->dimensions.isEmpty()
549551
&& !document->inappPlaybackFailed()
550-
&& document->useStreamingLoader()
551-
&& document->canBeStreamed();
552+
&& document->useStreamingLoader();
552553
};
553554
ranges::sort(
554555
qualities,
@@ -578,18 +579,65 @@ void DocumentData::setVideoQualities(
578579
}
579580
qualities.erase(qualities.begin() + count, qualities.end());
580581
if (!qualities.empty()) {
581-
if (const auto mine = resolveVideoQuality()) {
582-
if (mine > qualities.front()->resolveVideoQuality()) {
583-
qualities.insert(begin(qualities), this);
582+
auto transcodeMax = 0;
583+
for (const auto &quality : qualities) {
584+
const auto qres = quality->resolveVideoQuality();
585+
if (qres > transcodeMax) {
586+
transcodeMax = qres;
584587
}
585588
}
589+
const auto apiSize = isVideoFile() ? dimensions : QSize();
590+
const auto api = apiSize.isEmpty()
591+
? 0
592+
: std::min(apiSize.width(), apiSize.height());
593+
594+
// Heuristic: Trust API resolution unless it blatantly
595+
// contradicts server-side transcodes (>1.5x delta)
596+
auto mine = (transcodeMax > 0
597+
&& (api < transcodeMax || api > transcodeMax * 1.5))
598+
? transcodeMax
599+
: api;
600+
if (mine) {
601+
qualities.insert(begin(qualities), this);
602+
}
586603
}
587604
data->qualities = std::move(qualities);
588605
}
589606

590607
int DocumentData::resolveVideoQuality() const {
591-
const auto size = isVideoFile() ? dimensions : QSize();
592-
return size.isEmpty() ? 0 : std::min(size.width(), size.height());
608+
if (const auto data = video()) {
609+
if (!data->realVideoSize.isEmpty()) {
610+
// Always trust FFmpeg-parsed physical resolution
611+
const auto size = data->realVideoSize;
612+
return std::min(size.width(), size.height());
613+
}
614+
const auto apiSize = isVideoFile() ? dimensions : QSize();
615+
const auto api = apiSize.isEmpty()
616+
? 0
617+
: std::min(apiSize.width(), apiSize.height());
618+
if (!data->qualities.empty()) {
619+
auto transcodeMax = 0;
620+
for (const auto &quality : data->qualities) {
621+
if (quality != this) {
622+
const auto qres = quality->resolveVideoQuality();
623+
if (qres > transcodeMax) {
624+
transcodeMax = qres;
625+
}
626+
}
627+
}
628+
if (transcodeMax > 0) {
629+
// Trust transcodes if attributes appear fake
630+
if (api < transcodeMax || api > transcodeMax * 1.5) {
631+
return transcodeMax;
632+
}
633+
return api;
634+
}
635+
}
636+
}
637+
const auto apiSize = isVideoFile() ? dimensions : QSize();
638+
return apiSize.isEmpty()
639+
? 0
640+
: std::min(apiSize.width(), apiSize.height());
593641
}
594642

595643
auto DocumentData::resolveQualities(HistoryItem *context) const
@@ -611,19 +659,30 @@ not_null<DocumentData*> DocumentData::chooseQuality(
611659
return this;
612660
}
613661
const auto height = int(request.height);
614-
auto closest = this;
615-
auto closestAbs = std::abs(height - resolveVideoQuality());
616-
auto closestSize = size;
662+
if (height >= Media::kVideoQualityOriginalOffset) {
663+
return this;
664+
}
665+
666+
auto closest = (DocumentData*)nullptr;
667+
auto closestAbs = -1;
668+
auto closestSize = -1;
669+
617670
for (const auto &quality : list) {
618-
const auto abs = std::abs(height - quality->resolveVideoQuality());
619-
if (abs < closestAbs
620-
|| (abs == closestAbs && quality->size < closestSize)) {
671+
const auto qres = quality->resolveVideoQuality();
672+
const auto abs = std::abs(height - qres);
673+
// Prefer Original if it fits target resolution best,
674+
// falling back to transcode only on exact matches
675+
if (!closest
676+
|| abs < closestAbs
677+
|| (abs == closestAbs && (quality->size < closestSize
678+
|| (closest == this && quality != this)))) {
621679
closest = quality;
622680
closestAbs = abs;
623681
closestSize = quality->size;
624682
}
625683
}
626-
return closest;
684+
685+
return closest ? closest : this;
627686
}
628687

629688
void DocumentData::validateLottieSticker() {

Telegram/SourceFiles/data/data_document.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ struct VoiceData : public DocumentAdditionalData {
9999
struct VideoData : public DocumentAdditionalData {
100100
QString codec;
101101
std::vector<not_null<DocumentData*>> qualities;
102+
QSize realVideoSize;
102103
};
103104

104105
using RoundData = VoiceData;

Telegram/SourceFiles/media/media_common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ inline constexpr auto kSpeedMin = 0.5;
4141
inline constexpr auto kSpeedMax = 2.5;
4242
inline constexpr auto kSpedUpDefault = 1.7;
4343

44+
inline constexpr auto kVideoQualityOriginalOffset = 1000000;
45+
4446
[[nodiscard]] inline bool EqualSpeeds(float64 a, float64 b) {
4547
return int(base::SafeRound(a * 10.)) == int(base::SafeRound(b * 10.));
4648
}

Telegram/SourceFiles/media/player/media_player_button.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -437,13 +437,16 @@ void SettingsButton::prepareFrame() {
437437
: u"%1X"_q.arg(rounded / 10);
438438
paintBadge(p, text, RectPart::TopLeft, color);
439439
}
440-
const auto text = (!_quality)
440+
const auto displayQuality = (_quality >= Media::kVideoQualityOriginalOffset)
441+
? (_quality - Media::kVideoQualityOriginalOffset)
442+
: _quality;
443+
const auto text = (!displayQuality)
441444
? QString()
442-
: (_quality > 2000)
445+
: (displayQuality > 2000)
443446
? u"4K"_q
444-
: (_quality > 1000)
447+
: (displayQuality > 1000)
445448
? u"FHD"_q
446-
: (_quality > 700)
449+
: (displayQuality > 700)
447450
? u"HD"_q
448451
: u"SD"_q;
449452
if (!text.isEmpty()) {

Telegram/SourceFiles/media/player/media_player_dropdown.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ For license and copyright information please follow this link:
77
*/
88
#include "media/player/media_player_dropdown.h"
99

10+
#include <algorithm>
1011
#include "base/invoke_queued.h"
1112
#include "base/timer.h"
1213
#include "lang/lang_keys.h"
@@ -819,7 +820,13 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
819820

820821
const auto add = [&](int quality) {
821822
const auto automatic = tr::lng_mediaview_quality_auto(tr::now);
822-
const auto text = quality ? u"%1p"_q.arg(quality) : automatic;
823+
const auto offset = Media::kVideoQualityOriginalOffset;
824+
// Quality is height-based, except for Original which uses offset
825+
const auto text = !quality
826+
? automatic
827+
: (quality >= offset)
828+
? u"Original (%1p)"_q.arg(std::clamp(quality - offset, 0, 4320))
829+
: u"%1p"_q.arg(quality);
823830
auto action = base::make_unique_q<Ui::Menu::Action>(
824831
raw,
825832
st.qualityMenu,
@@ -829,15 +836,15 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
829836
[=] { _changeQuality(quality); }),
830837
nullptr,
831838
nullptr);
832-
const auto raw = action.get();
833-
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
839+
const auto rawAction = action.get();
840+
const auto check = Ui::CreateChild<Ui::RpWidget>(rawAction);
834841
check->resize(st.activeCheck.size());
835842
check->paintRequest(
836843
) | rpl::on_next([check, icon = &st.activeCheck] {
837844
auto p = QPainter(check);
838845
icon->paint(p, 0, 0, check->width());
839846
}, check->lifetime());
840-
raw->sizeValue(
847+
rawAction->sizeValue(
841848
) | rpl::on_next([=, skip = st.activeCheckSkip](QSize size) {
842849
check->moveToRight(
843850
skip,
@@ -850,13 +857,19 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
850857
const auto chosen = now.manual
851858
? (now.height == quality)
852859
: !quality;
853-
raw->action()->setEnabled(!chosen);
860+
rawAction->action()->setEnabled(!chosen);
854861
if (!quality) {
855-
raw->action()->setText(automatic
856-
+ (now.manual ? QString() : u"\t%1p"_q.arg(now.height)));
862+
const auto offset = Media::kVideoQualityOriginalOffset;
863+
const auto displayHeight = (now.height >= offset)
864+
? std::clamp(int(now.height - offset), 0, 4320)
865+
: now.height;
866+
const auto suffix = now.manual
867+
? QString()
868+
: u"\t%1p"_q.arg(displayHeight);
869+
rawAction->action()->setText(automatic + suffix);
857870
}
858871
check->setVisible(chosen);
859-
}, raw->lifetime());
872+
}, rawAction->lifetime());
860873
menu->addAction(std::move(action));
861874
};
862875

Telegram/SourceFiles/media/streaming/media_streaming_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ struct TrackState {
5959
struct VideoInformation {
6060
TrackState state;
6161
QSize size;
62+
QSize realSize;
6263
QImage cover;
6364
int rotation = 0;
6465
bool alpha = false;

Telegram/SourceFiles/media/streaming/media_streaming_player.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ void SaveValidVideoInformation(
6262

6363
SaveValidStateInformation(to.state, std::move(from.state));
6464
to.size = from.size;
65+
// Propagate physical resolution parsed from the stream
66+
to.realSize = from.realSize;
6567
to.cover = std::move(from.cover);
6668
to.rotation = from.rotation;
6769
to.alpha = from.alpha;

Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,10 @@ void VideoTrackObject::callReady() {
716716
const auto frame = _shared->frameForPaint();
717717
++_frameIndex;
718718

719+
const auto frameSize = frame->original.isNull()
720+
? frame->yuv.size
721+
: frame->original.size();
722+
719723
base::take(_ready)({ VideoInformation{
720724
.state = {
721725
.position = _syncTimePoint.trackTime,
@@ -725,7 +729,11 @@ void VideoTrackObject::callReady() {
725729
.duration = _stream.duration,
726730
},
727731
.size = FFmpeg::TransposeSizeByRotation(
728-
FFmpeg::CorrectByAspect(frame->original.size(), _stream.aspect),
732+
FFmpeg::CorrectByAspect(frameSize, _stream.aspect),
733+
_stream.rotation),
734+
// realSize captures physical resolution before SAR correction
735+
.realSize = FFmpeg::TransposeSizeByRotation(
736+
frameSize,
729737
_stream.rotation),
730738
.cover = frame->original,
731739
.rotation = _stream.rotation,

Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,8 @@ QSize OverlayWidget::videoSize() const {
11811181
Expects(videoShown());
11821182

11831183
const auto use = (_document && _chosenQuality != _document)
1184-
? _document->dimensions
1184+
// Use chosen quality dimensions instead of original
1185+
? _chosenQuality->dimensions
11851186
: _streamed->instance.info().video.size;
11861187
return flipSizeByRotation(use);
11871188
}
@@ -4300,8 +4301,21 @@ void OverlayWidget::initStreamingThumbnail() {
43004301
void OverlayWidget::streamingReady(Streaming::Information &&info) {
43014302
markStreamedReady();
43024303
if (videoShown()) {
4304+
if (_document && _streamed && _streamed->ready) {
4305+
const auto targetDocument = _chosenQuality ? _chosenQuality : _document;
4306+
if (const auto video = targetDocument->video()) {
4307+
video->realVideoSize = info.video.realSize;
4308+
}
4309+
}
43034310
applyVideoSize();
43044311
_streamedQualityChangeFrame = QImage();
4312+
if (_streamed && _streamed->controls) {
4313+
crl::on_main(_widget, [=] {
4314+
if (_streamed && _streamed->controls) {
4315+
_streamed->controls->updateSpeedToggleQuality();
4316+
}
4317+
});
4318+
}
43054319
} else {
43064320
updateContentRect();
43074321
}
@@ -4694,12 +4708,16 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
46944708
}
46954709
const auto overrideDuration = _stories
46964710
|| (_chosenQuality && _chosenQuality != _document);
4711+
const auto durationDocument = (_chosenQuality && _chosenQuality != _document)
4712+
? _chosenQuality
4713+
: _document;
4714+
46974715
auto options = Streaming::PlaybackOptions{
46984716
.position = position,
46994717
.durationOverride = ((overrideDuration
4700-
&& _document
4701-
&& _document->hasDuration())
4702-
? _document->duration()
4718+
&& durationDocument
4719+
&& durationDocument->hasDuration())
4720+
? durationDocument->duration()
47034721
: crl::time(0)),
47044722
.hwAllowed = Core::App().settings().hardwareAcceleratedVideo(),
47054723
.seekable = !_stories,
@@ -4804,21 +4822,30 @@ std::vector<int> OverlayWidget::playbackControlsQualities() {
48044822
}
48054823
auto result = std::vector<int>();
48064824
result.reserve(list.size());
4825+
auto seen = std::vector<int>();
48074826
for (const auto &quality : list) {
4808-
result.push_back(quality->resolveVideoQuality());
4827+
const auto res = quality->resolveVideoQuality();
4828+
const auto value = (quality == _document)
4829+
? (res + Media::kVideoQualityOriginalOffset)
4830+
: res;
4831+
if (!ranges::contains(seen, value)) {
4832+
result.push_back(value);
4833+
seen.push_back(value);
4834+
}
48094835
}
48104836
return result;
48114837
}
48124838

48134839
VideoQuality OverlayWidget::playbackControlsCurrentQuality() {
4814-
return _chosenQuality
4815-
? VideoQuality{
4816-
.manual = _quality.manual,
4817-
.height = uint32(_chosenQuality->resolveVideoQuality()),
4818-
}
4819-
: _quality;
4840+
if (!_chosenQuality) {
4841+
return _quality;
4842+
}
4843+
auto height = uint32(_chosenQuality->resolveVideoQuality());
4844+
if (_chosenQuality == _document) {
4845+
height += Media::kVideoQualityOriginalOffset;
4846+
}
4847+
return { .manual = _quality.manual, .height = height };
48204848
}
4821-
48224849
void OverlayWidget::playbackControlsQualityChanged(int quality) {
48234850
applyVideoQuality({
48244851
.manual = (quality > 0),
@@ -5126,6 +5153,7 @@ void OverlayWidget::updatePlaybackState() {
51265153
_streamedPosition = state.position;
51275154
if (_streamed->controls) {
51285155
_streamed->controls->updatePlayback(state);
5156+
_streamed->controls->updateSpeedToggleQuality();
51295157
_touchbarTrackState.fire_copy(state);
51305158
updatePowerSaveBlocker(state);
51315159
}

0 commit comments

Comments
 (0)