mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support rendering Webm videos with alpha.
This commit is contained in:
parent
1755ead681
commit
8b7d2c880e
25 changed files with 139 additions and 44 deletions
|
@ -858,8 +858,8 @@ void StickerSetBox::Inner::paintSticker(
|
||||||
const auto &media = element.documentMedia;
|
const auto &media = element.documentMedia;
|
||||||
media->checkStickerSmall();
|
media->checkStickerSmall();
|
||||||
|
|
||||||
const auto isAnimated = document->sticker()->animated;
|
const auto isLottie = document->sticker()->isLottie();
|
||||||
if (isAnimated
|
if (isLottie
|
||||||
&& !element.animated
|
&& !element.animated
|
||||||
&& media->loaded()) {
|
&& media->loaded()) {
|
||||||
const_cast<Inner*>(this)->setupLottie(index);
|
const_cast<Inner*>(this)->setupLottie(index);
|
||||||
|
@ -867,7 +867,7 @@ void StickerSetBox::Inner::paintSticker(
|
||||||
|
|
||||||
auto w = 1;
|
auto w = 1;
|
||||||
auto h = 1;
|
auto h = 1;
|
||||||
if (isAnimated && !document->dimensions.isEmpty()) {
|
if (isLottie && !document->dimensions.isEmpty()) {
|
||||||
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
||||||
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
||||||
w = std::max(size.width(), 1);
|
w = std::max(size.width(), 1);
|
||||||
|
|
|
@ -827,7 +827,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||||
const auto &media = sticker.documentMedia;
|
const auto &media = sticker.documentMedia;
|
||||||
if (!document->sticker()) continue;
|
if (!document->sticker()) continue;
|
||||||
|
|
||||||
if (document->sticker()->animated
|
if (document->sticker()->isLottie()
|
||||||
&& !sticker.animated
|
&& !sticker.animated
|
||||||
&& media->loaded()) {
|
&& media->loaded()) {
|
||||||
setupLottie(sticker);
|
setupLottie(sticker);
|
||||||
|
|
|
@ -141,7 +141,7 @@ void DicePack::generateLocal(int index, const QString &name) {
|
||||||
_map.emplace(index, document);
|
_map.emplace(index, document);
|
||||||
|
|
||||||
Ensures(document->sticker());
|
Ensures(document->sticker());
|
||||||
Ensures(document->sticker()->animated);
|
Ensures(document->sticker()->isLottie());
|
||||||
}
|
}
|
||||||
|
|
||||||
DicePacks::DicePacks(not_null<Main::Session*> session) : _session(session) {
|
DicePacks::DicePacks(not_null<Main::Session*> session) : _session(session) {
|
||||||
|
|
|
@ -1916,8 +1916,8 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto isAnimated = document->sticker()->animated;
|
const auto isLottie = document->sticker()->isLottie();
|
||||||
if (isAnimated
|
if (isLottie
|
||||||
&& !sticker.animated
|
&& !sticker.animated
|
||||||
&& media->loaded()) {
|
&& media->loaded()) {
|
||||||
setupLottie(set, section, index);
|
setupLottie(set, section, index);
|
||||||
|
@ -1936,7 +1936,7 @@ void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int section,
|
||||||
|
|
||||||
auto w = 1;
|
auto w = 1;
|
||||||
auto h = 1;
|
auto h = 1;
|
||||||
if (isAnimated && !document->dimensions.isEmpty()) {
|
if (isLottie && !document->dimensions.isEmpty()) {
|
||||||
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
const auto request = Lottie::FrameRequest{ boundingBoxSize() * cIntRetinaFactor() };
|
||||||
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
const auto size = request.size(document->dimensions, true) / cIntRetinaFactor();
|
||||||
w = std::max(size.width(), 1);
|
w = std::max(size.width(), 1);
|
||||||
|
|
|
@ -139,7 +139,7 @@ bool HasLottieThumbnail(
|
||||||
}
|
}
|
||||||
const auto document = media->owner();
|
const auto document = media->owner();
|
||||||
if (const auto info = document->sticker()) {
|
if (const auto info = document->sticker()) {
|
||||||
if (!info->animated) {
|
if (!info->isLottie()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
||||||
|
|
|
@ -109,6 +109,14 @@ MimeType MimeTypeForData(const QByteArray &data) {
|
||||||
return MimeType(QMimeDatabase().mimeTypeForData(data));
|
return MimeType(QMimeDatabase().mimeTypeForData(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsMimeStickerLottie(const QString &mime) {
|
||||||
|
return (mime == u"application/x-tgsticker"_q);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsMimeStickerWebm(const QString &mime) {
|
||||||
|
return (mime == u"video/webm"_q);
|
||||||
|
}
|
||||||
|
|
||||||
bool IsMimeStickerAnimated(const QString &mime) {
|
bool IsMimeStickerAnimated(const QString &mime) {
|
||||||
return (mime == u"application/x-tgsticker"_q);
|
return (mime == u"application/x-tgsticker"_q);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const auto kAnimatedStickerDimensions = QSize(
|
const auto kLottieStickerDimensions = QSize(
|
||||||
kStickerSideSize,
|
kStickerSideSize,
|
||||||
kStickerSideSize);
|
kStickerSideSize);
|
||||||
|
|
||||||
|
@ -262,6 +262,22 @@ Data::FileOrigin StickerData::setOrigin() const {
|
||||||
: Data::FileOrigin();
|
: Data::FileOrigin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StickerData::isStatic() const {
|
||||||
|
return (type == StickerType::Webp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StickerData::isLottie() const {
|
||||||
|
return (type == StickerType::Tgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StickerData::isAnimated() const {
|
||||||
|
return !isStatic();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StickerData::isWebm() const {
|
||||||
|
return (type == StickerType::Webm);
|
||||||
|
}
|
||||||
|
|
||||||
VoiceData::~VoiceData() {
|
VoiceData::~VoiceData() {
|
||||||
if (!waveform.isEmpty()
|
if (!waveform.isEmpty()
|
||||||
&& waveform[0] == -1
|
&& waveform[0] == -1
|
||||||
|
@ -380,12 +396,19 @@ void DocumentData::setattributes(
|
||||||
}
|
}
|
||||||
if (type == StickerDocument
|
if (type == StickerDocument
|
||||||
&& ((size > Storage::kMaxStickerBytesSize)
|
&& ((size > Storage::kMaxStickerBytesSize)
|
||||||
|| (!sticker()->animated
|
|| (!sticker()->isLottie()
|
||||||
&& !GoodStickerDimensions(
|
&& !GoodStickerDimensions(
|
||||||
dimensions.width(),
|
dimensions.width(),
|
||||||
dimensions.height())))) {
|
dimensions.height())))) {
|
||||||
type = FileDocument;
|
type = FileDocument;
|
||||||
_additional = nullptr;
|
_additional = nullptr;
|
||||||
|
} else if (type == FileDocument
|
||||||
|
&& hasMimeType(qstr("video/webm"))
|
||||||
|
&& (size < Storage::kMaxStickerBytesSize)
|
||||||
|
&& GoodStickerDimensions(dimensions.width(), dimensions.height())) {
|
||||||
|
type = StickerDocument;
|
||||||
|
_additional = std::make_unique<StickerData>();
|
||||||
|
sticker()->type = StickerType::Webm;
|
||||||
}
|
}
|
||||||
if (isAudioFile() || isAnimation() || isVoiceMessage()) {
|
if (isAudioFile() || isAnimation() || isVoiceMessage()) {
|
||||||
setMaybeSupportsStreaming(true);
|
setMaybeSupportsStreaming(true);
|
||||||
|
@ -397,8 +420,8 @@ void DocumentData::validateLottieSticker() {
|
||||||
&& hasMimeType(qstr("application/x-tgsticker"))) {
|
&& hasMimeType(qstr("application/x-tgsticker"))) {
|
||||||
type = StickerDocument;
|
type = StickerDocument;
|
||||||
_additional = std::make_unique<StickerData>();
|
_additional = std::make_unique<StickerData>();
|
||||||
sticker()->animated = true;
|
sticker()->type = StickerType::Tgs;
|
||||||
dimensions = kAnimatedStickerDimensions;
|
dimensions = kLottieStickerDimensions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,22 @@ struct DocumentAdditionalData {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StickerData : public DocumentAdditionalData {
|
enum class StickerType : uchar {
|
||||||
Data::FileOrigin setOrigin() const;
|
Webp,
|
||||||
|
Tgs,
|
||||||
|
Webm,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StickerData : public DocumentAdditionalData {
|
||||||
|
[[nodiscard]] Data::FileOrigin setOrigin() const;
|
||||||
|
[[nodiscard]] bool isStatic() const;
|
||||||
|
[[nodiscard]] bool isLottie() const;
|
||||||
|
[[nodiscard]] bool isAnimated() const;
|
||||||
|
[[nodiscard]] bool isWebm() const;
|
||||||
|
|
||||||
bool animated = false;
|
|
||||||
QString alt;
|
QString alt;
|
||||||
StickerSetIdentifier set;
|
StickerSetIdentifier set;
|
||||||
|
StickerType type = StickerType::Webp;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SongData : public DocumentAdditionalData {
|
struct SongData : public DocumentAdditionalData {
|
||||||
|
|
|
@ -49,7 +49,7 @@ enum class FileType {
|
||||||
|| owner->isAnimation()
|
|| owner->isAnimation()
|
||||||
|| owner->isWallPaper()
|
|| owner->isWallPaper()
|
||||||
|| owner->isTheme()
|
|| owner->isTheme()
|
||||||
|| (owner->sticker() && owner->sticker()->animated);
|
|| (owner->sticker() && owner->sticker()->isAnimated());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QImage PrepareGoodThumbnail(
|
[[nodiscard]] QImage PrepareGoodThumbnail(
|
||||||
|
@ -260,7 +260,7 @@ void DocumentMedia::checkStickerLarge() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
||||||
if (data->animated || !loaded()) {
|
if (data->isAnimated() || !loaded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_bytes.isEmpty()) {
|
if (_bytes.isEmpty()) {
|
||||||
|
@ -366,9 +366,9 @@ bool DocumentMedia::thumbnailEnoughForSticker() const {
|
||||||
|
|
||||||
void DocumentMedia::checkStickerSmall() {
|
void DocumentMedia::checkStickerSmall() {
|
||||||
const auto data = _owner->sticker();
|
const auto data = _owner->sticker();
|
||||||
if ((data && data->animated) || thumbnailEnoughForSticker()) {
|
if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
|
||||||
_owner->loadThumbnail(_owner->stickerSetOrigin());
|
_owner->loadThumbnail(_owner->stickerSetOrigin());
|
||||||
if (data && data->animated) {
|
if (data && data->isAnimated()) {
|
||||||
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -383,7 +383,7 @@ Image *DocumentMedia::getStickerLarge() {
|
||||||
|
|
||||||
Image *DocumentMedia::getStickerSmall() {
|
Image *DocumentMedia::getStickerSmall() {
|
||||||
const auto data = _owner->sticker();
|
const auto data = _owner->sticker();
|
||||||
if ((data && data->animated) || thumbnailEnoughForSticker()) {
|
if ((data && data->isAnimated()) || thumbnailEnoughForSticker()) {
|
||||||
return thumbnail();
|
return thumbnail();
|
||||||
}
|
}
|
||||||
return _sticker.get();
|
return _sticker.get();
|
||||||
|
|
|
@ -994,7 +994,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||||
const auto CreateSortKey = [&](
|
const auto CreateSortKey = [&](
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
int base) {
|
int base) {
|
||||||
if (document->sticker() && document->sticker()->animated) {
|
if (document->sticker() && document->sticker()->isAnimated()) {
|
||||||
base += kSlice;
|
base += kSlice;
|
||||||
}
|
}
|
||||||
return TimeId(base + int((document->id ^ seed) % kSlice));
|
return TimeId(base + int((document->id ^ seed) % kSlice));
|
||||||
|
@ -1005,7 +1005,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||||
auto myCounter = 0;
|
auto myCounter = 0;
|
||||||
const auto CreateMySortKey = [&](not_null<DocumentData*> document) {
|
const auto CreateMySortKey = [&](not_null<DocumentData*> document) {
|
||||||
auto base = kSlice * 6;
|
auto base = kSlice * 6;
|
||||||
if (!document->sticker() || !document->sticker()->animated) {
|
if (!document->sticker() || !document->sticker()->isAnimated()) {
|
||||||
base -= kSlice;
|
base -= kSlice;
|
||||||
}
|
}
|
||||||
return (base - (++myCounter));
|
return (base - (++myCounter));
|
||||||
|
@ -1019,7 +1019,7 @@ std::vector<not_null<DocumentData*>> Stickers::getListByEmoji(
|
||||||
const auto InstallDateAdjusted = [&](
|
const auto InstallDateAdjusted = [&](
|
||||||
TimeId date,
|
TimeId date,
|
||||||
not_null<DocumentData*> document) {
|
not_null<DocumentData*> document) {
|
||||||
return (document->sticker() && document->sticker()->animated)
|
return (document->sticker() && document->sticker()->isAnimated())
|
||||||
? date
|
? date
|
||||||
: date / 2;
|
: date / 2;
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,7 +38,7 @@ ItemSticker::ItemSticker(
|
||||||
? 1.0
|
? 1.0
|
||||||
: (_pixmap.height() / float64(_pixmap.width())));
|
: (_pixmap.height() / float64(_pixmap.width())));
|
||||||
});
|
});
|
||||||
if (stickerData->animated) {
|
if (stickerData->isLottie()) {
|
||||||
_lottie.player = ChatHelpers::LottiePlayerFromDocument(
|
_lottie.player = ChatHelpers::LottiePlayerFromDocument(
|
||||||
_mediaView.get(),
|
_mediaView.get(),
|
||||||
ChatHelpers::StickerLottieSize::MessageHistory,
|
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||||
|
|
|
@ -391,6 +391,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
auto request = ::Media::Streaming::FrameRequest();
|
auto request = ::Media::Streaming::FrameRequest();
|
||||||
request.outer = QSize(usew, painth) * cIntRetinaFactor();
|
request.outer = QSize(usew, painth) * cIntRetinaFactor();
|
||||||
request.resize = QSize(_thumbw, _thumbh) * cIntRetinaFactor();
|
request.resize = QSize(_thumbw, _thumbh) * cIntRetinaFactor();
|
||||||
|
request.keepAlpha = true; AssertIsDebug();
|
||||||
request.corners = roundCorners;
|
request.corners = roundCorners;
|
||||||
request.radius = roundRadius;
|
request.radius = roundRadius;
|
||||||
if (!activeRoundPlaying && activeOwnPlaying->instance.playerLocked()) {
|
if (!activeRoundPlaying && activeOwnPlaying->instance.playerLocked()) {
|
||||||
|
@ -992,6 +993,7 @@ void Gif::drawGrouped(
|
||||||
{ geometry.width(), geometry.height() });
|
{ geometry.width(), geometry.height() });
|
||||||
request.outer = geometry.size() * cIntRetinaFactor();
|
request.outer = geometry.size() * cIntRetinaFactor();
|
||||||
request.resize = pixSize * cIntRetinaFactor();
|
request.resize = pixSize * cIntRetinaFactor();
|
||||||
|
request.keepAlpha = true; AssertIsDebug();
|
||||||
request.corners = corners;
|
request.corners = corners;
|
||||||
request.radius = roundRadius;
|
request.radius = roundRadius;
|
||||||
if (activeOwnPlaying->instance.playerLocked()) {
|
if (activeOwnPlaying->instance.playerLocked()) {
|
||||||
|
|
|
@ -123,7 +123,7 @@ bool Sticker::readyToDrawLottie() {
|
||||||
ensureDataMediaCreated();
|
ensureDataMediaCreated();
|
||||||
_dataMedia->checkStickerLarge();
|
_dataMedia->checkStickerLarge();
|
||||||
const auto loaded = _dataMedia->loaded();
|
const auto loaded = _dataMedia->loaded();
|
||||||
if (sticker->animated && !_lottie && loaded) {
|
if (sticker->isLottie() && !_lottie && loaded) {
|
||||||
setupLottie();
|
setupLottie();
|
||||||
}
|
}
|
||||||
return (_lottie && _lottie->ready());
|
return (_lottie && _lottie->ready());
|
||||||
|
@ -147,7 +147,7 @@ void Sticker::draw(
|
||||||
if (readyToDrawLottie()) {
|
if (readyToDrawLottie()) {
|
||||||
paintLottie(p, context, r);
|
paintLottie(p, context, r);
|
||||||
} else if (!_data->sticker()
|
} else if (!_data->sticker()
|
||||||
|| (_data->sticker()->animated && _replacements)
|
|| (_data->sticker()->isLottie() && _replacements)
|
||||||
|| !paintPixmap(p, context, r)) {
|
|| !paintPixmap(p, context, r)) {
|
||||||
paintPath(p, context, r);
|
paintPath(p, context, r);
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,7 +554,7 @@ void Sticker::prepareThumbnail() const {
|
||||||
ensureDataMediaCreated(document);
|
ensureDataMediaCreated(document);
|
||||||
if (!_lottie
|
if (!_lottie
|
||||||
&& document->sticker()
|
&& document->sticker()
|
||||||
&& document->sticker()->animated
|
&& document->sticker()->isLottie()
|
||||||
&& _dataMedia->loaded()) {
|
&& _dataMedia->loaded()) {
|
||||||
setupLottie();
|
setupLottie();
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,8 +188,16 @@ crl::time FFMpegReaderImplementation::framePresentationTime() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time FFMpegReaderImplementation::durationMs() const {
|
crl::time FFMpegReaderImplementation::durationMs() const {
|
||||||
if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0;
|
const auto rebase = [](int64_t duration, const AVRational &base) {
|
||||||
return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
|
return (duration * 1000LL * base.num) / base.den;
|
||||||
|
};
|
||||||
|
const auto stream = _fmtContext->streams[_streamId];
|
||||||
|
if (stream->duration != AV_NOPTS_VALUE) {
|
||||||
|
return rebase(stream->duration, stream->time_base);
|
||||||
|
} else if (_fmtContext->duration != AV_NOPTS_VALUE) {
|
||||||
|
return rebase(_fmtContext->duration, AVRational{ 1, AV_TIME_BASE });
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
|
bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
|
||||||
|
@ -211,8 +219,12 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q
|
||||||
if (to.isNull() || to.size() != toSize || !to.isDetached() || !isAlignedImage(to)) {
|
if (to.isNull() || to.size() != toSize || !to.isDetached() || !isAlignedImage(to)) {
|
||||||
to = createAlignedImage(toSize);
|
to = createAlignedImage(toSize);
|
||||||
}
|
}
|
||||||
hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA));
|
const auto format = (_frame->format == AV_PIX_FMT_NONE)
|
||||||
if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) {
|
? _codecContext->pix_fmt
|
||||||
|
: _frame->format;
|
||||||
|
const auto bgra = (format == AV_PIX_FMT_BGRA);
|
||||||
|
hasAlpha = bgra || (format == AV_PIX_FMT_YUVA420P);
|
||||||
|
if (_frame->width == toSize.width() && _frame->height == toSize.height() && bgra) {
|
||||||
int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl);
|
int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl);
|
||||||
uchar *s = _frame->data[0], *d = to.bits();
|
uchar *s = _frame->data[0], *d = to.bits();
|
||||||
for (int32 i = 0, l = _frame->height; i < l; ++i) {
|
for (int32 i = 0, l = _frame->height; i < l; ++i) {
|
||||||
|
@ -228,7 +240,7 @@ bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const Q
|
||||||
int toLinesize[AV_NUM_DATA_POINTERS] = { int(to.bytesPerLine()), 0 };
|
int toLinesize[AV_NUM_DATA_POINTERS] = { int(to.bytesPerLine()), 0 };
|
||||||
sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize);
|
sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize);
|
||||||
}
|
}
|
||||||
if (hasAlpha) {
|
if (bgra) {
|
||||||
FFmpeg::PremultiplyInplace(to);
|
FFmpeg::PremultiplyInplace(to);
|
||||||
}
|
}
|
||||||
if (_rotation != Rotation::None) {
|
if (_rotation != Rotation::None) {
|
||||||
|
@ -391,6 +403,19 @@ bool FFMpegReaderImplementation::isGifv() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FFMpegReaderImplementation::isWebmSticker() const {
|
||||||
|
if (_hasAudioStream) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dataSize() > kMaxInMemory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_codecContext->codec_id != AV_CODEC_ID_VP9) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QString FFMpegReaderImplementation::logData() const {
|
QString FFMpegReaderImplementation::logData() const {
|
||||||
return u"for file '%1', data size '%2'"_q.arg(_location ? _location->name() : QString()).arg(_data->size());
|
return u"for file '%1', data size '%2'"_q.arg(_location ? _location->name() : QString()).arg(_data->size());
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ public:
|
||||||
QString logData() const;
|
QString logData() const;
|
||||||
|
|
||||||
bool isGifv() const;
|
bool isGifv() const;
|
||||||
|
bool isWebmSticker() const;
|
||||||
|
|
||||||
~FFMpegReaderImplementation();
|
~FFMpegReaderImplementation();
|
||||||
|
|
||||||
|
|
|
@ -845,6 +845,7 @@ Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const
|
||||||
auto durationMs = reader->durationMs();
|
auto durationMs = reader->durationMs();
|
||||||
if (durationMs > 0) {
|
if (durationMs > 0) {
|
||||||
result.isGifv = reader->isGifv();
|
result.isGifv = reader->isGifv();
|
||||||
|
result.isWebmSticker = reader->isWebmSticker();
|
||||||
// Use first video frame as a thumbnail.
|
// Use first video frame as a thumbnail.
|
||||||
// All other apps and server do that way.
|
// All other apps and server do that way.
|
||||||
//if (!result.isGifv) {
|
//if (!result.isGifv) {
|
||||||
|
@ -857,7 +858,7 @@ Ui::PreparedFileInformation::Video PrepareForSending(const QString &fname, const
|
||||||
auto readResult = reader->readFramesTill(-1, crl::now());
|
auto readResult = reader->readFramesTill(-1, crl::now());
|
||||||
auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success);
|
auto readFrame = (readResult == internal::ReaderImplementation::ReadResult::Success);
|
||||||
if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, QSize())) {
|
if (readFrame && reader->renderFrame(result.thumbnail, hasAlpha, QSize())) {
|
||||||
if (hasAlpha) {
|
if (hasAlpha && !result.isWebmSticker) {
|
||||||
auto cacheForResize = QImage();
|
auto cacheForResize = QImage();
|
||||||
auto request = FrameRequest();
|
auto request = FrameRequest();
|
||||||
request.framew = request.outerw = result.thumbnail.width();
|
request.framew = request.outerw = result.thumbnail.width();
|
||||||
|
|
|
@ -121,6 +121,7 @@ struct FrameRequest {
|
||||||
ImageRoundRadius radius = ImageRoundRadius();
|
ImageRoundRadius radius = ImageRoundRadius();
|
||||||
RectParts corners = RectPart::AllCorners;
|
RectParts corners = RectPart::AllCorners;
|
||||||
bool requireARGB32 = true;
|
bool requireARGB32 = true;
|
||||||
|
bool keepAlpha = false;
|
||||||
bool strict = true;
|
bool strict = true;
|
||||||
|
|
||||||
static FrameRequest NonStrict() {
|
static FrameRequest NonStrict() {
|
||||||
|
@ -138,6 +139,7 @@ struct FrameRequest {
|
||||||
&& (outer == other.outer)
|
&& (outer == other.outer)
|
||||||
&& (radius == other.radius)
|
&& (radius == other.radius)
|
||||||
&& (corners == other.corners)
|
&& (corners == other.corners)
|
||||||
|
&& (keepAlpha == other.keepAlpha)
|
||||||
&& (requireARGB32 == other.requireARGB32);
|
&& (requireARGB32 == other.requireARGB32);
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool operator!=(const FrameRequest &other) const {
|
[[nodiscard]] bool operator!=(const FrameRequest &other) const {
|
||||||
|
@ -146,6 +148,7 @@ struct FrameRequest {
|
||||||
|
|
||||||
[[nodiscard]] bool goodFor(const FrameRequest &other) const {
|
[[nodiscard]] bool goodFor(const FrameRequest &other) const {
|
||||||
return (requireARGB32 == other.requireARGB32)
|
return (requireARGB32 == other.requireARGB32)
|
||||||
|
&& (keepAlpha == other.keepAlpha)
|
||||||
&& ((*this == other) || (strict && !other.strict));
|
&& ((*this == other) || (strict && !other.strict));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,9 +89,10 @@ FFmpeg::AvErrorWrap ReadNextFrame(Stream &stream) {
|
||||||
|
|
||||||
bool GoodForRequest(
|
bool GoodForRequest(
|
||||||
const QImage &image,
|
const QImage &image,
|
||||||
|
bool hasAlpha,
|
||||||
int rotation,
|
int rotation,
|
||||||
const FrameRequest &request) {
|
const FrameRequest &request) {
|
||||||
if (image.isNull()) {
|
if (image.isNull() || (hasAlpha && !request.keepAlpha)) {
|
||||||
return false;
|
return false;
|
||||||
} else if (request.resize.isEmpty()) {
|
} else if (request.resize.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -170,6 +171,10 @@ QImage ConvertFrame(
|
||||||
frame->height,
|
frame->height,
|
||||||
data,
|
data,
|
||||||
linesize);
|
linesize);
|
||||||
|
|
||||||
|
if (frame->format == AV_PIX_FMT_YUVA420P) {
|
||||||
|
FFmpeg::PremultiplyInplace(storage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FFmpeg::ClearFrameMemory(frame);
|
FFmpeg::ClearFrameMemory(frame);
|
||||||
|
@ -274,8 +279,11 @@ void PaintFrameContent(
|
||||||
(full.height() - size.height()) / 2,
|
(full.height() - size.height()) / 2,
|
||||||
size.width(),
|
size.width(),
|
||||||
size.height());
|
size.height());
|
||||||
PaintFrameOuter(p, to, full);
|
if (!alpha || !request.keepAlpha) {
|
||||||
PaintFrameInner(p, to, original, alpha, rotation);
|
PaintFrameOuter(p, to, full);
|
||||||
|
}
|
||||||
|
const auto deAlpha = alpha && !request.keepAlpha;
|
||||||
|
PaintFrameInner(p, to, original, deAlpha, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyFrameRounding(QImage &storage, const FrameRequest &request) {
|
void ApplyFrameRounding(QImage &storage, const FrameRequest &request) {
|
||||||
|
@ -304,6 +312,10 @@ QImage PrepareByRequest(
|
||||||
storage = FFmpeg::CreateFrameStorage(outer);
|
storage = FFmpeg::CreateFrameStorage(outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (alpha && request.keepAlpha) {
|
||||||
|
storage.fill(Qt::transparent);
|
||||||
|
}
|
||||||
|
|
||||||
QPainter p(&storage);
|
QPainter p(&storage);
|
||||||
PaintFrameContent(p, original, alpha, rotation, request);
|
PaintFrameContent(p, original, alpha, rotation, request);
|
||||||
p.end();
|
p.end();
|
||||||
|
|
|
@ -51,6 +51,7 @@ struct Stream {
|
||||||
|
|
||||||
[[nodiscard]] bool GoodForRequest(
|
[[nodiscard]] bool GoodForRequest(
|
||||||
const QImage &image,
|
const QImage &image,
|
||||||
|
bool hasAlpha,
|
||||||
int rotation,
|
int rotation,
|
||||||
const FrameRequest &request);
|
const FrameRequest &request);
|
||||||
[[nodiscard]] QImage ConvertFrame(
|
[[nodiscard]] QImage ConvertFrame(
|
||||||
|
|
|
@ -444,7 +444,8 @@ void VideoTrackObject::rasterizeFrame(not_null<Frame*> frame) {
|
||||||
}
|
}
|
||||||
frame->format = FrameFormat::YUV420;
|
frame->format = FrameFormat::YUV420;
|
||||||
} else {
|
} else {
|
||||||
frame->alpha = (frame->decoded->format == AV_PIX_FMT_BGRA);
|
frame->alpha = (frame->decoded->format == AV_PIX_FMT_BGRA)
|
||||||
|
|| (frame->decoded->format == AV_PIX_FMT_YUVA420P);
|
||||||
frame->yuv420.size = {
|
frame->yuv420.size = {
|
||||||
frame->decoded->width,
|
frame->decoded->width,
|
||||||
frame->decoded->height
|
frame->decoded->height
|
||||||
|
@ -1110,8 +1111,11 @@ QImage VideoTrack::frame(
|
||||||
&& frame->format == FrameFormat::YUV420) {
|
&& frame->format == FrameFormat::YUV420) {
|
||||||
frame->original = ConvertToARGB32(frame->yuv420);
|
frame->original = ConvertToARGB32(frame->yuv420);
|
||||||
}
|
}
|
||||||
if (!frame->alpha
|
if (GoodForRequest(
|
||||||
&& GoodForRequest(frame->original, _streamRotation, useRequest)) {
|
frame->original,
|
||||||
|
frame->alpha,
|
||||||
|
_streamRotation,
|
||||||
|
useRequest)) {
|
||||||
return frame->original;
|
return frame->original;
|
||||||
} else if (changed || none || i->second.image.isNull()) {
|
} else if (changed || none || i->second.image.isNull()) {
|
||||||
const auto j = none
|
const auto j = none
|
||||||
|
@ -1187,8 +1191,11 @@ void VideoTrack::PrepareFrameByRequests(
|
||||||
const auto end = frame->prepared.end();
|
const auto end = frame->prepared.end();
|
||||||
for (auto i = begin; i != end; ++i) {
|
for (auto i = begin; i != end; ++i) {
|
||||||
auto &prepared = i->second;
|
auto &prepared = i->second;
|
||||||
if (frame->alpha
|
if (!GoodForRequest(
|
||||||
|| !GoodForRequest(frame->original, rotation, prepared.request)) {
|
frame->original,
|
||||||
|
frame->alpha,
|
||||||
|
rotation,
|
||||||
|
prepared.request)) {
|
||||||
auto j = begin;
|
auto j = begin;
|
||||||
for (; j != i; ++j) {
|
for (; j != i; ++j) {
|
||||||
if (j->second.request == prepared.request) {
|
if (j->second.request == prepared.request) {
|
||||||
|
|
|
@ -619,6 +619,7 @@ bool FileLoadTask::CheckForVideo(
|
||||||
static const auto extensions = {
|
static const auto extensions = {
|
||||||
qstr(".mp4"),
|
qstr(".mp4"),
|
||||||
qstr(".mov"),
|
qstr(".mov"),
|
||||||
|
qstr(".webm"),
|
||||||
};
|
};
|
||||||
if (!CheckMimeOrExtensions(filepath, result->filemime, mimes, extensions)) {
|
if (!CheckMimeOrExtensions(filepath, result->filemime, mimes, extensions)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -298,7 +298,7 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||||
UpdateImageDetails(file, previewWidth);
|
UpdateImageDetails(file, previewWidth);
|
||||||
file.type = PreparedFile::Type::Photo;
|
file.type = PreparedFile::Type::Photo;
|
||||||
} else if (Core::IsMimeSticker(file.information->filemime)
|
} else if (Core::IsMimeSticker(file.information->filemime)
|
||||||
|| image->animated) {
|
|| image->animated) {
|
||||||
file.type = PreparedFile::Type::None;
|
file.type = PreparedFile::Type::None;
|
||||||
}
|
}
|
||||||
} else if (const auto video = std::get_if<Video>(
|
} else if (const auto video = std::get_if<Video>(
|
||||||
|
|
|
@ -31,6 +31,7 @@ struct PreparedFileInformation {
|
||||||
};
|
};
|
||||||
struct Video {
|
struct Video {
|
||||||
bool isGifv = false;
|
bool isGifv = false;
|
||||||
|
bool isWebmSticker = false;
|
||||||
bool supportsStreaming = false;
|
bool supportsStreaming = false;
|
||||||
int duration = -1;
|
int duration = -1;
|
||||||
QImage thumbnail;
|
QImage thumbnail;
|
||||||
|
|
|
@ -262,7 +262,7 @@ QPixmap MediaPreviewWidget::currentImage() const {
|
||||||
if (_document) {
|
if (_document) {
|
||||||
if (const auto sticker = _document->sticker()) {
|
if (const auto sticker = _document->sticker()) {
|
||||||
if (_cacheStatus != CacheLoaded) {
|
if (_cacheStatus != CacheLoaded) {
|
||||||
if (sticker->animated && !_lottie && _documentMedia->loaded()) {
|
if (sticker->isLottie() && !_lottie && _documentMedia->loaded()) {
|
||||||
const_cast<MediaPreviewWidget*>(this)->setupLottie();
|
const_cast<MediaPreviewWidget*>(this)->setupLottie();
|
||||||
}
|
}
|
||||||
if (_lottie && _lottie->ready()) {
|
if (_lottie && _lottie->ready()) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue