Skip to content

Commit f2ec4d7

Browse files
committed
update hfa ui
1 parent 87116f6 commit f2ec4d7

9 files changed

Lines changed: 387 additions & 526 deletions

File tree

src/apps/HubertFA/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
2020
Qt${QT_VERSION_MAJOR}::Widgets
2121
audio-util nlohmann_json::nlohmann_json
2222
onnxruntime
23+
AsyncTaskWindow::AsyncTaskWindow
2324
)
2425

2526
file(GLOB_RECURSE onnx_files ../../libs/onnxruntime/lib/*.*)
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
#include "HubertFAWindow.h"
2+
3+
#include <filesystem>
4+
#include <fstream>
5+
6+
#include <QApplication>
7+
#include <QFileDialog>
8+
#include <QHBoxLayout>
9+
#include <QLabel>
10+
#include <QLineEdit>
11+
#include <QMessageBox>
12+
#include <QRadioButton>
13+
#include <QStandardPaths>
14+
#include <QTextCursor>
15+
16+
#include <nlohmann/json.hpp>
17+
18+
#include "../util/HfaThread.h"
19+
20+
namespace fs = std::filesystem;
21+
using json = nlohmann::json;
22+
23+
namespace HFA {
24+
25+
static bool check_configs(const std::string &model_dir, std::string &error) {
26+
const fs::path model_path(model_dir);
27+
const fs::path vocab_file = model_path / "vocab.json";
28+
if (!fs::exists(vocab_file)) {
29+
error = vocab_file.string() + " does not exist";
30+
return false;
31+
}
32+
const fs::path config_file = model_path / "config.json";
33+
if (!fs::exists(config_file)) {
34+
error = config_file.string() + " does not exist";
35+
return false;
36+
}
37+
const fs::path model_file = model_path / "model.onnx";
38+
if (!fs::exists(model_file)) {
39+
error = model_file.string() + " does not exist";
40+
return false;
41+
}
42+
std::ifstream vocab_stream(vocab_file);
43+
json vocab = json::parse(vocab_stream);
44+
const auto dictionaries = vocab["dictionaries"];
45+
if (dictionaries.is_object()) {
46+
for (const auto &[key, dict_node] : dictionaries.items()) {
47+
if (!dict_node.is_null()) {
48+
auto dict_path_str = dict_node.get<std::string>();
49+
fs::path dict_path = model_path / dict_path_str;
50+
if (!fs::exists(dict_path)) {
51+
error = dict_path.string() + " does not exist";
52+
return false;
53+
}
54+
}
55+
}
56+
}
57+
return true;
58+
}
59+
60+
HubertFAWindow::HubertFAWindow(QWidget *parent)
61+
: AsyncTaskWindow(parent), m_modelLoadBtn(nullptr), m_dynamicContainer(nullptr) {
62+
m_errorFormat.setForeground(Qt::red);
63+
setRunButtonText("Run Alignment");
64+
HubertFAWindow::init();
65+
slot_loadModel();
66+
}
67+
68+
HubertFAWindow::~HubertFAWindow() {
69+
delete m_hfa;
70+
}
71+
72+
void HubertFAWindow::init() {
73+
auto *outLabel = new QLabel("Output TextGrid directory:", this);
74+
m_outTgEdit = new QLineEdit(this);
75+
m_outTgEdit->setText(R"(C:\Users\99662\Desktop\hfa测试wav\wav\TextGrid)");
76+
auto *outBtn = new QPushButton("Browse...", this);
77+
auto *outLayout = new QHBoxLayout();
78+
outLayout->addWidget(outLabel);
79+
outLayout->addWidget(m_outTgEdit);
80+
outLayout->addWidget(outBtn);
81+
connect(outBtn, &QPushButton::clicked, this, &HubertFAWindow::slot_outTgPath);
82+
m_rightPanel->addLayout(outLayout);
83+
84+
auto *modelLabel = new QLabel("HFA model folder:", this);
85+
m_modelEdit = new QLineEdit(this);
86+
const QString defaultModelPath = QDir::cleanPath(
87+
#ifdef Q_OS_MAC
88+
QApplication::applicationDirPath() + "/../Resources/hfa_model"
89+
#else
90+
QApplication::applicationDirPath() + QDir::separator() + "model" + QDir::separator() + "HfaModel"
91+
#endif
92+
);
93+
m_modelEdit->setText(defaultModelPath);
94+
auto *modelBrowseBtn = new QPushButton("Browse...", this);
95+
m_modelLoadBtn = new QPushButton("Load Model", this);
96+
m_modelStatusLabel = new QLabel("Model not loaded", this);
97+
auto *modelLayout = new QHBoxLayout();
98+
modelLayout->addWidget(modelLabel);
99+
modelLayout->addWidget(m_modelEdit);
100+
modelLayout->addWidget(modelBrowseBtn);
101+
modelLayout->addWidget(m_modelLoadBtn);
102+
modelLayout->addWidget(m_modelStatusLabel);
103+
connect(modelBrowseBtn, &QPushButton::clicked, this, &HubertFAWindow::slot_browseModel);
104+
connect(m_modelLoadBtn, &QPushButton::clicked, this, &HubertFAWindow::slot_loadModel);
105+
m_topLayout->addLayout(modelLayout);
106+
107+
m_rightPanel->addStretch();
108+
}
109+
110+
void HubertFAWindow::runTask() {
111+
if (!m_hfa) {
112+
QMessageBox::warning(this, "Warning", "Model not loaded or invalid. Please load the model first.");
113+
return;
114+
}
115+
const QString outDir = m_outTgEdit->text().trimmed();
116+
if (outDir.isEmpty()) {
117+
QMessageBox::warning(this, "Warning", "Output directory cannot be empty.");
118+
return;
119+
}
120+
if (!QDir().exists(outDir)) {
121+
if (!QDir().mkpath(outDir)) {
122+
QMessageBox::warning(this, "Warning", "Cannot create output directory. Please check the path.");
123+
return;
124+
}
125+
}
126+
m_logOutput->clear();
127+
m_totalTasks = taskList()->count();
128+
if (m_totalTasks == 0) {
129+
QMessageBox::information(this, "Info", "No tasks to process.");
130+
return;
131+
}
132+
m_finishedTasks = 0;
133+
m_errorTasks = 0;
134+
m_errorDetails.clear();
135+
m_progressBar->setValue(0);
136+
m_progressBar->setMaximum(m_totalTasks);
137+
138+
std::vector<std::string> non_speech_ph;
139+
if (m_nonSpeechPhLayout) {
140+
for (int i = 0; i < m_nonSpeechPhLayout->count(); ++i) {
141+
const auto *checkBox = qobject_cast<QCheckBox *>(m_nonSpeechPhLayout->itemAt(i)->widget());
142+
if (checkBox && checkBox->isChecked()) {
143+
non_speech_ph.push_back(checkBox->text().toStdString());
144+
}
145+
}
146+
}
147+
std::string language = "zh";
148+
if (m_languageGroup && m_languageGroup->checkedButton()) {
149+
language = m_languageGroup->checkedButton()->text().toStdString();
150+
}
151+
152+
for (int i = 0; i < m_totalTasks; ++i) {
153+
const QListWidgetItem *item = taskList()->item(i);
154+
QString filename = item->text();
155+
QString filePath = item->data(Qt::UserRole + 1).toString();
156+
QString baseName = QFileInfo(filename).completeBaseName();
157+
QString outTgPath = outDir + QDir::separator() + baseName + ".TextGrid";
158+
159+
auto *thread = new HfaThread(m_hfa, filename, filePath, outTgPath, language, non_speech_ph);
160+
connect(thread, &HfaThread::oneFailed, this, &HubertFAWindow::slot_hfaFailed);
161+
connect(thread, &HfaThread::oneFinished, this, &HubertFAWindow::slot_hfaFinished);
162+
m_threadPool->start(thread);
163+
}
164+
}
165+
166+
void HubertFAWindow::onTaskFinished() {
167+
const QString msg = QString("Alignment completed! Total: %3, Succeeded: %1, Failed: %2")
168+
.arg(m_totalTasks - m_errorTasks)
169+
.arg(m_errorTasks)
170+
.arg(m_totalTasks);
171+
if (m_errorTasks > 0) {
172+
m_logOutput->appendPlainText("Failed tasks list:");
173+
for (const QString &detail : m_errorDetails)
174+
m_logOutput->appendPlainText(" " + detail);
175+
m_errorDetails.clear();
176+
}
177+
QMessageBox::information(this, QApplication::applicationName(), msg);
178+
m_totalTasks = m_finishedTasks = m_errorTasks = 0;
179+
}
180+
181+
void HubertFAWindow::slot_outTgPath() {
182+
const QString path =
183+
QFileDialog::getExistingDirectory(this, "Select Output TextGrid Directory", m_outTgEdit->text(),
184+
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
185+
if (!path.isEmpty())
186+
m_outTgEdit->setText(path);
187+
}
188+
189+
void HubertFAWindow::slot_browseModel() {
190+
const QString path =
191+
QFileDialog::getExistingDirectory(this, "Select HFA Model Folder", m_modelEdit->text(),
192+
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
193+
if (!path.isEmpty())
194+
m_modelEdit->setText(path);
195+
}
196+
197+
void HubertFAWindow::slot_loadModel() {
198+
if (m_hfa && m_hfa->initialized() && m_dynamicContainer != nullptr) {
199+
QMessageBox::information(this, "Info", "Model already loaded, no need to repeat.");
200+
return;
201+
}
202+
203+
QString modelFolder = m_modelEdit->text().trimmed();
204+
if (modelFolder.isEmpty()) {
205+
m_modelStatusLabel->setText("Model folder path is empty.");
206+
m_runBtn->setEnabled(false);
207+
return;
208+
}
209+
210+
QString errorStr;
211+
if (!checkModelConfig(modelFolder, errorStr)) {
212+
m_modelStatusLabel->setText("Model files missing: " + errorStr);
213+
m_runBtn->setEnabled(false);
214+
return;
215+
}
216+
217+
if (m_hfa) {
218+
delete m_hfa;
219+
m_hfa = nullptr;
220+
}
221+
222+
m_hfa = new HFA(modelFolder.toStdString(), ExecutionProvider::CPU, -1);
223+
if (m_hfa->initialized()) {
224+
m_modelStatusLabel->setText("Model loaded successfully.");
225+
m_runBtn->setEnabled(true);
226+
m_modelLoadBtn->setEnabled(false);
227+
228+
if (m_dynamicContainer == nullptr) {
229+
m_dynamicContainer = new QWidget(this);
230+
auto *dynamicLayout = new QVBoxLayout(m_dynamicContainer);
231+
232+
fs::path model_path(modelFolder.toStdString());
233+
fs::path vocab_file = model_path / "vocab.json";
234+
std::ifstream vocab_stream(vocab_file);
235+
json vocab = json::parse(vocab_stream);
236+
237+
if (vocab.contains("non_lexical_phonemes")) {
238+
auto nonLexicalPh = vocab["non_lexical_phonemes"].get<std::vector<std::string>>();
239+
if (!nonLexicalPh.empty()) {
240+
auto *phLabel = new QLabel("Non-speech phonemes:", m_dynamicContainer);
241+
dynamicLayout->addWidget(phLabel);
242+
243+
m_nonSpeechPhLayout = new QHBoxLayout();
244+
for (const auto &ph : nonLexicalPh) {
245+
auto *checkBox = new QCheckBox(QString::fromStdString(ph), m_dynamicContainer);
246+
m_nonSpeechPhLayout->addWidget(checkBox);
247+
}
248+
dynamicLayout->addLayout(m_nonSpeechPhLayout);
249+
}
250+
}
251+
252+
if (vocab.contains("dictionaries")) {
253+
auto languages = vocab["dictionaries"].get<std::map<std::string, std::string>>();
254+
if (!languages.empty()) {
255+
auto *langLabel = new QLabel("Language:", m_dynamicContainer);
256+
dynamicLayout->addWidget(langLabel);
257+
258+
m_languageGroup = new QButtonGroup(this);
259+
m_languageGroup->setExclusive(true);
260+
auto *langLayout = new QHBoxLayout();
261+
int radioId = 0;
262+
for (auto it = languages.rbegin(); it != languages.rend(); ++it) {
263+
auto *radio = new QRadioButton(QString::fromStdString(it->first), m_dynamicContainer);
264+
m_languageGroup->addButton(radio, radioId++);
265+
langLayout->addWidget(radio);
266+
}
267+
if (!langLayout->isEmpty())
268+
m_languageGroup->button(0)->setChecked(true);
269+
dynamicLayout->addLayout(langLayout);
270+
}
271+
}
272+
273+
int stretchIndex = m_rightPanel->count() - 1;
274+
m_rightPanel->insertWidget(stretchIndex, m_dynamicContainer);
275+
}
276+
} else {
277+
m_modelStatusLabel->setText("Model loading failed (initialization error).");
278+
m_runBtn->setEnabled(false);
279+
m_modelLoadBtn->setEnabled(true);
280+
}
281+
}
282+
283+
bool HubertFAWindow::checkModelConfig(const QString &modelDir, QString &error) {
284+
std::string err;
285+
const bool ok = check_configs(modelDir.toStdString(), err);
286+
if (!ok) {
287+
error = QString::fromStdString(err);
288+
}
289+
return ok;
290+
}
291+
292+
void HubertFAWindow::slot_hfaFailed(const QString &filename, const QString &msg) {
293+
m_finishedTasks++;
294+
m_errorTasks++;
295+
m_errorDetails.append(filename + ": " + msg);
296+
m_progressBar->setValue(m_finishedTasks);
297+
appendErrorMessage(filename + ": " + msg);
298+
if (m_finishedTasks == m_totalTasks) {
299+
onTaskFinished();
300+
}
301+
}
302+
303+
void HubertFAWindow::slot_hfaFinished(const QString &filename, const QString &msg) {
304+
m_finishedTasks++;
305+
m_progressBar->setValue(m_finishedTasks);
306+
if (!msg.isEmpty()) {
307+
m_logOutput->appendPlainText(filename + ": " + msg);
308+
}
309+
if (m_finishedTasks == m_totalTasks) {
310+
onTaskFinished();
311+
}
312+
}
313+
314+
void HubertFAWindow::appendErrorMessage(const QString &message) const {
315+
QTextCursor cursor = m_logOutput->textCursor();
316+
cursor.movePosition(QTextCursor::End);
317+
cursor.insertText(message + "\n", m_errorFormat);
318+
m_logOutput->ensureCursorVisible();
319+
}
320+
321+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#ifndef HFAWINDOW_H
2+
#define HFAWINDOW_H
3+
4+
#include <AsyncTaskWindow.h>
5+
#include <QButtonGroup>
6+
#include <QCheckBox>
7+
#include <QLabel>
8+
#include <QLineEdit>
9+
#include <QPushButton>
10+
#include <QTextCharFormat>
11+
12+
#include "../util/Hfa.h"
13+
14+
namespace HFA {
15+
16+
class HubertFAWindow : public AsyncTask::AsyncTaskWindow {
17+
Q_OBJECT
18+
public:
19+
explicit HubertFAWindow(QWidget *parent = nullptr);
20+
~HubertFAWindow() override;
21+
22+
protected:
23+
void init() override;
24+
void runTask() override;
25+
void onTaskFinished() override;
26+
27+
private slots:
28+
void slot_outTgPath();
29+
void slot_browseModel();
30+
void slot_loadModel();
31+
void slot_hfaFailed(const QString &filename, const QString &msg);
32+
void slot_hfaFinished(const QString &filename, const QString &msg);
33+
34+
private:
35+
void appendErrorMessage(const QString &message) const;
36+
static bool checkModelConfig(const QString &modelDir, QString &error);
37+
38+
QLineEdit *m_outTgEdit;
39+
QLineEdit *m_modelEdit;
40+
QLabel *m_modelStatusLabel;
41+
QPushButton *m_modelLoadBtn;
42+
QButtonGroup *m_languageGroup;
43+
QHBoxLayout *m_nonSpeechPhLayout;
44+
QWidget *m_dynamicContainer;
45+
HFA *m_hfa = nullptr;
46+
QTextCharFormat m_errorFormat;
47+
};
48+
49+
} // namespace HFA
50+
51+
#endif // HFAWINDOW_H

0 commit comments

Comments
 (0)