mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 16:13:46 +08:00
207 lines
5.0 KiB
C++
207 lines
5.0 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "editor/editor_message_video.h"
|
|
|
|
#include "core/mime_type.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_document_media.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_groups.h"
|
|
#include "data/data_media_types.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_streaming.h"
|
|
#include "editor/video/video_clip.h"
|
|
#include "history/history.h"
|
|
#include "history/history_item.h"
|
|
#include "main/main_session.h"
|
|
#include "media/media_video_encode.h"
|
|
#include "storage/storage_media_prepare.h"
|
|
#include "storage/streamed_file_downloader.h"
|
|
#include "ui/image/image_prepare.h"
|
|
|
|
#include <QtCore/QTemporaryFile>
|
|
|
|
namespace Editor {
|
|
namespace {
|
|
|
|
constexpr auto kMaxExtensionLength = 5;
|
|
|
|
[[nodiscard]] QString VideoExtension(not_null<DocumentData*> document) {
|
|
const auto extension = Core::FileExtension(document->filename());
|
|
const auto valid = !extension.isEmpty()
|
|
&& (extension.size() <= kMaxExtensionLength)
|
|
&& ranges::all_of(extension, [](QChar ch) {
|
|
return ch.isLetterOrNumber();
|
|
});
|
|
return valid ? extension : u"mp4"_q;
|
|
}
|
|
|
|
[[nodiscard]] QString TempVideoPath(not_null<DocumentData*> document) {
|
|
auto temp = QTemporaryFile(
|
|
Media::Encode::TempFileTemplate(VideoExtension(document)));
|
|
if (!temp.open()) {
|
|
return QString();
|
|
}
|
|
temp.setAutoRemove(false);
|
|
return temp.fileName();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
DocumentData *MessageVideo::Find(not_null<HistoryItem*> item) {
|
|
const auto media = item->media();
|
|
const auto document = media ? media->document() : nullptr;
|
|
if (!document
|
|
|| media->webpage()
|
|
|| item->history()->owner().groups().find(item)) {
|
|
return nullptr;
|
|
}
|
|
const auto video = document->isVideoFile()
|
|
|| document->isGifv()
|
|
|| document->isVideoMessage();
|
|
const auto fits = (document->size > 0)
|
|
&& (document->size <= Images::kReadBytesLimit);
|
|
return (video && fits) ? document : nullptr;
|
|
}
|
|
|
|
MessageVideo::MessageVideo(not_null<HistoryItem*> item)
|
|
: _document(Find(item))
|
|
, _itemId(item->fullId()) {
|
|
}
|
|
|
|
MessageVideo::~MessageVideo() {
|
|
_lifetime.destroy();
|
|
_loader = nullptr;
|
|
if (!_tempPath.isEmpty()) {
|
|
QFile::remove(_tempPath);
|
|
}
|
|
}
|
|
|
|
not_null<DocumentData*> MessageVideo::document() const {
|
|
return _document;
|
|
}
|
|
|
|
void MessageVideo::load() {
|
|
if (_source || _loader || _waiting || _reading || _failed) {
|
|
return;
|
|
}
|
|
_media = _document->createMediaView();
|
|
if (_media->loaded(true)) {
|
|
const auto path = _document->filepath(true);
|
|
readSource(path, path.isEmpty() ? _media->bytes() : QByteArray());
|
|
return;
|
|
} else if (_document->loading()) {
|
|
waitForLoader();
|
|
return;
|
|
}
|
|
const auto origin = Data::FileOrigin(_itemId);
|
|
auto reader = _document->owner().streaming().sharedReader(
|
|
_document,
|
|
origin,
|
|
true);
|
|
const auto path = reader ? TempVideoPath(_document) : QString();
|
|
if (path.isEmpty()) {
|
|
fail();
|
|
return;
|
|
}
|
|
_tempPath = path;
|
|
_loader = _document->createStreamedDownloader(
|
|
std::move(reader),
|
|
origin,
|
|
std::nullopt,
|
|
path,
|
|
LoadToFileOnly,
|
|
LoadFromCloudOrLocal,
|
|
false);
|
|
_loader->updates(
|
|
) | rpl::on_next_error_done([=] {
|
|
_changes.fire({});
|
|
}, [=](FileLoader::Error) {
|
|
crl::on_main(base::make_weak(this), [=] { fail(); });
|
|
}, [=] {
|
|
readSource(_tempPath, QByteArray());
|
|
}, _lifetime);
|
|
_loader->start();
|
|
_changes.fire({});
|
|
}
|
|
|
|
bool MessageVideo::loading() const {
|
|
return (_loader != nullptr) || _waiting || _reading;
|
|
}
|
|
|
|
float64 MessageVideo::progress() const {
|
|
return _loader
|
|
? _loader->currentProgress()
|
|
: _waiting
|
|
? _document->progress()
|
|
: _reading
|
|
? 1.
|
|
: 0.;
|
|
}
|
|
|
|
std::shared_ptr<VideoClipSource> MessageVideo::source() const {
|
|
return _source;
|
|
}
|
|
|
|
rpl::producer<> MessageVideo::changes() const {
|
|
return _changes.events();
|
|
}
|
|
|
|
void MessageVideo::waitForLoader() {
|
|
_waiting = true;
|
|
const auto weak = base::make_weak(this);
|
|
const auto lifetime = std::make_shared<rpl::lifetime>();
|
|
_document->session().downloaderTaskFinished(
|
|
) | rpl::on_next_done([=] {
|
|
const auto strong = weak.get();
|
|
if (strong && strong->_document->loading()) {
|
|
return;
|
|
}
|
|
lifetime->destroy();
|
|
if (strong) {
|
|
strong->_waiting = false;
|
|
strong->load();
|
|
}
|
|
}, [=] {
|
|
lifetime->destroy();
|
|
}, *lifetime);
|
|
_changes.fire({});
|
|
}
|
|
|
|
void MessageVideo::readSource(
|
|
const QString &path,
|
|
const QByteArray &content) {
|
|
_reading = true;
|
|
const auto done = crl::guard(this, [=](
|
|
Storage::PhotoEditorMedia &&media) {
|
|
_reading = false;
|
|
_loader = nullptr;
|
|
if (!media.video()) {
|
|
fail();
|
|
return;
|
|
}
|
|
_source = std::make_shared<VideoClipSource>(VideoClipSource{
|
|
.path = std::move(media.videoPath),
|
|
.content = std::move(media.videoContent),
|
|
.thumbnail = std::move(media.image),
|
|
.duration = media.videoDuration,
|
|
.hasAudio = media.videoHasAudio,
|
|
});
|
|
_changes.fire({});
|
|
});
|
|
Storage::ReadPhotoEditorMediaAsync(path, content, done);
|
|
}
|
|
|
|
void MessageVideo::fail() {
|
|
_failed = true;
|
|
_loader = nullptr;
|
|
_changes.fire({});
|
|
}
|
|
|
|
} // namespace Editor
|