Files
qt5-legatus-legacy/Lts/Tools/VideoImport/audio_video_widget.cpp
T
2026-08-13 18:33:10 +02:00

671 lines
19 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Handle Audio/Video output for thp through libvlc
* $Id$
* $HeadURL$
* $Author$
*/
#include "audio_video_widget.h"
#include "audio_video_widget_p.h"
#include "ui_audio_video_widget.h"
#include <QCoreApplication>
#include <QDirIterator>
#include <QFileInfo>
#include <QMutex>
#include <QScreen>
#include <QTimer>
#include <QUrl>
#include <QGuiApplication>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <LtsWidgets/lts_range_slider.h>
#include <QFileDialog>
#include <QProcess>
#include <math.h>
#include <stdio.h>
#include <vlc/libvlc_version.h>
namespace VideoImport
{
static const float s_speedArray[] =
{
1. / 8.,
1. / 4.,
1. / 2.,
1.,
2.,
4.,
8.
};
/**
* AudioVideoWidget::Private
*/
AudioVideoWidget::Private::Private(QFrame *parent)
: m_vlcinstance(nullptr)
, m_mPlayer(nullptr)
, m_mListPlayer(nullptr)
, m_mediaList(nullptr)
, currentSpeedIdx(3)
, m_parent(parent)
, m_timer(new QTimer)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("VideoImport.AudioVideoWidget"))
{
init();
connect(ui->btnSlower, &QToolButton::clicked, this, &Private::onSlower);
connect(ui->btnFaster, &QToolButton::clicked, this, &Private::onFaster);
connect(ui->btn100pct, &QToolButton::clicked, this, &Private::on100pct);
connect(ui->btnPlay, &QToolButton::clicked, this, &Private::onPlay);
connect(ui->btnStop, &QToolButton::clicked, this, &Private::onStop);
connect(ui->btnCut, &QPushButton::clicked, this, &Private::onCut);
connect(ui->sldPosition, &QSlider::valueChanged, this, &Private::onPositionChanged);
connect(ui->sldRange, &LtsWidgets::LtsRangeSlider::maximumValueChanged, this, &Private::onRangeMaximumChanged);
connect(ui->sldRange, &LtsWidgets::LtsRangeSlider::minimumValueChanged, this, &Private::onRangeMinimumChanged);
// connect(ui->sldPosition, &LtsWidgets::LtsRangeSlider::minimumValueChanged, this, &AudioVideoWidget::Private::onPositionChanged);
connect(ui->sldVolume, &QSlider::sliderMoved, this, &Private::onVolumeChanged);
connect(m_timer, &QTimer::timeout, this, &Private::onUpdateGUI);
m_initThread = new AudioVideoInitThread(this);
connect(m_initThread, &AudioVideoInitThread::finished, this, &Private::onVLCInstanceInitializd);
m_initThread->start(QThread::LowPriority);
ui->sldRange->setEnabled(false);
ui->sldPosition->setEnabled(false);
}
AudioVideoWidget::Private::~Private()
{
uninitVLCPlayer();
uninitVLCInstance();
delete ui;
delete m_timer;
}
bool AudioVideoWidget::Private::init()
{
if (!m_parent)
return false;
ui = new Ui::audioVideoWidget();
ui->setupUi(m_parent);
QPixmap pm(10, 10);
pm.fill(Qt::black);
ui->outputWidget->setPixmap(pm);
ui->outputWidget->setScaledContents(true);
ui->sldRange->setMaximum(1000);
ui->sldRange->setMaximumValue(1000);
ui->sldPosition->setMaximum(1000);
ui->sldVolume->setMaximum(100);
return true;
}
void AudioVideoWidget::Private::onVLCInstanceInitializd()
{
m_initThread->deleteLater();
m_initThread = nullptr;
}
void AudioVideoWidget::Private::setAV(bool isAudio)
{
if (isAudio)
{
delete ui->btn100pct;
delete ui->btnFaster;
delete ui->btnSlower;
delete ui->outputWidget;
ui->outputWidget = nullptr;
}
else
{
// delete ui->verticalSpacer; ui->verticalSpacer = 0;
delete ui->lblAudio;
}
}
static QMutex *serializeVLCInit()
{
static QMutex s_serializeVLCInit;
return &s_serializeVLCInit;
}
bool AudioVideoWidget::Private::initVLCInstance(bool bInInitThread)
{
QMutexLocker l(serializeVLCInit());
if (!bInInitThread && m_initThread != nullptr)
{
QLabel myLabel;
myLabel.setWindowTitle(tr("Loading VLC player..."));
myLabel.setText(tr("Waiting until VLC player is loaded"));
QRect r = QGuiApplication::primaryScreen()->availableGeometry();
myLabel.show();
myLabel.move(r.width() / 2 - myLabel.width() / 2, r.height() / 2 - myLabel.height() / 2);
myLabel.setWindowIcon(QIcon(QLatin1String(":icons/16x16/rde.ico")));
while (m_initThread)
QCoreApplication::processEvents();
}
if (m_vlcinstance != nullptr)
return true;
//preparation of the vlc command
const char *vlc_argv[20];
int iCount = 0;
vlc_argv[iCount++] = "-I";
vlc_argv[iCount++] = "dummy"; // Don't use any interface
// vlc_argv[iCount++] = "--ignore-config"; // Don't use VLC's config
#ifdef LTS_DEBUG
vlc_argv[iCount++] = "--extraintf=logger"; // log anything
vlc_argv[iCount++] = "--verbose=2"; // be much more verbose then normal for debugging purpose
#else
vlc_argv[iCount++] = "--quiet";
#endif
if (bInInitThread)
vlc_argv[iCount++] = "--no-media-library";
vlc_argv[iCount++] = "--";
vlc_argv[iCount] = nullptr;
//Initialize an instance of vlc
//create a new libvlc instance
m_vlcinstance = libvlc_new(iCount, vlc_argv); //tricky calculation of the char space used
if (!m_vlcinstance)
ERROR_LOG(m_logger) << "Error initializing libVLC - unable to play audio or video";
return (m_vlcinstance != nullptr);
}
bool AudioVideoWidget::Private::uninitVLCInstance()
{
QMutexLocker l(serializeVLCInit());
if (!uninitVLCPlayer())
return false;
if (m_vlcinstance)
{
libvlc_release(m_vlcinstance);
m_vlcinstance = nullptr;
}
return true;
}
bool AudioVideoWidget::Private::initVLCPlayer()
{
DEBUG_LOG(m_logger) << "Initializing libVLC";
if (m_mPlayer)
{
if (!ui)
return false;
ui->sldVolume->setValue(libvlc_audio_get_volume(m_mPlayer));
return true;
}
initVLCInstance();
//Initialize an instance of vlc
//Create a media player playing environement
m_mPlayer = libvlc_media_player_new(m_vlcinstance);
m_mListPlayer = libvlc_media_list_player_new(m_vlcinstance);
m_mediaList = libvlc_media_list_new(m_vlcinstance);
libvlc_media_list_player_set_media_player(m_mListPlayer, m_mPlayer);
libvlc_media_list_player_set_media_list(m_mListPlayer, m_mediaList);
ui->sldVolume->setValue(libvlc_audio_get_volume(m_mPlayer));
return true;
}
bool AudioVideoWidget::Private::uninitVLCPlayer()
{
DEBUG_LOG(m_logger) << "Uninitializing libVLC";
if (m_mListPlayer)
{
/* Stop playing */
libvlc_media_list_player_stop(m_mListPlayer);
/* Free the media_player */
libvlc_media_list_player_release(m_mListPlayer);
m_mListPlayer = nullptr;
}
if (m_mediaList)
{
clearMediaList();
libvlc_media_list_release(m_mediaList);
m_mediaList = nullptr;
}
if (m_mPlayer)
{
/* Stop playing */
libvlc_media_player_stop(m_mPlayer);
/* Free the media_player */
libvlc_media_player_release(m_mPlayer);
m_mPlayer = nullptr;
}
return true;
}
void AudioVideoWidget::Private::clearMediaList()
{
DEBUG_LOG(m_logger) << "Clearing current playlist";
if (!m_mediaList)
return;
libvlc_media_list_lock(m_mediaList);
const int cnt = libvlc_media_list_count(m_mediaList);
for (int i = cnt - 1; i >= 0; --i)
libvlc_media_list_remove_index(m_mediaList, i);
libvlc_media_list_unlock(m_mediaList);
}
bool AudioVideoWidget::Private::playVLC(const QUrl &url)
{
if (!ui)
return false;
DEBUG_LOG(m_logger) << "Try to play " << url.toString();
const QString path = url.toLocalFile();
if (path.isEmpty())
return false;
clearMediaList();
libvlc_media_list_lock(m_mediaList);
QFileInfo fi(path);
DEBUG_LOG(m_logger) << "path: " << path << " isFile: " << fi.isFile();
if (fi.isFile())
{
const QByteArray pNative = QDir::toNativeSeparators(path).toLocal8Bit();
libvlc_media_t *media = libvlc_media_new_path(m_vlcinstance, pNative.data());
if (!media)
{
ERROR_LOG(m_logger) << "Error setting media to '" << pNative << "'";
return false;
}
libvlc_media_list_add_media(m_mediaList, media);
// Todo: streams is always 0, although media was parsed. Why?
// If frames can be obtained: Both sliders should be as large as number of frames.
// libvlc_media_parse(media);
// if (libvlc_media_is_parsed(media))
// {
// libvlc_media_track_t **tracks;
// double framerate = 1;
// unsigned streams = libvlc_media_tracks_get(media, &tracks);
// for (unsigned i = 0; i < streams; ++i)
// {
// libvlc_video_track_t *video = tracks[i]->video;
// if (video)
// framerate = 1. * video->i_frame_rate_num / video->i_frame_rate_den;
// }
// libvlc_media_tracks_release(tracks, streams);
// m_frames = floor(1000. * libvlc_media_get_duration(media) * framerate);
// }
// else {
// m_frames = 1000;
// }
libvlc_media_release(media); // libvlc_media_new_location(), ref-counting!
}
else
{
ERROR_LOG(m_logger) << "Unknown media given: " << path;
libvlc_media_list_unlock(m_mediaList);
return false;
}
libvlc_media_list_unlock(m_mediaList);
if (ui->outputWidget)
{
#if defined(Q_OS_WIN)
libvlc_media_player_set_hwnd(m_mPlayer, (HWND)ui->outputWidget->winId());
#elif defined(Q_OS_MAC)
libvlc_media_player_set_agl(m_mPlayer, ui->outputWidget->winId()) ;
#else //Linux
libvlc_media_player_set_xwindow(m_mPlayer, ui->outputWidget->winId());
#endif
}
ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png")));
ui->btnPlay->setEnabled(true);
// ui->sldPosition->setMaximum(m_frames);
ui->sldPosition->setEnabled(false);
// ui->sldRange->setMaximum(m_frames);
ui->sldRange->setEnabled(false);
ui->btnStop->setEnabled(true);
onRangeMaximumChanged(0); // Initialize Min/Max
currentSpeedIdx = 3;
QTimer::singleShot(0, this, &AudioVideoWidget::Private::onPlay);
return true;
}
bool AudioVideoWidget::Private::stopVLC()
{
DEBUG_LOG(m_logger) << "Stop playing";
QTimer::singleShot(0, this, SLOT(onStop()));
return true;
}
void AudioVideoWidget::Private::setRate()
{
DEBUG_LOG(m_logger) << "Setting speed to " << s_speedArray[currentSpeedIdx];
libvlc_state_t state = getState();
if (state == libvlc_Playing || state == libvlc_Paused)
libvlc_media_player_set_rate(m_mPlayer, s_speedArray[currentSpeedIdx]);
}
libvlc_state_t AudioVideoWidget::Private::getState() const
{
libvlc_state_t s = libvlc_media_list_player_get_state(m_mListPlayer);
static libvlc_state_t lastState = libvlc_NothingSpecial;
if (s == lastState)
return s;
lastState = s;
if (s == libvlc_Error)
ERROR_LOG(m_logger) << "libVLC error: " << libvlc_errmsg();
return s;
}
void AudioVideoWidget::Private::onSlower()
{
if (currentSpeedIdx == 0)
return;
--currentSpeedIdx;
setRate();
onUpdateGUI();
}
void AudioVideoWidget::Private::onFaster()
{
if (currentSpeedIdx == 6)
return;
++currentSpeedIdx;
setRate();
onUpdateGUI();
}
void AudioVideoWidget::Private::on100pct()
{
currentSpeedIdx = 3;
setRate();
onUpdateGUI();
}
void AudioVideoWidget::Private::onPlay()
{
if (libvlc_media_list_player_is_playing(m_mListPlayer))
{
DEBUG_LOG(m_logger) << "Pause playing";
libvlc_media_list_player_pause(m_mListPlayer);
ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png")));
m_timer->stop();
onUpdateGUIWithState(libvlc_Paused);
}
else
{
DEBUG_LOG(m_logger) << "Start playing";
ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-pause.png")));
ui->sldPosition->setEnabled(true);
ui->sldRange->setEnabled(true);
libvlc_media_list_player_play(m_mListPlayer);
setRate();
m_timer->start(100);
onUpdateGUIWithState(libvlc_Playing);
}
}
void AudioVideoWidget::Private::onStop()
{
if (!m_mListPlayer)
return;
libvlc_state_t state = getState();
if (state == libvlc_Stopped)
return;
DEBUG_LOG(m_logger) << "Stop playing";
libvlc_media_list_player_stop(m_mListPlayer);
onUpdateGUI();
}
void AudioVideoWidget::Private::onCut()
{
double maxTimeSecs = libvlc_media_player_get_length(m_mPlayer) / 1000.;
double minimumRelative = ui->sldRange->minimumValue() / 1000.;
double maximumRelative = ui->sldRange->maximumValue() / 1000.;
QString inTime = timeString(maxTimeSecs * minimumRelative, true);
QString duration = timeString(maxTimeSecs * (maximumRelative - minimumRelative), true);
emit signalCut(inTime, duration);
}
void AudioVideoWidget::Private::onPositionChanged(int newPosition)
{
if (newPosition < ui->sldRange->minimumPosition())
{
ui->sldPosition->setValue(ui->sldRange->minimumValue());
return;
}
if (newPosition > ui->sldRange->maximumPosition())
{
ui->sldPosition->setValue(ui->sldRange->maximumValue());
return;
}
libvlc_state_t state = getState();
DEBUG_LOG(m_logger) << "Slider changed - going to new position";
if (state != libvlc_Playing && state != libvlc_Paused)
return;
// Position geht von 0..1, Slider hat 1000 Steps
// Todo: if slider is different than 1000 steps, correct here:
libvlc_media_player_set_position(m_mPlayer, newPosition / 1000.);
onUpdateGUI();
}
void AudioVideoWidget::Private::onRangeMinimumChanged(int newMinimum)
{
if (ui->sldPosition->value() < newMinimum)
ui->sldPosition->setValue(newMinimum);
double maxTimeSecs = libvlc_media_player_get_length(m_mPlayer) / 1000.;
double minimumRelative = newMinimum / 1000.;
double maximumRelative = ui->sldRange->maximumValue() / 1000.;
ui->lblMinMax->setText(timeString(maxTimeSecs * minimumRelative) + QLatin1Literal(" / ") + timeString(maxTimeSecs * maximumRelative));
}
void AudioVideoWidget::Private::onRangeMaximumChanged(int newMaximum)
{
if (ui->sldPosition->value() > newMaximum)
ui->sldPosition->setValue(newMaximum);
double maxTimeSecs = libvlc_media_player_get_length(m_mPlayer) / 1000.;
double minimumRelative = ui->sldRange->minimumValue() / 1000.;
double maximumRelative = newMaximum / 1000.;
ui->lblMinMax->setText(timeString(maxTimeSecs * minimumRelative) + QLatin1Literal(" / ") + timeString(maxTimeSecs * maximumRelative));
}
void AudioVideoWidget::Private::onVolumeChanged(int newVol)
{
libvlc_audio_set_volume(m_mPlayer, newVol);
onUpdateGUI();
}
void AudioVideoWidget::Private::onUpdateGUI()
{
onUpdateGUIWithState(libvlc_NothingSpecial);
}
QString AudioVideoWidget::Private::timeString(double curTime, bool exact)
{
const int minutesCur = floor(curTime / 60.);
const int secondsCur = curTime - 60 * minutesCur;
const int millies = floor((curTime - floor(curTime)) * 1000);
char timeStr[32];
if (exact)
snprintf(timeStr, sizeof(timeStr), "%02d:%02d.%03d", minutesCur, secondsCur, millies);
else
snprintf(timeStr, sizeof(timeStr), "%02d:%02d", minutesCur, secondsCur);
return QString::fromLatin1(timeStr);
}
void AudioVideoWidget::Private::onUpdateGUIWithState(libvlc_state_t overwrite_state)
{
libvlc_state_t state = overwrite_state;
if (state == libvlc_NothingSpecial)
state = getState();
DEBUG_LOG(m_logger) << "onUpdateGUI for state " << state;
if (state == libvlc_Playing || state == libvlc_Paused)
{
double maxTime = libvlc_media_player_get_length(m_mPlayer) / 1000.;
if (maxTime > 0.1)
{
libvlc_media_list_lock(m_mediaList);
const int cnt = libvlc_media_list_count(m_mediaList);
for (int i = cnt - 1; i >= 0; --i)
{
libvlc_media_t *media = libvlc_media_list_item_at_index(m_mediaList, i);
DEBUG_LOG(m_logger) << "Duration: " << libvlc_media_get_duration(media);
libvlc_media_release(media);
}
libvlc_media_list_unlock(m_mediaList);
}
double curTime = libvlc_media_player_get_time(m_mPlayer) / 1000.;
auto curPos = libvlc_media_player_get_position(m_mPlayer);
// Slider ist in 1000 Schritte eingeteilt
ui->sldPosition->setValue(qRound(curPos * 1000));
ui->lblPosition->setText(timeString(curTime) + QLatin1Literal(" / ") + timeString(maxTime));
if (state == libvlc_Playing && ui->sldPosition->value() >= ui->sldRange->maximumValue())
onPlay(); // -> Pause
}
else if (state == libvlc_Stopped || state == libvlc_Ended)
{
// wir können nicht onStop() aufrufen -> rekursion!
bool bStop = true;
if (state == libvlc_Ended)
{
// Timer und VLC *nicht* stoppen da der aktuelle State ggf. genau zwischen den einzelnen
// Video-Schnipseln einer Playlist abgefragt wurde
// --> prüfen, ob es das letzte Video der Playlist war
libvlc_media_t *media = libvlc_media_player_get_media(m_mPlayer);
if (media != nullptr)
{
const int cnt = libvlc_media_list_count(m_mediaList);
if (cnt > 0)
{
libvlc_media_t *media2 = libvlc_media_list_item_at_index(m_mediaList, cnt - 1);
bStop = (media2 == media);
libvlc_media_release(media2);
}
libvlc_media_release(media);
}
if (bStop)
libvlc_media_list_player_stop(m_mListPlayer);
}
if (bStop)
{
ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png")));
ui->sldPosition->setEnabled(false);
ui->sldRange->setEnabled(false);
m_timer->stop();
ui->sldPosition->setValue(ui->sldRange->minimumValue());
ui->lblPosition->setText(QLatin1String("--:-- / --:--"));
}
}
QString speedStr = QStringLiteral("%1x").arg(s_speedArray[currentSpeedIdx], 4, 'f', 2);
ui->lblSpeed->setText(speedStr);
}
/**
* AudioVideoInitThread
*/
AudioVideoInitThread::AudioVideoInitThread(AudioVideoWidget::Private *p)
: m_private(p)
{}
void AudioVideoInitThread::run()
{
m_private->initVLCInstance(true);
}
/**
* AudioVideoWidget
*/
AudioVideoWidget::AudioVideoWidget(QWidget *parent)
: QFrame(parent)
, d(new Private(this))
{
connect(d, &AudioVideoWidget::Private::signalCut, this, &AudioVideoWidget::onCut);
}
void AudioVideoWidget::onCut(const QString &inTime, const QString &duration)
{
const QString localFile = m_url.toLocalFile();
QString extension = localFile.mid(localFile.lastIndexOf(QLatin1Char('.')) + 1);
QUrl outUrl = QFileDialog::getSaveFileUrl(this, tr("Save Video File"), QUrl(), tr("Source File Format (*.%1)").arg(extension));
// ffmpeg -ss [inTime] -t [outTime] -i [m_url] [url]
QString command = tr("ffmpeg -i \"%3\" -ss %1 -t %2 -acodec copy -vcodec copy \"%4\"").arg(inTime, duration, m_url.toLocalFile(), outUrl.toLocalFile());
QProcess::startDetached(command);
}
AudioVideoWidget::~AudioVideoWidget()
{
delete d;
}
bool AudioVideoWidget::play(const QUrl &file)
{
d->initVLCPlayer();
setEnabled(true);
return d->playVLC(file);
}
bool AudioVideoWidget::stop()
{
return d->stopVLC();
}
void AudioVideoWidget::setAV(bool isAudio)
{
d->setAV(isAudio);
}
void AudioVideoWidget::onOpenFile()
{
QString folder = QString::fromLatin1(qgetenv("USER"));
if (folder.isEmpty())
folder = QString::fromLatin1(qgetenv("USERNAME"));
folder.prepend(QLatin1String("/run/media/"));
m_url = QFileDialog::getOpenFileUrl(this, tr("Open Video File"), QUrl::fromLocalFile(folder), tr("Video files (*.*)"));
play(m_url);
}
} // namespace VideoImport