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+ }
0 commit comments