瀏覽代碼

cmake-plugin: 添加基于源代码构建的 TTS 语音合成插件示例,运行时需要指定引擎

zinface 2 年之前
父節點
當前提交
e50b7d3ef0

+ 4 - 0
src/plugin/CMakeLists.txt

@@ -30,4 +30,8 @@ if(USE_NOTEPAD_PLUGIN)
         # 一个简单的 Linux 终端插件,但依赖于外部的 lxqt-qterminal
         add_subdirectory(linux-terminal-plugin)
     endif(UNIX AND NOT APPLE)
+    
+    # TTS 语音合成
+    add_subdirectory(TTS-plugin)
+
 endif(USE_NOTEPAD_PLUGIN)

+ 91 - 0
src/plugin/TTS-plugin/CMakeLists.txt

@@ -0,0 +1,91 @@
+# TTS-plugin/CMakeLists.txt
+    # 用于在源代码中构建一个插件的模板
+    
+# 如果不是在源代码中构建插件,请不要使用这个部分
+    # 如果想在源代码中添加更多内置模板
+    # 请替换 TTS-plugin 为你的插件名称
+    # TTS-plugin -> your plugin name
+
+set(LOCAL_PLUGIN_NAME "TTS-plugin")
+
+# TTS-plugin 核心构建
+# 在模块化构建中,这个部分代表着构建 TTS-plugin 插件
+# 1. 默认构建时产出的目标为 TTS-plugin
+# 2. 在此处可对 TTS-plugin 目标进行详细的构建计划
+
+if(TRUE)
+    # 准备构建 TTS-plugin 主程序扩展
+    spark_file_glob(LocalSources
+        ./*.h ./*.cpp ./*.ui
+    )
+    spark_add_library(${LOCAL_PLUGIN_NAME} SHARED ${LocalSources})
+    target_include_directories(${LOCAL_PLUGIN_NAME} PRIVATE
+        ${PROJECT_SOURCE_DIR}/src
+        ${PROJECT_SOURCE_DIR}/src/cceditor
+
+        ${PROJECT_SOURCE_DIR}/src/qscint/src
+        ${PROJECT_SOURCE_DIR}/src/qscint/src/Qsci
+        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/src
+        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/include
+        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/lexlib
+        ${PROJECT_SOURCE_DIR}/src/qscint/scintilla/boostregex
+    )
+    # target_link_libraries(${LOCAL_PLUGIN_NAME} QSci)
+    target_link_QSci(${LOCAL_PLUGIN_NAME})
+    if(USE_QT6)
+        # target_link_qt6_Core5Compat(${LOCAL_PLUGIN_NAME})   # 兼容性: Qt6 可使用 Core5Compat 少量更改 Qt5 部分
+        # target_link_qt6_PrintSupport(${LOCAL_PLUGIN_NAME})
+        # target_link_qt6_XmlPatterns(${LOCAL_PLUGIN_NAME}) # Bug 初期配置时无此依赖要求
+    else()
+        # target_link_qt5_PrintSupport(${LOCAL_PLUGIN_NAME})
+        # target_link_qt5_XmlPatterns(${LOCAL_PLUGIN_NAME})
+    endif(USE_QT6)
+
+    # 确保生成到 Notepad-- 的相对 plugin 目录下
+    set_target_properties(${LOCAL_PLUGIN_NAME}
+        PROPERTIES
+            RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>/plugin
+            LIBRARY_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>/plugin
+            ARCHIVE_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${PROJECT_NAME}>/plugin)
+
+    # 控制是否安装到 bin/plugin 而各种配方之下,每个位置或许都不一样(特别是 Linux)
+    # install(TARGETS ${LOCAL_PLUGIN_NAME} DESTINATION bin/plugin)
+endif(TRUE)
+
+
+# ----------------- TTS-plugin 构建宏支持相关  ----------------- #
+
+if(WIN32 AND NOTEPAD_BUILD_BY_SHARED)
+    # 在 Windows 中构建时,需要关注此库的构建形式,QScintilla 应该以何种方式编译
+    target_compile_definitions(${LOCAL_PLUGIN_NAME} 
+        PRIVATE 
+            NOTEPAD_PLUGIN_MANAGER
+            QSCINTILLA_DLL  # 目前在 Windows 中使用 QSci 库时应该采用 Q_DECL_IMPORT
+                            # 控制 QSCINTILLA_EXPORT 符号应为 Q_DECL_IMPORT
+    )
+else()
+    # 在 Windows 中构建时,需要关注此库的构建形式,QScintilla 应该以何种方式编译
+    target_compile_definitions(${LOCAL_PLUGIN_NAME} 
+    PRIVATE 
+        NOTEPAD_PLUGIN_MANAGER
+        # QSCINTILLA_DLL # 目前在 Windows 中使用 QSci 库时应该采用 Q_DECL_IMPORT
+                         # 控制 QSCINTILLA_EXPORT 符号应为 Q_DECL_IMPORT
+    )
+endif(WIN32 AND NOTEPAD_BUILD_BY_SHARED)
+
+if(UNIX)
+    # 默认在 Unix/Linux 中仅需要定义一个内部插件宏管理器
+    target_compile_definitions(${LOCAL_PLUGIN_NAME} 
+        PRIVATE 
+            NOTEPAD_PLUGIN_MANAGER
+    )
+endif(UNIX)
+
+# ----------------- TTS-plugin 构建的外部项目依赖 ----------------- #
+
+# include(ExternalProject)
+
+# ExternalProject_Add(
+#     GIT_REPOSITORY
+#         https://gitee.com/mirrors/ekho.git
+# )

+ 258 - 0
src/plugin/TTS-plugin/ndd_plugin_implement.cpp

@@ -0,0 +1,258 @@
+#include "ndd_plugin_implement.h"
+#include "ui_ndd_plugin_implement.h"
+
+#include <qsciscintilla.h>
+#include <QDebug>
+#include <QProcess>
+
+#include <QDir>
+#include <QDockWidget>
+#include <QFile>
+#include <QFileInfo>
+#include <QThread>
+NddPluginImplement::NddPluginImplement(QWidget *parent, QsciScintilla *pEdit) : QMainWindow(parent)
+    , currentWidget(parent)
+    , currentEdit(pEdit)
+    , ttsSettingWidget(new Ui::NddPluginImplement)
+{
+    ttsSettingWidget->setupUi(this);
+}
+
+NddPluginImplement::~NddPluginImplement()
+{
+    
+}
+
+void NddPluginImplement::setMenuActions(QMenu *menu)
+{
+
+    connect(menu->addAction(u8"TTS Settings"), &QAction::triggered, this, [=](){
+        this->setParent(nullptr);
+        this->show();
+    });
+
+    connect(menu->addAction(u8"TTS Settings(Dock)"), &QAction::triggered, this, [=](){
+        auto mainWindow = dynamic_cast<QMainWindow*>(currentWidget);
+        if(!mainWindow) {
+            qDebug() << "None";
+            return;
+        }
+
+        QDockWidget *m_dockWidget = new QDockWidget(mainWindow);
+        m_dockWidget->setMinimumSize(100, 200);
+        mainWindow->addDockWidget(Qt::RightDockWidgetArea, m_dockWidget);
+
+        if (!m_dockWidget->isVisible())
+            m_dockWidget->show();
+
+        m_dockWidget->setWidget(this);
+    });
+
+
+    // 
+    connect(menu->addAction(u8"TTS Start"), &QAction::triggered, this, [&](){
+        // 获取当前编辑器,并直接打印编辑器内容
+        QsciScintilla *edit = getCurrentEditFunc();
+
+        QStringList arguments = QStringList() << "-v" << "zh" << edit->selectedText();;
+        speakeSpeck(arguments);
+    });
+        
+    connect(menu->addAction(u8"TTS Stop"), &QAction::triggered, this, [&](){
+        if (process->state() != QProcess::NotRunning) {
+            process->kill();
+        }
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 eSpeak (未指定)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << getCurrentEditFunc()->selectedText();
+
+        speakeSpeck(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 eSpeak (普通话)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "zh" << getCurrentEditFunc()->selectedText();
+        
+        speakeSpeck(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (未指定)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << getCurrentEditFunc()->selectedText();
+
+        speakeSpeck(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (普通话)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Mandarin" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (广东话-粤语)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Cantonese" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (台山话)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Toisanese" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (客家话)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Hakka" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (藏语)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Tibetan" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (南京话)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Ngangien" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"阅读选中内容 Ekho (韩语)"), &QAction::triggered, this, [&](){
+        QStringList arguments = QStringList() << "-v" << "Hangul" << getCurrentEditFunc()->selectedText();
+        
+        speakEkho(arguments);
+    });
+
+    connect(menu->addAction(u8"监听变化"), &QAction::triggered, this, [&](){
+        // QThread *thread = new QThread;
+
+        currentEdit = getCurrentEditFunc();
+
+        // 光标移动声
+        connect(currentEdit, &QsciScintilla::selectionChanged, this, [=](){
+            speakeSpeckEditrMonitor("k", 450);
+        });
+
+        // 输入声 - change event
+        connect(currentEdit, &QsciScintilla::textChanged, this, [=](){
+            QStringList arguments = QStringList() << "-v" << "zh";
+            speakeSpeckEditrMonitor(u8"滴", arguments, 450);
+        });
+    });
+
+    // Cursor
+    connect(menu->addAction(u8"光标行号变化监听"), &QAction::triggered, this, [&](){
+        currentEdit = getCurrentEditFunc();
+        connect(currentEdit, &QsciScintilla::cursorPosChange, this, [=](int x, int y){
+            QStringList arguments = QStringList() << "-v" << "zh";
+            speakEkhoEditrMonitor(QString(u8"现在是第 %1").arg(x+1), arguments, 175);
+        });
+    });
+}
+
+// ...
+
+void NddPluginImplement::speakeSpeckEditrMonitor(const QString &text) {
+
+    QStringList arguments = QStringList() << "-v" << "en" << "-s" << "450" << text;
+
+    speakeSpeck(arguments);
+}
+
+void NddPluginImplement::speakeSpeckEditrMonitor(const QString &text, int speed) {
+    QStringList arguments = QStringList() << "-v" << "en" << "-s" << QString::number(speed) << text;
+
+    speakeSpeck(arguments);
+}
+
+void NddPluginImplement::speakeSpeckEditrMonitor(const QString &text, QStringList &arguments, int speed)
+{
+    arguments << "-s" << QString::number(speed) << text;
+
+    speakeSpeck(arguments);
+}
+
+void NddPluginImplement::speakEkhoEditrMonitor(const QString &text)
+{
+    QStringList arguments = QStringList() << text;
+
+    speakEkho(arguments);
+}
+
+void NddPluginImplement::speakEkhoEditrMonitor(const QString &text, int speed)
+{
+    QStringList arguments = QStringList() << "-s" << QString::number(speed) << text;
+
+    speakeSpeck(arguments);
+}
+
+void NddPluginImplement::speakEkhoEditrMonitor(const QString &text, QStringList &arguments, int speed)
+{
+    arguments << "-s" << QString::number(speed) << text;
+
+    speakeSpeck(arguments);
+}
+
+void NddPluginImplement::speakeSpeck(const QString &text) {
+    QStringList arguments = QStringList() << "-v" << "zh" << "-s" << "600";
+
+    arguments << "滴";
+
+    speakeSpeck(arguments);
+}
+
+void NddPluginImplement::speakeSpeck(const QStringList &arguments)
+{
+    if (process->state() == QProcess::Running) {
+        return;
+    }
+
+    QString eSpeakEngine = ttsSettingWidget->espeakEngine->text();
+    if (!QFile::exists(eSpeakEngine)) {
+        return;
+    }
+    QDir eSpeakEngineDir = QFileInfo(eSpeakEngine).absoluteDir();
+
+    if (process->state() == QProcess::NotRunning) {
+        process->start(eSpeakEngine, arguments);
+        if (!process->waitForStarted(1000)) {
+            qDebug() << "Failed to start espeak process";
+            return;
+        }
+    }
+}
+
+void NddPluginImplement::speakEkho(const QString &text) {
+    QStringList arguments = QStringList() << getCurrentEditFunc()->text();
+
+    speakEkho(arguments);
+}
+
+void NddPluginImplement::speakEkho(const QStringList &arguments)
+{
+    if (process->state() == QProcess::Running) {
+        process->kill();
+    }
+
+    QString ekhoEngine = ttsSettingWidget->ekhoEngine->text();
+    if (!QFile::exists(ekhoEngine)) {
+        return;
+    }
+    QDir ekhoEngineDir = QFileInfo(ekhoEngine).absoluteDir();
+
+    process->setWorkingDirectory(ekhoEngineDir.absolutePath());
+    if (process->state() == QProcess::NotRunning) {
+        process->start(ekhoEngine, arguments);
+        if (!process->waitForStarted(1000)) {
+            qDebug() << "Failed to start espeak process";
+            return;
+        }
+    }
+}
+
+void NddPluginImplement::on_pushButton_clicked()
+{
+
+}
+

+ 57 - 0
src/plugin/TTS-plugin/ndd_plugin_implement.h

@@ -0,0 +1,57 @@
+#ifndef NDD_PLUGIN_IMPLEMENT_H
+#define NDD_PLUGIN_IMPLEMENT_H
+
+#include <QMainWindow>
+#include <QMenu>
+#include <QProcess>
+
+class QsciScintilla;
+namespace Ui {
+class NddPluginImplement;
+};
+
+class NddPluginImplement : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    explicit NddPluginImplement(QWidget *parent = nullptr, QsciScintilla *pEdit = nullptr);
+    ~NddPluginImplement();
+
+    void setMenuActions(QMenu *menu);
+    void setCurrentEditFunc(std::function<QsciScintilla* ()> func) {
+        getCurrentEditFunc = func;
+    }
+
+private slots:
+    void onReadyRead();
+
+    void speakeSpeck(const QString &text);
+    void speakeSpeck(const QStringList &arguments);
+    void speakEkho(const QString &text);
+    void speakEkho(const QStringList &arguments);
+
+    void speakeSpeckEditrMonitor(const QString &text);
+    void speakeSpeckEditrMonitor(const QString &text, int speed);
+    void speakeSpeckEditrMonitor(const QString &text, QStringList &arguments, int speed);
+
+    void speakEkhoEditrMonitor(const QString &text);
+    void speakEkhoEditrMonitor(const QString &text, int speed);
+    void speakEkhoEditrMonitor(const QString &text, QStringList &arguments, int speed);
+
+    void on_pushButton_clicked();
+
+private:
+    // 目前看来需要准备一个完整内部状态
+    QWidget *currentWidget;
+    QsciScintilla *currentEdit;
+    std::function<QsciScintilla* ()> getCurrentEditFunc;
+
+    QProcess *process = new QProcess;
+
+private:
+    Ui::NddPluginImplement *ttsSettingWidget;
+
+};
+
+#endif // NDD_PLUGIN_IMPLEMENT_H

+ 106 - 0
src/plugin/TTS-plugin/ndd_plugin_implement.ui

@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NddPluginImplement</class>
+ <widget class="QMainWindow" name="NddPluginImplement">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>571</width>
+    <height>370</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>TTS Settings Panel</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QGroupBox" name="groupBox">
+      <property name="title">
+       <string>TTS Settings</string>
+      </property>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="0" column="0">
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <item>
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>TTS Engine(eSpeak):</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="espeakEngine">
+           <property name="text">
+            <string>/usr/bin/espeak</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="pushButton_2">
+           <property name="text">
+            <string>Open</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="1" column="0">
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <item>
+          <widget class="QLabel" name="label">
+           <property name="text">
+            <string>TTS Engine(Ekho):</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="ekhoEngine">
+           <property name="text">
+            <string>/usr/bin/ekho</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="pushButton">
+           <property name="text">
+            <string>Open</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="2" column="0">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>226</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>571</width>
+     <height>23</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 11 - 0
src/plugin/TTS-plugin/plugin.cpp

@@ -0,0 +1,11 @@
+#include <qobject.h>
+#include <qstring.h>
+#include <include/pluginGl.h>
+#include <functional>
+#include <qsciscintilla.h>
+#include "ndd_plugin_implement.h"
+
+
+NOTEPAD_PLUGIN_METADATA_IDENTIFY_V1(u8"TTS 语音合成", "0.1", "zinface", u8"二级菜单高级插件", "");
+
+NOTEPAD_PLUGIN_METADATA_IMPLEMENT_V1(NddPluginImplement, false);