Bring back original video quality option

This commit is contained in:
cumdev1337
2026-03-04 01:48:48 +02:00
committed by John Preston
parent c9ca79ab47
commit 827ec824d1
12 changed files with 168 additions and 44 deletions
+7 -1
View File
@@ -115,7 +115,13 @@ void LogPosition(const WindowPosition &position, const QString &name) {
auto result = Media::VideoQuality();
const auto data = static_cast<void*>(&result);
memcpy(data, &value, sizeof(result));
return (result.height <= 4320) ? result : Media::VideoQuality();
const auto height = result.height;
const auto offset = Media::kVideoQualityOriginalOffset;
const auto max = 4320;
return (height <= max || (height >= offset && height <= offset + max))
? result
: Media::VideoQuality();
}
} // namespace
+75 -14
View File
@@ -544,11 +544,12 @@ void DocumentData::setVideoQualities(
return;
}
const auto good = [&](not_null<DocumentData*> document) {
// Transcodes in alt_documents are always streamable,
// even if the supports_streaming flag is missing
return document->isVideoFile()
&& !document->dimensions.isEmpty()
&& !document->inappPlaybackFailed()
&& document->useStreamingLoader()
&& document->canBeStreamed();
&& document->useStreamingLoader();
};
ranges::sort(
qualities,
@@ -578,18 +579,67 @@ void DocumentData::setVideoQualities(
}
qualities.erase(qualities.begin() + count, qualities.end());
if (!qualities.empty()) {
if (const auto mine = resolveVideoQuality()) {
if (mine > qualities.front()->resolveVideoQuality()) {
qualities.insert(begin(qualities), this);
auto transcodeMax = 0;
for (const auto &quality : qualities) {
const auto qres = quality->resolveVideoQuality();
if (qres > transcodeMax) {
transcodeMax = qres;
}
}
const auto attributesSize = isVideoFile() ? dimensions : QSize();
const auto attributesQuality = attributesSize.isEmpty()
? 0
: std::min(attributesSize.width(), attributesSize.height());
// Heuristic: Trust attributes resolution unless it blatantly
// contradicts server-side transcodes (>1.5x delta)
auto mine = (transcodeMax > 0
&& (attributesQuality < transcodeMax
|| attributesQuality > transcodeMax * 1.5))
? transcodeMax
: attributesQuality;
if (mine) {
qualities.insert(begin(qualities), this);
}
}
data->qualities = std::move(qualities);
}
int DocumentData::resolveVideoQuality() const {
const auto size = isVideoFile() ? dimensions : QSize();
return size.isEmpty() ? 0 : std::min(size.width(), size.height());
if (const auto data = video()) {
if (!data->realVideoSize.isEmpty()) {
// Always trust FFmpeg-parsed physical resolution
const auto size = data->realVideoSize;
return std::min(size.width(), size.height());
}
const auto attributesSize = isVideoFile() ? dimensions : QSize();
const auto attributesQuality = attributesSize.isEmpty()
? 0
: std::min(attributesSize.width(), attributesSize.height());
if (!data->qualities.empty()) {
auto transcodeMax = 0;
for (const auto &quality : data->qualities) {
if (quality != this) {
const auto qres = quality->resolveVideoQuality();
if (qres > transcodeMax) {
transcodeMax = qres;
}
}
}
if (transcodeMax > 0) {
// Trust transcodes if attributes appear fake
if (attributesQuality < transcodeMax
|| attributesQuality > transcodeMax * 1.5) {
return transcodeMax;
}
return attributesQuality;
}
}
}
const auto attributesSize = isVideoFile() ? dimensions : QSize();
return attributesSize.isEmpty()
? 0
: std::min(attributesSize.width(), attributesSize.height());
}
auto DocumentData::resolveQualities(HistoryItem *context) const
@@ -611,19 +661,30 @@ not_null<DocumentData*> DocumentData::chooseQuality(
return this;
}
const auto height = int(request.height);
auto closest = this;
auto closestAbs = std::abs(height - resolveVideoQuality());
auto closestSize = size;
if (height >= Media::kVideoQualityOriginalOffset) {
return this;
}
auto closest = (DocumentData*)nullptr;
auto closestAbs = -1;
auto closestSize = -1;
for (const auto &quality : list) {
const auto abs = std::abs(height - quality->resolveVideoQuality());
if (abs < closestAbs
|| (abs == closestAbs && quality->size < closestSize)) {
const auto qres = quality->resolveVideoQuality();
const auto abs = std::abs(height - qres);
// Prefer Original if it fits target resolution best,
// falling back to transcode only on exact matches
if (!closest
|| abs < closestAbs
|| (abs == closestAbs && (quality->size < closestSize
|| (closest == this && quality != this)))) {
closest = quality;
closestAbs = abs;
closestSize = quality->size;
}
}
return closest;
return closest ? closest : this;
}
void DocumentData::validateLottieSticker() {
@@ -99,6 +99,7 @@ struct VoiceData : public DocumentAdditionalData {
struct VideoData : public DocumentAdditionalData {
QString codec;
std::vector<not_null<DocumentData*>> qualities;
QSize realVideoSize;
};
using RoundData = VoiceData;
@@ -41,6 +41,8 @@ inline constexpr auto kSpeedMin = 0.5;
inline constexpr auto kSpeedMax = 2.5;
inline constexpr auto kSpedUpDefault = 1.7;
inline constexpr auto kVideoQualityOriginalOffset = 1000000;
[[nodiscard]] inline bool EqualSpeeds(float64 a, float64 b) {
return int(base::SafeRound(a * 10.)) == int(base::SafeRound(b * 10.));
}
@@ -437,13 +437,16 @@ void SettingsButton::prepareFrame() {
: u"%1X"_q.arg(rounded / 10);
paintBadge(p, text, RectPart::TopLeft, color);
}
const auto text = (!_quality)
const auto displayQuality = (_quality >= Media::kVideoQualityOriginalOffset)
? (_quality - Media::kVideoQualityOriginalOffset)
: _quality;
const auto text = (!displayQuality)
? QString()
: (_quality > 2000)
: (displayQuality > 2000)
? u"4K"_q
: (_quality > 1000)
: (displayQuality > 1000)
? u"FHD"_q
: (_quality > 700)
: (displayQuality > 700)
? u"HD"_q
: u"SD"_q;
if (!text.isEmpty()) {
@@ -826,7 +826,13 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
const auto add = [&](int quality) {
const auto automatic = tr::lng_mediaview_quality_auto(tr::now);
const auto text = quality ? u"%1p"_q.arg(quality) : automatic;
const auto offset = Media::kVideoQualityOriginalOffset;
// Quality is height-based, except for Original which uses offset
const auto text = !quality
? automatic
: (quality >= offset)
? u"Original (%1p)"_q.arg(std::clamp(quality - offset, 0, 4320))
: u"%1p"_q.arg(quality);
auto action = base::make_unique_q<Ui::Menu::Action>(
raw,
st.qualityMenu,
@@ -836,15 +842,15 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
[=] { _changeQuality(quality); }),
nullptr,
nullptr);
const auto raw = action.get();
const auto check = Ui::CreateChild<Ui::RpWidget>(raw);
const auto rawAction = action.get();
const auto check = Ui::CreateChild<Ui::RpWidget>(rawAction);
check->resize(st.activeCheck.size());
check->paintRequest(
) | rpl::on_next([check, icon = &st.activeCheck] {
auto p = QPainter(check);
icon->paint(p, 0, 0, check->width());
}, check->lifetime());
raw->sizeValue(
rawAction->sizeValue(
) | rpl::on_next([=, skip = st.activeCheckSkip](QSize size) {
check->moveToRight(
skip,
@@ -857,13 +863,19 @@ void SpeedController::fillMenu(not_null<Ui::DropdownMenu*> menu) {
const auto chosen = now.manual
? (now.height == quality)
: !quality;
raw->action()->setEnabled(!chosen);
rawAction->action()->setEnabled(!chosen);
if (!quality) {
raw->action()->setText(automatic
+ (now.manual ? QString() : u"\t%1p"_q.arg(now.height)));
const auto offset = Media::kVideoQualityOriginalOffset;
const auto displayHeight = (now.height >= offset)
? std::clamp(int(now.height - offset), 0, 4320)
: now.height;
const auto suffix = now.manual
? QString()
: u"\t%1p"_q.arg(displayHeight);
rawAction->action()->setText(automatic + suffix);
}
check->setVisible(chosen);
}, raw->lifetime());
}, rawAction->lifetime());
menu->addAction(std::move(action));
};
@@ -59,6 +59,7 @@ struct TrackState {
struct VideoInformation {
TrackState state;
QSize size;
QSize realSize;
QImage cover;
int rotation = 0;
float64 fps = 0.;
@@ -62,6 +62,8 @@ void SaveValidVideoInformation(
SaveValidStateInformation(to.state, std::move(from.state));
to.size = from.size;
// Propagate physical resolution parsed from the stream
to.realSize = from.realSize;
to.cover = std::move(from.cover);
to.rotation = from.rotation;
to.fps = from.fps;
@@ -721,6 +721,10 @@ void VideoTrackObject::callReady() {
const auto frame = _shared->frameForPaint();
++_frameIndex;
const auto frameSize = frame->original.isNull()
? frame->yuv.size
: frame->original.size();
base::take(_ready)({ VideoInformation{
.state = {
.position = _syncTimePoint.trackTime,
@@ -730,7 +734,11 @@ void VideoTrackObject::callReady() {
.duration = _stream.duration,
},
.size = FFmpeg::TransposeSizeByRotation(
FFmpeg::CorrectByAspect(frame->original.size(), _stream.aspect),
FFmpeg::CorrectByAspect(frameSize, _stream.aspect),
_stream.rotation),
// realSize captures physical resolution before SAR correction
.realSize = FFmpeg::TransposeSizeByRotation(
frameSize,
_stream.rotation),
.cover = frame->original,
.rotation = _stream.rotation,
@@ -1246,7 +1246,8 @@ QSize OverlayWidget::videoSize() const {
Expects(videoShown());
const auto use = (_document && _chosenQuality != _document)
? _document->dimensions
// Use chosen quality dimensions instead of original
? _chosenQuality->dimensions
: _streamed->instance.info().video.size;
return flipSizeByRotation(use);
}
@@ -4714,8 +4715,21 @@ void OverlayWidget::initStreamingThumbnail() {
void OverlayWidget::streamingReady(Streaming::Information &&info) {
markStreamedReady();
if (videoShown()) {
if (_document && _streamed && _streamed->ready) {
const auto targetDocument = _chosenQuality ? _chosenQuality : _document;
if (const auto video = targetDocument->video()) {
video->realVideoSize = info.video.realSize;
}
}
applyVideoSize();
_streamedQualityChangeFrame = QImage();
if (_streamed && _streamed->controls) {
crl::on_main(_widget, [=] {
if (_streamed && _streamed->controls) {
_streamed->controls->updateSpeedToggleQuality();
}
});
}
} else {
updateContentRect();
}
@@ -5121,12 +5135,16 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
}
const auto overrideDuration = _stories
|| (_chosenQuality && _chosenQuality != _document);
const auto durationDocument = (_chosenQuality && _chosenQuality != _document)
? _chosenQuality
: _document;
auto options = Streaming::PlaybackOptions{
.position = position,
.durationOverride = ((overrideDuration
&& _document
&& _document->hasDuration())
? _document->duration()
&& durationDocument
&& durationDocument->hasDuration())
? durationDocument->duration()
: crl::time(0)),
.hwAllowed = Core::App().settings().hardwareAcceleratedVideo(),
.seekable = !_stories,
@@ -5231,21 +5249,30 @@ std::vector<int> OverlayWidget::playbackControlsQualities() {
}
auto result = std::vector<int>();
result.reserve(list.size());
auto seen = std::vector<int>();
for (const auto &quality : list) {
result.push_back(quality->resolveVideoQuality());
const auto res = quality->resolveVideoQuality();
const auto value = (quality == _document)
? (res + Media::kVideoQualityOriginalOffset)
: res;
if (!ranges::contains(seen, value)) {
result.push_back(value);
seen.push_back(value);
}
}
return result;
}
VideoQuality OverlayWidget::playbackControlsCurrentQuality() {
return _chosenQuality
? VideoQuality{
.manual = _quality.manual,
.height = uint32(_chosenQuality->resolveVideoQuality()),
}
: _quality;
if (!_chosenQuality) {
return _quality;
}
auto height = uint32(_chosenQuality->resolveVideoQuality());
if (_chosenQuality == _document) {
height += Media::kVideoQualityOriginalOffset;
}
return { .manual = _quality.manual, .height = height };
}
void OverlayWidget::playbackControlsQualityChanged(int quality) {
applyVideoQuality({
.manual = (quality > 0),
@@ -5556,6 +5583,7 @@ void OverlayWidget::updatePlaybackState() {
_streamedPosition = state.position;
if (_streamed->controls) {
_streamed->controls->updatePlayback(state);
_streamed->controls->updateSpeedToggleQuality();
_touchbarTrackState.fire_copy(state);
updatePowerSaveBlocker(state);
}
@@ -225,13 +225,13 @@ void PlaybackControls::saveSpeed(float64 speed) {
}
void PlaybackControls::saveQuality(int quality) {
_speedToggle->setQuality(_qualitiesList.empty() ? 0 : quality);
_speedToggle->setQuality(quality);
_delegate->playbackControlsQualityChanged(quality);
}
void PlaybackControls::updateSpeedToggleQuality() {
const auto quality = _delegate->playbackControlsCurrentQuality();
_speedToggle->setQuality(_qualitiesList.empty() ? 0 : quality.height);
_speedToggle->setQuality(quality.height);
}
void PlaybackControls::updatePlaybackSpeed(float64 speed) {
@@ -71,6 +71,8 @@ public:
void setLoadingProgress(int64 ready, int64 total);
void setTimestamps(std::vector<TimestampData> timestamps);
void setInFullScreen(bool inFullScreen);
void updatePlaybackSpeed(float64 speed);
void updateSpeedToggleQuality();
[[nodiscard]] bool hasTimestamps() const;
[[nodiscard]] std::optional<TimestampData> nextTimestamp(
float64 progress) const;
@@ -96,7 +98,6 @@ private:
[[nodiscard]] float64 countDownloadedTillPercent(
const Player::TrackState &state) const;
void updatePlaybackSpeed(float64 speed);
void updateVolumeToggleIcon();
void updateDownloadProgressPosition();
@@ -108,7 +109,6 @@ private:
void saveSpeed(float64 speed);
void saveQuality(int quality);
void updateSpeedToggleQuality();
void updateTimestampLabel();
const not_null<Delegate*> _delegate;