|
|
|
|
@ -26,6 +26,8 @@
|
|
|
|
|
|
|
|
|
|
extern "C" { |
|
|
|
|
#include "video/video.h" |
|
|
|
|
#include "media_async/attachment_send.h" |
|
|
|
|
#include "mem.h" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static bool isImageFile(const QString& suffix) { |
|
|
|
|
@ -183,6 +185,7 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
|
|
|
|
|
"QPushButton { border: none; color: white; font-size: 16px; background: transparent; }" |
|
|
|
|
"QPushButton:hover { color: #fdd; }"); |
|
|
|
|
connect(m_cancelRecBtn, &QPushButton::clicked, this, [this]() { |
|
|
|
|
m_pttActive = false; |
|
|
|
|
if (m_recorder && m_recorder->isRecording()) m_recorder->stopRecording(); |
|
|
|
|
}); |
|
|
|
|
roLayout->addWidget(m_cancelRecBtn); |
|
|
|
|
@ -244,17 +247,25 @@ void InputBar::onSendClicked() {
|
|
|
|
|
if (hasAttach) { |
|
|
|
|
if (m_pendingAttachAsImage) { |
|
|
|
|
GUI_INFO("send: image=%s name=%s", qPrintable(m_pendingAttachSrc), qPrintable(m_pendingAttachName)); |
|
|
|
|
emit sendImageAttachment(m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName); |
|
|
|
|
emit sendImageAttachment(m_channelIdForRecord, m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName); |
|
|
|
|
m_attachPreview->hide(); |
|
|
|
|
} else if (m_pendingAttachAsVideo) { |
|
|
|
|
GUI_INFO("send: video=%s name=%s", qPrintable(m_pendingAttachSrc), qPrintable(m_pendingAttachName)); |
|
|
|
|
emit sendVideoAttachment(m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName); |
|
|
|
|
auto* request = (struct attachment_send_req*)u_calloc(1, sizeof(struct attachment_send_req)); |
|
|
|
|
QByteArray source = m_pendingAttachSrc.toUtf8(), name = (QFileInfo(m_pendingAttachName).completeBaseName() + ".mp4").toUtf8(); |
|
|
|
|
if (!request || source.size() >= (int)sizeof(request->source) || name.size() >= (int)sizeof(request->info.name)) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "InputBar: video request allocation/path failed"); u_free(request); return; |
|
|
|
|
} |
|
|
|
|
request->info.kind = ATTACHMENT_VIDEO; request->transcode = 1; |
|
|
|
|
snprintf(request->source, sizeof(request->source), "%s", source.constData()); |
|
|
|
|
snprintf(request->info.name, sizeof(request->info.name), "%s", name.constData()); |
|
|
|
|
emit submitAttachment(m_channelIdForRecord, request); |
|
|
|
|
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); |
|
|
|
|
emit sendFileAttachment(m_channelIdForRecord, m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName); |
|
|
|
|
m_attachPreview->hide(); |
|
|
|
|
} |
|
|
|
|
m_pendingAttachSrc.clear(); |
|
|
|
|
@ -280,7 +291,7 @@ void InputBar::setRecordingContext(const QString& mediaDirBase, const QString& c
|
|
|
|
|
|
|
|
|
|
void InputBar::setDmMode(bool enabled) { |
|
|
|
|
m_dmMode = enabled; |
|
|
|
|
m_pttBtn->setEnabled(!enabled); |
|
|
|
|
m_pttBtn->setEnabled(true); |
|
|
|
|
m_attachBtn->setEnabled(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -292,7 +303,8 @@ void InputBar::setMediaEnabled(bool enabled) {
|
|
|
|
|
void InputBar::onPttPressed() { |
|
|
|
|
if (!m_recorder || !m_recorder->isInitialized()) return; |
|
|
|
|
m_recorder->startRecording(); |
|
|
|
|
m_pttActive = true; |
|
|
|
|
m_pttActive = m_recorder->isRecording(); |
|
|
|
|
m_recordTarget = m_channelIdForRecord; |
|
|
|
|
m_pttBtn->setStyleSheet(m_pttBtn->styleSheet() + |
|
|
|
|
"QPushButton { background: #e74c3c; border-radius: 4px; }"); |
|
|
|
|
} |
|
|
|
|
@ -311,47 +323,11 @@ void InputBar::onPttReleased() {
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const int16_t* pcm = m_recorder->bufferData(); |
|
|
|
|
size_t pcmSize = m_recorder->bufferSize(); |
|
|
|
|
if (pcmSize == 0) return; |
|
|
|
|
|
|
|
|
|
QString mediaPath = m_mediaDirBase + "/" + m_channelIdForRecord; |
|
|
|
|
VoiceEncoder::ensureMediaDir(mediaPath); |
|
|
|
|
GUI_INFO("InputBar::onPttReleased: mediaDir=%s exists=%d channel=%s", |
|
|
|
|
qPrintable(mediaPath), QDir(mediaPath).exists(), qPrintable(m_channelIdForRecord)); |
|
|
|
|
|
|
|
|
|
QString fileName = VoiceEncoder::generateFileName(); |
|
|
|
|
QString mediaFile = mediaPath + "/" + fileName; |
|
|
|
|
float durationSec = 0; |
|
|
|
|
int frames = VoiceEncoder::encodeToFile(pcm, pcmSize, 48000, 1, mediaFile, durationSec); |
|
|
|
|
if (frames <= 0) { |
|
|
|
|
QFile::remove(mediaFile); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
m_voiceFilePath = mediaFile; |
|
|
|
|
|
|
|
|
|
const auto& wf = m_recorder->waveformLevels(); |
|
|
|
|
QString wfStr = "wf="; |
|
|
|
|
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
|
|
|
|
int N = (int)wf.size(); |
|
|
|
|
for (int i = 0; i < 100; i++) { |
|
|
|
|
int s = i * N / 100, e = (i + 1) * N / 100; |
|
|
|
|
if (s >= N) s = N - 1; |
|
|
|
|
if (e <= s) e = s + 1; |
|
|
|
|
if (e > N) e = N; |
|
|
|
|
float sum = 0; int cnt = 0; |
|
|
|
|
for (int j = s; j < e; j++) { sum += wf[j]; cnt++; } |
|
|
|
|
float v = cnt > 0 ? sum / cnt : 0; |
|
|
|
|
float db = v > 0.00001f ? 20.0f * log10f(v) : -100.0f; |
|
|
|
|
int idx = (int)((db + 60.0f) / 60.0f * 64.0f); |
|
|
|
|
if (idx < 0) idx = 0; if (idx > 63) idx = 63; |
|
|
|
|
wfStr += b64[idx]; |
|
|
|
|
} |
|
|
|
|
wfStr += QString(";dur=%1;").arg(durationMs); |
|
|
|
|
GUI_INFO("InputBar: voice message ready: file=%s dur=%.1fs", |
|
|
|
|
qPrintable(mediaFile), durationSec); |
|
|
|
|
emit sendVoiceMessage(mediaFile, mediaFile, durationMs, wfStr); |
|
|
|
|
auto* request = (struct attachment_send_req*)u_calloc(1, sizeof(struct attachment_send_req)); |
|
|
|
|
if (!request) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "InputBar: voice request allocation failed"); return; } |
|
|
|
|
request->info.kind = ATTACHMENT_VOICE; request->preset = VoiceEncoder::preset(); |
|
|
|
|
if (!m_recorder->takeRecording(request)) { u_free(request); return; } |
|
|
|
|
emit submitAttachment(m_recordTarget, request); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void InputBar::onAttachClicked() { |
|
|
|
|
@ -360,7 +336,7 @@ void InputBar::onAttachClicked() {
|
|
|
|
|
if (srcPath.isEmpty()) return; |
|
|
|
|
|
|
|
|
|
QFileInfo fi(srcPath); |
|
|
|
|
if (m_dmMode) { |
|
|
|
|
if (m_dmMode && !QStringList{"mp4", "mov", "mkv", "avi", "webm", "m4v"}.contains(fi.suffix().toLower())) { |
|
|
|
|
m_pendingAttachSrc = srcPath; |
|
|
|
|
m_pendingAttachDest = srcPath; |
|
|
|
|
m_pendingAttachName = fi.fileName(); |
|
|
|
|
@ -381,9 +357,9 @@ void InputBar::onAttachClicked() {
|
|
|
|
|
QString ts = now.toString("yyyyMMdd-HHmmss"); |
|
|
|
|
int rnd = rand() & 0xFFFF; |
|
|
|
|
|
|
|
|
|
bool asImage = isImageFile(fi.suffix()); |
|
|
|
|
bool asImage = !m_dmMode && isImageFile(fi.suffix()); |
|
|
|
|
bool asVideo = false; |
|
|
|
|
if (!asImage && video_probe(srcPath.toUtf8().constData())) { |
|
|
|
|
if (!asImage && QStringList{"mp4", "mov", "mkv", "avi", "webm", "m4v"}.contains(fi.suffix().toLower())) { |
|
|
|
|
QMessageBox box(this); |
|
|
|
|
box.setWindowTitle(QString::fromUtf8("Отправить видео")); |
|
|
|
|
box.setText(QString::fromUtf8("Файл содержит видео. Как отправить?")); |
|
|
|
|
|