Commit 92673c25 authored by Andreas Cord-Landwehr's avatar Andreas Cord-Landwehr
Browse files

Implement sound download via trans

Right now no wikidata support is available yet, so fall back to
computer generated outputs.
parent 39354287
/*
SPDX-FileCopyrightText: 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net>
SPDX-FileCopyrightText: 2021 Andreas Cord-Landwehr <cordlandwehr@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "audiowidget.h"
#include <KEduVocDocument>
#include <KEduVocExpression>
#include <QDebug>
#include <QMediaPlayer>
......@@ -16,21 +16,37 @@ AudioWidget::AudioWidget(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
m_currentTranslation = -1;
m_player = 0;
connect(audioUrlRequester, &KUrlRequester::textChanged, this, &AudioWidget::slotAudioFileChanged);
connect(playButton, &QPushButton::clicked, this, &AudioWidget::playAudio);
// connect(recordButton, SIGNAL(clicked()), SLOT(recordAudio()));
connect(playButton, &QPushButton::clicked, this, &AudioWidget::startPlayback);
connect(recordButton, &QPushButton::clicked, this, &AudioWidget::startRecordAudio);
connect(downloadButton, &QPushButton::clicked, this, &AudioWidget::downloadWebserviceAudio);
connect(&m_player, &QMediaPlayer::stateChanged, this, &AudioWidget::slotPlaybackFinished);
connect(&m_webserviceDownloadWatcher, &QFutureWatcher<bool>::finished, this, &AudioWidget::handleDownloadWebserviceFinished);
playButton->setEnabled(false);
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
recordButton->setVisible(false);
// recordButton->setEnabled(false);
// recordButton->setIcon(QIcon::fromTheme("media-record"));
recordButton->setVisible(false); // TODO enable when recorder is implemented
recordButton->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
downloadButton->setEnabled(m_translateShell.isTranslateShellAvailable());
audioUrlRequester->setEnabled(false);
}
void AudioWidget::setDocument(KEduVocDocument *doc)
{
m_doc = doc;
}
QString AudioWidget::defaultOutputDirectory() const
{
if (m_doc) {
return m_doc->url().path().remove(".kvtml") + "/media";
} else {
return QString();
}
}
void AudioWidget::setTranslation(KEduVocExpression *entry, int translation)
{
m_currentTranslation = translation;
......@@ -43,13 +59,13 @@ void AudioWidget::setTranslation(KEduVocExpression *entry, int translation)
} else {
recordButton->setEnabled(false);
audioUrlRequester->setEnabled(false);
if (m_player) {
if (m_player->state() == QMediaPlayer::PlayingState) {
playButton->setEnabled(true);
} else {
playButton->setEnabled(false);
}
if (m_player.state() == QMediaPlayer::PlayingState) {
playButton->setEnabled(true);
} else {
playButton->setEnabled(false);
}
audioUrlRequester->clear();
}
}
......@@ -62,35 +78,78 @@ void AudioWidget::slotAudioFileChanged(const QString &url)
playButton->setEnabled(!url.isEmpty());
}
void AudioWidget::playAudio()
void AudioWidget::startPlayback()
{
QUrl soundFile = m_entry->translation(m_currentTranslation)->soundUrl();
if (!m_player) {
m_player = new QMediaPlayer(this);
connect(m_player, SIGNAL(finished()), SLOT(slotPlaybackFinished()));
} else {
if (m_player->state() == QMediaPlayer::PlayingState) {
m_player->stop();
slotPlaybackFinished();
}
if (m_player.state() == QMediaPlayer::PlayingState) {
m_player.stop();
}
m_player->setMedia(soundFile);
m_player->setVolume(50);
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop")));
m_player->play();
m_player.setMedia(soundFile);
m_player.play();
}
/*
void AudioWidget::recordAudio()
void AudioWidget::startRecordAudio()
{
/// FIXME: When Phonon gains the ability to record sound, implement me :)
}
*/
void AudioWidget::slotPlaybackFinished()
void AudioWidget::slotPlaybackFinished(QMediaPlayer::State state)
{
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
playButton->setEnabled(!audioUrlRequester->url().isEmpty());
switch (state) {
case QMediaPlayer::StoppedState:
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
playButton->setEnabled(!audioUrlRequester->url().isEmpty());
break;
case QMediaPlayer::PlayingState:
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop")));
playButton->setEnabled(false);
break;
case QMediaPlayer::PausedState:
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
playButton->setEnabled(!audioUrlRequester->url().isEmpty());
break;
}
}
void AudioWidget::downloadWebserviceAudio()
{
if (!m_entry || !m_doc) {
return;
}
QDir dir(defaultOutputDirectory());
if (!dir.exists()) {
dir.mkpath(dir.path());
}
QString soundFileName = m_entry->translation(m_currentTranslation)->soundUrl().toLocalFile();
if (soundFileName.isEmpty()) {
const QString fileName = m_entry->translation(m_currentTranslation)->text().replace(QRegExp("[^a-zA-Z\\s]"), QString());
int index{0};
do {
soundFileName = dir.path() + '/' + fileName + '_' + QString::number(index) + ".ts";
++index;
qDebug() << "test" << soundFileName;
} while (QFile::exists(soundFileName));
m_entry->translation(m_currentTranslation)->setSoundUrl(QUrl::fromLocalFile(soundFileName));
}
QFuture<bool> result = TranslateShellAdapter::downloadSoundFile(m_entry->translation(m_currentTranslation)->text(),
m_doc->identifier(m_currentTranslation).locale(),
soundFileName);
m_webserviceDownloadWatcher.setFuture(result);
}
void AudioWidget::handleDownloadWebserviceFinished()
{
if (!m_entry) {
return;
}
if (QFile::exists(m_entry->translation(m_currentTranslation)->soundUrl().toLocalFile())) {
playButton->setEnabled(true);
playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
audioUrlRequester->setUrl(m_entry->translation(m_currentTranslation)->soundUrl());
}
}
......@@ -6,10 +6,12 @@
#ifndef AUDIOWIDGET_H
#define AUDIOWIDGET_H
#include "translateshelladapter.h"
#include "ui_audiowidget.h"
#include <QFutureWatcher>
#include <QMediaPlayer>
class QMediaPlayer;
class KEduVocDocument;
class KEduVocExpression;
namespace Editor
......@@ -18,7 +20,8 @@ class AudioWidget : public QWidget, public Ui::AudioWidget
{
Q_OBJECT
public:
explicit AudioWidget(QWidget *parent = 0);
explicit AudioWidget(QWidget *parent = nullptr);
void setDocument(KEduVocDocument *doc);
public slots:
/**
......@@ -30,17 +33,23 @@ public slots:
private slots:
void slotAudioFileChanged(const QString &url);
void playAudio();
// void recordAudio();
void slotPlaybackFinished();
void startPlayback();
void slotPlaybackFinished(QMediaPlayer::State state);
void startRecordAudio();
void downloadWebserviceAudio();
void handleDownloadWebserviceFinished();
private:
QString defaultOutputDirectory() const;
/// Column in the document - corresponds to the language (-KV_COL_TRANS)
int m_currentTranslation;
int m_currentTranslation{-1};
/// Selection in the doc - if more than one row is selected behavior is different
KEduVocExpression *m_entry;
QMediaPlayer *m_player; ///< media object for the files
KEduVocExpression *m_entry{nullptr};
QMediaPlayer m_player; ///< media object for the files
TranslateShellAdapter m_translateShell;
KEduVocDocument *m_doc{nullptr};
QFutureWatcher<bool> m_webserviceDownloadWatcher;
};
}
......
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AudioWidget</class>
<widget class="QWidget" name="AudioWidget" >
<property name="geometry" >
<widget class="QWidget" name="AudioWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
......@@ -9,29 +10,39 @@
<height>137</height>
</rect>
</property>
<layout class="QVBoxLayout" >
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout" >
<layout class="QHBoxLayout">
<item>
<widget class="QPushButton" name="playButton" >
<property name="text" >
<widget class="QPushButton" name="playButton">
<property name="text">
<string>Play</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="recordButton" >
<property name="text" >
<widget class="QPushButton" name="recordButton">
<property name="text">
<string>Record</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="downloadButton">
<property name="toolTip">
<string>Download audio file with webservice</string>
</property>
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
......@@ -42,14 +53,14 @@
</layout>
</item>
<item>
<widget class="KUrlRequester" name="audioUrlRequester" />
<widget class="KUrlRequester" name="audioUrlRequester"/>
</item>
<item>
<spacer>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
......@@ -64,6 +75,7 @@
<class>KUrlRequester</class>
<extends>QFrame</extends>
<header>kurlrequester.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
......
......@@ -107,6 +107,7 @@ void EditorWindow::updateDocument(const std::shared_ptr<KEduVocDocument> &doc)
m_synonymWidget->setDocument(doc.get());
m_antonymWidget->setDocument(doc.get());
m_falseFriendWidget->setDocument(doc.get());
m_audioWidget->setDocument(doc.get());
if (!m_mainWindow->parleyDocument()->document()) {
return;
......@@ -309,16 +310,16 @@ void EditorWindow::initDockWidgets()
// Sound
QDockWidget *audioDock = new QDockWidget(i18n("Sound"), this);
audioDock->setObjectName(QStringLiteral("AudioDock"));
AudioWidget *audioWidget = new AudioWidget(this);
m_audioWidget = new AudioWidget(this);
QScrollArea *audioScrollArea = new QScrollArea(this);
audioScrollArea->setWidgetResizable(true);
audioScrollArea->setWidget(audioWidget);
audioScrollArea->setWidget(m_audioWidget);
audioDock->setWidget(audioScrollArea);
addDockWidget(Qt::RightDockWidgetArea, audioDock);
m_dockWidgets.append(audioDock);
actionCollection()->addAction(QStringLiteral("show_audio_dock"), audioDock->toggleViewAction());
audioDock->setVisible(false);
connect(m_vocabularyView, &VocabularyView::translationChanged, audioWidget, &AudioWidget::setTranslation);
connect(m_vocabularyView, &VocabularyView::translationChanged, m_audioWidget, &AudioWidget::setTranslation);
tabifyDockWidget(imageDock, audioDock);
// browser
......
......@@ -31,6 +31,7 @@ namespace Editor
class VocabularyView;
class LessonView;
class WordTypeView;
class AudioWidget;
class InflectionWidget;
class ComparisonWidget;
class SummaryWordWidget;
......@@ -135,6 +136,7 @@ private:
SynonymWidget *m_synonymWidget;
SynonymWidget *m_antonymWidget;
SynonymWidget *m_falseFriendWidget;
AudioWidget *m_audioWidget;
/// dock widgets to display lessons, word types, ...
LessonView *m_lessonView;
......
......@@ -51,7 +51,6 @@ TranslateShellAdapter::Translation TranslateShellAdapter::translate(const QStrin
"-show-original-dictionary=n",
"-show-dictionary=y",
"-no-warn"});
qDebug() << "RUNNING" << process.arguments();
process.waitForFinished();
if (process.exitCode() != 0) {
TranslateShellAdapter::Translation translation;
......@@ -62,6 +61,37 @@ TranslateShellAdapter::Translation TranslateShellAdapter::translate(const QStrin
}
}
QFuture<bool> TranslateShellAdapter::downloadSoundFile(const QString &word, const QString &language, const QString &filePath)
{
if (!filePath.endsWith(".ts")) {
qWarning() << "Sound file will have TS format and should have that suffix";
}
if (QFile::exists(filePath)) {
qWarning() << "File already exists, this operation will override the file" << filePath;
}
QDir directory = QFileInfo(filePath).absoluteDir();
if (!directory.exists(directory.absolutePath())) {
qWarning() << "Output directory does not exist, creating it" << directory;
directory.mkpath(directory.absolutePath());
}
QFuture<bool> result = QtConcurrent::run([word, language, filePath]() {
QProcess process;
process.start(QStringLiteral("trans"),
{"-l",
"en", // output language of CLI
"-t",
language,
word,
"-no-translate",
"-download-audio-as",
filePath});
process.waitForFinished();
return process.error() != QProcess::ProcessError::FailedToStart && process.exitCode() == 0;
});
return result;
}
TranslateShellAdapter::Translation TranslateShellAdapter::parseTranslateShellResult(const QString &output)
{
const QStringList lines = output.split('\n');
......
......@@ -74,6 +74,12 @@ public:
*/
static TranslateShellAdapter::Translation translate(const QString &word, const QString &sourceLanguage, const QString &targetLanguage);
/**
* @brief Asynchronous download of sound file for given phrase
*
*/
static QFuture<bool> downloadSoundFile(const QString &word, const QString &language, const QString &filePath);
private:
mutable bool m_translateShellAvailable{false};
};
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment