Added sending of silent looping video for animated edited photos.

This commit is contained in:
23rd
2026-07-16 18:43:26 +03:00
parent e704a6c4c4
commit 97e6ed84a1
5 changed files with 105 additions and 17 deletions

View File

@@ -2584,6 +2584,15 @@ void SendFilesBox::send(
Storage::ApplyModifications(_list);
if ((_limits & SendFilesAllow::OnlyOne)
&& (_list.files.size() > 1)
&& ranges::any_of(_list.files, [](const auto &file) {
return file.animationJob != nullptr;
})) {
showToast(tr::lng_slowmode_no_many(tr::now));
return;
}
_confirmed = true;
if (_confirmedCallback) {
auto caption = fieldText();

View File

@@ -319,8 +319,9 @@ void Uploader::upload(
file->thumbbytes));
document->uploadingData = std::make_unique<Data::UploadState>(
document->size);
preparing = (file->videoTranscodeHeight > 0)
&& (!file->content.isEmpty() || !file->filepath.isEmpty());
preparing = (file->animationJob != nullptr)
|| ((file->videoTranscodeHeight > 0)
&& (!file->content.isEmpty() || !file->filepath.isEmpty()));
if (preparing) {
document->uploadingData->preparing = true;
}
@@ -386,16 +387,10 @@ void Uploader::startTranscode(FullMsgId itemId) {
auto &entry = *i;
const auto file = entry.file;
const auto height = file->videoTranscodeHeight;
const auto job = file->animationJob;
const auto cancel = std::make_shared<std::atomic<bool>>(false);
entry.cancelPreparing = cancel;
crl::async([=, weak = base::make_weak(this)]() mutable {
auto source = file->content;
if (source.isEmpty() && !file->filepath.isEmpty()) {
auto f = QFile(file->filepath);
if (f.open(QIODevice::ReadOnly)) {
source = f.readAll();
}
}
auto lastReported = -1.;
const auto progress = [&](float64 value) {
if (value - lastReported >= 0.01 || value >= 1.) {
@@ -406,10 +401,36 @@ void Uploader::startTranscode(FullMsgId itemId) {
}
return !cancel->load();
};
auto bytes = Media::Encode::TranscodeVideoToMp4(
source,
height,
progress);
auto bytes = QByteArray();
if (job) {
auto result = Media::Encode::Run(
Media::Encode::Job(*job),
progress);
if (result.bytes.isEmpty() && !cancel->load()) {
auto fallback = Media::Encode::Job(*job);
fallback.overlay.erase(
ranges::remove_if(
fallback.overlay,
[](const Media::Encode::Layer &layer) {
return !std::get_if<QImage>(&layer);
}),
end(fallback.overlay));
result = Media::Encode::Run(std::move(fallback), nullptr);
}
bytes = std::move(result.bytes);
} else {
auto source = file->content;
if (source.isEmpty() && !file->filepath.isEmpty()) {
auto f = QFile(file->filepath);
if (f.open(QIODevice::ReadOnly)) {
source = f.readAll();
}
}
bytes = Media::Encode::TranscodeVideoToMp4(
source,
height,
progress);
}
crl::on_main(weak, [=, bytes = std::move(bytes)]() mutable {
if (!cancel->load()) {
finishTranscode(itemId, std::move(bytes));

View File

@@ -715,6 +715,48 @@ void FileLoadTask::process(ProcessArgs &&args) {
}
}
auto animationPreparing = false;
if (_animationJob) {
const auto still = _forceFile
? nullptr
: std::get_if<Media::Encode::StillSource>(&_animationJob->source);
auto preview = QImage();
if (_information) {
const auto media = &_information->media;
if (const auto image = std::get_if<
Ui::PreparedFileInformation::Image>(media)) {
preview = std::move(image->data);
}
}
if (still && !still->base.isNull() && still->duration > 0) {
if (preview.isNull()) {
preview = still->base;
} else if (preview.size() != still->base.size()) {
preview = preview.scaled(
still->base.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
auto information = std::make_unique<
Ui::PreparedFileInformation>();
information->filemime = "video/mp4";
information->media = Ui::PreparedFileInformation::Video{
.isGifv = true,
.supportsStreaming = true,
.duration = still->duration,
.thumbnail = std::move(preview),
};
_information = std::move(information);
_content = QByteArray();
_filepath = QString();
_type = SendMediaType::File;
_displayName = u"animation.mp4"_q;
_result->animationJob = _animationJob;
animationPreparing = true;
}
_animationJob = nullptr;
}
QString filename, filemime;
qint64 filesize = 0;
QByteArray filedata;
@@ -759,6 +801,19 @@ void FileLoadTask::process(ProcessArgs &&args) {
}
isAnimation = image->animated;
}
} else if (animationPreparing) {
const auto video = std::get_if<Ui::PreparedFileInformation::Video>(
&_information->media);
const auto seconds = std::max(
int64(video->duration / 1000),
int64(1));
filesize = seconds * 200'000;
filename = filedialogDefaultName(
u"animation"_q,
u".mp4"_q,
QString(),
true);
filemime = "video/mp4";
} else if (!_content.isEmpty()) {
filesize = _content.size();
if (isVoice) {

View File

@@ -211,6 +211,7 @@ struct FilePrepareResult {
bool spoiler = false;
bool forceFile = false;
int videoTranscodeHeight = 0;
std::shared_ptr<Media::Encode::Job> animationJob;
std::vector<MTPInputDocument> attachedStickers;

View File

@@ -135,10 +135,12 @@ struct HighQualityBadgeCache {
auto from = 0;
auto groupType = AlbumType::None;
for (auto i = 0; i != int(files.size()); ++i) {
const auto fileGroupType = GroupTypeForFile(
files[i].type,
groupFiles,
sendImagesAsPhotos);
const auto fileGroupType = files[i].animationJob
? AlbumType::None
: GroupTypeForFile(
files[i].type,
groupFiles,
sendImagesAsPhotos);
const auto count = (i - from);
if ((i > from && groupType != fileGroupType)
|| ((groupType != AlbumType::None) && (count == kMaxAlbumCount))) {