video: отправка видео-сообщений (FFmpeg пробинг+транскод H.264/AAC MP4)

- src/video/: video_probe/video_get_info/video_transcode_start (FFmpeg, HAVE_FFMPEG)
- chat_core/chat_msg: media_video флаг, chat_core_submit_media_message, транскод перед index
- headless: команда attach с --video
- chatgui: диалог 'Как видео' + прогресс транскода + sendVideoAttachment
- FFmpeg в сборке (configure.ac, Makefile.am, chatgui CMake, build_nix.sh)
- debug: категория DEBUG_CATEGORY_VIDEO
This commit is contained in:
evgeny committed 2026-09-08 00:41:04 +03:00
1 parent c8bd5975ae
commit 1855c61275
19 files changed
+1026 -29

No files matched your search

+64 -7
View File
@@ -10,6 +10,7 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QFileDialog>
#include <QMessageBox>
#include <QKeyEvent>
#include <QFontMetrics>
#include <QScrollBar>
@@ -19,8 +20,14 @@
#include <QDir>
#include <QProgressBar>
#include <QIcon>
#include <QTimer>
#include <QFileInfo>
#include <cmath>
extern "C" {
#include "video/video.h"
}
static QPushButton *makeIconBtn(const QIcon& icon, const QString &tooltip, QWidget *parent) {
auto *btn = new QPushButton(parent);
btn->setIcon(icon);
@@ -85,6 +92,10 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
m_attachPreview->hide();
mainLayout->addWidget(m_attachPreview);
m_videoProgressTimer = new QTimer(this);
m_videoProgressTimer->setInterval(150);
connect(m_videoProgressTimer, &QTimer::timeout, this, &InputBar::onVideoProgressTick);
auto *layout = new QHBoxLayout();
layout->setSpacing(4);
@@ -225,13 +236,22 @@ void InputBar::onSendClicked() {
if (!hasAttach && text.isEmpty()) return;
if (hasAttach) {
GUI_INFO("send: file=%s name=%s", qPrintable(m_pendingAttachSrc), qPrintable(m_pendingAttachName));
emit sendFileAttachment(m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName);
if (m_pendingAttachAsVideo) {
GUI_INFO("send: video=%s name=%s", qPrintable(m_pendingAttachSrc), qPrintable(m_pendingAttachName));
emit sendVideoAttachment(m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName);
m_attachPreviewLabel->setText(QString::fromUtf8("\xF0\x9F\x8E\xAC Перекодирование… 0%"));
m_attachPreview->show();
m_videoProgressTimer->start();
} else {
GUI_INFO("send: file=%s name=%s", qPrintable(m_pendingAttachSrc), qPrintable(m_pendingAttachName));
emit sendFileAttachment(m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName);
m_attachPreview->hide();
}
m_pendingAttachSrc.clear();
m_pendingAttachDest.clear();
m_pendingAttachName.clear();
m_pendingAttachSize = 0;
m_attachPreview->hide();
m_pendingAttachAsVideo = false;
}
if (!text.isEmpty()) {
emit sendMessage(text);
@@ -326,21 +346,46 @@ void InputBar::onAttachClicked() {
QDateTime now = QDateTime::currentDateTime();
QString ts = now.toString("yyyyMMdd-HHmmss");
int rnd = rand() & 0xFFFF;
QString destPath = QString("%1/%2_%3_%4.%5")
.arg(mediaPath, ts).arg(fi.completeBaseName()).arg(rnd, 4, 16, QChar('0')).arg(fi.suffix().isEmpty() ? "bin" : fi.suffix());
bool asVideo = false;
if (video_probe(srcPath.toUtf8().constData())) {
QMessageBox box(this);
box.setWindowTitle(QString::fromUtf8("Отправить видео"));
box.setText(QString::fromUtf8("Файл содержит видео. Как отправить?"));
QPushButton *fileBtn = box.addButton(QString::fromUtf8("Как файл"), QMessageBox::AcceptRole);
QPushButton *videoBtn = box.addButton(QString::fromUtf8("Как видео"), QMessageBox::DestructiveRole);
box.exec();
asVideo = (box.clickedButton() == videoBtn);
if (!box.clickedButton() || (!asVideo && box.clickedButton() != fileBtn)) {
return; /* закрыт без выбора */
}
GUI_INFO("InputBar::onAttachClicked: video detected, mode=%s",
asVideo ? "video" : "file");
}
QString destPath;
if (asVideo) {
destPath = QString("%1/%2_%3_%4.mp4")
.arg(mediaPath, ts).arg(fi.completeBaseName()).arg(rnd, 4, 16, QChar('0'));
} else {
destPath = QString("%1/%2_%3_%4.%5")
.arg(mediaPath, ts).arg(fi.completeBaseName()).arg(rnd, 4, 16, QChar('0'))
.arg(fi.suffix().isEmpty() ? "bin" : fi.suffix());
}
m_pendingAttachSrc = srcPath;
m_pendingAttachDest = destPath;
m_pendingAttachName = fi.fileName();
m_pendingAttachSize = fi.size();
m_pendingAttachAsVideo = asVideo;
QString labelText = QString::fromUtf8("\xF0\x9F\x93\x8E %1 (%2)")
.arg(fi.fileName(), formatFileSize(fi.size()));
m_attachPreviewLabel->setText(labelText);
m_attachPreview->show();
GUI_INFO("InputBar::onAttachClicked: pending %s size=%lld",
qPrintable(srcPath), (long long)fi.size());
GUI_INFO("InputBar::onAttachClicked: pending %s size=%lld video=%d",
qPrintable(srcPath), (long long)fi.size(), asVideo ? 1 : 0);
}
void InputBar::clearPendingAttachment() {
@@ -348,9 +393,21 @@ void InputBar::clearPendingAttachment() {
m_pendingAttachDest.clear();
m_pendingAttachName.clear();
m_pendingAttachSize = 0;
m_pendingAttachAsVideo = false;
m_videoProgressTimer->stop();
m_attachPreview->hide();
}
void InputBar::onVideoProgressTick() {
int p = video_transcode_get_progress();
if (p < 0) {
m_videoProgressTimer->stop();
m_attachPreview->hide();
return;
}
m_attachPreviewLabel->setText(QString::fromUtf8("\xF0\x9F\x8E\xAC Перекодирование… %1%").arg(p / 10));
}
void InputBar::resizeEvent(QResizeEvent* event) {
QWidget::resizeEvent(event);
if (m_recordOverlay) {