mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Bring back original video quality option
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user