ctr_main.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. '''
  2. (C) 2019 Raryel C. Souza
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. '''
  14. from PyQt5 import QtCore, QtWidgets
  15. from PyQt5.QtWidgets import QFileDialog, QMessageBox
  16. from PyQt5.QtCore import Qt
  17. from pathlib import Path
  18. from pytranscriber.model.param_autosub import Param_Autosub
  19. from pytranscriber.util.util import MyUtil
  20. from pytranscriber.control.thread_exec_autosub import Thread_Exec_Autosub
  21. from pytranscriber.control.thread_cancel_autosub import Thread_Cancel_Autosub
  22. from pytranscriber.gui.gui import Ui_window
  23. import os
  24. class Ctr_Main():
  25. def __init__(self):
  26. import sys
  27. app = QtWidgets.QApplication(sys.argv)
  28. window = QtWidgets.QMainWindow()
  29. self.objGUI = Ui_window()
  30. self.objGUI.setupUi(window)
  31. self.__initGUI()
  32. window.setFixedSize(window.size())
  33. window.show()
  34. sys.exit(app.exec_())
  35. def __initGUI(self):
  36. #language selection list
  37. list_languages = [ "en-US - English (United States)",
  38. "en-AU - English (Australia)",
  39. "en-CA - English (Canada)",
  40. "en-GB - English (United Kingdom)",
  41. "en-HK - English (Hong Kong)",
  42. "en-IN - English (India)",
  43. "en-GB - English (Ireland)",
  44. "en-NZ - English (New Zealand)",
  45. "en-PH - English (Philippines)",
  46. "en-SG - English (Singapore)",
  47. "af - Afrikaans",
  48. "ar - Arabic",
  49. 'ar-DZ - Arabic (Algeria)',
  50. 'ar-EG - Arabic (Egypt)',
  51. 'ar-IQ - Arabic (Iraq)',
  52. 'ar-IS - Arabic (Israel)',
  53. 'ar-JO - Arabic (Jordan)',
  54. 'ar-KW - Arabic (Kuwait)',
  55. 'ar-LB - Arabic (Lebanon)',
  56. 'ar-MA - Arabic (Morocco)',
  57. 'ar-OM - Arabic (Oman)',
  58. 'ar-QA - Arabic (Qatar)',
  59. 'ar-SA - Arabic (Saudi Arabia)',
  60. 'ar-PS - Arabic (State of Palestine)',
  61. 'ar-TN - Arabic (Tunisia)',
  62. 'ar-AE - Arabic (United Arab Emirates)',
  63. 'ar-YE - Arabic (Yemen)',
  64. "az - Azerbaijani",
  65. "be - Belarusian",
  66. "bg - Bulgarian",
  67. "bn - Bengali",
  68. "bs - Bosnian",
  69. "ca - Catalan",
  70. "ceb -Cebuano",
  71. "cs - Czech",
  72. "cy - Welsh",
  73. "da - Danish",
  74. "de - German",
  75. 'de-AT - German (Austria)',
  76. 'de-CH - German (Switzerland)',
  77. "el - Greek",
  78. "eo - Esperanto",
  79. 'es-ES - Spanish (Spain)',
  80. 'es-AR - Spanish (Argentina)',
  81. 'es-BO - Spanish (Bolivia)',
  82. 'es-CL - Spanish (Chile)',
  83. 'es-CO - Spanish (Colombia)',
  84. 'es-CR - Spanish (Costa Rica)',
  85. 'es-DO - Spanish (Dominican Republic)',
  86. 'es-EC - Spanish (Ecuador)',
  87. 'es-GT - Spanish (Guatemala)',
  88. 'es-HN - Spanish (Honduras)',
  89. 'es-MX - Spanish (Mexico)',
  90. 'es-NI - Spanish (Nicaragua)',
  91. 'es-PA - Spanish (Panama)',
  92. 'es-PE - Spanish (Peru)',
  93. 'es-PR - Spanish (Puerto Rico)',
  94. 'es-PY - Spanish (Paraguay)',
  95. 'es-SV - Spanish (El Salvador)',
  96. 'es-UY - Spanish (Uruguay)',
  97. 'es-US - Spanish (United States)',
  98. 'es-VE - Spanish (Venezuela)',
  99. "et - Estonian",
  100. "eu - Basque",
  101. "fa - Persian",
  102. 'fil-PH - Filipino (Philippines)',
  103. "fi - Finnish",
  104. "fr - French",
  105. 'fr-BE - French (Belgium)',
  106. 'fr-CA - French (Canada)',
  107. 'fr-CH - French (Switzerland)',
  108. "ga - Irish",
  109. "gl - Galician",
  110. "gu -Gujarati",
  111. "ha - Hausa",
  112. "hi - Hindi",
  113. "hmn - Hmong",
  114. "hr - Croatian",
  115. "ht - Haitian Creole",
  116. "hu - Hungarian",
  117. "hy - Armenian",
  118. "id - Indonesian",
  119. "ig - Igbo",
  120. "is - Icelandic",
  121. "it - Italian",
  122. 'it-CH - Italian (Switzerland)',
  123. "iw - Hebrew",
  124. "ja - Japanese",
  125. "jw - Javanese",
  126. "ka - Georgian",
  127. "kk - Kazakh",
  128. "km - Khmer",
  129. "kn - Kannada",
  130. "ko - Korean",
  131. "la - Latin",
  132. "lo - Lao",
  133. "lt - Lithuanian",
  134. "lv - Latvian",
  135. "mg - Malagasy",
  136. "mi - Maori",
  137. "mk - Macedonian",
  138. "ml - Malayalam",
  139. "mn - Mongolian",
  140. "mr - Marathi",
  141. "ms - Malay",
  142. "mt - Maltese",
  143. "my - Myanmar (Burmese)",
  144. "ne - Nepali",
  145. "nl - Dutch",
  146. "no - Norwegian",
  147. "ny - Chichewa",
  148. "pa - Punjabi",
  149. "pl - Polish",
  150. "pt-BR - Portuguese (Brazil)",
  151. "pt-PT - Portuguese (Portugal)",
  152. "ro - Romanian",
  153. "ru - Russian",
  154. "si - Sinhala",
  155. "sk - Slovak",
  156. "sl - Slovenian",
  157. "so - Somali",
  158. "sq - Albanian",
  159. "sr - Serbian",
  160. "st - Sesotho",
  161. "su - Sudanese",
  162. "sv - Swedish",
  163. "sw - Swahili",
  164. "ta - Tamil",
  165. 'ta-IN - Tamil (India)',
  166. 'ta-MY - Tamil (Malaysia)',
  167. 'ta-SG - Tamil (Singapore)',
  168. 'ta-LK - Tamil (Sri Lanka)',
  169. "te - Telugu",
  170. "tg - Tajik",
  171. "th - Thai",
  172. "tl - Filipino",
  173. "tr - Turkish",
  174. "uk - Ukrainian",
  175. "ur - Urdu",
  176. "uz - Uzbek",
  177. "vi - Vietnamese",
  178. "yi - Yiddish",
  179. "yo - Yoruba",
  180. "yue-Hant-HK - Cantonese (Traditional, HK)",
  181. "zh - Chinese (Simplified, China)",
  182. "zh-HK - Chinese (Simplified, Hong Kong)",
  183. "zh-TW - Chinese (Traditional, Taiwan)",
  184. "zu - Zulu" ]
  185. self.objGUI.cbSelectLang.addItems(list_languages)
  186. self.__listenerProgress("", 0)
  187. #default output folder at user desktop
  188. userHome = Path.home()
  189. pathOutputFolder = userHome / 'Desktop' / 'pyTranscriber'
  190. self.objGUI.qleOutputFolder.setText(str(pathOutputFolder))
  191. self.objGUI.bRemoveFile.setEnabled(False)
  192. self.objGUI.bCancel.hide()
  193. #button listeners
  194. self.objGUI.bConvert.clicked.connect(self.__listenerBExec)
  195. self.objGUI.bCancel.clicked.connect(self.__listenerBCancel)
  196. self.objGUI.bRemoveFile.clicked.connect(self.__listenerBRemove)
  197. self.objGUI.bSelectOutputFolder.clicked.connect(self.__listenerBSelectOuputFolder)
  198. self.objGUI.bOpenOutputFolder.clicked.connect(self.__listenerBOpenOutputFolder)
  199. self.objGUI.bSelectMedia.clicked.connect(self.__listenerBSelectMedia)
  200. self.objGUI.actionLicense.triggered.connect(self.__listenerBLicense)
  201. self.objGUI.actionDonation.triggered.connect(self.__listenerBDonation)
  202. self.objGUI.actionAbout_pyTranscriber.triggered.connect(self.__listenerBAboutpyTranscriber)
  203. def __resetGUIAfterSuccess(self):
  204. self.__resetGUIAfterCancel()
  205. self.objGUI.qlwListFilesSelected.clear()
  206. self.objGUI.bConvert.setEnabled(False)
  207. self.objGUI.bRemoveFile.setEnabled(False)
  208. def __resetGUIAfterCancel(self):
  209. self.__resetProgressBar()
  210. self.objGUI.bSelectMedia.setEnabled(True)
  211. self.objGUI.bSelectOutputFolder.setEnabled(True)
  212. self.objGUI.cbSelectLang.setEnabled(True)
  213. self.objGUI.chbxOpenOutputFilesAuto.setEnabled(True)
  214. self.objGUI.bCancel.hide()
  215. self.objGUI.bConvert.setEnabled(True)
  216. self.objGUI.bRemoveFile.setEnabled(True)
  217. def __lockButtonsDuringOperation(self):
  218. self.objGUI.bConvert.setEnabled(False)
  219. self.objGUI.bRemoveFile.setEnabled(False)
  220. self.objGUI.bSelectMedia.setEnabled(False)
  221. self.objGUI.bSelectOutputFolder.setEnabled(False)
  222. self.objGUI.cbSelectLang.setEnabled(False)
  223. self.objGUI.chbxOpenOutputFilesAuto.setEnabled(False)
  224. QtCore.QCoreApplication.processEvents()
  225. def __listenerProgress(self, str, percent):
  226. self.objGUI.labelCurrentOperation.setText(str)
  227. self.objGUI.progressBar.setProperty("value", percent)
  228. QtCore.QCoreApplication.processEvents()
  229. def __setProgressBarIndefinite(self):
  230. self.objGUI.progressBar.setMinimum(0)
  231. self.objGUI.progressBar.setMaximum(0)
  232. self.objGUI.progressBar.setValue(0)
  233. def __resetProgressBar(self):
  234. self.objGUI.progressBar.setMinimum(0)
  235. self.objGUI.progressBar.setMaximum(100)
  236. self.objGUI.progressBar.setValue(0)
  237. self.__listenerProgress("", 0)
  238. def __updateProgressFileYofN(self, str):
  239. self.objGUI.labelProgressFileIndex.setText(str)
  240. QtCore.QCoreApplication.processEvents()
  241. def __listenerBSelectOuputFolder(self):
  242. fSelectDir = QFileDialog.getExistingDirectory(self.objGUI.centralwidget)
  243. if fSelectDir:
  244. self.objGUI.qleOutputFolder.setText(fSelectDir)
  245. def __listenerBSelectMedia(self):
  246. #options = QFileDialog.Options()
  247. options = QFileDialog.DontUseNativeDialog
  248. files, _ = QFileDialog.getOpenFileNames(self.objGUI.centralwidget, "Select media", "","All Media Files (*.mp3 *.mp4 *.wav *.m4a *.wma)")
  249. if files:
  250. self.objGUI.qlwListFilesSelected.addItems(files)
  251. #enable the convert button only if list of files is not empty
  252. self.objGUI.bConvert.setEnabled(True)
  253. self.objGUI.bRemoveFile.setEnabled(True)
  254. def __listenerBExec(self):
  255. if not MyUtil.is_internet_connected():
  256. self.__showErrorMessage("Error! Cannot reach Google Speech Servers. \n\n1) Please make sure you are connected to the internet. \n2) If you are in China or other place that blocks access to Google servers: please install and enable a desktop-wide VPN app like Windscribe before trying to use pyTranscriber!")
  257. else:
  258. #extracts the two letter lang_code from the string on language selection
  259. selectedLanguage = self.objGUI.cbSelectLang.currentText()
  260. indexSpace = selectedLanguage.index(" ")
  261. langCode = selectedLanguage[:indexSpace]
  262. listFiles = []
  263. for i in range(self.objGUI.qlwListFilesSelected.count()):
  264. listFiles.append(str(self.objGUI.qlwListFilesSelected.item(i).text()))
  265. outputFolder = self.objGUI.qleOutputFolder.text()
  266. if self.objGUI.chbxOpenOutputFilesAuto.checkState() == Qt.Checked:
  267. boolOpenOutputFilesAuto = True
  268. else:
  269. boolOpenOutputFilesAuto = False
  270. objParamAutosub = Param_Autosub(listFiles, outputFolder, langCode,
  271. boolOpenOutputFilesAuto)
  272. #execute the main process in separate thread to avoid gui lock
  273. self.thread_exec = Thread_Exec_Autosub(objParamAutosub)
  274. #connect signals from work thread to gui controls
  275. self.thread_exec.signalLockGUI.connect(self.__lockButtonsDuringOperation)
  276. self.thread_exec.signalResetGUIAfterSuccess.connect(self.__resetGUIAfterSuccess)
  277. self.thread_exec.signalResetGUIAfterCancel.connect(self.__resetGUIAfterCancel)
  278. self.thread_exec.signalProgress.connect(self.__listenerProgress)
  279. self.thread_exec.signalProgressFileYofN.connect(self.__updateProgressFileYofN)
  280. self.thread_exec.signalErrorMsg.connect(self.__showErrorMessage)
  281. self.thread_exec.start()
  282. #Show the cancel button
  283. self.objGUI.bCancel.show()
  284. self.objGUI.bCancel.setEnabled(True)
  285. def __listenerBCancel(self):
  286. self.objGUI.bCancel.setEnabled(False)
  287. self.thread_cancel = Thread_Cancel_Autosub(self.thread_exec)
  288. #Only if worker thread is running
  289. if self.thread_exec and self.thread_exec.isRunning():
  290. #reset progress indicator
  291. self.__listenerProgress("Cancelling", 0)
  292. self.__setProgressBarIndefinite()
  293. self.__updateProgressFileYofN("")
  294. #connect the terminate signal to resetGUI
  295. self.thread_cancel.signalTerminated.connect(self.__resetGUIAfterCancel)
  296. #run the cancel autosub operation in new thread
  297. #to avoid progressbar freezing
  298. self.thread_cancel.start()
  299. self.thread_exec = None
  300. def __listenerBRemove(self):
  301. indexSelected = self.objGUI.qlwListFilesSelected.currentRow()
  302. if indexSelected >= 0:
  303. self.objGUI.qlwListFilesSelected.takeItem(indexSelected)
  304. #if no items left disables the remove and convert button
  305. if self.objGUI.qlwListFilesSelected.count() == 0:
  306. self.objGUI.bRemoveFile.setEnabled(False)
  307. self.objGUI.bConvert.setEnabled(False)
  308. def __listenerBOpenOutputFolder(self):
  309. pathOutputFolder = Path(self.objGUI.qleOutputFolder.text())
  310. #if folder exists and is valid directory
  311. if os.path.exists(pathOutputFolder) and os.path.isdir(pathOutputFolder):
  312. MyUtil.open_file(pathOutputFolder)
  313. else:
  314. self.__showErrorMessage("Error! Invalid output folder.")
  315. def __listenerBLicense(self):
  316. self.__showInfoMessage("<html><body><a href=\"https://www.gnu.org/licenses/gpl-3.0.html\">GPL License</a><br><br>"
  317. + "Copyright (C) 2019 Raryel C. Souza <raryel.costa at gmail.com><br>"
  318. + "<br>This program is free software: you can redistribute it and/or modify<br>"
  319. + "it under the terms of the GNU General Public License as published by<br>"
  320. + "the Free Software Foundation, either version 3 of the License, or<br>"
  321. + " any later version<br>"
  322. + "<br>"
  323. + "This program is distributed in the hope that it will be useful,<br>"
  324. + "but WITHOUT ANY WARRANTY; without even the implied warranty of<br>"
  325. + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>"
  326. + "GNU General Public License for more details.<br>"
  327. + "<br>"
  328. + "You should have received a copy of the GNU General Public License<br>"
  329. + "along with this program. If not, see <a href=\"https://www.gnu.org/licenses\">www.gnu.org/licenses</a>."
  330. + "</body></html>", "License")
  331. def __listenerBDonation(self):
  332. self.__showInfoMessage("<html><body>"
  333. + "pyTranscriber is developed as a hobby, so donations of any value are welcomed and essential for further improvements and fixes."
  334. + "<br><br>If you feel that this software has been useful and would like to contribute for it to continue improve and have more features and fixes you can <a href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=YHB854YHPJCU8&item_name=Donation+pyTranscriber&currency_code=BRL\">DONATE VIA PAYPAL</a> or <a href=\"https://blockchain.com/btc/payment_request?address=153LcqV59paxEEJX7riLrEHQbE54vhcko9&amount=0.00026351&message=Donation to support pyTranscriber development\"> DONATE US$5 VIA BITCOIN</a>."
  335. + "<br><br>Thanks in advance!"
  336. + "</body></html>", "DONATIONS")
  337. def __listenerBAboutpyTranscriber(self):
  338. self.__showInfoMessage("<html><body>"
  339. + "<a href=\"https://github.com/raryelcostasouza/pyTranscriber\">pyTranscriber</a> is an application that can be used "
  340. + "to generate <b>automatic transcription / automatic subtitles </b>"
  341. + "for audio/video files through a friendly graphical user interface. "
  342. + "<br><br>"
  343. + "The hard work of speech recognition is made by the <a href=\"https://cloud.google.com/speech/\">Google Speech Recognition API</a> "
  344. + "using <a href=\"https://github.com/agermanidis/autosub\">Autosub</a>"
  345. + "<br><br>pyTranscriber is developed as a hobby, so donations of any value are welcomed and essential for further improvements and fixes."
  346. + "<br><br>If you feel that this software has been useful and would like to contribute for it to continue improve and have more features and fixes you can <a href=\"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=YHB854YHPJCU8&item_name=Donation+pyTranscriber&currency_code=BRL\">DONATE VIA PAYPAL</a> or <a href=\"https://blockchain.com/btc/payment_request?address=153LcqV59paxEEJX7riLrEHQbE54vhcko9&amount=0.00026351&message=Donation to support pyTranscriber development\"> DONATE US$5 VIA BITCOIN</a>."
  347. + "<br><br>Thanks in advance!"
  348. + "</body></html>", "About pyTranscriber")
  349. def __showInfoMessage(self, info_msg, title):
  350. msg = QMessageBox()
  351. msg.setIcon(QMessageBox.Information)
  352. msg.setWindowTitle(title)
  353. msg.setText(info_msg)
  354. msg.exec()
  355. def __showErrorMessage(self, errorMsg):
  356. msg = QMessageBox()
  357. msg.setIcon(QMessageBox.Critical)
  358. msg.setWindowTitle("Error!")
  359. msg.setText(errorMsg)
  360. msg.exec()