@ -13,6 +13,8 @@
# include <QPainter>
# include <QPainter>
# include <QPixmap>
# include <QPixmap>
# include <QDateTime>
# include <QDateTime>
# include <QJsonDocument>
# include <QJsonObject>
# include <QFile>
# include <QFile>
# include <QScrollBar>
# include <QScrollBar>
# include <QApplication>
# include <QApplication>
@ -95,23 +97,26 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
if ( ct = = " audio/opus " ) {
if ( ct = = " audio/opus " ) {
QString raw = QString : : fromUtf8 ( data ) ;
QString raw = QString : : fromUtf8 ( data ) ;
int pipe1 = raw . indexOf ( ' | ' ) ;
if ( raw . startsWith ( ' { ' ) ) return ;
if ( pipe1 < 0 & & raw . startsWith ( ' { ' ) ) return ;
int pipe2 = raw . indexOf ( ' | ' , pipe1 + 1 ) ;
/* unified format: wf={base64};dur={ms};|{file_size}|{block_size}|{num_blocks}|{sigs} */
QString fname = pipe1 > = 0 ? raw . left ( pipe1 ) : raw ;
QString dur = pipe2 > = 0 ? raw . mid ( pipe1 + 1 , pipe2 - pipe1 - 1 ) : raw . mid ( pipe1 + 1 ) ;
QString rest = pipe2 > = 0 ? raw . mid ( pipe2 + 1 ) : QString ( ) ;
/* find end of wf (comma-separated floats), then file metadata */
int afterWf = - 1 ;
int pipeCount = 0 ;
for ( int i = 0 ; i < rest . size ( ) ; i + + ) {
if ( rest [ i ] = = ' | ' ) { pipeCount + + ; if ( pipeCount > = 4 ) { afterWf = i ; break ; } }
}
QString wfData ;
QString wfData ;
if ( afterWf > = 0 ) {
int durMs = 0 ;
wfData = rest . left ( afterWf ) ;
QString meta = rest . mid ( afterWf + 1 ) ;
if ( raw . startsWith ( " wf= " ) ) {
int durPos = raw . indexOf ( " ;dur= " , 3 ) ;
int durEnd = raw . indexOf ( ' ; ' , durPos + 5 ) ;
if ( durPos > 0 & & durEnd > 0 ) {
wfData = raw . mid ( 3 , durPos - 3 ) ; /* 100 base64 chars */
durMs = raw . mid ( durPos + 5 , durEnd - durPos - 5 ) . toInt ( ) ;
}
}
item - > setData ( QString : : number ( durMs / 1000.0 , ' f ' , 1 ) + " s " , MsgVoiceDurationRole ) ;
/* block metadata after last ; */
int pipePos = raw . indexOf ( ' | ' ) ;
if ( pipePos > 0 ) {
QString meta = raw . mid ( pipePos + 1 ) ;
QStringList parts = meta . split ( ' | ' ) ;
QStringList parts = meta . split ( ' | ' ) ;
if ( parts . size ( ) > = 4 ) {
if ( parts . size ( ) > = 4 ) {
item - > setData ( parts [ 0 ] . toULongLong ( ) , MsgVoiceFileSizeRole ) ;
item - > setData ( parts [ 0 ] . toULongLong ( ) , MsgVoiceFileSizeRole ) ;
@ -119,25 +124,32 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
item - > setData ( parts [ 2 ] . toUInt ( ) , MsgVoiceNumBlocksRole ) ;
item - > setData ( parts [ 2 ] . toUInt ( ) , MsgVoiceNumBlocksRole ) ;
item - > setData ( parts [ 3 ] , MsgVoiceSigsRole ) ;
item - > setData ( parts [ 3 ] , MsgVoiceSigsRole ) ;
}
}
} else {
wfData = rest ;
}
}
if ( ! dur . isEmpty ( ) ) item - > setData ( dur + " s " , MsgVoiceDurationRole ) ;
/* file path from local_attrs */
if ( ! fname . isEmpty ( ) ) {
QJsonObject la = QJsonDocument : : fromJson ( localAttrs ) . object ( ) ;
QString fullPath = mediaDirBase + " / " + channelId + " / " + fname ;
QString fp = la . value ( " fp " ) . toString ( ) ;
if ( ! fp . isEmpty ( ) ) {
QString fullPath = mediaDirBase + " / " + channelId + " / " + fp ;
item - > setData ( fullPath , MsgVoiceFileRole ) ;
item - > setData ( fullPath , MsgVoiceFileRole ) ;
DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " setVoiceMessageRoles: voice file=%s dur=%s wf=%d exists=%d " ,
qPrintable ( fullPath ) , qPrintable ( dur ) ,
wfData . split ( ' , ' ) . size ( ) , QFile : : exists ( fullPath ) ) ;
} else {
DEBUG_WARN ( DEBUG_CATEGORY_DEBUG , " setVoiceMessageRoles: empty filename, raw='%s' " , qPrintable ( raw ) ) ;
}
}
/* decode base64 waveform to normalized 0..1 float list */
if ( ! wfData . isEmpty ( ) ) {
if ( ! wfData . isEmpty ( ) ) {
QList < QVariant > wf ;
QList < QVariant > wf ;
for ( const auto & s : wfData . split ( ' , ' ) )
for ( int i = 0 ; i < wfData . size ( ) ; i + + ) {
wf . append ( s . toDouble ( ) ) ;
QChar c = wfData [ i ] ;
int idx = 0 ;
if ( c > = ' A ' & & c < = ' Z ' ) idx = c . unicode ( ) - ' A ' ;
else if ( c > = ' a ' & & c < = ' z ' ) idx = c . unicode ( ) - ' a ' + 26 ;
else if ( c > = ' 0 ' & & c < = ' 9 ' ) idx = c . unicode ( ) - ' 0 ' + 52 ;
else if ( c = = ' + ' ) idx = 62 ;
else if ( c = = ' / ' ) idx = 63 ;
wf . append ( ( float ) idx / 63.0f ) ;
}
item - > setData ( wf , MsgVoiceWaveformRole ) ;
item - > setData ( wf , MsgVoiceWaveformRole ) ;
DEBUG_DEBUG ( DEBUG_CATEGORY_DEBUG , " setVoiceMessageRoles: voice dur=%dms wf=%d exists=%d " ,
durMs , wf . size ( ) , QFile : : exists ( fp ) ) ;
}
}
}
}
if ( ct = = " application/octet-stream " ) {
if ( ct = = " application/octet-stream " ) {
@ -240,9 +252,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
int durationMs , const QString & wfStr ) {
int durationMs , const QString & wfStr ) {
if ( m_currentChannelId . isEmpty ( ) ) return ;
if ( m_currentChannelId . isEmpty ( ) ) return ;
QString displayName = basename + " . " + ext ;
QByteArray textData = wfStr . toUtf8 ( ) ;
QByteArray textData = ( displayName + " | " +
QString : : number ( durationMs / 1000.0 , ' f ' , 1 ) + " | " + wfStr ) . toUtf8 ( ) ;
auto ts = QDateTime : : currentMSecsSinceEpoch ( ) ;
auto ts = QDateTime : : currentMSecsSinceEpoch ( ) ;
struct chat_msg_submit * req = ( struct chat_msg_submit * )
struct chat_msg_submit * req = ( struct chat_msg_submit * )